Hello everyone , hope you are doing well , in this post I will be sharing my writeup for THM’s Fortress room which was a medium linux based . This room was a little challenging in a way that the foothold require some researching and thinking out of the box , there were 3 ports open on the machine ssh , ftp , telnet and http. Through ftp we can get a python 2.7 byte file compiled that we can de-compile by looking around for script that can do that which will present us a python file having username and password stored in long byte format which we can easily convert it to string , we can use these credentials on telent to reveal a secret page having html and php extension that , html version will reveal us the code behind the php file so we can understand how it’s checking the GET parameters and how we can bypass the conditions after researching about hash colliding we can bypass the check which would give us the ssh key for user and after that we are presented with a restricted bash shell that we have to break out of , after doing horizontal escalation we can see that the user we got is in “adm” group which means we can read log files that reveals the password that we can use to check sudo permissions and get root.


PORT     STATE SERVICE REASON         VERSION                                            
22/tcp open ssh syn-ack ttl 63 OpenSSH 7.2p2 Ubuntu 4ubuntu2.10 (Ubuntu Linux; protocol 2.0)
| ssh-hostkey:
| 2048 9f:d0:bb:c7:e2:ee:7f:91:fe:c2:6a:a6:bb:b2:e1:91 (RSA)
| ssh-rsa AAAAB3NzaC1yc2EAAAADAQABAAABAQCXx2nOQ7SVuA1liJqX+ZR2KK9Oipy+1cd4ZZ3iD+/xuAkvon338WPfjcGmNaBd0McHqunhvl1xJZZMsOsjVuMUSD0GUX3YF6BQ/RdVxQ00/g
| 256 06:4b:fe:c0:6e:e4:f4:7e:e1:db:1c:e7:79:9d:2b:1d (ECDSA)
| ecdsa-sha2-nistp256 AAAAE2VjZHNhLXNoYTItbmlzdHAyNTYAAAAIbmlzdHAyNTYAAABBBPBJBTN55zS77xduARAxZeA+xhJt04e3yVZpkmTObu2JMOjxTzFoK4mftWUdLsx1bs1mDIWWXL
| 256 0d:0e:ce:57:00:1a:e2:8d:d2:1b:2e:6d:92:3e:65:c4 (ED25519)
|_ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIJezjvXtsHInz+XQ4hYfNBX5kjinTpiKRYaK5rF1og71
5581/tcp open ftp syn-ack ttl 63 vsftpd 3.0.3
| ftp-anon: Anonymous FTP login allowed (FTP code 230)
|_-rw-r--r-- 1 ftp ftp 305 Jul 25 20:06 marked.txt
| ftp-syst:
| FTP server status:
| Connected to ::ffff:
| Logged in as ftp
| No session bandwidth limit
| Session timeout in seconds is 300
| Control connection is plain text
| Data connections will be plain text
| At session startup, client count was 2
| vsFTPd 3.0.3 - secure, fast, stable
|_End of status
5752/tcp open unknown syn-ack ttl 63
5752/tcp open unknown syn-ack ttl 63
| fingerprint-strings:
| DNSStatusRequestTCP, DNSVersionBindReqTCP, FourOhFourRequest, GenericLines, GetRequest, HTTPOptions, Help, LANDesk-RC, LPDString, RTSPRequest, S
IPOptions, X11Probe:
| Chapter 1: A Call for help
| Username: Password:
| Kerberos, LDAPBindReq, LDAPSearchReq, NCP, NULL, RPCCheck, SMBProgNeg, SSLSessionReq, TLSSessionReq, TerminalServer, TerminalServerCookie:
| Chapter 1: A Call for help
|_ Username:
7331/tcp open http syn-ack ttl 63 Apache httpd 2.4.18 ((Ubuntu))
| http-methods:
|_ Supported Methods: OPTIONS GET HEAD POST
|_http-server-header: Apache/2.4.18 (Ubuntu)
|_http-title: Apache2 Ubuntu Default Page: It works

We can see port 5581 which is ftp and anonymous login is enabled so we can login as anonymous user , on port 7331 , apache server is running and on 5752 seems like some response so we'll get too it also we are told to add these two domain names fortress, temple.fortress from the room description , we can add those to /etc/hosts file

PORT 5581 (FTP)

So we can download these files using get

We don’t find much information from marked.txt other than telling us the username veekay

And the other file is python 2.7 compiled byte-file

We decompile this file to human readable file using uncompyle2 , we can git clone it's repository and install the binary using python install

Here we see username and password which are hard coded converted from string to byte_to_long format , so let's try to convert a random string to see a long byte format also we can convert it back to a byte string using long_to_bytes

from Crypto.Util.number import bytes_to_long,long_to_bytestest = bytes("abcbbc","utf-8") # can be written as b"abcbbc" as well
long_test = bytes_to_long(test)
print (long_test)
print (long_to_bytes(long_test))

