CVE-2015-10072 (Ruby, published 2023-02-04T04:15Z)
Description: value: A vulnerability classified as problematic was found in NREL api-umbrella-web 0.7.1. This vulnerability affects unknown code of the component Flash Message Handler. The manipulation leads to cross site scripting. The attack can be initiated remotely. Upgrading to version 0.8.0 is able to address this issue. The name of the patch is bcc0e922c61d30367678c8f17a435950969315cd. It is recommended to upgrade the affected component. The identifier of this vulnerability is VDB-220060.
Severity: nan (CVSS v3 base score: 6.1)
Weakness: CWE-79 \u2014 Improper Neutralization of Input During Web Page Generation ('Cross-site Scripting')
The product does not neutralize or incorrectly neutralizes user-controllable input before it is placed in output that is used as a web page that is served to other users.
Repository: https://github.com/NREL/api-umbrella-web
Files changed:
Gemfile
Gemfile.lock
app/assets/javascripts/admin/controllers/admin_groups/form_controller.js
app/assets/javascripts/admin/controllers/admins/form_controller.js
app/assets/javascripts/admin/controllers/api_scopes/form_controller.js
app/assets/javascripts/admin/controllers/api_users/form_controller.js
app/assets/javascripts/admin/controllers/apis_form_controller.js
app/assets/javascripts/admin/controllers/website_backends/form_controller.js
spec/cassettes/elasticsearch_templates.yml
spec/controllers/admin/stats_controller_spec.rb
spec/factories/api_users.rb
spec/factories/log_items.rb
spec/features/admin/api_users_spec.rb
spec/features/admin/stats_logs_spec.rb
spec/features/admin/stats_users_spec.rb
spec/support/elasticsearch.rb
spec/support/vcr.rb
Commit message: Fix additional XSS issues in the flash messages in the admin.

A follow-up to XSS issues first identified in
f53a9fb87e10c457f0f3dd4f2af24d3b2f21b3ca.

I've gone through the rest of the admin, and I believe this covers
everything.

This commit is a bit bigger, since it fixes some issues with how our
test elasticsearch database was being setup for this web app (the
database didn't have our actual mappings loaded, so some fields weren't
being indexes as the proper types). These test fixes allow us to now
properly test the analytics by users list, where some additional XSS
tests have been added.


Vulnerable code:
        message: 'Successfully saved the admin group "' + this.get('model.name') + '"',
        message: 'Successfully saved the admin "' + this.get('model.username') + '"',
        message: 'Successfully saved the API scope "' + this.get('model.name') + '"',
          var message = 'Successfully saved the user "' + model.get('email') + '"';
            message += '<br>API Key: <code>' + model.get('apiKey') + '</code>';
        message: 'Successfully saved the "' + this.get('model.name') + '" API backend<br><strong>Note:</strong> Your changes are not yet live. <a href="/admin/#/config/publish">Publish Changes</a> to send your updates live.',
        message: 'Successfully saved the "' + this.get('model.frontendHost') + '" website backend<br><strong>Note:</strong> Your changes are not yet live. <a href="/admin/#/config/publish">Publish Changes</a> to send your updates live.',
    begin
      LogItem.gateway.client.indices.delete :index => LogItem.index_name
    rescue Elasticsearch::Transport::Transport::Errors::NotFound # rubocop:disable Lint/HandleExceptions
    end
  index_name "api-umbrella-logs-2015-01"
  attribute :backend_response_time, Float
  attribute :proxy_overhead, Float
  attribute :response_time, Float
      page.should_not have_selector(".xss-test", :visible => :all)
      page.should_not have_selector(".xss-test", :visible => :all)
describe "apis", :js => true do
    begin
      LogItem.gateway.client.indices.delete :index => LogItem.index_name
    rescue Elasticsearch::Transport::Transport::Errors::NotFound # rubocop:disable Lint/HandleExceptions
    end
      page.should_not have_selector(".xss-test", :visible => :all)

Fixed code:

  # For caching external web requests
  gem "vcr", "~> 2.9.3", :require => false
  gem "webmock", "~> 1.21.0", :require => false
    crack (0.4.2)
      safe_yaml (~> 1.0.0)
    vcr (2.9.3)
    webmock (1.21.0)
      addressable (>= 2.3.6)
      crack (>= 0.3.2)
  vcr (~> 2.9.3)
  webmock (~> 1.21.0)
        message: 'Successfully saved the admin group "' + _.escape(this.get('model.name')) + '"',
        message: 'Successfully saved the admin "' + _.escape(this.get('model.username')) + '"',
        message: 'Successfully saved the API scope "' + _.escape(this.get('model.name')) + '"',
          var message = 'Successfully saved the user "' + _.escape(model.get('email')) + '"';
            message += '<br>API Key: <code>' + _.escape(model.get('apiKey')) + '</code>';
        message: 'Successfully saved the "' + _.escape(this.get('model.name')) + '" API backend<br><strong>Note:</strong> Your changes are not yet live. <a href="/admin/#/config/publish">Publish Changes</a> to send your updates live.',
        message: 'Successfully saved the "' + _.escape(this.get('model.frontendHost')) + '" website backend<br><strong>Note:</strong> Your changes are not yet live. <a href="/admin/#/config/publish">Publish Changes</a> to send your updates live.',
