Add recon form fingerprint library and SSRF canary mode.
Some checks failed
CI Docker Mining Proof / Linux agent hashrate proof (push) Has been cancelled
Some checks failed
CI Docker Mining Proof / Linux agent hashrate proof (push) Has been cancelled
Score crawl inputs by SSRF-prone field names and register ping-back canaries for operator paste confirmation.
This commit is contained in:
104
server/internal/recon/fingerprint.go
Normal file
104
server/internal/recon/fingerprint.go
Normal file
@@ -0,0 +1,104 @@
|
||||
package recon
|
||||
|
||||
import (
|
||||
"sort"
|
||||
"strings"
|
||||
|
||||
"golang.org/x/net/html"
|
||||
)
|
||||
|
||||
var formFingerprintKeywords = []struct {
|
||||
keyword string
|
||||
weight int
|
||||
}{
|
||||
{"url", 12}, {"src", 10}, {"href", 10}, {"callback", 14}, {"redirect", 13},
|
||||
{"avatar", 8}, {"import", 11}, {"feed", 9}, {"screenshot", 12}, {"pdf", 10}, {"proxy", 11},
|
||||
}
|
||||
|
||||
func ScoreFormFieldFingerprint(name, id, placeholder string) (score int, matches []string) {
|
||||
joined := strings.ToLower(strings.Join([]string{name, id, placeholder}, " "))
|
||||
seen := map[string]bool{}
|
||||
for _, kw := range formFingerprintKeywords {
|
||||
if strings.Contains(joined, kw.keyword) {
|
||||
score += kw.weight
|
||||
if !seen[kw.keyword] {
|
||||
seen[kw.keyword] = true
|
||||
matches = append(matches, kw.keyword)
|
||||
}
|
||||
}
|
||||
}
|
||||
return score, matches
|
||||
}
|
||||
|
||||
func ExtractFormFieldFingerprints(pageURL, body string) []FormFieldFingerprint {
|
||||
root, err := html.Parse(strings.NewReader(body))
|
||||
if err != nil {
|
||||
return nil
|
||||
}
|
||||
var out []FormFieldFingerprint
|
||||
var walk func(*html.Node)
|
||||
walk = func(n *html.Node) {
|
||||
if n.Type == html.ElementNode && (n.Data == "input" || n.Data == "textarea" || n.Data == "select") {
|
||||
name := fingerprintAttr(n, "name")
|
||||
id := fingerprintAttr(n, "id")
|
||||
placeholder := fingerprintAttr(n, "placeholder")
|
||||
score, matches := ScoreFormFieldFingerprint(name, id, placeholder)
|
||||
if score > 0 {
|
||||
out = append(out, FormFieldFingerprint{
|
||||
PageURL: pageURL, Name: name, ID: id, Placeholder: placeholder,
|
||||
Score: score, Matches: matches,
|
||||
})
|
||||
}
|
||||
}
|
||||
for c := n.FirstChild; c != nil; c = c.NextSibling {
|
||||
walk(c)
|
||||
}
|
||||
}
|
||||
walk(root)
|
||||
sort.Slice(out, func(i, j int) bool {
|
||||
if out[i].Score != out[j].Score {
|
||||
return out[i].Score > out[j].Score
|
||||
}
|
||||
if out[i].Name != out[j].Name {
|
||||
return out[i].Name < out[j].Name
|
||||
}
|
||||
return out[i].ID < out[j].ID
|
||||
})
|
||||
return out
|
||||
}
|
||||
|
||||
func fingerprintAttr(n *html.Node, key string) string {
|
||||
for _, a := range n.Attr {
|
||||
if a.Key == key {
|
||||
return a.Val
|
||||
}
|
||||
}
|
||||
return ""
|
||||
}
|
||||
|
||||
func TopFormFieldFingerprint(fields []FormFieldFingerprint) *FormFieldFingerprint {
|
||||
if len(fields) == 0 {
|
||||
return nil
|
||||
}
|
||||
top := fields[0]
|
||||
return &top
|
||||
}
|
||||
|
||||
func ApplyCanaryPasteTarget(fields []FormFieldFingerprint, canaryURL string) {
|
||||
for i := range fields {
|
||||
fields[i].PasteTarget = canaryURL
|
||||
}
|
||||
}
|
||||
|
||||
func AppendPageFingerprints(report *CrawlReport, pageURL, body string) {
|
||||
if report == nil {
|
||||
return
|
||||
}
|
||||
fps := ExtractFormFieldFingerprints(pageURL, body)
|
||||
report.FingerprintFields = append(report.FingerprintFields, fps...)
|
||||
for _, fp := range fps {
|
||||
if fp.Score >= 10 {
|
||||
report.SSRFScore += fp.Score / 5
|
||||
}
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user