CVE-2015-10080 (JavaScript, published 2023-02-20T10:15Z)
Description: value: A vulnerability was found in NREL api-umbrella-web 0.7.1. It has been classified as problematic. This affects an unknown part of the component Admin Data Table Handler. The manipulation leads to cross site scripting. It is possible to initiate the attack remotely. Upgrading to version 0.8.0 is able to address this issue. The patch is named f53a9fb87e10c457f0f3dd4f2af24d3b2f21b3ca. It is recommended to upgrade the affected component. The associated identifier of this vulnerability is VDB-221487.
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:
app/assets/javascripts/admin/app.js
app/assets/javascripts/admin/views/admin_groups/table_view.js
app/assets/javascripts/admin/views/admins/table_view.js
app/assets/javascripts/admin/views/api_scopes/table_view.js
app/assets/javascripts/admin/views/api_users/table_view.js
app/assets/javascripts/admin/views/apis/table_view.js
app/assets/javascripts/admin/views/stats/logs_table_view.js
app/assets/javascripts/admin/views/stats/users_table_view.js
config/routes.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
Commit message: Fix XSS escaping issues in the admin data tables.

I mistakenly thought DataTables escaped things by default, but this is
not the case. Escape all data tables fields now and add capybara
integrations tests to ensure this is effective.

Setting up the Capybara tests required a slight change in the analytics
"Filter Logs" table, since the ajax request needs to be performed via
POST instead of GET. I think this is due to the length of the URL
involved in the query. This had worked in most browsers, but I'm
guessing this was broken in IE anyway, so it's probably a good idea
anyway.


Vulnerable code:
          render: function(names, type) {
            if(type === 'display' && names && names !== '-') {
              return names.join('<br>');
            }

            return names;
          }
          render: function(names, type) {
            if(type === 'display' && names && names !== '-') {
              return names.join('<br>');
            }

            return names;
          }
          render: function(names, type) {
            if(type === 'display' && names && names !== '-') {
              return names.join('<br>');
            }

            return names;
          }
          render: function(time, type) {
            if(type === 'display' && time && time !== '-') {
              return moment(time).format('YYYY-MM-DD HH:mm:ss');
            }

            return time;
          },
          render: function(time, type) {
            if(type === 'display' && time && time !== '-') {
              return moment(time).format('YYYY-MM-DD HH:mm:ss');
            }

            return time;
          },
          render: function(time, type) {
            if(type === 'display' && time && time !== '-') {
              return moment(time).format('YYYY-MM-DD HH:mm:ss');
            }

            return time;
          },
          render: function(time, type) {
            if(type === 'display' && time && time !== '-') {
              return moment(time).format('YYYY-MM-DD HH:mm:ss');
            }

            return time;
          },
          render: function(time, type) {
            if(type === 'display' && time && time !== '-') {
              return moment(time).format('YYYY-MM-DD HH:mm:ss');
            }
          },
          render: function(time, type) {
            if(type === 'display' && time && time !== '-') {
              return moment(time).format('YYYY-MM-DD HH:mm:ss');
            }

            return time;
          },
  attribute :request_accept, String
    request_accept "*/*"

Fixed code:

Admin.DataTablesHelpers = {
  renderEscaped: function(value, type) {
    if(type === 'display' && value) {
      return _.escape(value);
    }

    return value;
  },

  renderListEscaped: function(value, type) {
    if(type === 'display' && value) {
      if(_.isArray(value)) {
        return _.map(value, function(v) { return _.escape(v); }).join('<br>');
      } else {
        return _.escape(value);
      }
    }

    return value;
  },

  renderTime: function(value, type) {
    if(type === 'display' && value && value !== '-') {
      return moment(value).format('YYYY-MM-DD HH:mm:ss');
    }

    return value;
  },
};
          render: Admin.DataTablesHelpers.renderListEscaped,
          render: Admin.DataTablesHelpers.renderListEscaped,
          render: Admin.DataTablesHelpers.renderEscaped,
          render: Admin.DataTablesHelpers.renderEscaped,
          render: Admin.DataTablesHelpers.renderListEscaped,
          render: Admin.DataTablesHelpers.renderTime,
          render: Admin.DataTablesHelpers.renderTime,
          render: Admin.DataTablesHelpers.renderEscaped,
          render: Admin.DataTablesHelpers.renderEscaped,
          render: Admin.DataTablesHelpers.renderEscaped,
          render: Admin.DataTablesHelpers.renderEscaped,
          render: Admin.DataTablesHelpers.renderEscaped,
          render: Admin.DataTablesHelpers.renderTime,
          render: Admin.DataTablesHelpers.renderEscaped,
          render: Admin.DataTablesHelpers.renderEscaped,
          render: Admin.DataTablesHelpers.renderEscaped,
          render: Admin.DataTablesHelpers.renderEscaped,
          render: Admin.DataTablesHelpers.renderEscaped,
        // Use POST for this endpoint, since the URLs can be very long and
        // exceed URL length limits in IE (and apparently Capybara too).
        type: 'POST',
          render: Admin.DataTablesHelpers.renderTime,
          render: Admin.DataTablesHelpers.renderEscaped,
          render: Admin.DataTablesHelpers.renderEscaped,
          render: Admin.DataTablesHelpers.renderEscaped,
          render: Admin.DataTablesHelpers.renderEscaped,
          render: Admin.DataTablesHelpers.renderEscaped,
          render: Admin.DataTablesHelpers.renderEscaped,
          render: Admin.DataTablesHelpers.renderEscaped,
          render: Admin.DataTablesHelpers.renderEscaped,
          render: Admin.DataTablesHelpers.renderEscaped,
          render: Admin.DataTablesHelpers.renderEscaped,
          render: Admin.DataTablesHelpers.renderEscaped,
          render: Admin.DataTablesHelpers.renderEscaped,
          render: Admin.DataTablesHelpers.renderEscaped,
          render: Admin.DataTablesHelpers.renderTime,
          render: Admin.DataTablesHelpers.renderTime,
          render: Admin.DataTablesHelpers.renderTime,
        post "logs"

    factory :xss_api_user do
      first_name '"><script class="xss-test">alert("Hello first_name");</script>'
      last_name '"><script class="xss-test">alert("Hello last_name");</script>'
      use_description '"><script class="xss-test">alert("Hello use_description");</script>'
      registration_source '"><script class="xss-test">alert("Hello registration_source");</script>'
    end
  attribute :request_accept_encoding, String
  attribute :request_ip_city, String
  attribute :request_ip_country, String
  attribute :request_ip_region, String
    request_accept_encoding "*/*"
    request_ip_city "Golden"
    request_ip_country "US"
    request_ip_region "CO"

    factory :xss_log_item do
      request_accept_encoding '"><script class="xss-test">alert("1");</script>'
      request_host '"><script class="xss-test">alert("2");</script>'
      request_ip_city '"><script class="xss-test">alert("3");</script>'
      request_ip_country '"><script class="xss-test">alert("4");</script>'
      request_ip_region '"><script class="xss-test">alert("5");</script>'
      request_path '"><script class="xss-test">alert("6");</script>'
      request_query({ "foo" => '"><script class="xss-test">alert("7");</script>' })
      request_url '"><script class="xss-test">alert("8");</script>'
      request_user_agent '"><script class="xss-test">alert("9");</script>'
      response_content_type '"><script class="xss-test">alert("10");</script>'
      response_server '"><script class="xss-test">alert("11");</script>'
      user_email '"><script class="xss-test">alert("12");</script>'
      user_registration_source '"><script class="xss-test">alert("13");</script>'
    end
  describe "xss" do
    before(:all) do
      ApiUser.where(:registration_source.ne => "seed").delete_all
      @user = FactoryGirl.create(:xss_api_user)
    end

    it "escapes html entities in the table" do
      visit "/admin/#/api_users"

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

    it "escapes html entities in the form" do
      visit "/admin/#/api_users/#{@user.id}/edit"

      find_field("E-mail").value.should eql(@user.email)
      page.should_not have_selector(".xss-test", :visible => :all)
      find_field("First Name").value.should eql(@user.first_name)
      find_field("Last Name").value.should eql(@user.last_name)
      find_field("Purpose").value.should eql(@user.use_description)
      page.should have_content(@user.registration_source)
    end
  end

  describe "xss" do
    it "escapes html entities in the table" do
      log = FactoryGirl.create(:xss_log_item, :request_at => Time.parse("2015-01-16T06:06:28.816Z"), :request_method => "OPTIONS")
      LogItem.gateway.refresh_index!

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

      page.should have_content(log.request_method)
      page.should_not have_selector(".xss-test", :visible => :all)
      page.should have_content(log.request_accept_encoding)
      page.should have_content(log.request_ip_city)
      page.should have_content(log.request_ip_country)
      page.should have_content(log.request_ip_region)
      page.should have_content(log.request_user_agent)
      page.should have_content(log.response_content_type)
      page.should have_content(log.user_email)
    end
  end