---
http_interactions:
- request:
    method: get
    uri: https://raw.githubusercontent.com/NREL/api-umbrella-router/master/config/elasticsearch_templates.json
    body:
      encoding: US-ASCII
      string: ''
    headers:
      Accept:
      - "*/*; q=0.5, application/xml"
      Accept-Encoding:
      - gzip, deflate
      User-Agent:
      - Ruby
  response:
    status:
      code: 200
      message: OK
    headers:
      Date:
      - Fri, 10 Apr 2015 17:17:38 GMT
      Server:
      - Apache
      Access-Control-Allow-Origin:
      - https://render.githubusercontent.com
      Content-Security-Policy:
      - default-src 'none'
      X-Xss-Protection:
      - 1; mode=block
      X-Frame-Options:
      - deny
      X-Content-Type-Options:
      - nosniff
      Strict-Transport-Security:
      - max-age=31536000
      Etag:
      - '"d426630bd78408d97d4ae86d0d7a7c224849d203"'
      Content-Type:
      - text/plain; charset=utf-8
      Cache-Control:
      - max-age=300
      Content-Length:
      - '3937'
      Accept-Ranges:
      - bytes
      Via:
      - 1.1 varnish
      X-Served-By:
      - cache-mia1323-MIA
      X-Cache:
      - MISS
      X-Cache-Hits:
      - '0'
      Vary:
      - Authorization,Accept-Encoding
      Expires:
      - Fri, 10 Apr 2015 17:22:38 GMT
      Source-Age:
      - '0'
    body:
      encoding: ASCII-8BIT
      string: |
        [
          {
            "id": "api-umbrella-template",
            "template": {
              "template": "api-umbrella",
              "mappings": {
                "city": {
                  "_all": {
                    "enabled": false
                  },
                  "_timestamp": {
                    "enabled": true,
                    "path": "updated_at"
                  },
                  "properties": {
                    "country": {
                      "type": "string",
                      "index": "not_analyzed"
                    },
                    "region": {
                      "type": "string",
                      "index": "not_analyzed"
                    },
                    "city": {
                      "type": "string",
                      "index": "not_analyzed"
                    },
                    "location": {
                      "type": "geo_point",
                      "lat_lon": true
                    }
                  }
                }
              }
            }
          },
          {
            "id": "api-umbrella-log-v1-template",
            "template": {
              "template": "api-umbrella-logs-v1-*",
              "settings": {
                "index": {
                  "number_of_shards": 3
                },
                "analysis": {
                  "analyzer": {
                    "keyword_lowercase": {
                      "type": "custom",
                      "tokenizer": "keyword",
                      "filter": ["lowercase"]
                    },
                    "path_hierarchy_lowercase": {
                      "type": "custom",
                      "tokenizer": "path_hierarchy",
                      "filter": ["lowercase"]
                    }
                  }
                }
              },
              "mappings": {
                "log": {
                  "_all": {
                    "enabled": false
                  },
                  "_timestamp": {
                    "enabled": true,
                    "path": "request_at"
                  },
                  "date_detection": false,
                  "numeric_detection": false,
                  "dynamic_templates": [
                    {
                      "string_template": {
                        "match": "*",
                        "match_mapping_type": "string",
                        "mapping": {
                          "type": "string",
                          "index": "analyzed",
                          "analyzer": "keyword_lowercase"
                        }
                      }
                    }
                  ],
                  "properties": {
                    "api_key": {
                      "type": "string",
                      "index": "not_analyzed"
                    },
                    "user_id": {
                      "type": "string",
                      "index": "not_analyzed"
                    },
                    "request_at": {
                      "type": "date"
                    },
                    "request_ip": {
                      "type": "string",
                      "index": "not_analyzed"
                    },
                    "request_ip_country": {
                      "type": "string",
                      "index": "not_analyzed"
                    },
                    "request_ip_region": {
                      "type": "string",
                      "index": "not_analyzed"
                    },
                    "request_ip_city": {
                      "type": "string",
                      "index": "not_analyzed"
                    },
                    "request_path_hierarchy": {
                      "type": "string",
                      "analyzer": "path_hierarchy_lowercase"
                    },
                    "request_size": {
                      "type": "integer"
                    },
                    "response_status": {
                      "type": "short"
                    },
                    "response_age": {
                      "type": "integer"
                    },
                    "response_content_length": {
                      "type": "integer"
                    },
                    "response_size": {
                      "type": "integer"
                    },
                    "response_time": {
                      "type": "integer"
                    },
                    "backend_response_time": {
                      "type": "integer"
                    },
                    "proxy_overhead": {
                      "type": "integer"
                    },
                    "internal_response_time": {
                      "type": "float"
                    },
                    "internal_gatekeeper_time": {
                      "type": "float"
                    },
                    "request_ip_location": {
                      "type": "geo_point",
                      "lat_lon": true
                    }
                  }
                }
              }
            }
          }
        ]
    http_version: 
  recorded_at: Fri, 10 Apr 2015 07:01:09 GMT
