|
| 1 | +/* |
| 2 | +·━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━· |
| 3 | +: : |
| 4 | +: █▀ █ █▀▀ · Blazing-fast pentesting suite : |
| 5 | +: ▄█ █ █▀ · BSD 3-Clause License : |
| 6 | +: : |
| 7 | +: (c) 2022-2026 vmfunc, xyzeva, : |
| 8 | +: lunchcat alumni & contributors : |
| 9 | +: : |
| 10 | +·━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━· |
| 11 | +*/ |
| 12 | + |
| 13 | +/* |
| 14 | +
|
| 15 | + BSD 3-Clause License |
| 16 | + (c) 2022-2026 vmfunc, xyzeva & contributors |
| 17 | +
|
| 18 | +*/ |
| 19 | + |
| 20 | +package detectors |
| 21 | + |
| 22 | +import ( |
| 23 | + "net/http" |
| 24 | + |
| 25 | + fw "github.com/vmfunc/sif/internal/scan/frameworks" |
| 26 | +) |
| 27 | + |
| 28 | +// The detectors here identify the WAF in front of a target. Each keys on a |
| 29 | +// header, Set-Cookie name, or block-page string the WAF itself sets, so a page |
| 30 | +// that merely names the vendor in prose does not trip it. |
| 31 | +func init() { |
| 32 | + fw.Register(&cloudflareWAFDetector{}) |
| 33 | + fw.Register(&sucuriDetector{}) |
| 34 | + fw.Register(&incapsulaDetector{}) |
| 35 | + fw.Register(&bigipASMDetector{}) |
| 36 | + fw.Register(&modSecurityDetector{}) |
| 37 | +} |
| 38 | + |
| 39 | +// cloudflareWAFDetector detects an active Cloudflare WAF or bot challenge via the |
| 40 | +// cf-mitigated header and __cf_chl_ tokens, which appear only on an interception |
| 41 | +// response (plain proxying is covered by the cf-ray CDN marker). |
| 42 | +type cloudflareWAFDetector struct{} |
| 43 | + |
| 44 | +func (d *cloudflareWAFDetector) Name() string { return "Cloudflare WAF" } |
| 45 | + |
| 46 | +func (d *cloudflareWAFDetector) Signatures() []fw.Signature { |
| 47 | + return []fw.Signature{ |
| 48 | + {Pattern: "cf-mitigated", Weight: 0.6, HeaderOnly: true}, |
| 49 | + {Pattern: "__cf_chl_", Weight: 0.5}, |
| 50 | + {Pattern: "Attention Required! | Cloudflare", Weight: 0.4}, |
| 51 | + } |
| 52 | +} |
| 53 | + |
| 54 | +func (d *cloudflareWAFDetector) Detect(body string, headers http.Header) (float32, string) { |
| 55 | + base := fw.NewBaseDetector(d.Name(), d.Signatures()) |
| 56 | + return sigmoidConfidence(base.MatchSignatures(body, headers)), "" |
| 57 | +} |
| 58 | + |
| 59 | +// sucuriDetector detects the Sucuri (GoDaddy) cloud WAF via its x-sucuri-id and |
| 60 | +// x-sucuri-cache headers, backed by the block-page string. |
| 61 | +type sucuriDetector struct{} |
| 62 | + |
| 63 | +func (d *sucuriDetector) Name() string { return "Sucuri WAF" } |
| 64 | + |
| 65 | +func (d *sucuriDetector) Signatures() []fw.Signature { |
| 66 | + return []fw.Signature{ |
| 67 | + {Pattern: "x-sucuri-id", Weight: 0.6, HeaderOnly: true}, |
| 68 | + {Pattern: "x-sucuri-cache", Weight: 0.4, HeaderOnly: true}, |
| 69 | + {Pattern: "Sucuri WebSite Firewall", Weight: 0.5}, |
| 70 | + } |
| 71 | +} |
| 72 | + |
| 73 | +func (d *sucuriDetector) Detect(body string, headers http.Header) (float32, string) { |
| 74 | + base := fw.NewBaseDetector(d.Name(), d.Signatures()) |
| 75 | + return sigmoidConfidence(base.MatchSignatures(body, headers)), "" |
| 76 | +} |
| 77 | + |
| 78 | +// incapsulaDetector detects the Imperva Incapsula cloud WAF via its x-iinfo |
| 79 | +// header, incap_ses_ Set-Cookie name, and block-page incident string. Each is |
| 80 | +// Incapsula-exclusive and clears the threshold on its own. |
| 81 | +type incapsulaDetector struct{} |
| 82 | + |
| 83 | +func (d *incapsulaDetector) Name() string { return "Imperva Incapsula" } |
| 84 | + |
| 85 | +func (d *incapsulaDetector) Signatures() []fw.Signature { |
| 86 | + return []fw.Signature{ |
| 87 | + {Pattern: "x-iinfo", Weight: 0.6, HeaderOnly: true}, |
| 88 | + {Pattern: "incap_ses_", Weight: 0.5, HeaderOnly: true}, |
| 89 | + {Pattern: "Incapsula incident ID", Weight: 0.5}, |
| 90 | + } |
| 91 | +} |
| 92 | + |
| 93 | +func (d *incapsulaDetector) Detect(body string, headers http.Header) (float32, string) { |
| 94 | + base := fw.NewBaseDetector(d.Name(), d.Signatures()) |
| 95 | + return sigmoidConfidence(base.MatchSignatures(body, headers)), "" |
| 96 | +} |
| 97 | + |
| 98 | +// bigipASMDetector detects the F5 BIG-IP Application Security Manager via its |
| 99 | +// block-page rejection sentence and support-id line. Block-page only, so it never |
| 100 | +// fires on a passing response. |
| 101 | +type bigipASMDetector struct{} |
| 102 | + |
| 103 | +func (d *bigipASMDetector) Name() string { return "F5 BIG-IP ASM" } |
| 104 | + |
| 105 | +func (d *bigipASMDetector) Signatures() []fw.Signature { |
| 106 | + return []fw.Signature{ |
| 107 | + {Pattern: "The requested URL was rejected. Please consult with your administrator.", Weight: 0.6}, |
| 108 | + {Pattern: "Your support ID is:", Weight: 0.3}, |
| 109 | + } |
| 110 | +} |
| 111 | + |
| 112 | +func (d *bigipASMDetector) Detect(body string, headers http.Header) (float32, string) { |
| 113 | + base := fw.NewBaseDetector(d.Name(), d.Signatures()) |
| 114 | + return sigmoidConfidence(base.MatchSignatures(body, headers)), "" |
| 115 | +} |
| 116 | + |
| 117 | +// modSecurityDetector detects ModSecurity via its generated block page (primary) |
| 118 | +// and the Mod_Security/NOYB server-token values (supporting, since suppressible). |
| 119 | +type modSecurityDetector struct{} |
| 120 | + |
| 121 | +func (d *modSecurityDetector) Name() string { return "ModSecurity" } |
| 122 | + |
| 123 | +func (d *modSecurityDetector) Signatures() []fw.Signature { |
| 124 | + return []fw.Signature{ |
| 125 | + {Pattern: "This error was generated by Mod_Security", Weight: 0.6}, |
| 126 | + {Pattern: "Mod_Security", Weight: 0.3, HeaderOnly: true}, |
| 127 | + {Pattern: "NOYB", Weight: 0.2, HeaderOnly: true}, |
| 128 | + } |
| 129 | +} |
| 130 | + |
| 131 | +func (d *modSecurityDetector) Detect(body string, headers http.Header) (float32, string) { |
| 132 | + base := fw.NewBaseDetector(d.Name(), d.Signatures()) |
| 133 | + return sigmoidConfidence(base.MatchSignatures(body, headers)), "" |
| 134 | +} |
0 commit comments