Intigriti November XSS challenge

In November, Intigriti challenged security researchers to solve an XSS Challenge, which solution needed to have the following rules:

  • Should work on the latest version of Firefox or Chrome
  • Should execute the following JS: alert(document.domain)
  • Should be executed on this domain (challenge-1120.intigriti.io)
  • Shouldn’t be self-XSS or related to MiTM attacks

This is how the challenge appeared when browsing on the relative webpage:

From an initial glance, it appeared like a static webpage having nothing more than a suspicious QR Code. As the first step, I inspected part of the HTML source code.

From the above image, I concluded that probably I was working on the wrong endpoint since the suspicious QRCode was inside a <iframe> pointing to qr.html. Therefore, I continued to work on https://challenge-1120.intigriti.io/qr.html?url=https://go.intigriti.com/submit-solution.

Pretty nice, right? A QRCode is displayed on a webpage and the url GET parameter probably is there to allow setting the data content when it is generated. Obviously, the first thing I tried was to inject the javascript: payload inside the mentioned parameter.

But hey, life is not simple at all. The RegEx was elegantly doing its work. However, then things started to become clearer by inspecting the subsequent JS code.

If the size GET parameter was specified in the URL, it would have been assigned to the JS size constant, which later was used to construct part of the QRCode object (the library used to generate the QRCode was https://github.com/papnkukn/qrcode-svg). Another aspect to highlight here is that the parseInt() Javascript function can be pretty dangerous sometimes, especially when used like in the above code, as illustrated in the following image.

A payload like 1;test allowed bypassing the check performed from the qrcode-svg library to set the width and height of the QR Code, but at the same time, this value constituted part of the style attribute of the QRCode SVG tag created from the library.

Therefore, tampering the size GET parameter, it was possible to modify any CSS property of the QRCode <svg> tag. But how could it be helpful to trigger an XSS? Well, it took me a lot of time to get closer to a conclusion regarding it. First, I searched for known vulnerabilities in the other used JS libraries (https://github.com/niklasvh/html2canvas and https://github.com/cozmo/jsQR), but nothing interesting was found. Then, I read the subsequent JS code more carefully (which is always the right solution when you get stuck).

A click on the QRCode tag triggered the scanCode() function, which performed the following actions:

  • Convert the <svg> element containing the QRCode into a canvas using the html2canvas library.
  • Convert the canvas into an image.
  • Read the QRCode data using the jsQR library.
  • Opening the resulting URL using window.open()… Clearly, it was necessary to open javascript:alert(document.domain) in some way to trigger the XSS.

Since there was not a method to directly modify the DOM elements, but it was possible to modify the style of the <svg> QR Code tag, what about directly tampering the QRCode SVG with another QRCode to confuse the reading library?

This required me a lot of trial and error, but I finally managed to bring out a solution, using the padding and the background-image CSS properties, respectively to move the existing QRCode to the bottom of the screen and setting another one as replacement. Following the final payload:

https://challenge-1120.intigriti.io/qr.html?url=https://www.google.it&size=200px;padding-top:200px;background-image:%20url(data:image/gif;base64,R0lGODlhZABkAKEBAAAAAP%2f%2f%2f%2f%2f%2f%2f%2f%2f%2f%2fyH%2bEUNyZWF0ZWQgd2l0aCBHSU1QACH5BAEKAAIALAAAAABkAGQAAAL%2bjI%2bpy%2b0Po5y02ouz3rz7D4biSJbmA6TqyraKOrlGC8%2f0zUb4nr69JAvgDrydrnjzAWI5G42IfEJqx6WTGrRgf9UukBvMVrZWL4o7BTeFa626nIY3yGH0te2EyhFkfX2%2fQLdG5YfHVkaY0He3omc2lKeYJIlox%2fdW2Bh5Vil1mOnIuXjZ%2bfc4%2bckY6mBKmTnK%2btZ6CMswuwqZyvmKaSuLWoQLbBn4q6mrxHscvJmb6Fs6SNzMozzqPO361blqbVgbC5i8LbwXZd5rFs7Evel9XM5edX6dXt9OSy9OqY9%2fH0%2bt3z5S%2fQDq%2bhew4Il56QwuNHEr4MOFEQ9mA3Hu3T3%2bcLPAhWOWz0jChqokuosDMuNIfKBMIoujbNzLj5YUErxYTozFgfxq6lPyrFg0eC5R%2bqQHFCc0kDmVzjGmU4NKUi0vCPLUYWpTnusYRWX37GvIsPakoVtmSChZojfZbiQZsirNs0G9Ht3arWRcDlcrPmVrlKtVqGbBOu0p2M1Qv0sPt81arfFiswKz1RWFKulktJUtO24r9g%2b2zvsug%2f4GV3OuvGo%2fv5Np2i7Wk60TI9bp8XZm2poHi6wtGu5qaFIjA6csfLfQ4r9VI2cpG2aJvqih7%2fzb%2bwN1t8mtsxbr%2b7W2mbnJp8Uuu3yG0d%2fPj0%2fpmiHn9oBhG4%2ftu%2bNR%2fOb%2bkSKWKd1ptlGlW3N6sYaegOEVONxeBKoTnWIxSYZbWZuNxt92fzWI0Fj3ufdgg%2bKFaOFU4NH31mxdCWjTilVtxx9tpIGIYonKJahRFA%2blqCFvMtLoAY%2bEefdjfSEIuRmCRWZYmZIsPmdQjAx%2byJWJhRVlmH8zOeeJfkTuQs54L3pm5IlgQthfeaEB6eKZNX65ppGndGmhQ0tSKeVOXkZ515Sc5dnjdS6txWVwAwZ6lWJmrmWmnlc6mCV%2bjLKJqDGKAjmpnFulKNeB71FJQaVDdQrpp78BOiRenpZK4qmfScnpf27KuiWOZyZKq62P%2bUhcmI7SpdRljFGo40oJCrsoH5uFgrqqnQ9OOEaTxvZaa5rKToRtttpuy2233n4LbrjijktuuRwUAAA7%0a%0a);background-repeat:no-repeat;height:400

Some things to highlight about the payload:

  • The width and height CSS properties needed to stay around 400, in order to properly throw aside the other QR Code and read the new one. In fact, the value of the size GET parameter was the one actually used to create the canvas from the DOM elements displayed on the screen.

  • The padding-top property was used to move the existing QRCode to the bottom side of the screen. A value of 200px was okay for most screen sizes.
  • The background-image needed to be encoded in base64, as I noticed that html2canvas has some conversion bugs when URL images are specified. The base64 encoded image specified contained a GIF QR Code created from a random QR Code generator website. The Qr Code contained the following data: javascript:alert(document.domain)
  • The background-repeat:no-repeat property was necessary to show only one QR Code and prevent reading issues using the jsQR library.

This is the result using the above payload.

Clicking on one of the two QR Codes, successfully triggered the javascript payload specified having the correct domain as scope.

 

Share it