@@ -40,6 +40,58 @@ class ConstantStateFlowConf extends DataFlow::Configuration {
40
40
override predicate isSink ( DataFlow:: Node sink ) { isSink ( sink , _) }
41
41
}
42
42
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
+
43
95
/** A flow to a printer function of the fmt package. */
44
96
class FlowToPrint extends DataFlow:: Configuration {
45
97
FlowToPrint ( ) { this = "FlowToPrint" }
@@ -108,6 +160,7 @@ where
108
160
cfg .hasFlowPath ( source , sink ) and
109
161
cfg .isSink ( sink .getNode ( ) , sinkCall ) and
110
162
// 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 )
112
165
select sink .getNode ( ) , source , sink , "Using a constant $@ to create oauth2 URLs." , source .getNode ( ) ,
113
166
"state string"
0 commit comments