1blogenterBlog

The Family of Safe Golang Libraries is Growing!

Imre Rad
Information Security Engineer
Published: Feb 26, 2024
Security Engineering

The Family of Safe Golang Libraries is Growing!

We are thrilled to introduce three new open-source libraries in Go that provide secure and efficient solutions:

  • SafeText for YAML and shell command templating
  • SafeOpen for opening files in a base directory
  • SafeArchive for processing archive files

These libraries have been meticulously crafted to address common secure coding challenges, protecting against the following weaknesses from the CWE Top 25:

  • #5 CWE-78: Improper Neutralization of Special Elements used in an OS Command ('OS Command Injection')
  • #6 CWE-20: (special cases of) Improper Input Validation
  • #8 CWE-22: Improper Limitation of a Pathname to a Restricted Directory (‘Path Traversal’)

The Safe libraries offer robust protection mechanisms. By leveraging them, you can perform these primitive operations in a vulnerability-free manner – even if the input is attacker-controlled.

SafeText

SafeText was the first member of the "safe family" that we published at the beginning of 2023, and GitHub is already tracking hundreds of integrations. So what is SafeText? And what is it useful for?

The story started with identifying a number of applications that constructed YAML documents with the help of Golang’s text/template. This approach is vulnerable to YAML injection unless all inputs are validated carefully. Expecting all users to do this properly is error prone and hard to scale: we prefer solutions that are safe-by-default.

YAML

SafeText was designed to be a drop-in replacement of text/template; you can use it to process your YAML template in the same way that you used text/template. The difference is that the SafeText library returns an error when an injection attempt is detected.

Let’s see an example! Given the following template:

---
sensitive: data
innocent: "{{ .input}}"

If an attacker controls the value of .input, they could inject newline characters and override other fields or change the structure of the document. Depending on the use-case, the impact could be critical, for example when the result is used as a configuration file for a production system.

Switching to SafeText is as easy as adjusting the corresponding import line:

text/template becomes template "safetext/yamltemplate"

When the same attack is attempted again, SafeText would return the error: YAML Injection Detected, and prevent the attack.

Shell commands

Building on the original YAML feature of the library, we decided to also add support for templating shell commands. shsprintf was designed to ensure that none of the input data strings would be able to inject additional commands or flags regardless of potentially incorrect escaping.

Consider the following (vulnerable) example:

result := fmt.Sprintf("git commit -m %s", message)

If the message variable is attacker-controlled and the concatenated string is executed at some point, then this is a vulnerability. Depending on the exact execution of the attack, either OS commands or parameters to the executable (git cli in this example) could be injected.

This is where shsprintf comes to the rescue! Two different syntaxes are supported:

message := "`whoami`"
result, err := shsprintf.Sprintf("git commit -m %s", message)

Or

message := "`whoami`"
result := shsprintf.MustSprintf("git commit -m %s", shsprintf.EscapeDefaultContext(message))

Both variants detect injection attempts. The first one returns an error, the second one panics. In addition, there is a third option: you can use the shtemplate feature of SafeText and build your shell commands with the help of templates (similar to text/template).

SafeOpen

SafeOpen was designed to protect against path traversal attacks – it does so by providing functions to open files within a base directory. The idea is simple: you specify a trusted root directory and the library enforces that file operations cannot break out of this directory. This offers robust protection in cases where the pathname of the file to open is attacker-controlled (meaning it may include ../ path components) or the root directory is “dirty” (e.g. it contains symbolic links).

The SafeOpen library leverages the best available OS-level primitives (e.g. openat2 + RESOLVE_BENEATH, falling back to openat for Linux kernel < 5.6.0). Thanks to this construct, SafeOpen is safe to use even in shared environments where an attacker could mount race condition attacks (TOCTOU). The Windows operating system is also supported.

Two modes are supported:

  • At ensures that the file to open is located directly in the root directory (and not in a subdirectory)
  • Beneath specifies that the file to open may be located in a subdirectory of the root directory

The wrapper functions are implemented for the following OS-level primitives:

  • os.Open
  • os.OpenFile
  • os.Create
  • os.ReadFile
  • os.WriteFile

An example open would look like this:

rootDir:= "/data"
f, err := safeopen.OpenBeneath(rootDir, userInput)
if err != nil {
    t.Fatalf("OpenBeneath(%q, %q) error: %v", rootDir, userInput, err)
}
// ... use f as an *os.File just like before

SafeOpen is part of a larger engineering effort to address path traversal issues – we’ll be publishing a dedicated blog post about that in the near future.

SafeArchive

SafeArchive is a set of safe-by-construction libraries designed to prevent path traversal attacks (aka zip slip) as well as various attacks related to the handling of archive files. This library was designed as a drop-in replacement of Golang core’s archive/tar and archive/zip, so switching to SafeArchive is as easy as adjusting the import line:

import "archive/tar" becomes import "safearchive/tar"

By using SafeArchive, malicious entries in crafted archives are sanitized. For example, if the library encounters a .zip file with a rogue ../../../etc/cron.daily/cronjob entry, it sanitizes the name and returns it as etc/cron.daily/cronjob instead. In addition, the following extra protection measures are implemented:

  • Skipping special files (e.g. device nodes)
  • Sanitizing file modes (e.g. clear the setuid bit)
  • Sanitizing file name (to prevent path traversal attacks)
  • DropXattrs (to prevent abusing extra filesystem ACLs or capabilities)
  • Preventing traversal via symbolic links

This functionality can be fine-tuned using the SetSecurityMode method of the reader. The default settings are both secure and non-breaking.

Summary

The three Golang libraries showcased in this blog post offer safe-by-design solutions that address the root causes of entire vulnerability classes. They are widely used inside Google: we encourage our developers to adopt them with the help of linters :).

Now that these libraries are also open to the public, we recommend you adopt them and hope they will eventually save the day for your applications as well. For more information, including detailed documentation and usage examples, please visit the following links:

And join us in welcoming them to the "safe family"!