Offensive Development with GitHub Actions

Offensive Development with GitHub Actions


Actions is a CI/CD pipeline, built into GitHub, which was made generally available back in November 2019. Actions allows us to build, test and deploy our code based on triggers such as check-ins, pull requests etc.

Around the time Actions was released, I wrote a post which detailed how to set up Actions to build tooling, and showed how we could integrate obfuscation into our CI workflow. At that time, there was no way to integrate with Actions, outside of the GitHub UI. Since then, the GitHub API has been expanded to include support for Actions, which allows us to start integrating Actions into other tooling.


In this post, we will look at how we can use the Actions API to download and execute tooling within Cobalt Strike and release the accompanying aggressor script so you can start using these features in your engagements.

It’s worth pointing out that this is a proof of concept example and we would wholly recommend manually reviewing any code you download, regularly tracking changes and updates accordingly.

Before we dive into the API, it’s worth quickly recapping on how we configure actions. For this post, I’m using a private repo, containing a number of commonly used tools, such as GhostPack and SharpHound. To set this up, I’ve created a private repo on GitHub, and added these tools as submodules, using the following command:

git submodule add

After adding the repos and doing a commit and push, we end up with a private repo, containing references to the tools we want to build. These are tied to a specific version of the linked repos, you can update them later if you want to bring in new changes.

Using submodules rather than copying the repos means we can easily take advantage of any updates made to the repos we have referenced. We can also make our repo private without any issues.

Next, we need to configure our workflow. Workflows are configured through the “actions” tab.

Offensive Actions

There are a number of pre-configured actions to choose from, but for this post we’ll build the workflow ourselves. Selecting this option will create a scaffold yml file, which we can edit within the GitHub UI.

Here we get access to a range of actions, which we can combine into our workflow. For this post, we’re going to stick to something quite simple which just builds our tooling and publishes the artifacts.

To achieve this, we need to check out our repo, including the submodules, restore any packages we need and then execute MSBuild in release mode.

We’ll set this up to happen every time there’s a push made to the repo:

name: Build

on: [push]
    runs-on: windows-latest
      - uses: actions/checkout@v2
      - uses: actions/checkout@v2
      - name: Checkout submodules
        shell: bash
        run: |
          # If your submodules are configured to use SSH instead of HTTPS please uncomment the following line
          # git config --global url."".insteadOf ""
          auth_header="$(git config --local --get http."
          git submodule sync --recursive
          git -c "http.extraheader=$auth_header" -c protocol.version=2 submodule update --init --force --recursive --depth=1

- name: Setup Nuget.exe
        uses: warrenbuckley/Setup-Nuget@v1

      - name: Nuget Restore
        run: nuget restore $Env:GITHUB_WORKSPACE\SharpHound3\SharpHound3.sln
      - name: Build SharpHound3
        run: |
          cd "C:\Program Files (x86)\Microsoft Visual Studio\2019\Enterprise\MSBuild\Current\Bin\"
          .\MSBuild.exe $Env:GITHUB_WORKSPACE\SharpHound3\SharpHound3\SharpHound3.csproj /property:Configuration=Release
      - uses: actions/upload-artifact@master
          name: SharpHound3
          path: SharpHound3\SharpHound3\bin\Release\SharpHound.exe

The syntax can be a little confusing, so it helps to know what’s going on under the hood. Each build is run on an ephemeral container, that container has the tool chain we need to run git commands and perform builds. For this build, as we’re targeting .NET code, we are using a windows container (the runs-on: tag).

Our first step just checks out our repo. Then, we restore our submodules. This command is a bit more complicated, but all that’s happening is we drop into a shell and run a git command directly.

From here, we grab a setup-nuget action, which allows us to run nuget restore against SharpHound3. This command fetches the dependences required to build SharpHound.

Once that step completes, we call MSBuild via the shell, passing in the path to the SharpHound csproj file and specifying a release build. This runs MSBuild, which drops the compiled EXE to disk in our container.

To access the compiled artefact, we need to upload it. This makes it available from the GitHub UI, and via the Actions API. Without this step, the artefact would be destroyed along with the build container. Adding in the appropriate build steps for each of our tools and triggering a build via a commit should result in a list of available artifacts.

With our build actions configured, we can start to look at the API. To access the API, we need credentials. GitHub supports a number of authentication methods, but Personal Access Tokens suit our needs. We can use a PAT in place of a password, while still allowing it to be revoked at any time. For Actions, we need the repo scope.

Once the token has been created, we can start issuing API requests via cURL:

curl -u two06:TryHarder ‘’

This should return details of the artifacts available in our repo.

This gives us all the information we need to start using these artifacts in other tooling.

I’m not going to go into too much detail on how to write Aggressor scripts. You can check out the official documentation here, or have a look at the code which accompanies this post. The important part is we can call the GitHub API using the Exec method, like this:

sub make_API_request{
    $cmd = @('curl', '-u ' . $username . ':' . $access_token, $api_url . $repo_url . $endpoint);
    $curl_command = exec($cmd);
    $data = readAll($curl_command);
    return $data;

Here we pass in the username, access token and build the URL we want to call. We then read the data and return it for processing.

With some work, we can access a list of available artifacts from Cobalt Strike.

Under the hood, this just calls the actions API and grabs the unique list of available artefact names. The API returns ALL the artifacts, including old builds, so we grab the latest version.

Using the artefact name, we can then look up the download URL and fetch the ZIP file containing our artefact. We can then extract the archive and execute the artefact using Beacons Execute-Assembly command.

Putting it all together, we end up with something like this:

Hopefully this post has shown how we can create CI/CD pipelines for our code on GitHub and access the compiled artifacts directly from cobalt strike. The Aggressor script which accompanies this post can be found here.

This blog post was written by James Williams.

written by

MDSec Research

Ready to engage
with MDSec?

Copyright 2021 MDSec