Summary
Artalk’s frontend HTML sanitizer allowlists the data: URL scheme. Comment HTML such as <a href="data:text/html;base64,…"> therefore survives sanitization and is injected via innerHTML. Clicking the link executes arbitrary JavaScript in a data: browsing context.
This is present in v2.9.1 (insane) and still present in v2.10.0 after the sanitizer was switched to DOMPurify. v2.10.0’s release notes say the previous URL-scheme allowlist was intentionally retained, so upgrading to 2.10.0 does not close this issue.
Source (v2.9.1 ui/artalk/src/lib/sanitizer.ts):
allowedSchemes: [
'http',
'https',
'mailto',
'data', // for support base64 encoded image (安全性有待考虑)
],
v2.10.0 still has:
const allowedSchemes = new Set(['http', 'https', 'mailto', 'data'])
// …
ADD_DATA_URI_TAGS: ['a'],
The comment already notes that allowing data is a security concern. The intended use appears to be base64 images, but the allowlist does not restrict MIME type, so data:text/html on <a href> is accepted.
<script> tags are stripped (allowlist does not include script). The bypass is the allowed data: scheme on a[href].
Affected versions
- Confirmed in source: v2.9.1 (
ui/artalk/src/lib/sanitizer.ts + insane@2.6.2)
- Confirmed in source: v2.10.0 (
DOMPurify, same data scheme + ADD_DATA_URI_TAGS: ['a'])
- v2.10.0 tests still expect
<a href="data:text/plain,hello"> to be kept
Reproduction (local, sanitizer only)
Using the v2.9.1 insane options copied from sanitizer.ts:
import insane from 'insane'
const insaneOptions = {
allowedSchemes: ['http', 'https', 'mailto', 'data'],
allowedTags: ['a', 'p', 'img' /* …same as Artalk… */],
allowedAttributes: {
a: ['href', 'name', 'target', 'aria-label', 'rel'],
img: ['src', 'alt', 'title'],
},
}
const payload =
'<p><a href="data:text/html;base64,PHNjcmlwdD5hbGVydCgxKTwvc2NyaXB0Pg==" target="_blank">click</a></p>'
console.log(insane(payload, insaneOptions))
// → <p><a href="data:text/html;base64,…" target="_blank">click</a></p>
// data: survives
console.log(insane(payload, { ...insaneOptions, allowedSchemes: ['http', 'https', 'mailto'] }))
// → <p><a target="_blank">click</a></p>
// href stripped
Base64 payload decodes to <script>alert(1)</script>.
In the live UI: put that HTML in the comment editor and open Preview. Artalk runs marked then sanitize() / getContentMarked() into innerHTML. A data:text/html link appears; clicking it (often target="_blank") executes the script.
Impact
- Unauthenticated comment content (or preview of unsaved content) can carry a
data:text/html link.
- Clicking the link runs attacker JavaScript. That document’s
origin is "null" (data: URLs), so this is not classic same-origin cookie theft from the host site (document.cookie on data: pages is blocked).
- Practical impact: phishing / fake “support” pages, arbitrary JS in the
data: document, social-engineering of moderators who click links while reviewing comments.
<img src="https://…"> in comments can also fire automatically when HTML is rendered (OOB ping), which is a separate issue from JS execution.
Suggested fix
- Remove
'data' from allowedSchemes, or only allow data: on <img src> with an image MIME (data:image/png, data:image/jpeg, data:image/gif, data:image/webp) and never on <a href>.
- Explicitly reject
data:text/html, data:text/javascript, data:application/javascript, and data:image/svg+xml.
- Drop
ADD_DATA_URI_TAGS: ['a'] in the DOMPurify config.
- Add a regression test:
sanitize('<a href="data:text/html;base64,PHNjcmlwdD5hbGVydCgxKTwvc2NyaXB0Pg==">x</a>') must not keep that href.
- Server-side: do not trust client HTML; sanitize
content the same way so pending/moderator views cannot render raw data:text/html.
v2.10.0 replacing insane with DOMPurify is a good hardening step for ReDoS / unmaintained parser issues; it does not by itself fix this allowlist bug.
Summary
Artalk’s frontend HTML sanitizer allowlists the
data:URL scheme. Comment HTML such as<a href="data:text/html;base64,…">therefore survives sanitization and is injected viainnerHTML. Clicking the link executes arbitrary JavaScript in adata:browsing context.This is present in v2.9.1 (
insane) and still present in v2.10.0 after the sanitizer was switched to DOMPurify. v2.10.0’s release notes say the previous URL-scheme allowlist was intentionally retained, so upgrading to 2.10.0 does not close this issue.Source (v2.9.1
ui/artalk/src/lib/sanitizer.ts):v2.10.0 still has:
The comment already notes that allowing
datais a security concern. The intended use appears to be base64 images, but the allowlist does not restrict MIME type, sodata:text/htmlon<a href>is accepted.<script>tags are stripped (allowlist does not includescript). The bypass is the alloweddata:scheme ona[href].Affected versions
ui/artalk/src/lib/sanitizer.ts+insane@2.6.2)DOMPurify, samedatascheme +ADD_DATA_URI_TAGS: ['a'])<a href="data:text/plain,hello">to be keptReproduction (local, sanitizer only)
Using the v2.9.1
insaneoptions copied fromsanitizer.ts:Base64 payload decodes to
<script>alert(1)</script>.In the live UI: put that HTML in the comment editor and open Preview. Artalk runs
markedthensanitize()/getContentMarked()intoinnerHTML. Adata:text/htmllink appears; clicking it (oftentarget="_blank") executes the script.Impact
data:text/htmllink.originis"null"(data:URLs), so this is not classic same-origin cookie theft from the host site (document.cookieondata:pages is blocked).data:document, social-engineering of moderators who click links while reviewing comments.<img src="https://…">in comments can also fire automatically when HTML is rendered (OOB ping), which is a separate issue from JS execution.Suggested fix
'data'fromallowedSchemes, or only allowdata:on<img src>with an image MIME (data:image/png,data:image/jpeg,data:image/gif,data:image/webp) and never on<a href>.data:text/html,data:text/javascript,data:application/javascript, anddata:image/svg+xml.ADD_DATA_URI_TAGS: ['a']in the DOMPurify config.sanitize('<a href="data:text/html;base64,PHNjcmlwdD5hbGVydCgxKTwvc2NyaXB0Pg==">x</a>')must not keep thathref.contentthe same way so pending/moderator views cannot render rawdata:text/html.v2.10.0 replacing
insanewith DOMPurify is a good hardening step for ReDoS / unmaintained parser issues; it does not by itself fix this allowlist bug.