Stored XSS into ‘onclick’ Event with Angle Brackets and Double Quotes HTML-Encoded and Single Quotes and Backslash Escaped

Marduk I Am
6 min readFeb 4, 2024

--

Welcome back!

Lab Description:

This lab contains a stored cross-site scripting vulnerability in the comment functionality.

To solve this lab, submit a comment that calls the ‘alert’ function when the comment author name is clicked.

Encoded vs. Escaped:

Although both ‘encoded’ and ‘escaped’ were mentioned in the title of my last write-up, I didn’t think to include a section defining the two because encoding never came into play in the solution.

I think a definition is due now, so here we go.

Encoding involves transforming data into a different format that is safe for transmission or storage in a particular context. In web development and security, encoding is commonly used to ensure that user input does not break the intended structure of data or introduce security vulnerabilities.

For example:

  • HTML Encoding: Characters such as <, >, “, ‘, and & are replaced with their corresponding HTML entities (&lt;, &gt;, &quot;, &apos;, &amp;) to prevent them from being interpreted as HTML tags or causing markup injection.
  • URL Encoding: Special characters in URLs are replaced with percent-encoded representations (e.g., space becomes %20, ampersand becomes %26) to ensure safe transmission over the web.

Escaping involves adding special characters or sequences to data to prevent it from being interpreted in a way that could alter the intended behavior of a system or introduce security vulnerabilities.

For example:

  • HTML Escaping: Characters such as <, >, “, ‘, and & are preceded by a backslash (\) or another character (e.g., &lt;, &gt;, &quot;, &apos;, &amp;) to prevent them from being interpreted as HTML tags or causing injection attacks.

Encoding is generally more automated. For escaping, developers need to manually define how each individual character will be replaced or escaped. Sometime, developers miss or forget about certain characters leading to vulnerabilities. This is why we solved our last lab.

Now, let’s see what this lab has to offer.

Getting Started:

Access the lab. Our blog page, this time, does not have a search bar. However, it does have comment forms on each blog post where we can leave our thoughts.

Click on your favorite blog and scroll down to the comment form. Enter a comment and fill in the places for name, email, and website. Click ‘Post Comment’.

Comment form with places for name, email, and website

Thank you for your comment! Click ‘Back to blog’.

Thank you for your comment. Also shows where ‘Back to blog’ is.

Scroll down to view your comment. Notice how your name is a hyperlink? Let’s see how the server is processing our post.

Right-click on your name and select ‘Inspect’ from the drop-down menu.

In the DOM-browser you can see what is happening behind the scenes.

So, when the anchor element is clicked, the JavaScript code within the ‘onclick’ attribute is executed. It creates an object named ‘tracker’ with a method ‘track’, and then it calls the ‘track’ method with the URL we provided as an argument.

The site is trying to have a comment section where a user can click on another author’s name and they will be taken to the website that that author provided. Not very secure, but OK, here we go.

Crafting the Payload:

If this site was in the ‘real world’, and we were testing for XSS vulnerabilities, this anchor (<a>) tag contains two spots to check if ‘breaking out’ is a possibility.

  • The ‘href’ attribute has the website we provided wrapped in double quotes. We could start by trying to add a double quote to the end of our website to see how it is handled.
  • Or the ‘onclick’ attribute has our website wrapped in single quotes. We are going to start here since the lab title told us this is where our stored XSS will be.

Start by focusing on the ‘onclick’ attribute as if we DO NOT know what has been encoded or escaped. Let’s test and get a feel for how this site works.

First Attempt:

Leave another comment. This time, after filling out the whole comment form again, add a single quote to the end of the website you provide.

Comment form filled out again, this time website ‘payload’ is https://blah.com’.
Click ‘Post Comment’ when done.

Thank you for your comment again! Click ‘Back to blog’.

Thank you for your comment. Showing where ‘Back to blog’ is.

Scroll down to see your comments. Let’s see how the DOM-browser looks now by right-clicking on your latest post and selecting ‘Inspect’ from the drop-down menu.

Screenshot of our comments showing where to right-click.

Notice in the DOM-browser how the DOM handled the addition of the single quote (apostrophe). The DOM escaped the single quote using a backslash.

DOM-browser view focusing on the <a> tag: <a id=”author” href=”https://blah.com\'" onclick=”var tracker={track(){}};tracker.track(‘https://blah.com\'');">Marduk</a>

Second Attempt:

Now that we see the site is escaping the single quote, we could try escaping the escape with our own backslash, effectively cancelling out theirs.

<!-- My payload attempt -->
https://blah.com\'+alert(1)+\'

The objective, here, is to prematurely terminate the ‘onclick’ attribute by closing it with a single quote, enabling the concatenation of our ‘alert(1)’ function within the attribute.

Fill out the whole form again adding our new payload to the ‘Website:’ portion.

Comment form filled in again with https://blah.com\'+alert(1)+\' as the website.

Here are our DOM results:

<a id="author" href="https://blah.com\\\'+alert(1)+\\\'" onclick="var tracker={track(){}};
tracker.track('https://blah.com\\\'+alert(1)+\\\'');">Marduk</a>

As expected, our backslash is being escaped as well. Right now our browser is saying “the user wanted that backslash and quote there, this is not part of my code.”

What if we told the browser “no, this IS part of your code”. This is where HTML encoding comes in: ‘&apos;’, the HTML encoded version of the single quote.

Lab Solution:

When it comes to escaping, developers are tasked with manually inputting each individual character they want escaped. Sometimes things are missed. We are only human, for now.

If we try replacing our single quotes with their HTML encoded equivalent (&apos;), we can tell the browser that the single quote should be treated as part of its code, allowing our JavaScript payload to be interpreted correctly.

<!-- Final payload -->
https://blah.com?&apos;-alert(1)-&apos;

Fill out another comment form. This time placing our final payload in the ‘Website:’ portion.

Bottom portion of filled out comment form with https://blah.com?&apos;-alert(1)-&apos; in website.
Insert your website payload here

Click ‘Post Comment’.

You’ll be greeted with a ‘Congratulations’ message that you solved the lab, but to get the ‘alert(1)’ to pop-up, click ‘Back to blog’. Scroll down to see your posts.

Arrow showing our last post. Right-click to inspect. left-click to trigger alert.

Clicking on your name in the last post will trigger your stored xss pop-up.

Our pop-up alert window.

By right-clicking anywhere on the page and selecting ‘View Page Source’ from the drop-down menu, you can view the source code being returned from the server.

<!-- Response from server. Still HTML encoded. --> 
<a id="author" href="https://blah.com?&apos;-alert(1)-&apos;" onclick="var tracker={track(){}};tracker.track('https://blah.com?&apos;-alert(1)-&apos;');">

Right-clicking and inspecting your name will reveal how the DOM has interpreted our payload.

<a id="author" href="https://blah.com?'-alert(1)-'" onclick="var tracker={track(){}};
tracker.track('https://blah.com?'-alert(1)-'');">Marduk</a>

Our ‘alert(1)’ function is broken out and is allowed to be interpreted correctly.

Congratulations! You have solved another one! keep up the great work!

--

--

Marduk I Am

Cybersecurity enthusiast. Currently focusing on write-ups and bug bounties. Twitter: @marduk_I_am | Mastodon: @Marduk_James@infosec.exchange