But we don’t get L at the end of long byte string , let's try removing it from username and password variables and try to convert it back to byte string format

These are aleady in long byte format so we just need to use long_to_bytes

So we got the username and password in a string format but the question is where do we send these credentials ? I tried making a http request on port 5752 but connection timed out so it must be running on some other protocol

PORT 5752 (Telnet)

We get this text t3mple_0f_y0ur_51n5 which is from that secrets.txt because it was calling the function which would return the contents of that file on providing correct credentials

PORT 7331 (HTTP)

On the apache web server we only get the default web page , I tried running gobuster with big.txt , common.txt but came up with nothing , so then tried look for the page we got from secrets.txt but it didn't loaded until I added a php extension to it

Again we don’t see much on this page but after viewing the source code through ctrl+u

The reason why we are seeing html code is because browser executes php code but renders html code that’s why we can html tags here , also going to css file we can get a “hint”

This looks like base64 encoded text which on decoding we get this

It’s talking about “colliding” something maybe a secret or a hash ? Judging from that html commented code we saw , let’s try changing the extension to .html

And we got a different page with input fields also viewing the html source code

We can see some php code here

What it’s doing is that , taking two GET parameters user and pass doing a type check also checking it's SHA-1 hash if they are similar which is what we call hash collision and back in 2017 someone discovered a collision in SHA-1 by calculating the hash of two pdf files

So what if we make a python script that will fetch those files content in variables and then we will make a GET request to t3mple_0f_y0ur_51n5.php with those parameters

import requests# Fetching 2 pdf's file which cause SHA-1 collisionpdf1 = requests.get("")
pdf2 = requests.get("")
# Assinging pdf's content into the GET parametersparams = {'user': pdf1.content, 'pass': pdf2.content}r = requests.get("http://temple.fortress:7331/t3mple_0f_y0ur_51n5.php/",params=params)
print (r.text)

But this didn’t worked as pdf file’s “length exceeds the capacity”

Maximum capacity of url request is 8 KB while we exceed this limit as combined size of those files is 825 KB

I found the way around through a writeup from a 2017 CTF challenge which was based on the same concept of SHA-1 hash collision

We have a total of 1.6 KB and if we check SHA1 hash of both these files

They are similar , so here I am just going to host them on my own machine and fetch it

import requests# Fetching 2 pdf's file which cause SHA-1 collisionpdf1 = requests.get("http://localhost/1-pdf.192")
pdf2 = requests.get("http://localhost/2-pdf.192")
# Assinging pdf's content into the GET parametersparams = {'user': pdf1.content, 'pass': pdf2.content}r = requests.get("http://temple.fortress:7331/t3mple_0f_y0ur_51n5.php/",params=params)
print (r.text)

Although we have succeeded in making the request smaller but the contents are identical so we according the writeup we need to put first 320 bytes of the pdf file

This makes a total of 640 bytes , also checking the SHA1 hashes

These two files look different but fingers crossed

import requests# Fetching 2 pdf's file which cause SHA-1 collisionpdf1 = requests.get("http://localhost/shattered-1.dat")
pdf2 = requests.get("http://localhost/shattered-2.dat")
# Assinging pdf's content into the GET parametersparams = {'user': pdf1.content, 'pass': pdf2.content}r = requests.get("http://temple.fortress:7331/t3mple_0f_y0ur_51n5.php/",params=params)
print (r.text)

But this didn’t work

This is the reason why it didn’t worked as both values are having a length of 320 and there’s a condition that user must have a length greater than 600 and pass must have a length greater than 500

I found two other files whose SHA1 hashes collide

Here we can see both are of 640 bytes which passes the condition and total size is 1.2KB so this request can be allowed

We get a hidden file m0td_f0r_j4x0n.txt , so this must be a username j4x0n, on visiting that file we'll get the private key

But the message here was kinda vague as it stated that “I am leaving a private key for you j4x0n” which was written by h4rdy

So this key was for h4rdy, if we try to do sudo -l it won't work it seems that we are in restricted bash

If we try to change PATH variable it won’t allow as it’s set to read only

I tried doing autocomplete to see if I can see any files or directories

But if we try to login using -t which enables "pseudo-tty allocation"

We can run cd and export commands so let's set the SHELL variable to /bin/bash and also change the PATH variable

Privilege Escalation (ja4xon)

We can read these two files

Let’s just copy the id_rsa key (private key) and login as j4x0n

But still we can’t use sudo -l as we don't know the password

So we need to maybe find this user’s password as he is in sudoers group

In /opt directory we see a SUID binary named bt on running tells that it's spawning a root shell but instead keeps printing bunch of gibberish on the terminal and force us to exit out of ssh connection , I didn't find anything , manually tried looking into directories , checking local ports and cron jobs but we were in adm group which can read log files so I though of visiting /var/logs/auth.log

Let’s give this password a try

With this we rooted this room.