# Hack The Box - Kryptos

## Quick Summary

Hey guys today Kryptos retired and here’s my write-up about it. It’s one of the hardest boxes I’ve ever seen and it definitely taught me a lot. As you may have already guessed, it had a lot of cryptography stuff, it also had a long chain of web vulnerabilities, starting with authentication bypass and ending with SQL injection allowing arbitrary file write. It’s a Linux box and its ip is 10.10.10.129, I added it to /etc/hosts as kryptos.htb. Let’s jump right in !

## Nmap

As always we will start with nmap to scan for open ports and services :

Only http on port 80 and ssh.

## Initial Web Enumeration

http://kryptos.htb :

The index page is a login page titled Cryptor Login, I checked the directories with gobuster but the only interesting thing I got was a forbidden directory called dev :

I tested the login page with test:test and I intercepted the request with burp:

Request :

I noticed a parameter called db, I changed its value to test to see what will happen :
Request :

Response :

I got a PDO exception, I searched for PDO and found this page. I looked at the user contributed notes and saw this example :

I guessed that the POST parameter db is being used in a similar code and dbname gets its value from it, so I thought of injecting that parameter with ;host=10.10.xx.xx and see If I can successfully change the host parameter.
Request :

Response :

I got another exception code (2002) which is Connection refused, I listened on port 3306 (Default mysql port) with nc to verify that I’m getting a connection, then I sent the same request again. And I got a connection :

We can run a fake mysql database and use this injection to make the server send the login query to our database, the database will respond that the credentials are valid and we will be able to bypass the authentication. However, to do this we need to get the database credentials and the login query, then depending on them we will setup the database.

## Authentication Bypass: Getting Database Credentials

To get the database credentials I used metasploit‘s module auxiliary/server/capture/mysql. It will run as a fake mysql server and capture the username and the password hash, the option JOHNPWFILE will save the hash in john format in an external file which we can use later to crack the hash.

After running the module I sent this request again :

And the server caught the credentials :

I cracked it with john :

Database name : cryptor

Username : dbuser

Password : krypt0n1te
Now we need to create the database, create the database user dbuser and grant them permission to the database cryptor :

Let’s test it :

It’s working, dbuser can login and access cryptor.
By default the server listens on localhost only, I changed bind-address in /etc/mysql/mariadb.conf.d/50-server.cnf from 127.0.0.1 to 0.0.0.0 then I restarted the service.

## Authentication Bypass: Getting Login Query

Now the server will be able to authenticate to the database as dbuser, however the login query will fail because the database is empty :
Request :

Response :

I ran tcpdump on tun0 then I sent the login request again with these credentials : rick:rick

tcpdump :

Then I opened the pcap file in wireshark and got the query :

## Authentication Bypass: Setting-up the Database

From the query now we know that we need to create a table called users with the columns username and paassword, we also know that the passwords are saved md5 hashed as we saw for the password rick in the query :

I created the table users with the columns username, password :

Then I inserted my credentials (rick:rick) :

Let’s test the query :

It’s working.
Now we can simply go to the login page, edit the form input id and inject the host parameter then login with rick:rick.

## RC4

After getting in I got this application which had 2 pages, first one was to encrypt a file :

Second one was to decrypt a file but it was under construction :

The encryption page had two encryption methods, AES-CBC and RC4, I searched about RC4 and read about it here. As the article said, RC4 is a stream cipher and it’s XOR based. The article also gave an example :

As you can see, decryption and encryption are the exact same operation, which means that applying the encryption function on encrypted data will eventually decrypt that data if the key is the same used to encrypt it before. In other words, we can use the RC4 encryption option in encrypt.php to encrypt and decrypt data. Let’s test it.
I created a file called test.txt with the word test in it, then I hosted it on a python server :

Then I encrypted it :

It returned a base-64 encoded string, if we try to decode it we will get nothing readable because the data is encrypted :

I decoded it and saved the result in another file and called it test2.txt then I ran the python server again :

I applied the same encryption on the encrypted file :

And I got the original data decrypted :

## SSRF

Great, we can encrypt and decrypt files, but reading our files isn’t really helpful. Earlier during the initial enumeration there was a forbidden directory /dev. When we provide a url to /encrypt.php to request a file and encrypt it the requests were made by the server, so I tested for server side request forgery :

It worked.
This process of “encryption –> decoding and saving to a file –> decryption –> decoding” was a lot of steps and doing it manually through burp or the browser wasn’t efficient, so I wrote a script to automate it :
ssrf.py :

This script sends a request to /encrypt.php with the given url, then it retrieves the encrypted data and saves it in a file. It sends another request to /encrypt.php with the url to the encrypted file then it retrieves the decrypted data, saves it in a file then it prints it.
It saves encrypted files and decrypted files and names them according to this pattern : ENCRYPTED_N, OUTPUT_N where N is a number which is incremented by 1 every request.
I created a directory and called it ssrf, I ran a python server there then I ran my script :

/dev :

I went to /dev/index.php?view=todo and I found some interesting stuff :

There were 2 “done” things in this todo list : restricting access to /dev and disabling dangerous php functions which will be a problem if we could somehow upload/write files. However the things that haven’t been done yet were removing a php page called sqlite_test_page.php and removing a folder writable by everyone which was used for sqlite testing, probably used by the sqlite_test_page.php
I went to /dev/sqlite_test_page.php but I only got an empty html page :

