Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
44 changes: 22 additions & 22 deletions autodiscover/autodiscover.go
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ import (
"bytes"
"crypto/tls"
"fmt"
"io/ioutil"
"io"
"net"
"net/http"
"net/url"
Expand All @@ -13,20 +13,20 @@ import (
"strings"
"text/template"

"github.com/sensepost/ruler/http-ntlm"
httpntlm "github.com/sensepost/ruler/http-ntlm"
"github.com/sensepost/ruler/utils"
)

//globals

//SessionConfig holds the configuration for this autodiscover session
// SessionConfig holds the configuration for this autodiscover session
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 basicAuth = false

//the xml for the autodiscover service
// 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">
<Request><EMailAddress>{{.Email}}</EMailAddress>
<AcceptableResponseSchema>http://schemas.microsoft.com/exchange/autodiscover/outlook/responseschema/2006a</AcceptableResponseSchema>
Expand All @@ -43,10 +43,10 @@ func parseTemplate(tmpl string) (string, error) {
return buff.String(), nil
}

//createAutodiscover generates a domain name of the format autodiscover.domain.com
//and checks if a DNS entry exists for it. If it doesn't it tries DNS for just the domain name.
//returns an empty string if no valid domain was found.
//returns the full (expected) autodiscover URL
// createAutodiscover generates a domain name of the format autodiscover.domain.com
// and checks if a DNS entry exists for it. If it doesn't it tries DNS for just the domain name.
// returns an empty string if no valid domain was found.
// returns the full (expected) autodiscover URL
func createAutodiscover(domain string, https bool) string {
_, err := net.LookupHost(domain)
if err != nil {
Expand All @@ -58,7 +58,7 @@ func createAutodiscover(domain string, https bool) string {
return fmt.Sprintf("http://%s/autodiscover/autodiscover.xml", domain)
}

//GetMapiHTTP gets the details for MAPI/HTTP
// GetMapiHTTP gets the details for MAPI/HTTP
func GetMapiHTTP(email, autoURLPtr string, resp *utils.AutodiscoverResp) (*utils.AutodiscoverResp, string, error) {
//var resp *utils.AutodiscoverResp
var err error
Expand Down Expand Up @@ -87,7 +87,7 @@ func GetMapiHTTP(email, autoURLPtr string, resp *utils.AutodiscoverResp) (*utils
return resp, rawAutodiscover, nil
}

//GetRPCHTTP exports the RPC details for RPC/HTTP
// GetRPCHTTP exports the RPC details for RPC/HTTP
func GetRPCHTTP(email, autoURLPtr string, resp *utils.AutodiscoverResp) (*utils.AutodiscoverResp, string, string, string, bool, error) {
//var resp *utils.AutodiscoverResp
var err error
Expand Down Expand Up @@ -190,7 +190,7 @@ func GetRPCHTTP(email, autoURLPtr string, resp *utils.AutodiscoverResp) (*utils.
return resp, rawAutodiscover, RPCURL, user, ntlmAuth, nil
}

//CheckCache checks to see if there is a stored copy of the autodiscover record
// CheckCache checks to see if there is a stored copy of the autodiscover record
func CheckCache(email string) *utils.AutodiscoverResp {
//check the cache folder for a stored autodiscover record
email = strings.Replace(email, "@", "_", -1)
Expand All @@ -205,7 +205,7 @@ func CheckCache(email string) *utils.AutodiscoverResp {
return nil
}
utils.Info.Println("Found cached Autodiscover record. Using this (use --nocache to force new lookup)")
data, err := ioutil.ReadFile(path)
data, err := os.ReadFile(path)
if err != nil {
utils.Error.Println("Error reading stored record ", err)
return nil
Expand All @@ -215,7 +215,7 @@ func CheckCache(email string) *utils.AutodiscoverResp {
return &autodiscoverResp
}

//CreateCache function stores the raw autodiscover record to file
// CreateCache function stores the raw autodiscover record to file
func CreateCache(email, autodiscover string) {

if autodiscover == "" { //no autodiscover record passed in, don't try write
Expand All @@ -240,7 +240,7 @@ func CreateCache(email, autodiscover string) {
}
}

//Autodiscover function to retrieve mailbox details using the autodiscover mechanism from MS Exchange
// 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{
Expand All @@ -258,8 +258,8 @@ func Autodiscover(domain string) (*utils.AutodiscoverResp, string, error) {
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
// 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) {
//set transport
if SessionConfig.Proxy == "" {
Expand Down Expand Up @@ -344,7 +344,7 @@ func autodiscover(domain string, mapi bool) (*utils.AutodiscoverResp, string, er

if SessionConfig.Basic == true {
if SessionConfig.Domain != "" {
req.SetBasicAuth(SessionConfig.Domain + "\\" + SessionConfig.User, SessionConfig.Pass)
req.SetBasicAuth(SessionConfig.Domain+"\\"+SessionConfig.User, SessionConfig.Pass)
} else {
req.SetBasicAuth(SessionConfig.Email, SessionConfig.Pass)
}
Expand Down Expand Up @@ -374,7 +374,7 @@ func autodiscover(domain string, mapi bool) (*utils.AutodiscoverResp, string, er

defer resp.Body.Close()

body, err := ioutil.ReadAll(resp.Body)
body, err := io.ReadAll(resp.Body)
if err != nil {
return nil, "", err
}
Expand Down Expand Up @@ -491,17 +491,17 @@ func redirectAutodiscover(redirdom string) (string, error) {
return resp.Header.Get("Location"), nil
}

//InsecureRedirectsO365 allows forwarding the Authorization header even when we shouldn't
// InsecureRedirectsO365 allows forwarding the Authorization header even when we shouldn't
type InsecureRedirectsO365 struct {
Transport http.RoundTripper
User string
Pass string
Insecure bool
}

//RoundTrip custom redirector that allows us to forward the auth header, even when the domain changes.
//This is needed as some office365 domains will redirect from autodiscover.domain.com to autodiscover.outlook.com
//and Go does not forward Sensitive headers such as Authorization (https://golang.org/src/net/http/client.go#41)
// RoundTrip custom redirector that allows us to forward the auth header, even when the domain changes.
// This is needed as some office365 domains will redirect from autodiscover.domain.com to autodiscover.outlook.com
// 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

Expand Down
29 changes: 14 additions & 15 deletions autodiscover/brute.go
Original file line number Diff line number Diff line change
Expand Up @@ -3,19 +3,19 @@ package autodiscover
import (
"crypto/tls"
"fmt"
"io/ioutil"
"net/http"
"net/http/cookiejar"
"net/url"
"os"
"regexp"
"strings"
"time"
"net/url"

"github.com/sensepost/ruler/http-ntlm"
httpntlm "github.com/sensepost/ruler/http-ntlm"
"github.com/sensepost/ruler/utils"
)

//Result struct holds the result of a bruteforce attempt
// Result struct holds the result of a bruteforce attempt
type Result struct {
Username string
Password string
Expand Down Expand Up @@ -114,7 +114,7 @@ func autodiscoverDomain(domain string) string {
return ""
}

//Init function to setup the brute-force session
// Init function to setup the brute-force session
func Init(domain, usersFile, passwordsFile, userpassFile, pURL, u, n string, b, i, s, v bool, c, d, t int) error {
stopSuccess = s
insecure = i
Expand All @@ -133,7 +133,6 @@ func Init(domain, usersFile, passwordsFile, userpassFile, pURL, u, n string, b,
return fmt.Errorf("No autodiscover end-point found")
}


if autodiscoverURL == "https://autodiscover-s.outlook.com/autodiscover/autodiscover.xml" {
basic = true
}
Expand All @@ -157,16 +156,16 @@ func Init(domain, usersFile, passwordsFile, userpassFile, pURL, u, n string, b,
return nil
}

//BruteForce function takes a domain/URL, file path to users and filepath to passwords whether to use BASIC auth and to trust insecure SSL
//And whether to stop on success
// BruteForce function takes a domain/URL, file path to users and filepath to passwords whether to use BASIC auth and to trust insecure SSL
// And whether to stop on success
func BruteForce() {

attempts := 0
stp := false

for index, p := range passwords {
if index % 10 == 0 {
utils.Info.Printf("%d of %d passwords checked",index,len(passwords))
if index%10 == 0 {
utils.Info.Printf("%d of %d passwords checked", index, len(passwords))
}
if p != "" {
attempts++
Expand Down Expand Up @@ -251,15 +250,15 @@ func BruteForce() {
}
}

//UserPassBruteForce function does a bruteforce using a supplied user:pass file
// UserPassBruteForce function does a bruteforce using a supplied user:pass file
func UserPassBruteForce() {

count := 0
sem := make(chan bool, concurrency)
stp := false
for index, up := range userpass {
if index % 10 == 0 {
utils.Info.Printf("%d of %d checked",index,len(userpass))
if index%10 == 0 {
utils.Info.Printf("%d of %d checked", index, len(userpass))
}
count++
if up == "" {
Expand Down Expand Up @@ -312,7 +311,7 @@ func UserPassBruteForce() {
func readFile(filename string) []string {
var outputs []string

data, err := ioutil.ReadFile(filename)
data, err := os.ReadFile(filename)
if err != nil {
utils.Error.Println("Input file not found")
return nil
Expand All @@ -339,7 +338,7 @@ func connect(autodiscoverURL, user, password string, basic, insecure bool) Resul
return result
}
tr = &http.Transport{Proxy: http.ProxyURL(proxy),
TLSClientConfig: &tls.Config{InsecureSkipVerify: true},
TLSClientConfig: &tls.Config{InsecureSkipVerify: true},
DisableKeepAlives: true,
}
}
Expand Down
22 changes: 11 additions & 11 deletions forms/rulerforms.go
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@ import (
"github.com/sensepost/ruler/utils"
)

//CreateFormAttachmentPointer creates the first attachment that holds info about the new form
// CreateFormAttachmentPointer creates the first attachment that holds info about the new form
func CreateFormAttachmentPointer(folderid, messageid []byte) error {
utils.Info.Println("Create Form Pointer Attachment")
data := []byte("FormStg=%d\\FS525C.tmp\nMsgCls=IPM.Note.grr\nBaseMsgCls=IPM.Note\n") //don't think this is strictly necessary
Expand All @@ -31,17 +31,17 @@ func CreateFormAttachmentPointer(folderid, messageid []byte) error {
return err
}

//CreateFormAttachmentTemplate creates the template attachment holding the actual command to execute
// CreateFormAttachmentTemplate creates the template attachment holding the actual command to execute
func CreateFormAttachmentTemplate(folderid, messageid []byte, pstr string) error {
return CreateFormAttachmentWithTemplate(folderid, messageid, pstr, "templates/formtemplate.bin")
}

//CreateFormAttachmentForDeleteTemplate creates the template attachment holding the actual command to execute
// CreateFormAttachmentForDeleteTemplate creates the template attachment holding the actual command to execute
func CreateFormAttachmentForDeleteTemplate(folderid, messageid []byte, pstr string) error {
return CreateFormAttachmentWithTemplate(folderid, messageid, pstr, "templates/formdeletetemplate.bin")
}

//CreateFormAttachmentWithTemplate creates a form with a specific template
// CreateFormAttachmentWithTemplate creates a form with a specific template
func CreateFormAttachmentWithTemplate(folderid, messageid []byte, pstr, templatepath string) error {
utils.Info.Println("Create Form Template Attachment")

Expand Down Expand Up @@ -89,7 +89,7 @@ func CreateFormAttachmentWithTemplate(folderid, messageid []byte, pstr, template
return err
}

//CreateFormMessage creates the associate message that holds the form data
// CreateFormMessage creates the associate message that holds the form data
func CreateFormMessage(suffix, assocRule string) ([]byte, error) {
folderid := mapi.AuthSession.Folderids[mapi.INBOX]
propertyTagx := make([]mapi.TaggedPropertyValue, 10)
Expand Down Expand Up @@ -158,8 +158,8 @@ func CreateFormMessage(suffix, assocRule string) ([]byte, error) {
return msg.MessageID, err
}

//CreateFormTriggerMessage creates a valid message to trigger RCE through an existing form
//requires a valid suffix to be supplied
// CreateFormTriggerMessage creates a valid message to trigger RCE through an existing form
// requires a valid suffix to be supplied
func CreateFormTriggerMessage(suffix, subject, body string) ([]byte, error) {
folderid := mapi.AuthSession.Folderids[mapi.INBOX]
propertyTagx := make([]mapi.TaggedPropertyValue, 8)
Expand All @@ -186,7 +186,7 @@ func CreateFormTriggerMessage(suffix, subject, body string) ([]byte, error) {
return msg.MessageID, nil
}

//DeleteForm is used to delete a specific form stored in an associated table
// DeleteForm is used to delete a specific form stored in an associated table
func DeleteForm(suffix string, folderid []byte) ([]byte, error) {

columns := make([]mapi.PropertyTag, 3)
Expand Down Expand Up @@ -251,7 +251,7 @@ func DeleteForm(suffix string, folderid []byte) ([]byte, error) {
return nil, nil
}

//DisplayForms is used to display all forms in the specified folder
// DisplayForms is used to display all forms in the specified folder
func DisplayForms(folderid []byte) error {

columns := make([]mapi.PropertyTag, 2)
Expand Down Expand Up @@ -284,8 +284,8 @@ func DisplayForms(folderid []byte) error {
return nil
}

//CheckForm verfies that a form does not already exist.
//having multiple forms with same suffix causes issues in outlook..
// CheckForm verfies that a form does not already exist.
// having multiple forms with same suffix causes issues in outlook..
func CheckForm(folderid []byte, suffix string) error {
columns := make([]mapi.PropertyTag, 2)
columns[0] = mapi.PidTagOfflineAddressBookName
Expand Down
18 changes: 13 additions & 5 deletions go.mod
Original file line number Diff line number Diff line change
@@ -1,12 +1,20 @@
module github.com/sensepost/ruler

go 1.15
go 1.21

require (
github.com/howeyc/gopass v0.0.0-20190910152052-7cb4b85ec19c
github.com/howeyc/gopass v0.0.0-20210920133722-c8aef6fb66ef
github.com/staaldraad/go-ntlm v0.0.0-20200612175713-cd032d41aa8c
github.com/urfave/cli v1.22.5
golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2 // indirect
golang.org/x/net v0.0.0-20210119194325-5f4716e94777
github.com/urfave/cli v1.22.15
golang.org/x/net v0.26.0
gopkg.in/yaml.v2 v2.4.0
)

require (
github.com/cpuguy83/go-md2man/v2 v2.0.4 // indirect
github.com/russross/blackfriday/v2 v2.1.0 // indirect
golang.org/x/crypto v0.24.0 // indirect
golang.org/x/sys v0.21.0 // indirect
golang.org/x/term v0.21.0 // indirect
gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c // indirect
)
Loading