diff --git a/autodiscover/autodiscover.go b/autodiscover/autodiscover.go index 5fb8093..df5b96c 100644 --- a/autodiscover/autodiscover.go +++ b/autodiscover/autodiscover.go @@ -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 = ` @@ -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) } @@ -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 @@ -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 } } @@ -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 { @@ -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 @@ -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 { diff --git a/config.yml b/config.yml index d9451c7..bc8d3b6 100644 --- a/config.yml +++ b/config.yml @@ -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/" diff --git a/http-ntlm/ntlmtransport.go b/http-ntlm/ntlmtransport.go index 97d0edd..351a5d3 100644 --- a/http-ntlm/ntlmtransport.go +++ b/http-ntlm/ntlmtransport.go @@ -10,10 +10,12 @@ package httpntlm import ( "crypto/tls" "errors" + "fmt" "io" "io/ioutil" "net/http" "net/http/cookiejar" + "net/url" "strings" "time" @@ -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) { @@ -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) diff --git a/mapi/mapi.go b/mapi/mapi.go index e4ddc13..94b54e6 100644 --- a/mapi/mapi.go +++ b/mapi/mapi.go @@ -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) @@ -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 } } @@ -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} } @@ -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 } diff --git a/rpc-http/rpctransport.go b/rpc-http/rpctransport.go index 113a0d8..c7b74e4 100644 --- a/rpc-http/rpctransport.go +++ b/rpc-http/rpctransport.go @@ -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) @@ -127,10 +126,12 @@ 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 @@ -138,6 +139,7 @@ func setupHTTP(rpctype string, URL string, ntlmAuth bool, full bool) (net.Conn, if err != nil { utils.Error.Println("Authentication Err") + utils.Debug.Println(string(data)) return nil, err } } @@ -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) } @@ -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.") } @@ -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") } @@ -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:]...) } diff --git a/ruler.go b/ruler.go index 699d42e..c3f36eb 100644 --- a/ruler.go +++ b/ruler.go @@ -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 @@ -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", diff --git a/utils/datatypes.go b/utils/datatypes.go index b1c54d6..3134c27 100644 --- a/utils/datatypes.go +++ b/utils/datatypes.go @@ -17,6 +17,7 @@ type Config struct { Insecure bool Verbose bool Admin bool + Proxy string } //Session stores authentication cookies ect @@ -25,6 +26,7 @@ type Session struct { Pass string Email string Domain string + Proxy string Basic bool Insecure bool Verbose bool