## LFI

As we saw, /dev/index.php had a parameter called view which was used to view other pages, There was a possible local file inclusion vulnerability here so I tested on /index.php and it worked :

I wanted to read the php code of sqlite_test_page.php to know what’s in there so I tried the php://filter/convert.base64-encode wrapper (got it from here) and it worked :

## SQLI –> Arbitrary File Write

sqlite_test_page.php :

It takes two parameters : no_results and bookid :

And it appends bookid to a sqlite query without any filtering :

We also can see the name of the writable directory d9e28afcf0b274a5e0542abb67db0784 :

With this SQL injection and the name of a writable directory, we can use the SQLite command Attach Database to write files (got it from here).
I tried a test file first so the payload was like this :

First attempt failed apparently because of encoding issues so I url-encoded the payload and tried again:

The file was successfully created :

We know that dangerous php functions are disabled, but let’s check which functions are exactly disabled, I overwrote my test.php file and made it call phpinfo() instead of printing test :

Then I requested :

and got the phpinfo page. It’s a very long page, I opened it in firefox and here’s the disabled functions part :

Almost any function that can get us code execution is disabled, but scandir() and file_get_contents() aren’t disabled, I wrote a php file that takes file and dir parameters to read a file or list a directory.

Let’s test :

It’s working fine, but as you can see it prints some weird characters which i guess are caused by the sqlite query.
In /home there was a directory for a user called rijndael :

I couldn’t read user.txt or go to the ssh directory, but I could read creds.txt and creds.old :

I copied both of these files from my directory (ssrf) :

But because of that weird characters thing the files were partially damaged, so I wrote another php file to print the base-64 encoded value of the file between a bunch of newlines :

## Decrypting the Credentials, User Flag

The old credentials : rijndael / Password1 didn’t work with ssh.
And the new credentials are encrypted :

I searched about how vim encrypts files and if there were any known vulnerabilities. I found this article which was talking about a weakness in this encryption system.
I won’t repeat what was said in the article but these are the important parts :

The Vim editor has two modes of encryption. The old pkzip based system (which is broken, but still the default for compatiblity reasons) and the new (as of Vim 7.3) blowfish based system.

Blowfish is a block cipher, this means it encrypts a block of data at a time. There is no state kept between blocks, this is important to understand; it means the same input will result in the same output (if the key is the same).

the issue is that Vim actually ends up using the same IV for the first 8 blocks (essentially repeating the first part of the diagram 8 times, then going on to the next operation that mixes in the output). So the result is something like CFB but with the first 64 bytes lacking any protection.

The way CFB works is to compute a stream of data (the keystream), then XOR it with the plaintext. However Vim is reusing the keystream, in pseudocode:

This means by a simple relationship we can recover the keystream:

With this information I started testing some stuff. We have creds.old :

As you can see rijndael which is the username might be also present in the encrypted file as the first 8 bytes. So I tried using rijndael as the known plaintext.
In python I opened the file for reading :

We don’t need the first 28 bytes : 12 bytes for the header (VimCrypt~02!) + 8 bytes for the salt + 8 bytes for the IV. So I stored them in a variable that I won’t use :

Then we have about 4 blocks left (each block 8 bytes) :

I used the same relation from the article to get the key :

I XORed the plaintext (rijndael) with the first block :

Then I tried to decode the first 2 blocks with the retrieved key and it worked successfully :

After my testing was successful I wrote this script which does it automatically :
vim-decrypt.py :

I got the credentials :

Then I could ssh into the box as rijndael :

We owned user.

## kryptos.py: Analysis

In the home directory of rijndael there was a directory called kryptos which had a python script called kryptos.py :

kryptos.py :

First thing I noticed was that It’s a server which runs on localhost on port 81 :

I checked the listening ports on the box and the server was running :

There’s a route called /eval which accepts POST requests in json and evaluates whatever is in expr, However there are two problems, first one is that it only evaluates signed expressions, second one is that even if we could bypass that protection we can’t execute something useful because builtins are disabled :

Let’s take a look at the keys and the secure random number generator function (secure_rng) :

I noticed this comment :

I took the rng function from the script and wrote a script to test how random is it :
test.py :

I set the range to 15 to print 15 samples :

As you can see, it’s not that “random”

## kryptos.py: Bruteforcing the Seed

Knowing that the random number generator is not random enough, It’s possible to bruteforce the seed.
I tunneled port 81 to my box :

Then I wanted to test the server response :

I wrote a script to bruteforce the seed :
brute.py :

The script sends a simple expression : 1+1 and uses the functions from kryptos.py to sign it, every attempt the server responds with Bad signature it regenerates the signing key and tries again.
It could find the right seed after 2 attempts, however sometimes it takes between 30-40 attempts and sometimes it takes more than 100 attempts.

I wrote this script to sign and evaluate expressions depending on the given seed :
eval.py

Let’s try the seed we got :

It worked.

## kryptos.py: Exploitation, Root Flag

We still have the problem of disabled builtins, as you can see we can’t do anything :

xct had a bypass for that on his blog :

I gave it a try and it worked :

Now that we have RCE, I wrapped it all up in one exploit :
exploit.py

It takes the ip, port and whether bruteforces the seed or takes the seed as an argument then it spawns a reverse shell.

And we owned root !
That’s it , Feedback is appreciated !