HTB Kryptos

10.10.10.129 | 50 pts | Synack Track | Cracker Badge

PART 1 : INITIAL RECON

1.1 NMAP SCAN

$ nmap --min-rate 700 -p- -v 10.10.10.129

  PORT   STATE SERVICE
  22/tcp open  ssh
  80/tcp open  http

$ nmap -oN kryptos.nmap -p 22,80 -sC -sV -v 10.10.10.129

  PORT   STATE SERVICE VERSION
  22/tcp open  ssh     OpenSSH 7.6p1 Ubuntu 4ubuntu0.3 (Ubuntu Linux; protocol 2.0)
  | ssh-hostkey: 
  |   2048 2c:b3:7e:10:fa:91:f3:6c:4a:cc:d7:f4:88:0f:08:90 (RSA)
  |   256 0c:cd:47:2b:96:a2:50:5e:99:bf:bd:d0:de:05:5d:ed (ECDSA)
  |_  256 e6:5a:cb:c8:dc:be:06:04:cf:db:3a:96:e7:5a:d5:aa (ED25519)
  80/tcp open  http    Apache httpd 2.4.29 ((Ubuntu))
  | http-cookie-flags: 
  |   /: 
  |     PHPSESSID: 
  |_      httponly flag not set
  | http-methods: 
  |_  Supported Methods: GET HEAD POST OPTIONS
  |_http-server-header: Apache/2.4.29 (Ubuntu)
  |_http-title: Cryptor Login
  Service Info: OS: Linux; CPE: cpe:/o:linux:linux_kernel

1.2 GOBUSTER

