Skip to content

Commit bfc95c6

Browse files
authored
Merge pull request #16510 from erik-krogh/go-command
Go: Update the QHelp for `go/command-injection`.
2 parents b639f60 + 384649b commit bfc95c6

13 files changed

+266
-41
lines changed

go/ql/lib/semmle/go/security/CommandInjectionCustomizations.qll

Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@
55
*/
66

77
import go
8+
private import semmle.go.dataflow.barrierguardutil.RegexpCheck
89

910
/**
1011
* Provides extension points for customizing the taint tracking configuration for reasoning about
@@ -45,4 +46,26 @@ module CommandInjection {
4546

4647
override predicate doubleDashIsSanitizing() { exec.doubleDashIsSanitizing() }
4748
}
49+
50+
/**
51+
* A call to a regexp match function, considered as a barrier guard for command injection.
52+
*/
53+
class RegexpCheckBarrierAsSanitizer extends Sanitizer instanceof RegexpCheckBarrier { }
54+
55+
private predicate noDoubleDashPrefixCheck(DataFlow::Node hasPrefixNode, Expr e, boolean branch) {
56+
exists(StringOps::HasPrefix hasPrefix | hasPrefix = hasPrefixNode |
57+
e = hasPrefix.getBaseString().asExpr() and
58+
hasPrefix.getSubstring().asExpr().getStringValue() = "--" and
59+
branch = false
60+
)
61+
}
62+
63+
/**
64+
* A call that confirms that the string does not start with `--`, considered as a barrier guard for command injection.
65+
*/
66+
class NoDoubleDashPrefixSanitizer extends Sanitizer {
67+
NoDoubleDashPrefixSanitizer() {
68+
this = DataFlow::BarrierGuard<noDoubleDashPrefixCheck/3>::getABarrierNode()
69+
}
70+
}
4871
}

go/ql/src/Security/CWE-078/CommandInjection.go

Lines changed: 0 additions & 12 deletions
This file was deleted.

go/ql/src/Security/CWE-078/CommandInjection.qhelp

Lines changed: 33 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -12,26 +12,49 @@ a malicious user may be able to run commands to exfiltrate data or compromise th
1212

1313
<recommendation>
1414
<p>
15-
If possible, use hard-coded string literals to specify the command to run. Instead of interpreting
16-
user input directly as command names, examine the input and then choose among hard-coded string
17-
literals.
15+
Whenever possible, use hard-coded string literals for commands and avoid shell
16+
string interpreters like <code>sh -c</code>.
1817
</p>
1918
<p>
20-
If this is not possible, then add sanitization code to verify that the user input is safe before
21-
using it.
19+
If given arguments as a single string, avoid simply splitting the string on
20+
whitespace. Arguments may contain quoted whitespace, causing them to split into
21+
multiple arguments.
22+
</p>
23+
<p>
24+
If this is not possible, sanitize user input to avoid characters like spaces and
25+
various kinds of quotes that can alter the meaning of the command.
2226
</p>
2327
</recommendation>
2428

2529
<example>
2630
<p>
27-
In the following example, assume the function <code>handler</code> is an HTTP request handler in a
28-
web application, whose parameter <code>req</code> contains the request object:
31+
In the following example, assume the function <code>handler</code> is an HTTP request
32+
handler in a web application, whose parameter <code>req</code> contains the request object:
33+
</p>
34+
<sample src="examples/CommandInjection.go"/>
35+
<p>
36+
The handler extracts the image file name from the request and uses the image name to construct a
37+
shell command that is executed using <code>`sh -c`</code>, which can lead to command injection.
38+
</p>
39+
<p>
40+
It's better to avoid shell commands by using the <code>exec.Command</code> function directly,
41+
as shown in the following example:
42+
</p>
43+
<sample src="examples/CommandInjectionGood.go"/>
44+
<p>
45+
Alternatively, a regular expression can be used to ensure that the image name is safe to use
46+
in a shell command:
47+
</p>
48+
<sample src="examples/CommandInjectionGood2.go"/>
49+
<p>
50+
Some commands, like <code>git</code>, can indirectly execute commands if an attacker specifies
51+
the flags given to the command.
2952
</p>
30-
<sample src="CommandInjection.go"/>
3153
<p>
32-
The handler extracts the name of a system command from the request object, and then runs it without
33-
any further checks, which can cause a command-injection vulnerability.
54+
To mitigate this risk, either add a <code>--</code> argument to ensure subsequent arguments are
55+
not interpreted as flags, or verify that the argument does not start with <code>"--"</code>.
3456
</p>
57+
<sample src="examples/CommandInjectionGood3.go"/>
3558
</example>
3659
<references>
3760
<li>

