go: os/exec: execution of batch-files (.cmd/.bat) is vulnerable in go-lang for windows / insufficient escape

Execution of batch-files using os/exec with arguments containing some special meta-characters is vulnerable and may be used to execute foreign data/code.

What version of Go are you using (go version)?

Latest stable build: go1.10.3 windows/386

Does this issue reproduce with the latest release?

Yes

What operating system and processor architecture are you using (go env)?

Windows / x86

set GOARCH=386
set GOBIN=
set GOCACHE=%USERPROFILE%\AppData\Local\go-build
set GOEXE=.exe
set GOHOSTARCH=386
set GOHOSTOS=windows
set GOOS=windows
set GOPATH=%USERPROFILE%\go
set GORACE=
set GOROOT=C:\dev\go
set GOTMPDIR=
set GOTOOLDIR=C:\dev\go\pkg\tool\windows_386
set GCCGO=gccgo
set GO386=sse2
set CC=gcc
set CXX=g++
set CGO_ENABLED=1
set CGO_CFLAGS=-g -O2
set CGO_CPPFLAGS=
set CGO_CXXFLAGS=-g -O2
set CGO_FFLAGS=-g -O2
set CGO_LDFLAGS=-g -O2
set PKG_CONFIG=pkg-config
set GOGCCFLAGS=-m32 -mthreads -fno-caret-diagnostics -Qunused-arguments -fmessage-length=0 -fdebug-prefix-map=C:\Temp\go-build080181794=/tmp/go-build -gno-record-gcc-switches

What did you do?

Execution of batch-file using os/exec with arguments containing some special meta-characters.

A recipe for reproducing the error as well as more extensive PoC with additional info (and more lang’s affected also) - github/sebres/PoC/SB-0D-001-win-exec A complete runnable program - test-dump-part.go:

Content of test-dump-part.go
package main 

import (
  "os"
  "os/exec"
)

func main() {
  args := os.Args[2:]
  cmd := exec.Command(os.Args[1], args...)

  cmd.Stdout = os.Stdout
  cmd.Stderr = os.Stderr

  err := cmd.Start()
  if err != nil {
    os.Exit(1)
  }
}

An example:

# invoke exe-file:
go run test-dump-part.go test-dump.exe "test&whoami"
+    `test-dump.exe´ `test&whoami´
# invoke cmd-file:
go run test-dump-part.go test-dump.CMD "test&whoami"
-    `test-dump.exe´ `test´my_domain\sebres

For more “broken” cases see the result of my test-suite: https://github.com/sebres/PoC/blob/master/SB-0D-001-win-exec/results/go.diff

What did you expect to see?

Arguments are escaped/quoted properly.

What did you see instead?

Arguments are insufficient escaped/quoted, so it is vulnerable currently.

Solution:

For possible solution see the algorithm description resp. how it was fixed in TCL (see the function BuildCommandLine)

Possible similar issues:

#17149, #3752

About this issue

  • Original URL
  • State: closed
  • Created 6 years ago
  • Comments: 17 (8 by maintainers)

Commits related to this issue

Most upvoted comments

Minimal reproducer:

exec.go

package main

import (
	"log"
	"os"
	"os/exec"
)

func main() {
	cmd := exec.Command(`echo.bat`, "&whoami")
	cmd.Stdin = os.Stdin
	cmd.Stdout = os.Stdout
	cmd.Stderr = os.Stderr

	err := cmd.Run()
	if err != nil {
		log.Fatal(err)
	}
}

echo.bat

@echo hi

output

C:\Users\Andrew\Go\src>go run exec.go
hi
kenshin\andrew

expected output

C:\Users\Andrew\Go\src>go run exec.go
hi

I looked into this and it does appear to be a real issue.

Background

On Windows, command line arguments are not passed to programs as an array like they are on UNIX, but as a single, unprocessesed string.

In principle, this means that any program is free to parse the command line string any way it wants, using any quoting rules it wants.

In practice, most programs use the CommandLineToArgv function to parse command line arguments, so Go’s os/exec package quotes the argument array in such a way that CommandLineToArgv will yield the same arguments.

The problem

Most windows programs use CommandLineToArgv to parse their arguments, but there is an important exception: cmd.exe. Also, .BAT and .CMD files are executed on windows by running CMD.EXE /C. If you run a .BAT file with arguments, as in e.g.,

echo.bat hi&whoami

then windows ends up running

C:\WINDOWS\system32\cmd.exe /c echo.bat hi&whoami

The part after /c is interpreted as a command, which may contain metacharacters like <>&. cmd.exe also has different quoting rules: in particular, backslash escapes like \" are not recognized and the caret ^ is treated as an escape character.

All together, this means that if a Go program ever executes a .bat file with os/exec, it is likely that the command line string will be misinterpreted. Since CMD.EXE can execute arbitrary commands, this leads directly to arbitrary code execution.


@sebres does that about sum it up?

Incidentally, it looks like this problem was already recognized and reported two years ago (https://github.com/golang/go/issues/15566, maybe https://github.com/golang/go/issues/17149) and a fix proposed (https://golang.org/cl/30947) but never merged.

In my opinion it is a 0-day vuln, that should be fixed asap in all still supported versions.

FWIW (suspected) security vulnerabilities should be disclosed privately (see https://golang.org/security).