A forbidden directory is found (/dev/ and opening url.php, aes.php, and rc4.php leads to empty pages so they might be php files included inside /encrypt.php

PART 2 : PORT ENUMERATION

2.1 TCP PORT 80

Opening http://10.10.10.129/ on your browser leads you to:

It contains a login form and examining how the login works reveals that:

There are two hidden values, db and token, and fuzzing the db parameter returns PDOException code: 1044. Also, the token value is not reusable and should be renewed every legitimate request.

PDO (PHP Data Objects) grants PHP access to various databases and its object contructor is as follows:

PDOException code: 1044 refers to "Access denied for user..." and I assume that it is returned when connecting to a non-existent database.

PART 3 : EXPLOITATION

3.1 Data Source Name (DSN)

What is a Data Source Name (DSN)?

Supplying a DSN is required in creating a PDO instance. The contents of the DSN depends on what database is used.

Assuming that the database in use is MySQL, then the DSN format for the PDO_MYSQL is as follows:

Supplying the port is optional (defaults to 3306). The $dsn when passed to a PDO constructor is a string and all strings in PHP, if unsanitized, are vulnerable to injection when affected by user input.

It is also important to note that the login form has a hidden db parameter with a default value cryptor and perhaps the $dsn string is as follows:

The host parameter could also be blank or unsupplied entirely like so:

3.2 DSN Login Bypass

Create a script that can repeatedly send requests on the login page:

The script automatically replaces the request token since it cannot be reused and the purpose of this script is to capture the PDOException code generated by the request. After running script (kryptos_login.py) using python3:

Since the host now points to the local machine, a new error was generated -- PDOException code: 2002 is returned when PHP cannot connect to the MySQL service. There is no MySQL service running on the local machine; therefore, PHP has nothing to connect to:

Send another request using the running script, kryptos_login.py:

A new error message was generated -- PDOException code: 1045 is returned when authentication to the MySQL service was not validated. Capturing remote connections to the MySQL service using tcpdump:

Continue the running script, kryptos_login.py, then check the captured packets:

The client authenticates using the mysql_native_password plugin which means the password is hashed and is therefore difficult to retrieve. This also returned a new error -- PDOException code: 1045:

But since the MySQL server being connected to by the application is hosted locally, dbuser could just be created:

The authentication would still throw an error since the password cannot be authenticated but it could be ignored by setting the --skip-grant-tables flag. This flag is commonly used to reset forgotten root passwords in MySQL and lets you use the service without authenticating which is particularly useful since the database instance is running locally:

Continue the script, kryptos_login.py, then check the packets captured by tcpdump:

An error still thrown -- PDOException code: 1049. This occurs when the client is trying to connect to a non-existent database. An Unkown database 'cryptor' was captured by tcpdump so we just create one in the local instance:

Continue the script, kryptos_login.py, again then also check the packets captured by tcpdump:

There are no more PDOException errors which means the login page can now connect to the local MySQL service without any problems.

The query generated by the login page and the server response were intercepted by tcpdump:

Which returned the following error:

The credentials being sent by kryptos_login.py is placeholder:placeholder wherein6a99c575ab87f8c7d1ed1e52e7e349ce must be a hash of placeholder. A table, cryptor.users, with the credentials from the request was created:

Log in should now be possible using the following POST Request data:

3.3 /encrypt.php

After a successful login:

The page can encrypt files loaded via http:// (which includes files loaded from a local webserver). It can encrypt in either AES-CBC or RC4and interestingly, the ciphertext never changes for the same plaintext.

In RC4, the plaintext is XORed with a key stream to generate the ciphertext. This means that the key stream generated for encryption/decryption never changes. Therefore, the key stream used could be generated if the plaintext and ciphertext are known.

Since files from the local machine could be encrypted by the server, contents of the encrypted file are known as well the generated ciphertext which means the key stream could be generated:

It is a hard requirement that the input starts with the string http://. Otherwise, An error would be thrown:

A different error would be thrown if the file doesn't exist or is not found:

Now, create a long string of characters then host the file in an http server:

Encrypt averylongstring.txt using kryptos_encrypt.py:

Generate the key stream using the known plaintext and ciphertext by XORing the two strings:

The key stream file was saved in a file named rc4_key_stream.

Now that the key stream has been extracted, it could now be used to decrypt files from within the webserver:

Using kryptos_decrypt.py to read the source code of index.php:

Although /index.php was requested, a rendered html of the PHP file was returned after the decryption. Maybe the pages are being passed through curl inside encrypt.php or url.php (from the earlier gobuster enumeration)

3.4 SSRF to /dev

Using kryptos_decrypt.py to read the source code of http://10.10.10.129/dev:

Requesting /dev still returns 403 Forbidden but since the http requests are being handled by the server, requesting the pages via localhost or 127.0.0.1 should return the same results when passed through curl. This time, requesting http://127.0.0.1/dev using kryptos_decrypt.py:

Following the redirection, there is another web application running:

The get parameter, view, is used to load page content and the assumptions is that the pages are loaded with the extension (.php) truncated. Checking out ?view=todo:

There is an sqlite_test_page.php but returns no relevant content when called via ?view=. However, this might change if PHP filters are allowed. It is also important to note that the world writable directory should still exist somewhere since it is still in the ToDo List

3.5 Local File Inclusion (LFI)

Using PHP Filters to read actual source code:

Using kryptos_read_php.py to read sqlite_test_page.php:

sqlite_test_page.php takes two GET parameters -- no_results and bookid:

  • bookid is fed to the query "SELECT * FROM books WHERE id=".$bookid

  • If no_results is set with a value, the query is run using $db->exec($query)

  • Otherwise, the query would run using $db->query($query)

$db->query($query) return values from the database while $db->exec($query) doesn't so the earlier is often used for SELECT statements while the latter is used for CREATE, DELETE, DROP, INSERT, or UPDATE.

Important to note as well that the world writable directory mentioned earlier must be d9e28afcf0b274a5e0542abb67db0784/. To abuse sqlite_test_page.php, create an SQLite3 Injection payload that will write a file into the directory:

1. ATTACH DATABASE "d9e28afcf0b274a5e0542abb67db0784/lfi.php" AS fileview;

  • This creates a database file with a database named, fileview.

  • This is written to a file named, lfi.php and if it doesn't exist, it will be created.

2. CREATE TABLE fileview.code (php text);

  • This creates a table, code, with one column (php)

3. INSERT INTO fileview.code (php) VALUES ("...omitted...");

  • This will write the code to be executed when lfi.php is called.

According to ?view=todo, dangerous PHP functions are prohibited:

  • exec(), shell_exec(), and system() are prohibited.

Which is why the following functions were used:

  • file_get_contents() read contents of a file while scandrir() returns a listing of the requested directory.

Incorporating the SQLite payload to read arbitrary files in the server:

Contents of /etc/passwd:

Directory listing of /home/rijndael:

Contents of /home/rijndael/creds.old ([[[ and ]]] are just markers to know where the file contents start and end):

Contents of /home/rijndael/creds.txt:

Base64 decoded then RC4 decrypted then hex encoded content:

creds.txt is an encrypted vim file:

  • It is encrypted using VimCrypt~02! or using :set cm=blowfish

  • blowfish is a block cipher and vim's implementation is using a Cipher Feedback (CFB) mode of encrpytion.

The salt, IV, and data are 0b 18 e4 35 cb 56 12 9a, 35 44 80 40 70 3b 96 2d, and 93 0d a8 10 76 6e 64 5d c1 4b e2 1c 79 59 43 7d d9 35 fb 36 67 4d 52 41 8b 6e respectively.

PART 4 : GENERATE USER SHELL

4.1 VIM BLOWFISH

To decrpyt creds.txt using creds.old, hex encode the first 8 bytes of creds.old:

XOR the first 8 bytes of creds.old with the first 8 bytes of data from creds.txt:

XOR the output from above with the entire data from creds.txt:

Decode the hex output from above:

This works since vim's implementation of blowfish is vulnerable for small files (~64 bytes):

  • The first 8 blocks use the same IV along with a key to form a key stream.

  • Knowing part of the plaintext already can make corresponding parts of the ciphertext guessable.

That along with the assumption that creds.old follows the same format as creds.txt

  • The first 8 bytes of creds.old decodes rijndael

  • The first 8 bytes of creds.txt should also be rijndael

4.2 SSH as rijndael

While inside rijndael's shell:

PART 5 : PRIVESC (rijndael -> root)

5.1 kryptos.py

Check for processes run by root:

There is a kryptos.py file stored in rijndael's home directory and examining its contents:

This must be similar with the application being run by root which is also hosted locally on port 81:

Requesting on /eval requires an expression (expr) and a signature (sig)

  • expr is passed to an eval() function.

  • However, a valid signature is needed for the string to be evaluated.

  • The expression is signed using ECDSA with an NIST384p curve.

The exponent used to generate the signature is derived from the "random number generator" function, secure_rng(seed).

  • The seed is derived using random.getrandbits(128).

5.2 PRNG Exploit

The application uses a weak pseudorandom number generator to sign requests. The value of which is already static since it is only defined once during the initial runtime of the program:

Function definitions and their reasoning:

This is based on the definition of primitive roots:

  • If g is a primitive root of p, then the congruence classes will contain all totatives of p.

  • Totatives are defined by all values, n, where gcd(n,p) = 1.

  • The goal is to find all the possible totatives or at least the order of values relative to p.

It is said that g is a primitive root modulo p if a value, a, coprime to p is congruent to g^k mod p

  • Primitive root or not, g relative to p should still have a cycle which is a subset of the congruence classes.

The defined function above’s return value, order_p, is an array of integers relatively prime to p.

Upload then execute kryptos_root.py inside the kryptos machine:

Execute the python script from within the Kryptos machine:

Last updated