go/ql/src/Security/CWE-078/StoredCommand.qhelp

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -28,13 +28,13 @@ using it.
2828
In the following example, the function <code>run</code> runs a command directly from the result of a
2929
query:
3030
</p>
31-
<sample src="StoredCommand.go"/>
31+
<sample src="examples/StoredCommand.go"/>
3232
<p>
3333
The function extracts the name of a system command from the database query, and then runs it without
3434
any further checks, which can cause a command-injection vulnerability. A possible solution is to
3535
ensure that commands are checked against a whitelist:
3636
</p>
37-
<sample src="StoredCommandGood.go"/>
37+
<sample src="examples/StoredCommandGood.go"/>
3838
</example>
3939

4040
<references>
Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,14 @@
1+
package main
2+
3+
import (
4+
"net/http"
5+
"os/exec"
6+
)
7+
8+
func handler(req *http.Request) {
9+
imageName := req.URL.Query()["imageName"][0]
10+
outputPath := "/tmp/output.svg"
11+
cmd := exec.Command("sh", "-c", fmt.Sprintf("imagetool %s > %s", imageName, outputPath))
12+
cmd.Run()
13+
// ...
14+
}
Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,28 @@
1+
package main
2+
3+
import (
4+
"log"
5+
"net/http"
6+
"os"
7+
"os/exec"
8+
)
9+
10+
func handler(req *http.Request) {
11+
imageName := req.URL.Query()["imageName"][0]
12+
outputPath := "/tmp/output.svg"
13+
14+
// Create the output file
15+
outfile, err := os.Create(outputPath)
16+
if err != nil {
17+
log.Fatal(err)
18+
}
19+
defer outfile.Close()
20+
21+
// Prepare the command
22+
cmd := exec.Command("imagetool", imageName)
23+
24+
// Set the output to our file
25+
cmd.Stdout = outfile
26+
27+
cmd.Run()
28+
}
Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,23 @@
1+
package main
2+
3+
import (
4+
"log"
5+
"net/http"
6+
"os/exec"
7+
"regexp"
8+
)
9+
10+
func handler(req *http.Request) {
11+
imageName := req.URL.Query()["imageName"][0]
12+
outputPath := "/tmp/output.svg"
13+
14+
// Validate the imageName with a regular expression
15+
validImageName := regexp.MustCompile(`^[a-zA-Z0-9_\-\.]+$`)
16+
if !validImageName.MatchString(imageName) {
17+
log.Fatal("Invalid image name")
18+
return
19+
}
20+
21+
cmd := exec.Command("sh", "-c", fmt.Sprintf("imagetool %s > %s", imageName, outputPath))
22+
cmd.Run()
23+
}
Lines changed: 31 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,31 @@
1+
package main
2+
3+
import (
4+
"log"
5+
"net/http"
6+
"os/exec"
7+
"strings"
8+
)
9+
10+
func handler(req *http.Request) {
11+
repoURL := req.URL.Query()["repoURL"][0]
12+
outputPath := "/tmp/repo"
13+
14+
// Sanitize the repoURL to ensure it does not start with "--"
15+
if strings.HasPrefix(repoURL, "--") {
16+
log.Fatal("Invalid repository URL")
17+
} else {
18+
cmd := exec.Command("git", "clone", repoURL, outputPath)
19+
err := cmd.Run()
20+
if err != nil {
21+
log.Fatal(err)
22+
}
23+
}
24+
25+
// Or: add "--" to ensure that the repoURL is not interpreted as a flag
26+
cmd := exec.Command("git", "clone", "--", repoURL, outputPath)
27+
err := cmd.Run()
28+
if err != nil {
29+
log.Fatal(err)
30+
}
31+
}

0 commit comments

Comments
 (0)