I always vendor Go dependencies, in part for reviewing the commit (though I admit I don’t always scrutinize that diff). If someone goes through the trouble of reviewing Go dependencies, I would expect they would just vendor the code. This is for a good part what vendoring is for.
Some time ago I thought about making a service that monitors the Go sum transparency log and tries fetching all modules as well. As a second verification. Could be good to fetch and compare periodically. But it would be lots of work, and will involve dealing with URls that disappear. Anyway, it would be good to find repo’s with tags that change. Nothing good comes of that.
The success of this attack relied on the design of the Go Module Proxy service, which prioritizes caching for performance and availability
I don’t think that’s the reason. It’s more that the attacker created a new repo with a name that looked like the original and tricked someone into imported it instead of the real thing.
The next evolution in these attacks may be the attacker also creating lots of unrelated repo’s that import their malicious package, and get them into pkg.go.dev. To raise the “imported by” count, potentially making it look more popular than the original.
This copycat could have been detected by its use of a similar github username as the original. For regular forks (under existing github accounts, without a name involving boltdb) repo’s with malicious modifications may not be as easy to detect. But they would probably more clearly say they are a fork, so developers would not as easily include them as dependency.
One of the benefits of microservices is that if you have a malicious dependency that requires the ability to talk to arbitrary public up addresses you can just isolate it. No need to put it in the same process/ address space as the code that mints API keys, etc.
To me, that’s the solution to these problems. Arbitrary egress isn’t something you typically need and it’s sketchy enough to move it to a sidecar if you do. Of course, very few people do that, but it seems way cheaper than auditing every dependency, though maybe AI would help.
TBH I was a bit surprised to see this run as part of the deployed server. Seems like running in a build script is going to yield better results/ wins but that’s just how I’d do it personally.
TBH I was a bit surprised to see this run as part of the deployed server. Seems like running in a build script is going to yield better results/ wins but that’s just how I’d do it personally.
The Go SDK has no way to execute arbitrary code when building.
Go has some stuff like go generate but again that doesn’t actually cause go build to execute code.
But yeah most of the weird stuff that pops up when looking at go build and dependency history can be explained by “the people who wrote go thought of building and dependency management as being the responsibility of a monorepo build system which has explicit support for generated files as something distinct from source code and binaries”.
One time someone found a way to make cgo linker and compiler flags execute arbitrary code, and that was considered a vulnerability. https://github.com/golang/go/issues/42556
For comparison, it’s normal for cargo build to execute arbitrary code (build.rs).
The mechanism was reported last year: https://github.com/golang/go/issues/66653
Neat find. These “AI” checks about dodgy calls seem useful to have for dependencies. I wouldn’t mind having it locally.
Looks like an application from one person is importing the code: https://pkg.go.dev/github.com/boltdb-go/bolt?tab=importedby Aside: I think most maintained software using “boltdb” is actually using the maintained fork at https://pkg.go.dev/go.etcd.io/bbolt.
I always vendor Go dependencies, in part for reviewing the commit (though I admit I don’t always scrutinize that diff). If someone goes through the trouble of reviewing Go dependencies, I would expect they would just vendor the code. This is for a good part what vendoring is for.
Some time ago I thought about making a service that monitors the Go sum transparency log and tries fetching all modules as well. As a second verification. Could be good to fetch and compare periodically. But it would be lots of work, and will involve dealing with URls that disappear. Anyway, it would be good to find repo’s with tags that change. Nothing good comes of that.
I don’t think that’s the reason. It’s more that the attacker created a new repo with a name that looked like the original and tricked someone into imported it instead of the real thing.
The next evolution in these attacks may be the attacker also creating lots of unrelated repo’s that import their malicious package, and get them into pkg.go.dev. To raise the “imported by” count, potentially making it look more popular than the original.
This copycat could have been detected by its use of a similar github username as the original. For regular forks (under existing github accounts, without a name involving boltdb) repo’s with malicious modifications may not be as easy to detect. But they would probably more clearly say they are a fork, so developers would not as easily include them as dependency.
Does govulncheck report the dependency?
Nothing on SourceGraph uses it, fortunately: https://sourcegraph.com/search?q=context:global+github.com/boltdb-go/bolt&patternType=keyword&sm=0
One of the benefits of microservices is that if you have a malicious dependency that requires the ability to talk to arbitrary public up addresses you can just isolate it. No need to put it in the same process/ address space as the code that mints API keys, etc.
To me, that’s the solution to these problems. Arbitrary egress isn’t something you typically need and it’s sketchy enough to move it to a sidecar if you do. Of course, very few people do that, but it seems way cheaper than auditing every dependency, though maybe AI would help.
TBH I was a bit surprised to see this run as part of the deployed server. Seems like running in a build script is going to yield better results/ wins but that’s just how I’d do it personally.
The Go SDK has no way to execute arbitrary code when building.
Oh interesting. I guess that makes sense, the expectations is that you’d use bazel or something.
Go has some stuff like
go generatebut again that doesn’t actually causego buildto execute code.But yeah most of the weird stuff that pops up when looking at go build and dependency history can be explained by “the people who wrote go thought of building and dependency management as being the responsibility of a monorepo build system which has explicit support for generated files as something distinct from source code and binaries”.
One time someone found a way to make cgo linker and compiler flags execute arbitrary code, and that was considered a vulnerability. https://github.com/golang/go/issues/42556
For comparison, it’s normal for cargo build to execute arbitrary code (build.rs).
I think the idea was avoiding a series of vulnerabilities related to this
I assume because Go was built at Google so bazel is already the build tool. And bazel already solves a ton of these problems, which is great.
Most of the Go SDK that isn’t the compiler is not used when compiling Go code with Bazel.