How binding.gyp can be exploited

How binding.gyp can be exploited

gyp stands for Generate Your Projects, a build process similar to CMake that Google developed and is now unmaintained. A node package called node-gyp allows node packages to include and run GYP build steps. Those build steps are defined in a configuration file called binding.gyp.

A binding.gyp file looks like this:

{
  "targets": [
    {
      "target_name": "binding",
      "sources": [ "src/binding.cc" ]
    }
  ]
}

Like a makefile, a binding.gyp file specifies targets and sources, and node-gyp builds those sources based on which targets are needed.

Obviously, this is a powerful tool for node packages that need to use native code. It is cross-platform, supported across CI/CD and local development, and allows for flexible extension of node packages using native bindings. However, unless you're building node packages that need native bindings, you've probably never heard of the binding.gyp file – so why is it all over the news?

We already know that some of the new supply chain worms are taking advantage of tasks.json or package.json to run their exploits. A lot of security scanners are now checking package.json for postinstall scripts that are compromised, and some tools (including DevDefender!) are scanning for malicious tasks.json scripts.

Few tools are scanning for binding.gyp vulnerabilities.

Here's an example of a malicious binding.gyp file from the recent Miasma worm:

{
  "targets": [
    {
      "target_name": "Setup",
      "type": "none",
      "sources": ["<!(node index.js > /dev/null 2>&1 && echo stub.c)"]
    }
  ]
}

According to Step Security:

- node index.js runs the malicious payload
- > /dev/null 2>&1 silences all output
- && echo stub.c returns a fake source filename so gyp does not error

The <!( {{payload}} ) syntax is a "Command Expansion" in GYP – there are many ways to use them, including nested expansions, expansions that are evaluated as strings, and expansions that are evaluated as a source filename. These command expansions are one reason binding.gyp exploits are hard to detect: there are so many legitimate command expansions, and ones that are malicious could certainly not appear particularly malicious if you're only looking at the binding.gyp file.

Command expansions are run at parse time, before any compilation happens. This means that malicious command expansions will run before anything else, and before node-gyp even checks the binding.gyp for errors.

Here are some examples of command expansions that could be used to hide malicious code:

  • "targets": "<!(python my_exploit.py && echo sup.cc)"
  • "targets": "<!(node index.js && echo sup.cc)"
  • "made_up_key": "<!(bash -c 'echo ZWNobyAiaGkgbW9tIg== | base64 -d | bash' && echo 0)"

This kind of exploit is hard to detect, and hard to mitigate against. Many of the IOCs for this attack are legitimate files that every developer machine will have, and require manual review. This manual review changes with every worm iteration, as they shift and morph along with defense patterns.

DevDefender offers real-time alerting when these files change on your machine – allowing you to quickly see the blast radius of, say, opening a repository that automatically runs an exploit. If you open a repo and suddenly get alerts that every Claude file on your computer was modified – you know something is up.

Stay safe out there, dev.