diff --git a/.github/workflows/github_actions.yml b/.github/workflows/github_actions.yml index 880302da3..f5a79d9d0 100644 --- a/.github/workflows/github_actions.yml +++ b/.github/workflows/github_actions.yml @@ -3,17 +3,28 @@ name: 'BrowserStack Test' on: pull_request_target: branches: [ master ] - -jobs: + types: [ labeled ] + +jobs: ubuntu-job: name: 'BrowserStack Test on Ubuntu' - runs-on: ubuntu-latest # Can be self-hosted runner also - environment: - name: Integrate Pull Request - env: + runs-on: ubuntu-latest + if: github.event.label.name == 'safe_to_test' + env: GITACTIONS: true steps: + - name: 'Remove safe_to_test label' + uses: actions/github-script@v8 + with: + script: | + await github.rest.issues.removeLabel({ + owner: context.repo.owner, + repo: context.repo.repo, + issue_number: context.payload.pull_request.number, + name: 'safe_to_test' + }); + - name: 'BrowserStack Env Setup' # Invokes the setup-env action uses: browserstack/github-actions/setup-env@master with: @@ -27,7 +38,7 @@ jobs: local-identifier: random - name: 'Checkout the repository' - uses: actions/checkout@v4 + uses: actions/checkout@v6 with: ref: ${{ github.event.pull_request.head.sha }} fetch-depth: 2 @@ -40,11 +51,13 @@ jobs: run: | sudo apt update sudo apt install libcurl4 libcurl4-openssl-dev + - name: 'Configure Bundle testing and install gems' run: | bundle config unset --local without bundle config set --local with 'test' 'development' bundle install + - name: 'Run BrowserStack simple verification' run: | bundle exec rake browserstack --trace diff --git a/Gemfile b/Gemfile index b6a795aa9..08fa8d132 100644 --- a/Gemfile +++ b/Gemfile @@ -24,7 +24,7 @@ gem 'rake', '~> 13.3' gem 'activerecord', '~> 8.1' gem 'otr-activerecord', '~> 2.6.0' gem 'sqlite3', '~> 2.9' -gem 'rubocop', '~> 1.84.1', require: false +gem 'rubocop', '~> 1.85.0', require: false # Geolocation support group :geoip do @@ -64,10 +64,10 @@ group :test do gem 'simplecov', '~> 0.22' gem 'test-unit-full', '~> 0.0.5' gem 'rspec', '~> 3.13' - gem 'rdoc', '~> 7.1' + gem 'rdoc', '~> 7.2' gem 'browserstack-local', '~> 1.4' - gem 'irb', '~> 1.16' + gem 'irb', '~> 1.17' gem 'pry-byebug', '~> 3.12' gem 'rest-client', '~> 2.1.0' @@ -80,7 +80,7 @@ group :test do # Note: selenium-webdriver 3.x is incompatible with Firefox version 48 and prior # gem 'selenium' # Requires old version of selenium which is no longer available gem 'geckodriver-helper', '~> 0.24.0' - gem 'selenium-webdriver', '~> 4.40' + gem 'selenium-webdriver', '~> 4.41' # Note: nokogiri is needed by capybara which may require one of the below commands # sudo apt-get install libxslt-dev libxml2-dev diff --git a/Gemfile.lock b/Gemfile.lock index ff4604ea0..a2ddae7b8 100644 --- a/Gemfile.lock +++ b/Gemfile.lock @@ -85,16 +85,22 @@ GEM io-console (0.8.2) io-endpoint (0.15.2) io-like (0.4.0) - irb (1.16.0) + irb (1.17.0) pp (>= 0.6.0) + prism (>= 1.3.0) rdoc (>= 4.0.0) reline (>= 0.4.2) json (2.18.1) + json-schema (6.1.0) + addressable (~> 2.8) + bigdecimal (>= 3.1, < 5) language_server-protocol (3.17.0.5) lint_roller (1.1.0) logger (1.7.0) matrix (0.4.3) maxmind-db (1.4.0) + mcp (0.7.1) + json-schema (>= 4.1) method_source (1.1.0) mime-types (3.7.0) logger @@ -115,27 +121,27 @@ GEM net-protocol netrc (0.11.0) nio4r (2.7.4) - nokogiri (1.18.9-aarch64-linux-gnu) + nokogiri (1.19.1-aarch64-linux-gnu) racc (~> 1.4) - nokogiri (1.18.9-aarch64-linux-musl) + nokogiri (1.19.1-aarch64-linux-musl) racc (~> 1.4) - nokogiri (1.18.9-arm-linux-gnu) + nokogiri (1.19.1-arm-linux-gnu) racc (~> 1.4) - nokogiri (1.18.9-arm-linux-musl) + nokogiri (1.19.1-arm-linux-musl) racc (~> 1.4) - nokogiri (1.18.9-arm64-darwin) + nokogiri (1.19.1-arm64-darwin) racc (~> 1.4) - nokogiri (1.18.9-x86_64-darwin) + nokogiri (1.19.1-x86_64-darwin) racc (~> 1.4) - nokogiri (1.18.9-x86_64-linux-gnu) + nokogiri (1.19.1-x86_64-linux-gnu) racc (~> 1.4) - nokogiri (1.18.9-x86_64-linux-musl) + nokogiri (1.19.1-x86_64-linux-musl) racc (~> 1.4) otr-activerecord (2.6.0) activerecord (>= 6.0, < 9.0) parallel (1.27.0) parseconfig (1.1.2) - parser (3.3.10.1) + parser (3.3.10.2) ast (~> 2.4.1) racc power_assert (2.0.5) @@ -158,7 +164,7 @@ GEM mojo_magick (~> 0.6.5) rqrcode_core (~> 1.0) racc (1.8.1) - rack (3.2.4) + rack (3.2.5) rack-protection (4.2.1) base64 (>= 0.1.0) logger (>= 1.6.0) @@ -170,7 +176,7 @@ GEM rack (>= 1.3) rainbow (3.1.1) rake (13.3.1) - rdoc (7.1.0) + rdoc (7.2.0) erb psych (>= 4.0.0) tsort @@ -198,10 +204,11 @@ GEM diff-lcs (>= 1.2.0, < 2.0) rspec-support (~> 3.13.0) rspec-support (3.13.6) - rubocop (1.84.1) + rubocop (1.85.0) json (~> 2.3) language_server-protocol (~> 3.17.0.2) lint_roller (~> 1.1.0) + mcp (~> 0.6) parallel (~> 1.10) parser (>= 3.3.0.2) rainbow (>= 2.2.2, < 4.0) @@ -219,7 +226,7 @@ GEM json rest-client securerandom (0.4.1) - selenium-webdriver (4.40.0) + selenium-webdriver (4.41.0) base64 (~> 0.2) logger (~> 1.4) rexml (~> 3.2, >= 3.2.5) @@ -322,7 +329,7 @@ DEPENDENCIES eventmachine (~> 1.2, >= 1.2.7) execjs (~> 2.10) geckodriver-helper (~> 0.24.0) - irb (~> 1.16) + irb (~> 1.17) json maxmind-db (~> 1.4) mime-types (~> 3.7) @@ -335,13 +342,13 @@ DEPENDENCIES rack (~> 3.2) rack-protection (~> 4.2.1) rake (~> 13.3) - rdoc (~> 7.1) + rdoc (~> 7.2) rest-client (~> 2.1.0) rspec (~> 3.13) - rubocop (~> 1.84.1) + rubocop (~> 1.85.0) rubyzip (~> 3.2) rushover (~> 0.3.0) - selenium-webdriver (~> 4.40) + selenium-webdriver (~> 4.41) simplecov (~> 0.22) sinatra (~> 4.1) slack-notifier (~> 2.4) diff --git a/core/filters/base.rb b/core/filters/base.rb index c8da8ff9a..1c152aca9 100644 --- a/core/filters/base.rb +++ b/core/filters/base.rb @@ -187,7 +187,7 @@ module BeEF def self.has_valid_browser_details_chars?(str) return false unless is_non_empty_string?(str) - !(str =~ %r{[^\w\d\s()-.,;:_/!\302\256]}).nil? + (str =~ %r{[^\w\d\s()-.,;:_/!\302\256]}).nil? end # Check for valid base details characters diff --git a/core/filters/browser.rb b/core/filters/browser.rb index c7c4cb1fa..d441e91ec 100644 --- a/core/filters/browser.rb +++ b/core/filters/browser.rb @@ -11,7 +11,7 @@ module BeEF def self.is_valid_browsername?(str) # rubocop:disable Naming/PredicatePrefix return false unless is_non_empty_string?(str) return false if str.length > 2 - return false if has_non_printable_char?(str) + return false unless has_valid_browser_details_chars?(str) true end @@ -21,7 +21,7 @@ module BeEF # @return [Boolean] If the string has valid Operating System name characters def self.is_valid_osname?(str) # rubocop:disable Naming/PredicatePrefix return false unless is_non_empty_string?(str) - return false if has_non_printable_char?(str) + return false unless has_valid_browser_details_chars?(str) return false if str.length < 2 true @@ -32,7 +32,7 @@ module BeEF # @return [Boolean] If the string has valid Hardware name characters def self.is_valid_hwname?(str) # rubocop:disable Naming/PredicatePrefix return false unless is_non_empty_string?(str) - return false if has_non_printable_char?(str) + return false unless has_valid_browser_details_chars?(str) return false if str.length < 2 true @@ -71,7 +71,7 @@ module BeEF # @return [Boolean] If the string has valid browser / ua string characters def self.is_valid_browserstring?(str) # rubocop:disable Naming/PredicatePrefix return false unless is_non_empty_string?(str) - return false if has_non_printable_char?(str) + return false unless has_valid_browser_details_chars?(str) return false if str.length > 300 true @@ -93,7 +93,7 @@ module BeEF # @return [Boolean] If the string has valid system platform characters def self.is_valid_system_platform?(str) # rubocop:disable Naming/PredicatePrefix return false unless is_non_empty_string?(str) - return false if has_non_printable_char?(str) + return false unless has_valid_browser_details_chars?(str) return false if str.length > 200 true diff --git a/core/main/handlers/browserdetails.rb b/core/main/handlers/browserdetails.rb index 4d75411b2..8e28482a6 100644 --- a/core/main/handlers/browserdetails.rb +++ b/core/main/handlers/browserdetails.rb @@ -44,7 +44,7 @@ module BeEF # hooked window host name log_zombie_port = 0 - if !@data['results']['browser.window.hostname'].nil? + if !@data['results']['browser.window.hostname'].nil? && BeEF::Filters.is_valid_hostname?(@data['results']['browser.window.hostname']) log_zombie_domain = @data['results']['browser.window.hostname'] elsif !@data['request'].referer.nil? and !@data['request'].referer.empty? referer = @data['request'].referer @@ -59,7 +59,7 @@ module BeEF end # hooked window host port - if @data['results']['browser.window.hostport'].nil? + if @data['results']['browser.window.hostport'].nil? || !BeEF::Filters.is_valid_port?(@data['results']['browser.window.hostport'].to_s) log_zombie_domain_parts = log_zombie_domain.split(':') log_zombie_port = log_zombie_domain_parts[1].to_i if log_zombie_domain_parts.length > 1 else @@ -92,6 +92,7 @@ module BeEF BD.set(session_id, 'browser.name.friendly', browser_friendly_name) else err_msg "Invalid browser name returned from the hook browser's initial connection." + browser_name = 'Unknown' end if BeEF::Filters.is_valid_ip?(zombie.ip) @@ -242,11 +243,17 @@ module BeEF X_FORWARDED X_FORWARDED_FOR ].each do |header| - proxy_clients << (JSON.parse(zombie.httpheaders)[header]).to_s unless JSON.parse(zombie.httpheaders)[header].nil? + val = JSON.parse(zombie.httpheaders)[header] + unless val.nil? + val.to_s.split(',').each do |ip| + proxy_clients << ip.strip if BeEF::Filters.is_valid_ip?(ip.strip) + end + end end # retrieve proxy server proxy_server = JSON.parse(zombie.httpheaders)['VIA'] unless JSON.parse(zombie.httpheaders)['VIA'].nil? + proxy_server = nil unless proxy_server.nil? || BeEF::Filters.has_valid_browser_details_chars?(proxy_server) # store and log proxy details if using_proxy == true @@ -273,6 +280,7 @@ module BeEF BD.set(session_id, 'browser.version', browser_version) else err_msg "Invalid browser version returned from the hook browser's initial connection." + browser_version = 'Unknown' end # get and store browser string @@ -293,7 +301,11 @@ module BeEF # get and store browser language browser_lang = get_param(@data['results'], 'browser.language') - BD.set(session_id, 'browser.language', browser_lang) + if BeEF::Filters.has_valid_browser_details_chars?(browser_lang) + BD.set(session_id, 'browser.language', browser_lang) + else + err_msg "Invalid browser language returned from the hook browser's initial connection." + end # get and store the cookies cookies = get_param(@data['results'], 'browser.window.cookies') @@ -309,6 +321,7 @@ module BeEF BD.set(session_id, 'host.os.name', os_name) else err_msg "Invalid operating system name returned from the hook browser's initial connection." + os_name = 'Unknown' end # get and store the OS family @@ -322,15 +335,28 @@ module BeEF # get and store the OS version # - without checks as it can be very different, for instance on linux/bsd) os_version = get_param(@data['results'], 'host.os.version') - BD.set(session_id, 'host.os.version', os_version) + if BeEF::Filters.has_valid_browser_details_chars?(os_version) + BD.set(session_id, 'host.os.version', os_version) + else + err_msg "Invalid operating system version returned from the hook browser's initial connection." + os_version = 'Unknown' + end - # get and store the OS arch - without checks + # get and store the OS arch os_arch = get_param(@data['results'], 'host.os.arch') - BD.set(session_id, 'host.os.arch', os_arch) + if BeEF::Filters.has_valid_browser_details_chars?(os_arch) + BD.set(session_id, 'host.os.arch', os_arch) + else + err_msg "Invalid operating system architecture returned from the hook browser's initial connection." + end # get and store default browser default_browser = get_param(@data['results'], 'host.software.defaultbrowser') - BD.set(session_id, 'host.software.defaultbrowser', default_browser) + if BeEF::Filters.has_valid_browser_details_chars?(default_browser) + BD.set(session_id, 'host.software.defaultbrowser', default_browser) + else + err_msg "Invalid default browser returned from the hook browser's initial connection." + end # get and store the hardware type hw_type = get_param(@data['results'], 'hardware.type') diff --git a/extensions/admin_ui/media/javascript/ui/panel/zombiesTreeList.js b/extensions/admin_ui/media/javascript/ui/panel/zombiesTreeList.js index e9c1c2ace..3d81e42e4 100644 --- a/extensions/admin_ui/media/javascript/ui/panel/zombiesTreeList.js +++ b/extensions/admin_ui/media/javascript/ui/panel/zombiesTreeList.js @@ -467,24 +467,26 @@ try{ } // set zombie hover balloon text for tree node + // Use Ext.util.Format.htmlEncode() to prevent XSS via malicious browser properties + var encode = Ext.util.Format.htmlEncode; var balloon_text = ""; - balloon_text += hooked_browser.ip; + balloon_text += encode(hooked_browser.ip); balloon_text += "
" balloon_text += " "; - balloon_text += "Origin: " + hooked_browser.domain + ":" + hooked_browser.port; + balloon_text += "Origin: " + encode(hooked_browser.domain) + ":" + encode(hooked_browser.port); balloon_text += "
"; balloon_text += " "; - balloon_text += "Browser: " + hooked_browser.browser_name + " " + hooked_browser.browser_version; + balloon_text += "Browser: " + encode(hooked_browser.browser_name) + " " + encode(hooked_browser.browser_version); balloon_text += "
"; balloon_text += " "; if (hooked_browser.os_version == 'Unknown') { - balloon_text += "OS: " + hooked_browser.os_name; + balloon_text += "OS: " + encode(hooked_browser.os_name); } else { - balloon_text += "OS: " + hooked_browser.os_name + ' ' + hooked_browser.os_version; + balloon_text += "OS: " + encode(hooked_browser.os_name) + ' ' + encode(hooked_browser.os_version); } balloon_text += "
"; balloon_text += " "; - balloon_text += "Hardware: " + hooked_browser.hw_name; + balloon_text += "Hardware: " + encode(hooked_browser.hw_name); balloon_text += "
"; if ( !hooked_browser.country || !hooked_browser.country_code || hooked_browser.country == 'Unknown' ) { @@ -492,11 +494,11 @@ try{ balloon_text += "Location: Unknown"; } else { balloon_text += " "; - balloon_text += "Location: " + hooked_browser.city + ", " + hooked_browser.country; + balloon_text += "Location: " + encode(hooked_browser.city) + ", " + encode(hooked_browser.country); } balloon_text += "
"; - balloon_text += "Local Date: " + hooked_browser.date; + balloon_text += "Local Date: " + encode(hooked_browser.date); hooked_browser.qtip = balloon_text; // set zombie text label for tree node @@ -511,7 +513,7 @@ try{ text += " "; } - text += hooked_browser.ip; + text += encode(hooked_browser.ip); hooked_browser.text = text; //save a new online HB