recorded_with: VCR 2.9.3
    LogItem.gateway.client.delete_by_query :index => LogItem.index_name, :body => {
      :query => {
        :match_all => {},
      },
    }
      email 'a@"><script class="xss-test">alert("Hello first_name");</script>.com'
  index_name "api-umbrella-logs-write-2015-01"
  document_type "log"
  attribute :backend_response_time, Integer
  attribute :proxy_overhead, Integer
  attribute :response_time, Integer
      page.should_not have_selector(".xss-test", :visible => :all)
      page.should_not have_selector(".xss-test", :visible => :all)
    end

    it "escapes html entities in flash confirmation message" do
      visit "/admin/#/api_users/#{@user.id}/edit"

      fill_in "Last Name", :with => "Doe"
      click_button("Save")

      page.should have_content("Successfully saved the user \"#{@user.email}\"")
      page.should_not have_selector(".xss-test", :visible => :all)
describe "analytics filter logs", :js => true do
    LogItem.gateway.client.delete_by_query :index => LogItem.index_name, :body => {
      :query => {
        :match_all => {},
      },
    }
      page.should_not have_selector(".xss-test", :visible => :all)
require "spec_helper"

describe "analytics by users", :js => true do
  login_admin

  before(:each) do
    LogItem.gateway.client.delete_by_query :index => LogItem.index_name, :body => {
      :query => {
        :match_all => {},
      },
    }
  end

  describe "xss" do
    it "escapes html entities in the table" do
      user = FactoryGirl.create(:xss_api_user)
      FactoryGirl.create(:xss_log_item, :request_at => Time.parse("2015-01-16T06:06:28.816Z"), :api_key => user.api_key, :user_id => user.id, :user_email => user.email, :user_registration_source => user.registration_source)
      LogItem.gateway.refresh_index!

      visit "/admin/#/stats/users/tz=America%2FDenver&search=&start_at=2015-01-12&end_at=2015-01-18&interval=day"

      page.should have_content(user.email)
      page.should have_content(user.first_name)
      page.should have_content(user.last_name)
      page.should have_content(user.use_description)
      page.should_not have_selector(".xss-test", :visible => :all)
    end
  end
end
require "support/vcr"

client = Elasticsearch::Client.new

# Fetch the elasticsearch template file from the router project. Cache it with
# VCR, but periodically re-record it to make sure we stay up-to-date.
VCR.use_cassette("elasticsearch_templates", :re_record_interval => 1.day) do
  templates = MultiJson.load(RestClient.get("https://raw.githubusercontent.com/NREL/api-umbrella-router/master/config/elasticsearch_templates.json"))
  templates.each do |template|
    client.indices.put_template({
      :name => template["id"],
      :body => template["template"],
    })
  end
end

# For simplicity sake, we're assuming our tests only deal with the 2015-01
# index currently. First delete any existing indexes.
%w(api-umbrella-logs-v1-2015-01 api-umbrella-logs-2015-01 api-umbrella-logs-write-2015-01).each do |index_name|
  begin
    client.indices.delete :index => index_name
  rescue Elasticsearch::Transport::Transport::Errors::NotFound # rubocop:disable Lint/HandleExceptions
  end
end

# Create the index with proper aliases setup.
client.indices.create(:index => "api-umbrella-logs-v1-2015-01", :body => {
  :aliases => {
    "api-umbrella-logs-2015-01" => {},
    "api-umbrella-logs-write-2015-01" => {},
  },
})
require "vcr"

VCR.configure do |c|
  c.cassette_library_dir = "#{::Rails.root}/spec/cassettes"
  c.hook_into :webmock
  c.default_cassette_options = {
    :record => :new_episodes,

    # Store gzip responses as plaintext in the YAML.
    :decode_compressed_response => true,

    # Allow the same response to be used multiple times in a single test.
    :allow_playback_repeats => true,
  }

  # Allow localhost connections for ElasticSearch.
  c.ignore_localhost = true

  c.configure_rspec_metadata!
end