Skip to content
This repository was archived by the owner on Jan 5, 2023. It is now read-only.

Commit 050a823

Browse files
committed
OAuth2 exclusion: hide cases that clearly target an out-of-band process or private HTTP server
1 parent bcb6515 commit 050a823

File tree

1 file changed

+54
-1
lines changed

1 file changed

+54
-1
lines changed

ql/src/experimental/CWE-352/ConstantOauth2State.ql

Lines changed: 54 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -40,6 +40,58 @@ class ConstantStateFlowConf extends DataFlow::Configuration {
4040
override predicate isSink(DataFlow::Node sink) { isSink(sink, _) }
4141
}
4242

43+
/**
44+
* A flow of a URL indicating the OAuth redirect doesn't point to a publically
45+
* accessible address, to the receiver of an AuthCodeURL call.
46+
*
47+
* Note we accept localhost and 127.0.0.1 on the assumption this is probably a transient
48+
* listener; if it actually is a persistent server then that really is vulnerable to CSRF.
49+
*/
50+
class PrivateUrlFlowsToAuthCodeUrlCall extends DataFlow::Configuration {
51+
PrivateUrlFlowsToAuthCodeUrlCall() { this = "PrivateUrlFlowsToConfig" }
52+
53+
override predicate isSource(DataFlow::Node source) {
54+
// The following are all common ways to indicate out-of-band OAuth2 flow, in which case
55+
// the authenticating party does not redirect but presents a code for the user to copy
56+
// instead.
57+
source.asExpr().(StringLit).getValue() in ["urn:ietf:wg:oauth:2.0:oob",
58+
"urn:ietf:wg:oauth:2.0:oob:auto", "oob", "code"] or
59+
// Alternatively some non-web tools will create a temporary local webserver to handle the
60+
// OAuth2 redirect:
61+
source.asExpr().(StringLit).getValue().matches("%://localhost%") or
62+
source.asExpr().(StringLit).getValue().matches("%://127.0.0.1%")
63+
}
64+
65+
/**
66+
* Propagates a URL written to a RedirectURL field to the whole Config object.
67+
*/
68+
override predicate isAdditionalFlowStep(DataFlow::Node pred, DataFlow::Node succ) {
69+
exists(Write w, Field f | f.hasQualifiedName("golang.org/x/oauth2", "Config", "RedirectURL") |
70+
w.writesField(succ.(DataFlow::PostUpdateNode).getPreUpdateNode(), f, pred)
71+
)
72+
}
73+
74+
predicate isSink(DataFlow::Node sink, DataFlow::CallNode call) {
75+
exists(AuthCodeURL m | call = m.getACall() | sink = call.getReceiver())
76+
}
77+
78+
override predicate isSink(DataFlow::Node sink) { isSink(sink, _) }
79+
}
80+
81+
/**
82+
* Holds if a URL indicating the OAuth redirect doesn't point to a publically
83+
* accessible address, to the receiver of an AuthCodeURL call.
84+
*
85+
* Note we accept localhost and 127.0.0.1 on the assumption this is probably a transient
86+
* listener; if it actually is a persistent server then that really is vulnerable to CSRF.
87+
*/
88+
predicate privateUrlFlowsToAuthCodeUrlCall(DataFlow::CallNode call) {
89+
exists(PrivateUrlFlowsToAuthCodeUrlCall flowConfig, DataFlow::Node receiver |
90+
flowConfig.hasFlowTo(receiver) and
91+
flowConfig.isSink(receiver, call)
92+
)
93+
}
94+
4395
/** A flow to a printer function of the fmt package. */
4496
class FlowToPrint extends DataFlow::Configuration {
4597
FlowToPrint() { this = "FlowToPrint" }
@@ -108,6 +160,7 @@ where
108160
cfg.hasFlowPath(source, sink) and
109161
cfg.isSink(sink.getNode(), sinkCall) and
110162
// Exclude cases that seem to be oauth flows done from within a terminal:
111-
not seemsLikeDoneWithinATerminal(sinkCall)
163+
not seemsLikeDoneWithinATerminal(sinkCall) and
164+
not privateUrlFlowsToAuthCodeUrlCall(sinkCall)
112165
select sink.getNode(), source, sink, "Using a constant $@ to create oauth2 URLs.", source.getNode(),
113166
"state string"

0 commit comments

Comments
 (0)