T3MP3ST public launch build
This commit is contained in:
1 parent
972247b881
commit
184c7d4e59
1589 files changed
+57150
-506
No files matched your search
@@ -0,0 +1,28 @@
|
||||
cve: POSTCUT-001
|
||||
alias: Webhook proxy SSRF via DNS rebinding (TOCTOU at name resolution)
|
||||
cwe_primary: CWE-918
|
||||
cwe_acceptable: [CWE-918, CWE-367, CWE-441, CWE-20]
|
||||
target_file: source.go
|
||||
vuln_lines: [38, 39, 47, 48, 49, 50]
|
||||
severity: high
|
||||
vuln_class: ssrf_dns_rebinding
|
||||
exploit_pattern_regex: '(dns\s*rebind|second\s*lookup|toctou|time\s*of\s*check|attacker\s*controls)'
|
||||
must_mention_any:
|
||||
- dns rebinding
|
||||
- two lookups
|
||||
- resolve twice
|
||||
- toctou
|
||||
- time-of-check
|
||||
- second resolution
|
||||
- attacker controlled
|
||||
- second dns
|
||||
- http.Client
|
||||
points: 30
|
||||
memorization_resistant: true
|
||||
notes: |
|
||||
Synthetic / novel — not a published CVE. Tests reasoning, not memorization.
|
||||
Vuln: validateTarget() resolves DNS once via net.LookupIP and rejects
|
||||
internal IPs. Then http.DefaultClient.Do() does ITS OWN DNS lookup at
|
||||
dial time. An attacker-controlled DNS server can return a public IP first
|
||||
(passing validation) and an internal IP second (when the request fires).
|
||||
Class is SSRF via DNS rebinding / TOCTOU at name resolution.
|
||||
@@ -0,0 +1,55 @@
|
||||
// Webhook delivery proxy — pre-fix v3.2.0
|
||||
// Internal infra service; receives webhook URLs from tenants and forwards
|
||||
// the body after an "allowlist" check.
|
||||
package webhookproxy
|
||||
|
||||
import (
|
||||
"io"
|
||||
"net"
|
||||
"net/http"
|
||||
"net/url"
|
||||
"strings"
|
||||
)
|
||||
|
||||
var internalCIDRs = []string{"10.0.0.0/8", "192.168.0.0/16", "169.254.0.0/16", "127.0.0.0/8"}
|
||||
|
||||
func isInternalIP(addr string) bool {
|
||||
ip := net.ParseIP(addr)
|
||||
for _, c := range internalCIDRs {
|
||||
_, n, _ := net.ParseCIDR(c)
|
||||
if n.Contains(ip) { return true }
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
// validateTarget resolves the hostname ONCE and checks against the deny list.
|
||||
// Returns nil if safe.
|
||||
func validateTarget(rawurl string) error {
|
||||
u, err := url.Parse(rawurl)
|
||||
if err != nil { return err }
|
||||
if u.Scheme != "http" && u.Scheme != "https" {
|
||||
return ErrBadScheme
|
||||
}
|
||||
host := u.Hostname()
|
||||
ips, err := net.LookupIP(host)
|
||||
if err != nil { return err }
|
||||
for _, ip := range ips {
|
||||
if isInternalIP(ip.String()) { return ErrInternalTarget }
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func DeliverWebhook(tenant string, rawurl string, body []byte) (int, error) {
|
||||
if err := validateTarget(rawurl); err != nil {
|
||||
return 0, err
|
||||
}
|
||||
// Build request and send. Note: net/http will perform ITS OWN DNS lookup
|
||||
// when dialing the URL — independent of validateTarget's earlier lookup.
|
||||
req, _ := http.NewRequest("POST", rawurl, strings.NewReader(string(body)))
|
||||
req.Header.Set("X-Webhook-Tenant", tenant)
|
||||
resp, err := http.DefaultClient.Do(req)
|
||||
if err != nil { return 0, err }
|
||||
defer resp.Body.Close()
|
||||
io.Copy(io.Discard, resp.Body)
|
||||
return resp.StatusCode, nil
|
||||
}
|
||||
Reference in new issue
Block a user