Golang Command
Injection: Examples
and Prevention

stackhawk

StackHawk|October 18, 2021

What is a command injection, examples of command injections, followed by how to fix and prevent them.

Many developers are using Go (Golang) to build applications in the cloud—mostly because of the built-in concurrency, speed, and easy-to-understand syntax associated with the language. However, the same environment we deploy to has a plague: hackers trying to control said applications through Golang command injection attacks, XSS, and SQL injection attacks.

This post explores the threat of Golang command injection to web application's integrity. Our initial efforts will be to expose the motive, nature, and logic of command injection attacks. Thereafter, we'll examine a few protective methods that fend off command injection attempts. Let's begin by providing some Golang context to command injection attacks.

What Is Golang Command Injection?

At the very least, a command injection targeting Golang applications intends to run system operations without the knowledge of the app's developers. The motive can be to reveal hidden system or application information. With extreme motives, hackers can even take over the host machine itself from running system commands to change their privilege level and passwords.

Golang Command Injection: Examples and Prevention - Picture 1 image

The graphic above depicts just a few probable outcomes when hackers successfully run Golang command injections. Needless to say, these are the last things any application's owner ever dreams of.

Typically, once a hacker has set their crosshairs on your Golang application, they send a couple commands to the server. These commands, if your application is vulnerable, reveal more information that functions as a launchpad for more harmful commands. Common commands your application server will respond to include the following:

Command: whoami

This command asks the server to identify your application's default access level. Every user inherits this user and their associated privileges just by using your application. Typically, they're able to read files but cannot change source files. This command works for both Windows and Linux operating systems.

taurai@Taurais-MacBook-Pro my-wave-portal % whoami
Taurai

Command: ver (Windows) or uname -a (Linux)

This is a query of the operating system on which you're hosting your Golang application. Knowing this piece of information narrows the hacker's commands down to your OS.

taurai@Taurais-MacBook-Pro my-wave-portal % uname -a
Darwin Taurais-MacBook-Pro.local 19.6.0 Darwin Kernel
Version 19.6.0: Thu Sep 16 20:58:47 PDT 2021;
root:xnu-6153.141.40.1~1/RELEASE_X86_64 x86_64

Command: ifconfig (Linux) or ipconfig /all (Windows)

This is perhaps the worst information for a hacker to acquire from your server—your entire network configuration. The screenshot below is from a Golang application running on a localhost-served Ethernet blockchain network.

Golang Command Injection: Examples and Prevention - Picture 2 image

Command: netstat -an (both Linux and Windows)

This command reveals all connections currently feeding and fetching packets to the server. When used with advanced hacking tools, this can give enough information to single out users and mimic their connections as hackers hide in plain sight.

Command: ps -ef (Linux) or tasklist (Windows)

This command shows all running processes on the server. It's a good way for hackers to learn about supporting tools they can also use to penetrate the server completely.

Command Injection Example

Now that you know what's possible when a Golang command injection happens, let's inspect typical injections in action.

Interactive applications often require user input through forms; the more involved the user in the flow of your application logic, the better their user experience. This often means running commands on the server side from a user's input. This is how you'd typically achieve this with Go:

binary, lookErr := exec.LookPath("sh")
  if lookErr != nil {
      panic(lookErr)
}
env := os.Environ()
args := []string{"sh", "-c", req.FormValue("name")}
execErr := syscall.Exec(binary, args, env)
if execErr != nil {
    panic(execErr)
}

The code above uses the default exec.command() Golang function to pass FormValue ("name") in the OS terminal ("sh" stands for shell). Now this code will work just fine to achieve the intended goal. However, it has a few vulnerabilities.

The ("sh") command opens the command line to accept arbitrary commands that can be enshrouded in the string variable required further down execution. For instance, a char-type string can be separated with varying combinations of the &, $, '', and | operators to invoke commands on the OS level.

So instead of a basic "John Doe" form entry, the following will be parsed by your Golang applications when a hacker injects it:

"John & whoami > /var/www/static/whoami.txt "

Remember that whoami command? This time, your server forwards the output to any file destination specified. In this instance, all outputs forwarded (using the > operator) print on the whoami.txt file. This can also print on your log any file that externally shows in the browser.

This kind of command injection is a blind attack as it can't return the results on the front-end pages that belong to the application's file tree. The output file will need to be planted on the server for the injection example to work. A clever way for hackers to check if your Golang application is vulnerable is by delaying its response time by a preset amount of seconds.

& ping -c 20 localhost

The command above triggers the server to loop back on itself, but it waits 20 seconds before rendering the same page it runs on. If your application responds as directed, then any other command will surely pass.

How to Fix Golang Command Injections

Let's look at how best to secure your Golang applications from accepting injections discussed so far. To start with, if you want your Golang application to display the form input, you should explicitly run the echo command. The unsecure code snippet we reviewed above changes as follows:

binary, lookErr := exec.LookPath("echo")
  if lookErr != nil {
      panic(lookErr)
}
env := os.Environ()
args := []string{"echo", req.FormValue("name")}
execErr := syscall.Exec(binary, args, env)
if execErr != nil {
    panic(execErr)
}

Notice how even when the terminal opens on the back end, it only executes the "echo" command. However, this is by far the end of our solution to the injection issue. Technically, the echo command will allow another command placed after &&. To resolve this, you can create a whitelist (array) of allowable string characters as FormValue validation. However, this would apply only to the form input instance. Half-efforts will only leave the rest of your application vulnerable.

The best way to sanitize all input is by having a site wide sanitization method. You can do this by creating a method that you call along with any form value requirement. This can take a lot of effort and needs a lot of testing. The smart way of achieving full sanitization is by constantly scanning new code for vulnerabilities as part of your continuous integration campaign.

Ready to Test Your App

Conclusion: A Patch in Time

A patch in time will save you a ton of embarrassment and money. Filter out command parameters from your input forms and you'll always be a step ahead of hackers. This is best done on a global scale. Then have a tool like StackHawk automatically sniff out vulnerabilities for your developers to patch before they become a hacker's gateway.

The code examples we've examined are convincing proof that even when you don't know it, your code ethics can be the end of your application. Hackers have been known to hold companies hostage, having taken over entire networks and apps therein, before asking for millions in ransom.

Beyond being a wonderful language to use on the back end of your web applications, Golang is relatively easy to code protections into. Construct and test a function once and call it to remove any injections from your input and you will have a safer Golang app. More important, though, is having these secure code blocks implemented even as your codebase increases.

This post was written by Taurai Mutimutema. Taurai is a systems analyst with a knack for writing, which was probably sparked by the need to document technical processes during code and implementation sessions. He enjoys learning new technology and talks about tech even more than he writes.


StackHawk  |  October 18, 2021