Skip to content

Commit

Permalink
add --proxy option and --debug option
Browse files Browse the repository at this point in the history
  • Loading branch information
Etienne Stalmans committed Jun 7, 2017
1 parent 409e606 commit a54ca8f
Show file tree
Hide file tree
Showing 7 changed files with 116 additions and 39 deletions.
45 changes: 35 additions & 10 deletions autodiscover/autodiscover.go
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,8 @@ import (
var SessionConfig *utils.Session
var autodiscoverStep int
var secondaryEmail string //a secondary email to use, edge case seen in office365
var Transport http.Transport
var useBasic = false

//the xml for the autodiscover service
const autodiscoverXML = `<?xml version="1.0" encoding="utf-8"?><Autodiscover xmlns="http://schemas.microsoft.com/exchange/autodiscover/outlook/requestschema/2006">
Expand Down Expand Up @@ -198,13 +200,39 @@ func CreateCache(email, autodiscover string) {

//Autodiscover function to retrieve mailbox details using the autodiscover mechanism from MS Exchange
func Autodiscover(domain string) (*utils.AutodiscoverResp, string, error) {
if SessionConfig.Proxy == "" {
Transport = http.Transport{
TLSClientConfig: &tls.Config{InsecureSkipVerify: SessionConfig.Insecure},
}
} else {
proxyURL, err := url.Parse(SessionConfig.Proxy)
if err != nil {
return nil, "", fmt.Errorf("Invalid proxy url format %s", err)
}
Transport = http.Transport{Proxy: http.ProxyURL(proxyURL),
TLSClientConfig: &tls.Config{InsecureSkipVerify: SessionConfig.Insecure},
}
}
return autodiscover(domain, false)
}

//MAPIDiscover function to do the autodiscover request but specify the MAPI header
//indicating that the MAPI end-points should be returned
func MAPIDiscover(domain string) (*utils.AutodiscoverResp, string, error) {
//fmt.Println("Doing Autodiscover for domain")
//set transport
if SessionConfig.Proxy == "" {
Transport = http.Transport{
TLSClientConfig: &tls.Config{InsecureSkipVerify: SessionConfig.Insecure},
}
} else {
proxyURL, err := url.Parse(SessionConfig.Proxy)
if err != nil {
return nil, "", fmt.Errorf("Invalid proxy url format %s", err)
}
Transport = http.Transport{Proxy: http.ProxyURL(proxyURL),
TLSClientConfig: &tls.Config{InsecureSkipVerify: SessionConfig.Insecure},
}
}
return autodiscover(domain, true)
}

Expand All @@ -214,9 +242,7 @@ func autodiscover(domain string, mapi bool) (*utils.AutodiscoverResp, string, er
autodiscoverResp := utils.AutodiscoverResp{}
//for now let's rely on autodiscover.domain/autodiscover/autodiscover.xml
//var client http.Client
client := http.Client{Transport: &http.Transport{
TLSClientConfig: &tls.Config{InsecureSkipVerify: SessionConfig.Insecure},
}}
client := http.Client{Transport: &Transport}

if SessionConfig.Basic == false {
//check if this is a first request or a redirect
Expand Down Expand Up @@ -290,13 +316,13 @@ func autodiscover(domain string, mapi bool) (*utils.AutodiscoverResp, string, er
if err != nil {
return nil, "", err
}
useBasic = true
} else {
if autodiscoverStep < 2 {
autodiscoverStep++
return autodiscover(domain, mapi)
}
//we've done all three steps of autodiscover and all three failed

return nil, "", err
}
}
Expand All @@ -310,7 +336,7 @@ func autodiscover(domain string, mapi bool) (*utils.AutodiscoverResp, string, er

//check if we got a 200 response
if resp.StatusCode == 200 {

SessionConfig.Basic = useBasic
err := autodiscoverResp.Unmarshal(body)
if err != nil {
if SessionConfig.Verbose == true {
Expand Down Expand Up @@ -376,9 +402,7 @@ func redirectAutodiscover(redirdom string) (string, error) {
//create the autodiscover url
autodiscoverURL := fmt.Sprintf("http://autodiscover.%s/autodiscover/autodiscover.xml", redirdom)
req, _ := http.NewRequest("GET", autodiscoverURL, nil)
var DefaultTransport http.RoundTripper = &http.Transport{
TLSClientConfig: &tls.Config{InsecureSkipVerify: SessionConfig.Insecure},
}
var DefaultTransport = &Transport
resp, err := DefaultTransport.RoundTrip(req)
if err != nil {
return "", err
Expand All @@ -402,8 +426,9 @@ type InsecureRedirectsO365 struct {
//and Go does not forward Sensitive headers such as Authorization (https://golang.org/src/net/http/client.go#41)
func (l InsecureRedirectsO365) RoundTrip(req *http.Request) (resp *http.Response, err error) {
t := l.Transport

if t == nil {
t = http.DefaultTransport
t = &Transport
}
resp, err = t.RoundTrip(req)
if err != nil {
Expand Down
12 changes: 6 additions & 6 deletions config.yml
Original file line number Diff line number Diff line change
@@ -1,12 +1,12 @@
username: ""
email: ""
password: ""
email: "etienne@0x04.cc"
password: "Koosis'ndoos"
hash: ""
domain: ""
userdn: "/o=ExchangeLabs/ou=Exchange Administrative Group (FYDIBOHF23SPDLT)/cn=Recipients/cn=130d3c8295784d0aa504b05fe83a"
mailbox: "d4094721-eafc-483d-adf4-4f39fe6bbc"
userdn: "/o=ExchangeLabs/ou=Exchange Administrative Group (FYDIBOHF23SPDLT)/cn=Recipients/cn=130d3c8295784d0aa504b05fe83acd7b-etienne"
mailbox: "d4094721-eafc-483d-adf4-4f39fe6bbc22@0x04.cc"
rpcurl: "https://outlook.office365.com/rpc/rpcproxy.dll"
rpc: false
rpc: true
rpcencrypt: true
ntlm: true
ntlm: false
mapiurl: "https://outlook.office365.com/mapi/emsmdb/"
21 changes: 19 additions & 2 deletions http-ntlm/ntlmtransport.go
Original file line number Diff line number Diff line change
Expand Up @@ -10,10 +10,12 @@ package httpntlm
import (
"crypto/tls"
"errors"
"fmt"
"io"
"io/ioutil"
"net/http"
"net/http/cookiejar"
"net/url"
"strings"
"time"

Expand All @@ -26,11 +28,14 @@ type NtlmTransport struct {
Domain string
User string
Password string
Proxy string
NTHash []byte
Insecure bool
CookieJar *cookiejar.Jar
}

var Transport http.Transport

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

Expand All @@ -50,10 +55,22 @@ func (t NtlmTransport) RoundTrip(req *http.Request) (res *http.Response, err err
r, _ := http.NewRequest("GET", req.URL.String(), strings.NewReader(""))
r.Header.Add("Authorization", "NTLM "+utils.EncBase64(b.Bytes()))

tr := &http.Transport{
TLSClientConfig: &tls.Config{InsecureSkipVerify: t.Insecure},
if t.Proxy == "" {
Transport = http.Transport{
TLSClientConfig: &tls.Config{InsecureSkipVerify: t.Insecure},
}
} else {
proxyURL, e := url.Parse(t.Proxy)
if e != nil {
return nil, fmt.Errorf("Invalid proxy url format %s", e)
}
Transport = http.Transport{Proxy: http.ProxyURL(proxyURL),
TLSClientConfig: &tls.Config{InsecureSkipVerify: t.Insecure},
}
}

tr := &Transport

client := http.Client{Transport: tr, Timeout: time.Minute, Jar: t.CookieJar}

resp, err := client.Do(r)
Expand Down
49 changes: 35 additions & 14 deletions mapi/mapi.go
Original file line number Diff line number Diff line change
Expand Up @@ -56,16 +56,32 @@ func Init(config *utils.Session, lid, URL, ABKURL string, transport int) {
if transport == HTTP {
AuthSession.URL, _ = url.Parse(URL)
AuthSession.ABKURL, _ = url.Parse(ABKURL)
client = http.Client{
Transport: &httpntlm.NtlmTransport{
Domain: AuthSession.Domain,
User: AuthSession.User,
Password: AuthSession.Pass,
NTHash: AuthSession.NTHash,
Insecure: AuthSession.Insecure,
CookieJar: AuthSession.CookieJar,
},
Jar: AuthSession.CookieJar,
if AuthSession.Basic == true {
var Transport http.Transport
if AuthSession.Proxy == "" {
Transport = http.Transport{
TLSClientConfig: &tls.Config{InsecureSkipVerify: AuthSession.Insecure},
}
} else {
proxyURL, _ := url.Parse(AuthSession.Proxy)
Transport = http.Transport{Proxy: http.ProxyURL(proxyURL),
TLSClientConfig: &tls.Config{InsecureSkipVerify: AuthSession.Insecure},
}
}
client = http.Client{Jar: AuthSession.CookieJar, Transport: &Transport}
} else {
client = http.Client{
Transport: &httpntlm.NtlmTransport{
Domain: AuthSession.Domain,
User: AuthSession.User,
Password: AuthSession.Pass,
NTHash: AuthSession.NTHash,
Insecure: AuthSession.Insecure,
CookieJar: AuthSession.CookieJar,
Proxy: AuthSession.Proxy,
},
Jar: AuthSession.CookieJar,
}
}
} else {
AuthSession.URL, _ = url.Parse(AuthSession.RPCURL)
Expand Down Expand Up @@ -105,10 +121,12 @@ func sendMapiRequest(mapi ExecuteRequest) (*ExecuteResponse, error) {
var err error
if AuthSession.Transport == HTTP { //this is always going to be an "Execute" request
if rawResp, err = mapiRequestHTTP(AuthSession.URL.String(), "Execute", mapi.Marshal()); err != nil {
utils.Debug.Println(rawResp)
return nil, err
}
} else {
if rawResp, err = mapiRequestRPC(mapi); err != nil {
utils.Debug.Println(rawResp)
return nil, err
}
}
Expand Down Expand Up @@ -145,10 +163,7 @@ func mapiRequestHTTP(URL, mapiType string, body []byte) ([]byte, error) {
if err != nil {
//check if this error was because of ntml auth when basic auth was expected.
if m, _ := regexp.Match("illegal base64", []byte(err.Error())); m == true {
AuthSession.Client = http.Client{Jar: AuthSession.CookieJar, Transport: &http.Transport{
TLSClientConfig: &tls.Config{InsecureSkipVerify: AuthSession.Insecure},
}}
resp, err = AuthSession.Client.Do(req)
resp, err = client.Do(req)
} else {
return nil, err //&TransportError{err}
}
Expand Down Expand Up @@ -422,6 +437,12 @@ func AuthenticateFetchMailbox(essdn []byte) (*RopLogonResponse, error) {

logonResponse := RopLogonResponse{}
logonResponse.Unmarshal(execResponse.RopBuffer)
if len(logonResponse.FolderIds) == 0 {
if AuthSession.Admin {
return nil, fmt.Errorf("Unable to retrieve mailbox as admin")
}
return nil, fmt.Errorf("Unable to retrieve mailbox as user")
}
specialFolders(logonResponse.FolderIds)
return &logonResponse, nil
}
Expand Down
19 changes: 13 additions & 6 deletions rpc-http/rpctransport.go
Original file line number Diff line number Diff line change
Expand Up @@ -107,7 +107,6 @@ func setupHTTP(rpctype string, URL string, ntlmAuth bool, full bool) (net.Conn,
}
}
}
//utils.Trace.Println(string(data))

ntlmChallengeString := strings.Replace(ntlmChallengeHeader, "NTLM ", "", 1)
challengeBytes, err := utils.DecBase64(ntlmChallengeString)
Expand All @@ -127,17 +126,20 @@ func setupHTTP(rpctype string, URL string, ntlmAuth bool, full bool) (net.Conn,
// parse NTLM challenge
challenge, err := ntlm.ParseChallengeMessage(challengeBytes)
if err != nil {
utils.Debug.Println(string(data))
return nil, err
}
err = session.ProcessChallengeMessage(challenge)
if err != nil {
utils.Debug.Println(string(data))
return nil, err
}
// authenticate user
authenticate, err = session.GenerateAuthenticateMessage()

if err != nil {
utils.Error.Println("Authentication Err")
utils.Debug.Println(string(data))
return nil, err
}
}
Expand Down Expand Up @@ -308,6 +310,7 @@ func RPCBind() error {
authenticate, err := rpcntlmsession.GenerateAuthenticateMessageAV()

if err != nil {
utils.Debug.Println(string(resp.Body))
return fmt.Errorf("Bad authenticate message %s", err)
}

Expand Down Expand Up @@ -346,7 +349,7 @@ func EcDoRPCExt2(MAPI []byte, auxLen uint32) ([]byte, error) {
}

if len(resp.PDU) < 28 {
utils.Error.Println(resp)
utils.Debug.Println(resp)
return nil, fmt.Errorf("Invalid response.")
}

Expand Down Expand Up @@ -384,17 +387,17 @@ func DoConnectExRequest(MAPI []byte, auxLen uint32) ([]byte, error) {
if err != nil {
return nil, err
}

var dec []byte
//decrypt response PDU
if AuthSession.RPCNetworkAuthLevel == RPC_C_AUTHN_LEVEL_PKT_PRIVACY {
dec, _ := rpcntlmsession.UnSeal(resp.PDU[8:])
fmt.Println(string(dec))
dec, _ = rpcntlmsession.UnSeal(resp.PDU[8:])
AuthSession.ContextHandle = dec[4:20] //decrypted
} else {
AuthSession.ContextHandle = resp.PDU[12:28]
}

if utils.DecodeUint32(AuthSession.ContextHandle[0:4]) == 0x0000 {
utils.Debug.Printf("%s\n%x\n", string(dec), resp)
return nil, fmt.Errorf("\nUnable to obtain a session context\nTry again using the --encrypt flag. It is possible that the target requires 'Encrypt traffic between Outlook and Exchange' to be enabled")
}

Expand Down Expand Up @@ -552,9 +555,13 @@ func RPCRead(callID int) (RPCResponse, error) {
//check if there is a 401 or other error message
for k, v := range httpResponses {
st := string(v)
if er := strings.Split(strings.Split(st, "\r\n")[0], " "); er[1] != "200" {

if er := strings.Split(strings.Split(st, "\r\n")[0], " "); len(er) > 1 && er[1] != "200" {
utils.Debug.Println(st)
return RPCResponse{}, fmt.Errorf("Invalid HTTP response: %s", er)
} else if len(er) <= 1 {
utils.Debug.Println(st)
return RPCResponse{}, fmt.Errorf("Invalid HTTP response: %s", st)
}
httpResponses = append(httpResponses[:k], httpResponses[k+1:]...)
}
Expand Down
7 changes: 6 additions & 1 deletion ruler.go
Original file line number Diff line number Diff line change
Expand Up @@ -206,7 +206,7 @@ func connect(c *cli.Context) error {
config.Admin = c.GlobalBool("admin")
config.RPCEncrypt = !c.GlobalBool("noencrypt")
config.CookieJar, _ = cookiejar.New(nil)

config.Proxy = c.GlobalString("proxy")
//add supplied cookie to the cookie jar
if c.GlobalString("cookie") != "" {
//split into cookies and then into name : value
Expand Down Expand Up @@ -723,6 +723,11 @@ A tool by @_staaldraad from @sensepost to abuse Exchange Services.`
Value: "",
Usage: "If you know the Autodiscover URL or the autodiscover service is failing. Requires full URI, https://autodisc.d.com/autodiscover/autodiscover.xml",
},
cli.StringFlag{
Name: "proxy",
Value: "",
Usage: "If you need to use an upstream proxy. Works with https://user:pass@ip:port or https://ip:port",
},
cli.BoolFlag{
Name: "insecure,k",
Usage: "Ignore server SSL certificate errors",
Expand Down
2 changes: 2 additions & 0 deletions utils/datatypes.go
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@ type Config struct {
Insecure bool
Verbose bool
Admin bool
Proxy string
}

//Session stores authentication cookies ect
Expand All @@ -25,6 +26,7 @@ type Session struct {
Pass string
Email string
Domain string
Proxy string
Basic bool
Insecure bool
Verbose bool
Expand Down

0 comments on commit a54ca8f

Please sign in to comment.