-
Notifications
You must be signed in to change notification settings - Fork 9
Expand file tree
/
Copy pathPotentiallyUnguardedProtocolHandler.ql
More file actions
149 lines (137 loc) · 4.93 KB
/
PotentiallyUnguardedProtocolHandler.ql
File metadata and controls
149 lines (137 loc) · 4.93 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
/**
* @name Potentially unguarded protocol handler invocation
* @id tob/cpp/unguarded-protocol-handler
* @description Detects calls to URL protocol handlers with untrusted input that may not be properly validated for dangerous protocols
* @kind path-problem
* @tags security
* external/cwe/cwe-939
* @precision medium
* @problem.severity warning
* @security-severity 6.5
* @group security
*/
import cpp
private import semmle.code.cpp.ir.dataflow.TaintTracking
private import semmle.code.cpp.security.FlowSources
private import semmle.code.cpp.security.CommandExecution
/**
* Generic case: invoke protocol handling through OS's protocol handling utilities. This aligns with CVE-2022-43550.
*/
class ShellProtocolHandler extends SystemFunction {
ShellProtocolHandler() {
// Check if any calls to this function contain protocol handler invocations
exists(FunctionCall call |
call.getTarget() = this and
exists(Expr arg |
arg = call.getArgument(0).getAChild*() and
exists(StringLiteral sl | sl = arg or sl.getParent*() = arg |
sl.getValue()
.regexpMatch("(?i).*(rundll32.*url\\.dll.*FileProtocolHandler|xdg-open|\\bopen\\b).*")
)
)
)
}
string getHandlerType() {
exists(FunctionCall call, StringLiteral sl |
call.getTarget() = this and
sl.getParent*() = call.getArgument(0) and
(
sl.getValue().regexpMatch("(?i).*rundll32.*url\\.dll.*FileProtocolHandler.*") and
result = "rundll32 url.dll,FileProtocolHandler"
or
sl.getValue().regexpMatch("(?i).*xdg-open.*") and
result = "xdg-open"
or
sl.getValue().regexpMatch("(?i).*\\bopen\\b.*") and
result = "open"
)
)
}
}
/**
* Qt's QDesktopServices::openUrl method
*/
class QtProtocolHandler extends FunctionCall {
QtProtocolHandler() { this.getTarget().hasQualifiedName("QDesktopServices", "openUrl") }
Expr getUrlArgument() { result = this.getArgument(0) }
}
/**
* A sanitizer node that represents URL scheme validation
*/
class UrlSchemeValidationSanitizer extends DataFlow::Node {
UrlSchemeValidationSanitizer() {
exists(FunctionCall fc |
fc = this.asExpr() and
(
// String comparison on the untrusted URL
fc.getTarget().getName() =
[
"strcmp", "strncmp", "strcasecmp", "strncasecmp", "strstr", "strcasestr", "_stricmp",
"_strnicmp"
]
or
// Qt QUrl::scheme() comparison - QUrl::scheme() returns QString
// Pattern: url.scheme() == "http" or url.scheme() == "https"
exists(FunctionCall schemeCall |
schemeCall.getTarget().hasQualifiedName("QUrl", "scheme") and
(
// Direct comparison
fc.getTarget().hasName(["operator==", "operator!="]) and
fc.getAnArgument() = schemeCall
or
// QString comparison methods
fc = schemeCall and
exists(FunctionCall qstringCmp |
qstringCmp.getQualifier() = schemeCall and
qstringCmp.getTarget().hasQualifiedName("QString", ["compare", "operator=="])
)
)
)
or
// Qt QString startsWith check for direct URL strings
fc.getTarget().hasQualifiedName("QString", "startsWith")
)
)
}
}
/**
* Configuration for tracking untrusted data to protocol handler invocations
*/
module PotentiallyUnguardedProtocolHandlerConfig implements DataFlow::ConfigSig {
predicate isSource(DataFlow::Node source) { source instanceof RemoteFlowSource }
predicate isSink(DataFlow::Node sink) {
// QDesktopServices::openUrl()
exists(QtProtocolHandler call | sink.asExpr() = call.getUrlArgument())
or
// Shell protocol handlers (rundll32, xdg-open, open) via system()/popen()/exec*()
exists(FunctionCall call |
call.getTarget() instanceof ShellProtocolHandler and
sink.asExpr() = call.getArgument(0)
)
}
predicate isBarrier(DataFlow::Node node) { node instanceof UrlSchemeValidationSanitizer }
}
module PotentiallyUnguardedProtocolHandlerFlow =
TaintTracking::Global<PotentiallyUnguardedProtocolHandlerConfig>;
import PotentiallyUnguardedProtocolHandlerFlow::PathGraph
from
PotentiallyUnguardedProtocolHandlerFlow::PathNode source,
PotentiallyUnguardedProtocolHandlerFlow::PathNode sink, FunctionCall call, string callType
where
PotentiallyUnguardedProtocolHandlerFlow::flowPath(source, sink) and
(
exists(QtProtocolHandler qtCall |
call = qtCall and
sink.getNode().asExpr() = qtCall.getUrlArgument() and
callType = "QDesktopServices::openUrl()"
)
or
exists(ShellProtocolHandler shellFunc |
call.getTarget() = shellFunc and
sink.getNode().asExpr() = call.getArgument(0) and
callType = shellFunc.getHandlerType()
)
)
select call, source, sink,
callType + " is called with untrusted input from $@ without proper URL scheme validation.",
source.getNode(), "this source"