Skip to content

Commit 1b615e2

Browse files
committed
Merge branch 'main' into alexdenisov/macros
2 parents f5ea133 + a8fcfd1 commit 1b615e2

File tree

13 files changed

+429
-88
lines changed

13 files changed

+429
-88
lines changed

cpp/ql/src/Security/CWE/CWE-170/ImproperNullTerminationTainted.ql

Lines changed: 32 additions & 67 deletions
Original file line numberDiff line numberDiff line change
@@ -12,79 +12,44 @@
1212

1313
import cpp
1414
import semmle.code.cpp.commons.NullTermination
15-
import semmle.code.cpp.ir.dataflow.internal.DefaultTaintTrackingImpl
15+
import semmle.code.cpp.security.FlowSources as FS
16+
import semmle.code.cpp.dataflow.new.TaintTracking
17+
import semmle.code.cpp.ir.IR
1618

17-
/** A user-controlled expression that may not be null terminated. */
18-
class TaintSource extends VariableAccess {
19-
TaintSource() {
20-
exists(SecurityOptions x, string cause |
21-
this.getTarget() instanceof SemanticStackVariable and
22-
x.isUserInput(this, cause)
23-
|
24-
cause = ["read", "fread", "recv", "recvfrom", "recvmsg"]
25-
)
26-
}
19+
predicate isSource(FS::FlowSource source, string sourceType) {
20+
sourceType = source.getSourceType() and
21+
exists(VariableAccess va, Call call |
22+
va = source.asDefiningArgument() and
23+
call.getAnArgument() = va and
24+
va.getTarget() instanceof SemanticStackVariable and
25+
call.getTarget().hasGlobalName(["read", "fread", "recv", "recvfrom", "recvmsg"])
26+
)
27+
}
2728

28-
/**
29-
* Holds if `sink` is a tainted variable access that must be null
30-
* terminated.
31-
*/
32-
private predicate isSink(VariableAccess sink) {
33-
tainted(this, sink) and
34-
variableMustBeNullTerminated(sink)
35-
}
29+
predicate isSink(DataFlow::Node sink, VariableAccess va) {
30+
va = [sink.asExpr(), sink.asIndirectExpr()] and
31+
variableMustBeNullTerminated(va)
32+
}
3633

37-
/**
38-
* Holds if this source can reach `va`, possibly using intermediate
39-
* reassignments.
40-
*/
41-
private predicate sourceReaches(VariableAccess va) {
42-
definitionUsePair(_, this, va)
43-
or
44-
exists(VariableAccess mid, Expr def |
45-
this.sourceReaches(mid) and
46-
exprDefinition(_, def, mid) and
47-
definitionUsePair(_, def, va)
48-
)
49-
}
34+
private module Config implements DataFlow::ConfigSig {
35+
predicate isSource(DataFlow::Node source) { isSource(source, _) }
5036

51-
/**
52-
* Holds if the sink `sink` is reachable both from this source and
53-
* from `va`, possibly using intermediate reassignments.
54-
*/
55-
private predicate reachesSink(VariableAccess va, VariableAccess sink) {
56-
this.isSink(sink) and
57-
va = sink
37+
predicate isBarrier(DataFlow::Node node) {
38+
isSink(node) and node.asExpr().getUnspecifiedType() instanceof ArithmeticType
39+
or
40+
node.asInstruction().(StoreInstruction).getResultType() instanceof ArithmeticType
5841
or
59-
exists(VariableAccess mid, Expr def |
60-
this.reachesSink(mid, sink) and
61-
exprDefinition(_, def, va) and
62-
definitionUsePair(_, def, mid)
63-
)
42+
mayAddNullTerminator(_, node.asIndirectExpr())
6443
}
6544

66-
/**
67-
* Holds if `sink` is a tainted variable access that must be null
68-
* terminated, and no access which null terminates its contents can
69-
* either reach the sink or be reached from the source. (Ideally,
70-
* we should instead look for such accesses only on the path from
71-
* this source to `sink` found via `tainted(source, sink)`.)
72-
*/
73-
predicate reaches(VariableAccess sink) {
74-
this.isSink(sink) and
75-
not exists(VariableAccess va |
76-
va != this and
77-
va != sink and
78-
mayAddNullTerminator(_, va)
79-
|
80-
this.sourceReaches(va)
81-
or
82-
this.reachesSink(va, sink)
83-
)
84-
}
45+
predicate isSink(DataFlow::Node sink) { isSink(sink, _) }
8546
}
8647

87-
from TaintSource source, VariableAccess sink
88-
where source.reaches(sink)
89-
select sink, "String operation depends on a $@ that may not be null terminated.", source,
90-
"user-provided value"
48+
module Flow = TaintTracking::Global<Config>;
49+
50+
from DataFlow::Node source, DataFlow::Node sink, VariableAccess va, string sourceType
51+
where
52+
Flow::flow(source, sink) and
53+
isSource(source, sourceType) and
54+
isSink(sink, va)
55+
select va, "String operation depends on $@ that may not be null terminated.", source, sourceType

cpp/ql/src/Security/CWE/CWE-807/TaintedCondition.ql

Lines changed: 63 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -12,30 +12,83 @@
1212
* external/cwe/cwe-807
1313
*/
1414

15-
import semmle.code.cpp.ir.dataflow.internal.DefaultTaintTrackingImpl
16-
import TaintedWithPath
15+
import cpp
16+
import semmle.code.cpp.security.Security
17+
import semmle.code.cpp.security.FlowSources
18+
import semmle.code.cpp.ir.dataflow.TaintTracking
19+
import semmle.code.cpp.ir.IR
20+
import Flow::PathGraph
21+
22+
Expr getExprWithoutNot(Expr expr) {
23+
result = expr and not expr instanceof NotExpr
24+
or
25+
result = getExprWithoutNot(expr.(NotExpr).getOperand()) and expr instanceof NotExpr
26+
}
1727

1828
predicate sensitiveCondition(Expr condition, Expr raise) {
1929
raisesPrivilege(raise) and
2030
exists(IfStmt ifstmt |
21-
ifstmt.getCondition() = condition and
31+
getExprWithoutNot(ifstmt.getCondition()) = condition and
2232
raise.getEnclosingStmt().getParentStmt*() = ifstmt
2333
)
2434
}
2535

26-
class Configuration extends TaintTrackingConfiguration {
27-
override predicate isSink(Element tainted) { sensitiveCondition(tainted, _) }
36+
private predicate constantInstruction(Instruction instr) {
37+
instr instanceof ConstantInstruction
38+
or
39+
instr instanceof StringConstantInstruction
40+
or
41+
constantInstruction(instr.(UnaryInstruction).getUnary())
42+
}
43+
44+
predicate isSource(FlowSource source, string sourceType) { sourceType = source.getSourceType() }
45+
46+
module Config implements DataFlow::ConfigSig {
47+
predicate isSource(DataFlow::Node node) { isSource(node, _) }
48+
49+
predicate isSink(DataFlow::Node node) {
50+
sensitiveCondition([node.asExpr(), node.asIndirectExpr()], _)
51+
}
52+
53+
predicate isBarrier(DataFlow::Node node) {
54+
// Block flow into binary instructions if both operands are non-constant
55+
exists(BinaryInstruction iTo |
56+
iTo = node.asInstruction() and
57+
not constantInstruction(iTo.getLeft()) and
58+
not constantInstruction(iTo.getRight()) and
59+
// propagate taint from either the pointer or the offset, regardless of constant-ness
60+
not iTo instanceof PointerArithmeticInstruction
61+
)
62+
or
63+
// Block flow through calls to pure functions if two or more operands are non-constant
64+
exists(Instruction iFrom1, Instruction iFrom2, CallInstruction iTo |
65+
iTo = node.asInstruction() and
66+
isPureFunction(iTo.getStaticCallTarget().getName()) and
67+
iFrom1 = iTo.getAnArgument() and
68+
iFrom2 = iTo.getAnArgument() and
69+
not constantInstruction(iFrom1) and
70+
not constantInstruction(iFrom2) and
71+
iFrom1 != iFrom2
72+
)
73+
}
2874
}
2975

76+
module Flow = TaintTracking::Global<Config>;
77+
3078
/*
3179
* Produce an alert if there is an 'if' statement whose condition `condition`
3280
* is influenced by tainted data `source`, and the body contains
3381
* `raise` which escalates privilege.
3482
*/
3583

36-
from Expr source, Expr condition, Expr raise, PathNode sourceNode, PathNode sinkNode
84+
from
85+
Expr raise, string sourceType, DataFlow::Node source, DataFlow::Node sink,
86+
Flow::PathNode sourceNode, Flow::PathNode sinkNode
3787
where
38-
taintedWithPath(source, condition, sourceNode, sinkNode) and
39-
sensitiveCondition(condition, raise)
40-
select condition, sourceNode, sinkNode, "Reliance on untrusted input $@ to raise privilege at $@.",
41-
source, source.toString(), raise, raise.toString()
88+
source = sourceNode.getNode() and
89+
sink = sinkNode.getNode() and
90+
isSource(source, sourceType) and
91+
sensitiveCondition([sink.asExpr(), sink.asIndirectExpr()], raise) and
92+
Flow::flowPath(sourceNode, sinkNode)
93+
select sink, sourceNode, sinkNode, "Reliance on $@ to raise privilege at $@.", source, sourceType,
94+
raise, raise.toString()
Original file line numberDiff line numberDiff line change
@@ -1,2 +1,2 @@
1-
| test.cpp:466:10:466:15 | buffer | String operation depends on a $@ that may not be null terminated. | test.cpp:465:18:465:23 | buffer | user-provided value |
2-
| test.cpp:481:10:481:15 | buffer | String operation depends on a $@ that may not be null terminated. | test.cpp:480:9:480:14 | buffer | user-provided value |
1+
| test.cpp:466:10:466:15 | buffer | String operation depends on $@ that may not be null terminated. | test.cpp:465:18:465:23 | read output argument | buffer read by read |
2+
| test.cpp:481:10:481:15 | buffer | String operation depends on $@ that may not be null terminated. | test.cpp:480:9:480:14 | fread output argument | string read by fread |
Lines changed: 5 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -1,13 +1,11 @@
11
edges
2-
| test.cpp:20:29:20:34 | call to getenv | test.cpp:24:10:24:35 | ! ... |
3-
| test.cpp:20:29:20:34 | call to getenv | test.cpp:24:11:24:16 | call to strcmp |
4-
| test.cpp:20:29:20:47 | call to getenv | test.cpp:24:10:24:35 | ! ... |
52
| test.cpp:20:29:20:47 | call to getenv | test.cpp:24:11:24:16 | call to strcmp |
6-
subpaths
3+
| test.cpp:20:29:20:47 | call to getenv indirection | test.cpp:24:11:24:16 | call to strcmp |
74
nodes
8-
| test.cpp:20:29:20:34 | call to getenv | semmle.label | call to getenv |
95
| test.cpp:20:29:20:47 | call to getenv | semmle.label | call to getenv |
10-
| test.cpp:24:10:24:35 | ! ... | semmle.label | ! ... |
6+
| test.cpp:20:29:20:47 | call to getenv indirection | semmle.label | call to getenv indirection |
117
| test.cpp:24:11:24:16 | call to strcmp | semmle.label | call to strcmp |
8+
subpaths
129
#select
13-
| test.cpp:24:10:24:35 | ! ... | test.cpp:20:29:20:34 | call to getenv | test.cpp:24:10:24:35 | ! ... | Reliance on untrusted input $@ to raise privilege at $@. | test.cpp:20:29:20:34 | call to getenv | call to getenv | test.cpp:25:9:25:27 | ... = ... | ... = ... |
10+
| test.cpp:24:11:24:16 | call to strcmp | test.cpp:20:29:20:47 | call to getenv | test.cpp:24:11:24:16 | call to strcmp | Reliance on $@ to raise privilege at $@. | test.cpp:20:29:20:47 | call to getenv | an environment variable | test.cpp:25:9:25:27 | ... = ... | ... = ... |
11+
| test.cpp:24:11:24:16 | call to strcmp | test.cpp:20:29:20:47 | call to getenv indirection | test.cpp:24:11:24:16 | call to strcmp | Reliance on $@ to raise privilege at $@. | test.cpp:20:29:20:47 | call to getenv indirection | an environment variable | test.cpp:25:9:25:27 | ... = ... | ... = ... |

go/ql/lib/change-notes/released/0.7.3.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@
22

33
### Minor Analysis Improvements
44

5-
* Added the [gin cors](https://github.com/gin-contrib/cors) library to the CorsMisconfiguration.ql query
5+
* Added the [gin-contrib/cors](https://github.com/gin-contrib/cors) library to the experimental query "CORS misconfiguration" (`go/cors-misconfiguration`).
66

77
### Bug Fixes
88

Lines changed: 35 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,35 @@
1+
<!DOCTYPE qhelp PUBLIC "-//Semmle//qhelp//EN" "qhelp.dtd">
2+
<qhelp>
3+
<overview>
4+
<p>
5+
Web Cache Deception is a security vulnerability where an attacker tricks a web server into caching sensitive information and then accesses that cached data.
6+
</p>
7+
<p>
8+
This attack exploits certain behaviors in caching mechanisms by requesting URLs that trick the server into thinking that a non-cachable page is cachable. If a user then accesses sensitive information on these pages, it could be cached and later retrieved by the attacker.
9+
</p>
10+
</overview>
11+
<recommendation>
12+
<p>
13+
To prevent Web Cache Deception attacks, web applications should clearly define cacheable and non-cacheable resources. Implementing strict cache controls and validating requested URLs can mitigate the risk of sensitive data being cached.
14+
</p>
15+
</recommendation>
16+
<example>
17+
<p>
18+
Vulnerable code example: A web server is configured to cache all responses ending in '.css'. An attacker requests 'profile.css', and the server processes 'profile', a sensitive page, and caches it.
19+
</p>
20+
<sample src="WebCacheDeceptionBad.go" />
21+
</example>
22+
<example>
23+
<p>
24+
Secure code example: The server is configured with strict cache controls and URL validation, preventing caching of dynamic or sensitive pages regardless of their URL pattern.
25+
</p>
26+
<sample src="WebCacheDeceptionGood.go" />
27+
</example>
28+
<references>
29+
<li>
30+
OWASP Web Cache Deception Attack:
31+
<a href="https://owasp.org/www-community/attacks/Web_Cache_Deception">Understanding Web Cache Deception Attacks</a>
32+
</li>
33+
<!-- Additional references can be added here -->
34+
</references>
35+
</qhelp>
Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,27 @@
1+
/*
2+
* @name Web Cache Deception
3+
* @description A caching system has been detected on the application and is vulnerable to web cache deception. By manipulating the URL it is possible to force the application to cache pages that are only accessible by an authenticated user. Once cached, these pages can be accessed by an unauthenticated user.
4+
* @kind problem
5+
* @problem.severity error
6+
* @security-severity 9
7+
* @precision high
8+
* @id go/web-cache-deception
9+
* @tags security
10+
* external/cwe/cwe-525
11+
*/
12+
13+
import go
14+
15+
from
16+
DataFlow::CallNode httpHandleFuncCall, DataFlow::ReadNode rn, Http::HeaderWrite::Range hw,
17+
DeclaredFunction f
18+
where
19+
httpHandleFuncCall.getTarget().hasQualifiedName("net/http", "HandleFunc") and
20+
httpHandleFuncCall.getArgument(0).getStringValue().matches("%/") and
21+
httpHandleFuncCall.getArgument(1) = rn and
22+
rn.reads(f) and
23+
f.getParameter(0) = hw.getResponseWriter() and
24+
hw.getHeaderName() = "cache-control"
25+
select httpHandleFuncCall.getArgument(0),
26+
"Wildcard Endpoint used with " + httpHandleFuncCall.getArgument(0) + " and '" + hw.getHeaderName()
27+
+ "' Header is used"
Lines changed: 87 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,87 @@
1+
package bad
2+
3+
import (
4+
"fmt"
5+
"html/template"
6+
"log"
7+
"net/http"
8+
"os/exec"
9+
"strings"
10+
"sync"
11+
)
12+
13+
var sessionMap = make(map[string]string)
14+
15+
var (
16+
templateCache = make(map[string]*template.Template)
17+
mutex = &sync.Mutex{}
18+
)
19+
20+
type Lists struct {
21+
Uid string
22+
UserName string
23+
UserLists []string
24+
ReadFile func(filename string) string
25+
}
26+
27+
func parseTemplateFile(templateName string, tmplFile string) (*template.Template, error) {
28+
mutex.Lock()
29+
defer mutex.Unlock()
30+
31+
// Check if the template is already cached
32+
if cachedTemplate, ok := templateCache[templateName]; ok {
33+
fmt.Println("cached")
34+
return cachedTemplate, nil
35+
}
36+
37+
// Parse and store the template in the cache
38+
parsedTemplate, _ := template.ParseFiles(tmplFile)
39+
fmt.Println("not cached")
40+
41+
templateCache[templateName] = parsedTemplate
42+
return parsedTemplate, nil
43+
}
44+
45+
func ShowAdminPageCache(w http.ResponseWriter, r *http.Request) {
46+
47+
if r.Method == "GET" {
48+
fmt.Println("cache called")
49+
sessionMap[r.RequestURI] = "admin"
50+
51+
// Check if a session value exists
52+
if _, ok := sessionMap[r.RequestURI]; ok {
53+
cmd := "mysql -h mysql -u root -prootwolf -e 'select id,name,mail,age,created_at,updated_at from vulnapp.user where name not in (\"" + "admin" + "\");'"
54+
55+
// mysql -h mysql -u root -prootwolf -e 'select id,name,mail,age,created_at,updated_at from vulnapp.user where name not in ("test");--';echo");'
56+
fmt.Println(cmd)
57+
58+
res, err := exec.Command("sh", "-c", cmd).Output()
59+
if err != nil {
60+
fmt.Println("err : ", err)
61+
}
62+
63+
splitedRes := strings.Split(string(res), "\n")
64+
65+
p := Lists{Uid: "1", UserName: "admin", UserLists: splitedRes}
66+
67+
parsedTemplate, _ := parseTemplateFile("page", "./views/admin/userlists.gtpl")
68+
w.Header().Set("Cache-Control", "no-store, no-cache")
69+
err = parsedTemplate.Execute(w, p)
70+
}
71+
} else {
72+
http.NotFound(w, nil)
73+
}
74+
75+
}
76+
77+
func main() {
78+
fmt.Println("Vulnapp server listening : 1337")
79+
80+
http.Handle("/assets/", http.StripPrefix("/assets/", http.FileServer(http.Dir("assets/"))))
81+
82+
http.HandleFunc("/adminusers/", ShowAdminPageCache)
83+
err := http.ListenAndServe(":1337", nil)
84+
if err != nil {
85+
log.Fatal("ListenAndServe: ", err)
86+
}
87+
}

0 commit comments

Comments
 (0)