@@ -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. */
4496class 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 )
112165select sink .getNode ( ) , source , sink , "Using a constant $@ to create oauth2 URLs." , source .getNode ( ) ,
113166 "state string"
0 commit comments