Path: blob/dev/pkg/fuzz/analyzers/xss/analyzer_test.go
4538 views
package xss12import "testing"34func TestAnalyzeReflectionContext(t *testing.T) {5const marker = "FUZZ1337MARKER"67tests := []struct {8name string9body string10marker string11expected XSSContext12}{13// === HTML Body Context ===14{15name: "reflection in plain HTML body text",16body: `<html><body><p>Hello FUZZ1337MARKER world</p></body></html>`,17marker: marker,18expected: ContextHTMLBody,19},20{21name: "reflection in nested div body text",22body: `<div><span>text FUZZ1337MARKER more</span></div>`,23marker: marker,24expected: ContextHTMLBody,25},2627// === Generic Attribute Context ===28{29name: "reflection in regular attribute value",30body: `<input type="text" value="FUZZ1337MARKER">`,31marker: marker,32expected: ContextHTMLAttribute,33},34{35name: "reflection in class attribute",36body: `<div class="foo FUZZ1337MARKER bar">text</div>`,37marker: marker,38expected: ContextHTMLAttribute,39},40{41name: "reflection in data-custom attribute",42body: `<div data-info="FUZZ1337MARKER">text</div>`,43marker: marker,44expected: ContextHTMLAttribute,45},46{47name: "reflection in title attribute",48body: `<img title="FUZZ1337MARKER">`,49marker: marker,50expected: ContextHTMLAttribute,51},5253// === URL Attribute Context ===54{55name: "reflection in href with regular URL",56body: `<a href="https://example.com/FUZZ1337MARKER">link</a>`,57marker: marker,58expected: ContextHTMLAttributeURL,59},60{61name: "reflection in src attribute",62body: `<img src="/images/FUZZ1337MARKER.png">`,63marker: marker,64expected: ContextHTMLAttributeURL,65},66{67name: "reflection in action attribute",68body: `<form action="/submit/FUZZ1337MARKER"></form>`,69marker: marker,70expected: ContextHTMLAttributeURL,71},72{73name: "reflection in formaction attribute",74body: `<button formaction="/do/FUZZ1337MARKER">Go</button>`,75marker: marker,76expected: ContextHTMLAttributeURL,77},7879{80name: "reflection in longdesc attribute",81body: `<img longdesc="https://example.com/desc/FUZZ1337MARKER">`,82marker: marker,83expected: ContextHTMLAttributeURL,84},8586// === Event Handler Attribute Context ===87{88name: "reflection in onclick handler",89body: `<button onclick="doSomething('FUZZ1337MARKER')">Click</button>`,90marker: marker,91expected: ContextHTMLAttributeEvent,92},93{94name: "reflection in onmouseover handler",95body: `<div onmouseover="alert('FUZZ1337MARKER')">hover</div>`,96marker: marker,97expected: ContextHTMLAttributeEvent,98},99{100name: "reflection in onerror handler",101body: `<img src="x" onerror="log('FUZZ1337MARKER')">`,102marker: marker,103expected: ContextHTMLAttributeEvent,104},105{106name: "reflection in onload handler",107body: `<body onload="init('FUZZ1337MARKER')">`,108marker: marker,109expected: ContextHTMLAttributeEvent,110},111{112name: "reflection in onauxclick handler",113body: `<div onauxclick="handle('FUZZ1337MARKER')">right-click</div>`,114marker: marker,115expected: ContextHTMLAttributeEvent,116},117{118name: "reflection in onbeforeinput handler",119body: `<input onbeforeinput="check('FUZZ1337MARKER')">`,120marker: marker,121expected: ContextHTMLAttributeEvent,122},123124// === Script Context (executable) ===125{126name: "reflection in script block with no type",127body: `<script>var x = "FUZZ1337MARKER";</script>`,128marker: marker,129expected: ContextScript,130},131{132name: "reflection in script type=text/javascript",133body: `<script type="text/javascript">var x = "FUZZ1337MARKER";</script>`,134marker: marker,135expected: ContextScript,136},137{138name: "reflection in script type=module",139body: `<script type="module">import "FUZZ1337MARKER";</script>`,140marker: marker,141expected: ContextScript,142},143{144name: "reflection in script type=application/javascript",145body: `<script type="application/javascript">var y = "FUZZ1337MARKER";</script>`,146marker: marker,147expected: ContextScript,148},149{150name: "script type with MIME parameters still executable",151body: `<script type="text/javascript; charset=utf-8">var x = "FUZZ1337MARKER";</script>`,152marker: marker,153expected: ContextScript,154},155156// === javascript: URI -> ContextScript ===157{158name: "javascript URI in href must be ContextScript",159body: `<a href="javascript:alert('FUZZ1337MARKER')">xss</a>`,160marker: marker,161expected: ContextScript,162},163{164name: "javascript URI with whitespace prefix",165body: `<a href=" javascript:void(FUZZ1337MARKER)">xss</a>`,166marker: marker,167expected: ContextScript,168},169{170name: "javascript URI case-insensitive",171body: `<a href="JavaScript:alert('FUZZ1337MARKER')">xss</a>`,172marker: marker,173expected: ContextScript,174},175{176name: "data:text/html URI in src",177body: `<iframe src="data:text/html,<h1>FUZZ1337MARKER</h1>">`,178marker: marker,179expected: ContextScript,180},181{182name: "data:application/xhtml+xml URI in src",183body: `<iframe src="data:application/xhtml+xml,<html xmlns='http://www.w3.org/1999/xhtml'><body>FUZZ1337MARKER</body></html>">`,184marker: marker,185expected: ContextScript,186},187{188name: "data:image/svg+xml URI in iframe src",189body: `<iframe src="data:image/svg+xml,<svg onload=alert(FUZZ1337MARKER)>">`,190marker: marker,191expected: ContextScript,192},193{194name: "data:image/svg+xml URI in img src does not execute",195body: `<img src="data:image/svg+xml,<svg>FUZZ1337MARKER</svg>">`,196marker: marker,197expected: ContextHTMLAttributeURL,198},199{200name: "vbscript URI in href",201body: `<a href="vbscript:msgbox(FUZZ1337MARKER)">click</a>`,202marker: marker,203expected: ContextScript,204},205{206name: "javascript URI in img src does not execute",207body: `<img src="javascript:alert('FUZZ1337MARKER')">`,208marker: marker,209expected: ContextHTMLAttributeURL,210},211{212name: "javascript URI in ping does not execute",213body: `<a href="/" ping="javascript:FUZZ1337MARKER">click</a>`,214marker: marker,215expected: ContextHTMLAttributeURL,216},217{218name: "reflection in ping attribute",219body: `<a href="/" ping="https://tracker.example.com/FUZZ1337MARKER">click</a>`,220marker: marker,221expected: ContextHTMLAttributeURL,222},223224// === ScriptData Context (non-executable script) ===225{226name: "reflection in script type=application/json",227body: `<script type="application/json">{"key": "FUZZ1337MARKER"}</script>`,228marker: marker,229expected: ContextScriptData,230},231{232name: "reflection in script type=text/template",233body: `<script type="text/template"><div>FUZZ1337MARKER</div></script>`,234marker: marker,235expected: ContextScriptData,236},237{238name: "reflection in script type=text/x-handlebars-template",239body: `<script type="text/x-handlebars-template">{{FUZZ1337MARKER}}</script>`,240marker: marker,241expected: ContextScriptData,242},243{244name: "reflection in script type=application/ld+json",245body: `<script type="application/ld+json">{"name":"FUZZ1337MARKER"}</script>`,246marker: marker,247expected: ContextScriptData,248},249250{251name: "duplicate type attributes uses first per HTML5 spec",252body: `<script type="application/json" type="text/javascript">{"key": "FUZZ1337MARKER"}</script>`,253marker: marker,254expected: ContextScriptData,255},256257// === Style Context ===258{259name: "reflection in style block",260body: `<style>.foo { color: FUZZ1337MARKER; }</style>`,261marker: marker,262expected: ContextStyle,263},264{265name: "reflection in style attribute",266body: `<div style="color: FUZZ1337MARKER">text</div>`,267marker: marker,268expected: ContextStyle,269},270271// === Comment Context ===272{273name: "reflection in HTML comment",274body: `<html><!-- FUZZ1337MARKER --><body></body></html>`,275marker: marker,276expected: ContextComment,277},278{279name: "reflection in comment between tags",280body: `<div>text</div><!-- secret: FUZZ1337MARKER --><p>more</p>`,281marker: marker,282expected: ContextComment,283},284285// === srcdoc attribute -> HTMLBody ===286{287name: "reflection in srcdoc attribute",288body: `<iframe srcdoc="<b>FUZZ1337MARKER</b>"></iframe>`,289marker: marker,290expected: ContextHTMLBody,291},292293// === Case-insensitive matching ===294{295name: "case-insensitive marker matching (lowercase body)",296body: `<p>fuzz1337marker appears here</p>`,297marker: marker,298expected: ContextHTMLBody,299},300{301name: "case-insensitive marker matching (mixed case body)",302body: `<p>Fuzz1337Marker appears here</p>`,303marker: marker,304expected: ContextHTMLBody,305},306{307name: "case-insensitive in attribute",308body: `<input value="fuzz1337marker">`,309marker: marker,310expected: ContextHTMLAttribute,311},312313// === Edge cases: marker not found ===314{315name: "marker not found in response",316body: `<html><body><p>Hello world</p></body></html>`,317marker: marker,318expected: ContextUnknown,319},320321// === Edge cases: empty inputs ===322{323name: "empty response body",324body: ``,325marker: marker,326expected: ContextUnknown,327},328{329name: "empty marker",330body: `<p>Hello</p>`,331marker: "",332expected: ContextUnknown,333},334335// === Malformed HTML ===336{337name: "malformed HTML with unclosed tags",338body: `<div><p>FUZZ1337MARKER<span>`,339marker: marker,340expected: ContextHTMLBody,341},342{343name: "malformed HTML with no tags at all",344body: `just plain text FUZZ1337MARKER`,345marker: marker,346expected: ContextHTMLBody,347},348{349name: "malformed script tag not closed",350body: `<script>var x = "FUZZ1337MARKER";`,351marker: marker,352expected: ContextScript,353},354{355name: "broken HTML with unclosed attribute quote",356body: `<a href = "FUZZ1337MARKER >broken`,357marker: marker,358expected: ContextUnknown, // tokenizer cannot parse unclosed quote reliably359},360{361name: "broken HTML with missing closing quote but valid parse",362body: `<a href=FUZZ1337MARKER>broken</a>`,363marker: marker,364expected: ContextHTMLAttributeURL,365},366367// === Multiple reflections: first context wins ===368{369name: "multiple reflections returns first context",370body: `<!-- FUZZ1337MARKER --><p>FUZZ1337MARKER</p>`,371marker: marker,372expected: ContextComment,373},374375// === Self-closing tags ===376{377name: "reflection in self-closing tag attribute",378body: `<img src="FUZZ1337MARKER"/>`,379marker: marker,380expected: ContextHTMLAttributeURL,381},382383// === Script tag attribute reflections ===384{385name: "reflection in script src attribute",386body: `<script src="FUZZ1337MARKER"></script>`,387marker: marker,388expected: ContextHTMLAttributeURL,389},390{391name: "reflection in script src with type attribute",392body: `<script type="text/javascript" src="FUZZ1337MARKER"></script>`,393marker: marker,394expected: ContextHTMLAttributeURL,395},396{397name: "script tag with src and type but reflection in text",398body: `<script type="text/javascript" src="app.js">var z = "FUZZ1337MARKER";</script>`,399marker: marker,400expected: ContextScript,401},402{403name: "non-executable script with marker in src attribute",404body: `<script type="application/json" src="FUZZ1337MARKER"></script>`,405marker: marker,406expected: ContextHTMLAttributeURL,407},408409// === Noscript tag ===410{411name: "reflection inside noscript",412body: `<noscript><p>FUZZ1337MARKER</p></noscript>`,413marker: marker,414expected: ContextHTMLBody,415},416417// === Textarea ===418{419name: "reflection inside textarea",420body: `<textarea>FUZZ1337MARKER</textarea>`,421marker: marker,422expected: ContextHTMLBody,423},424}425426for _, tc := range tests {427t.Run(tc.name, func(t *testing.T) {428result, err := AnalyzeReflectionContext(tc.body, tc.marker)429if err != nil {430t.Fatalf("unexpected error: %v", err)431}432if result != tc.expected {433t.Errorf("got %s, want %s", result, tc.expected)434}435})436}437}438439func TestAnalyzeReflectionContext_NoPanic(t *testing.T) {440// Ensure no panics on various malformed inputs.441inputs := []string{442`<`,443`<>`,444`</>`,445`<<<>>>`,446`<script`,447`<script>`,448`</script>`,449`<!-- `,450`<!-- -->`,451`<div attr=">">`,452`<div attr='`,453string([]byte{0, 1, 2, 3, 4, 5}),454`<script type="">FUZZ1337MARKER</script>`,455`<` + string(make([]byte, 0)) + `>`,456}457458for i, input := range inputs {459func() {460defer func() {461if r := recover(); r != nil {462t.Errorf("panic on input %d: %v", i, r)463}464}()465_, _ = AnalyzeReflectionContext(input, "FUZZ1337MARKER")466}()467}468}469470func TestXSSContextString(t *testing.T) {471tests := []struct {472ctx XSSContext473expected string474}{475{ContextUnknown, "Unknown"},476{ContextHTMLBody, "HTMLBody"},477{ContextHTMLAttribute, "HTMLAttribute"},478{ContextHTMLAttributeURL, "HTMLAttributeURL"},479{ContextHTMLAttributeEvent, "HTMLAttributeEvent"},480{ContextScript, "Script"},481{ContextScriptData, "ScriptData"},482{ContextStyle, "Style"},483{ContextComment, "Comment"},484{XSSContext(99), "XSSContext(99)"},485}486487for _, tc := range tests {488t.Run(tc.expected, func(t *testing.T) {489if got := tc.ctx.String(); got != tc.expected {490t.Errorf("got %q, want %q", got, tc.expected)491}492})493}494}495496497