Skip to content

Commit 82bbecc

Browse files
authored
Merge pull request #16307 from owen-mc/go/fix/incomplete-hostname-regex
Go: fix flow through string concatenation in `go/incomplete-hostname-regex`
2 parents 13ff941 + c61177c commit 82bbecc

File tree

6 files changed

+66
-14
lines changed

6 files changed

+66
-14
lines changed

go/ql/src/Security/CWE-020/IncompleteHostnameRegexp.ql

Lines changed: 10 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -81,14 +81,12 @@ predicate regexpGuardsError(RegexpPattern regexp) {
8181

8282
module IncompleteHostNameRegexpConfig implements DataFlow::ConfigSig {
8383
additional predicate isSourceString(DataFlow::Node source, string hostPart) {
84-
exists(Expr e |
85-
e = source.asExpr() and
86-
isIncompleteHostNameRegexpPattern(e.getStringValue(), hostPart)
87-
|
88-
e instanceof StringLit
89-
or
90-
e instanceof AddExpr and
91-
not isIncompleteHostNameRegexpPattern(e.(AddExpr).getAnOperand().getStringValue(), _)
84+
exists(Expr e | e = source.asExpr() |
85+
isIncompleteHostNameRegexpPattern(e.getStringValue(), hostPart) and
86+
// Exclude constant names to avoid duplicate results, because the string
87+
// literals which they are initialised with are also considered as
88+
// sources.
89+
not e instanceof ConstantName
9290
)
9391
}
9492

@@ -101,6 +99,10 @@ module IncompleteHostNameRegexpConfig implements DataFlow::ConfigSig {
10199
) and
102100
not regexpGuardsError(sink)
103101
}
102+
103+
predicate isAdditionalFlowStep(DataFlow::Node node1, DataFlow::Node node2) {
104+
StringOps::Concatenation::taintStep(node1, node2)
105+
}
104106
}
105107

106108
module Flow = DataFlow::Global<IncompleteHostNameRegexpConfig>;

go/ql/src/Security/CWE-020/IncompleteHostnameRegexpGood2.go

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,7 @@ import (
66
"regexp"
77
)
88

9-
func checkRedirectGood(req *http.Request, via []*http.Request) error {
9+
func checkRedirectGood2(req *http.Request, via []*http.Request) error {
1010
// GOOD: the host of `req.URL` must be `example.com`, `www.example.com` or `beta.example.com`
1111
re := `^((www|beta)\.)?example\.com/`
1212
if matched, _ := regexp.MatchString(re, req.URL.Host); matched {
Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,4 @@
1+
---
2+
category: minorAnalysis
3+
---
4+
* The query `go/incomplete-hostname-regexp` now recognizes more sources involving concatenation of string literals and also follows flow through string concatenation. This may lead to more alerts.
Lines changed: 14 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,12 +1,22 @@
11
edges
22
| IncompleteHostnameRegexp.go:11:8:11:36 | "^((www\|beta).)?example.com/" | IncompleteHostnameRegexp.go:12:38:12:39 | re | provenance | |
3+
| main.go:49:21:49:45 | `https://www.example.com` | main.go:62:15:62:25 | sourceConst | provenance | |
4+
| main.go:62:15:62:25 | sourceConst | main.go:65:15:65:23 | localVar3 | provenance | |
35
nodes
46
| IncompleteHostnameRegexp.go:11:8:11:36 | "^((www\|beta).)?example.com/" | semmle.label | "^((www\|beta).)?example.com/" |
57
| IncompleteHostnameRegexp.go:12:38:12:39 | re | semmle.label | re |
6-
| main.go:39:60:39:79 | "^test2.github.com$" | semmle.label | "^test2.github.com$" |
7-
| main.go:44:15:44:39 | `https://www.example.com` | semmle.label | `https://www.example.com` |
8+
| main.go:40:60:40:79 | "^test2.github.com$" | semmle.label | "^test2.github.com$" |
9+
| main.go:45:15:45:39 | `https://www.example.com` | semmle.label | `https://www.example.com` |
10+
| main.go:49:21:49:45 | `https://www.example.com` | semmle.label | `https://www.example.com` |
11+
| main.go:56:15:56:34 | ...+... | semmle.label | ...+... |
12+
| main.go:58:15:58:42 | ...+... | semmle.label | ...+... |
13+
| main.go:62:15:62:25 | sourceConst | semmle.label | sourceConst |
14+
| main.go:65:15:65:23 | localVar3 | semmle.label | localVar3 |
815
subpaths
916
#select
1017
| IncompleteHostnameRegexp.go:11:8:11:36 | "^((www\|beta).)?example.com/" | IncompleteHostnameRegexp.go:11:8:11:36 | "^((www\|beta).)?example.com/" | IncompleteHostnameRegexp.go:12:38:12:39 | re | This regular expression has an unescaped dot before ')?example.com', so it might match more hosts than expected when $@. | IncompleteHostnameRegexp.go:12:38:12:39 | re | the regular expression is used |
11-
| main.go:39:60:39:79 | "^test2.github.com$" | main.go:39:60:39:79 | "^test2.github.com$" | main.go:39:60:39:79 | "^test2.github.com$" | This regular expression has an unescaped dot before 'github.com', so it might match more hosts than expected when $@. | main.go:39:60:39:79 | "^test2.github.com$" | the regular expression is used |
12-
| main.go:44:15:44:39 | `https://www.example.com` | main.go:44:15:44:39 | `https://www.example.com` | main.go:44:15:44:39 | `https://www.example.com` | This regular expression has an unescaped dot before 'example.com', so it might match more hosts than expected when $@. | main.go:44:15:44:39 | `https://www.example.com` | the regular expression is used |
18+
| main.go:40:60:40:79 | "^test2.github.com$" | main.go:40:60:40:79 | "^test2.github.com$" | main.go:40:60:40:79 | "^test2.github.com$" | This regular expression has an unescaped dot before 'github.com', so it might match more hosts than expected when $@. | main.go:40:60:40:79 | "^test2.github.com$" | the regular expression is used |
19+
| main.go:45:15:45:39 | `https://www.example.com` | main.go:45:15:45:39 | `https://www.example.com` | main.go:45:15:45:39 | `https://www.example.com` | This regular expression has an unescaped dot before 'example.com', so it might match more hosts than expected when $@. | main.go:45:15:45:39 | `https://www.example.com` | the regular expression is used |
20+
| main.go:49:21:49:45 | `https://www.example.com` | main.go:49:21:49:45 | `https://www.example.com` | main.go:65:15:65:23 | localVar3 | This regular expression has an unescaped dot before 'example.com', so it might match more hosts than expected when $@. | main.go:65:15:65:23 | localVar3 | the regular expression is used |
21+
| main.go:56:15:56:34 | ...+... | main.go:56:15:56:34 | ...+... | main.go:56:15:56:34 | ...+... | This regular expression has an unescaped dot before 'example.com', so it might match more hosts than expected when $@. | main.go:56:15:56:34 | ...+... | the regular expression is used |
22+
| main.go:58:15:58:42 | ...+... | main.go:58:15:58:42 | ...+... | main.go:58:15:58:42 | ...+... | This regular expression has an unescaped dot before 'example.com', so it might match more hosts than expected when $@. | main.go:58:15:58:42 | ...+... | the regular expression is used |
Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,16 @@
1+
package main
2+
3+
import (
4+
"errors"
5+
"net/http"
6+
"regexp"
7+
)
8+
9+
func checkRedirectGood2(req *http.Request, via []*http.Request) error {
10+
// GOOD: the host of `req.URL` must be `example.com`, `www.example.com` or `beta.example.com`
11+
re := `^((www|beta)\.)?example\.com/`
12+
if matched, _ := regexp.MatchString(re, req.URL.Host); matched {
13+
return nil
14+
}
15+
return errors.New("Invalid redirect")
16+
}

go/ql/test/query-tests/Security/CWE-020/IncompleteHostnameRegexp/main.go

Lines changed: 21 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3,10 +3,11 @@
33
package main
44

55
import (
6-
"github.com/elazarl/goproxy"
76
"net/http"
87
"regexp"
98
"time"
9+
10+
"github.com/elazarl/goproxy"
1011
)
1112

1213
func Match(notARegex string) bool {
@@ -44,3 +45,22 @@ func main() {
4445
regexp.Match(`https://www.example.com`, []byte("")) // NOT OK
4546
regexp.Match(`https://www\.example\.com`, []byte("")) // OK
4647
}
48+
49+
const sourceConst = `https://www.example.com`
50+
const firstHalfConst = `https://www.example.`
51+
52+
func concatenateStrings() {
53+
firstHalf := `https://www.example.`
54+
regexp.Match(firstHalf+`com`, []byte("")) // MISSING: NOT OK
55+
56+
regexp.Match(firstHalfConst+`com`, []byte("")) // NOT OK
57+
58+
regexp.Match(`https://www.example.`+`com`, []byte("")) // NOT OK
59+
}
60+
61+
func avoidDuplicateResults() {
62+
localVar1 := sourceConst
63+
localVar2 := localVar1
64+
localVar3 := localVar2
65+
regexp.Match(localVar3, []byte("")) // NOT OK
66+
}

0 commit comments

Comments
 (0)