[SHEHZADE//ABDULLAH ANSARI]
Red Team Consultant @ Google | MS in Cybersecurity @ NYU
● Notice //GitHub (@shehzade) is temporarily suspended pending an automated false-positive review. All tools are hosted on this Code Mirror and available on request (contact); GitHub links will return once reinstated.
PUBLIC MIRRORGo v1.0
Multi-protocol network egress assessment and DLP verification framework featuring automated L4 sweeps, corporate proxy traversal, and covert protocol smuggling across 20+ channels.
$
git clone https://shehzade.io/repos/contraband.git
Copy
Source ZIP (1.93 MB)
138 lines · 3.3 KB
| 1 | package proxy |
| 2 | |
| 3 | import ( |
| 4 | "crypto/md5" |
| 5 | "crypto/rand" |
| 6 | "encoding/hex" |
| 7 | "fmt" |
| 8 | "strings" |
| 9 | ) |
| 10 | |
| 11 | // DigestChallenge represents parsed fields from Proxy-Authenticate: Digest ... |
| 12 | type DigestChallenge struct { |
| 13 | Realm string |
| 14 | Nonce string |
| 15 | QOP string |
| 16 | Algorithm string |
| 17 | Opaque string |
| 18 | } |
| 19 | |
| 20 | // ParseDigestChallenge extracts key-value directives from a Digest challenge string. |
| 21 | func ParseDigestChallenge(header string) DigestChallenge { |
| 22 | c := DigestChallenge{ |
| 23 | Algorithm: "MD5", |
| 24 | } |
| 25 | header = strings.TrimSpace(header) |
| 26 | if strings.HasPrefix(strings.ToLower(header), "digest ") { |
| 27 | header = strings.TrimSpace(header[7:]) |
| 28 | } |
| 29 | |
| 30 | parts := splitDirectives(header) |
| 31 | for k, v := range parts { |
| 32 | switch strings.ToLower(k) { |
| 33 | case "realm": |
| 34 | c.Realm = v |
| 35 | case "nonce": |
| 36 | c.Nonce = v |
| 37 | case "qop": |
| 38 | c.QOP = v |
| 39 | case "algorithm": |
| 40 | c.Algorithm = v |
| 41 | case "opaque": |
| 42 | c.Opaque = v |
| 43 | } |
| 44 | } |
| 45 | return c |
| 46 | } |
| 47 | |
| 48 | func splitDirectives(s string) map[string]string { |
| 49 | res := make(map[string]string) |
| 50 | for len(s) > 0 { |
| 51 | s = strings.TrimSpace(s) |
| 52 | commaIdx := strings.Index(s, ",") |
| 53 | equalIdx := strings.Index(s, "=") |
| 54 | if equalIdx == -1 { |
| 55 | break |
| 56 | } |
| 57 | |
| 58 | key := strings.TrimSpace(s[:equalIdx]) |
| 59 | rest := strings.TrimSpace(s[equalIdx+1:]) |
| 60 | |
| 61 | var val string |
| 62 | if strings.HasPrefix(rest, "\"") { |
| 63 | // Quoted string |
| 64 | quoteEnd := strings.Index(rest[1:], "\"") |
| 65 | if quoteEnd != -1 { |
| 66 | val = rest[1 : quoteEnd+1] |
| 67 | after := rest[quoteEnd+2:] |
| 68 | if nextComma := strings.Index(after, ","); nextComma != -1 { |
| 69 | s = after[nextComma+1:] |
| 70 | } else { |
| 71 | s = "" |
| 72 | } |
| 73 | } else { |
| 74 | val = strings.Trim(rest, "\"") |
| 75 | s = "" |
| 76 | } |
| 77 | } else { |
| 78 | // Unquoted token |
| 79 | if commaIdx != -1 && commaIdx > equalIdx { |
| 80 | val = strings.TrimSpace(rest[:commaIdx-equalIdx-1]) |
| 81 | s = rest[commaIdx-equalIdx:] |
| 82 | if len(s) > 0 && s[0] == ',' { |
| 83 | s = s[1:] |
| 84 | } |
| 85 | } else { |
| 86 | val = rest |
| 87 | s = "" |
| 88 | } |
| 89 | } |
| 90 | res[key] = val |
| 91 | } |
| 92 | return res |
| 93 | } |
| 94 | |
| 95 | func md5Hex(data string) string { |
| 96 | h := md5.Sum([]byte(data)) |
| 97 | return hex.EncodeToString(h[:]) |
| 98 | } |
| 99 | |
| 100 | // BuildDigestAuthHeader computes the RFC 7616 Digest response for HTTP CONNECT. |
| 101 | func BuildDigestAuthHeader(username, password, targetAddr string, ch DigestChallenge, nc int) string { |
| 102 | ncStr := fmt.Sprintf("%08x", nc) |
| 103 | cnonceBytes := make([]byte, 8) |
| 104 | _, _ = rand.Read(cnonceBytes) |
| 105 | cnonce := hex.EncodeToString(cnonceBytes) |
| 106 | |
| 107 | ha1 := md5Hex(fmt.Sprintf("%s:%s:%s", username, ch.Realm, password)) |
| 108 | ha2 := md5Hex(fmt.Sprintf("CONNECT:%s", targetAddr)) |
| 109 | |
| 110 | var response string |
| 111 | hasAuthQOP := strings.Contains(strings.ToLower(ch.QOP), "auth") |
| 112 | |
| 113 | if hasAuthQOP { |
| 114 | response = md5Hex(fmt.Sprintf("%s:%s:%s:%s:auth:%s", ha1, ch.Nonce, ncStr, cnonce, ha2)) |
| 115 | } else { |
| 116 | response = md5Hex(fmt.Sprintf("%s:%s:%s", ha1, ch.Nonce, ha2)) |
| 117 | } |
| 118 | |
| 119 | var sb strings.Builder |
| 120 | sb.WriteString("Digest ") |
| 121 | sb.WriteString(fmt.Sprintf("username=\"%s\", ", username)) |
| 122 | sb.WriteString(fmt.Sprintf("realm=\"%s\", ", ch.Realm)) |
| 123 | sb.WriteString(fmt.Sprintf("nonce=\"%s\", ", ch.Nonce)) |
| 124 | sb.WriteString(fmt.Sprintf("uri=\"%s\", ", targetAddr)) |
| 125 | sb.WriteString(fmt.Sprintf("response=\"%s\"", response)) |
| 126 | |
| 127 | if hasAuthQOP { |
| 128 | sb.WriteString(fmt.Sprintf(", qop=auth, nc=%s, cnonce=\"%s\"", ncStr, cnonce)) |
| 129 | } |
| 130 | if ch.Opaque != "" { |
| 131 | sb.WriteString(fmt.Sprintf(", opaque=\"%s\"", ch.Opaque)) |
| 132 | } |
| 133 | if ch.Algorithm != "" { |
| 134 | sb.WriteString(fmt.Sprintf(", algorithm=%s", ch.Algorithm)) |
| 135 | } |
| 136 | |
| 137 | return sb.String() |
| 138 | } |