Skip to content
This repository was archived by the owner on Aug 18, 2022. It is now read-only.

Commit 89900b3

Browse files
Merge pull request #8 from github/s-samadi-oauth2-notes
wip-lecture notes
2 parents 6d6080d + 349e73a commit 89900b3

File tree

1 file changed

+296
-7
lines changed

1 file changed

+296
-7
lines changed

go/oauth2-notes.org

Lines changed: 296 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
*
1+
* overview
22
The gist https://gist.github.com/hohn/b4c32ce35b6bdc2ade04c911985c7d46
33

44
* fix: Get("X-Redirect") is "X-Auth-Request-Redirect"
@@ -13,6 +13,7 @@
1313

1414
3. a taint flow configuration to connect the source and sink
1515

16+
* Getting started
1617
from..select
1718

1819
introduce Function
@@ -27,6 +28,7 @@
2728

2829
cartesian product in the select
2930

31+
* Source
3032
mapping from ast to Go and back
3133
look at this
3234
https://codeql.github.com/docs/codeql-language-guides/abstract-syntax-tree-classes-for-working-with-go-programs/
@@ -40,16 +42,21 @@
4042

4143
pick up the type from the select and then...
4244

45+
#+BEGIN_SRC java
4346
from CallExpr c, string funcName
4447
where funcName = c.getCalleeName()
4548
and funcName = "Get"
4649
select funcName
50+
#+END_SRC
51+
4752

4853
Narrow some
54+
#+BEGIN_SRC java
4955
from CallExpr c, string funcName
5056
where funcName = c.getCalleeName()
5157
and funcName = "Get"
5258
select funcName, c, c.getAnArgument()
59+
#+END_SRC
5360

5461
Point out the AST parts we now get in the query; at the above url, find
5562
| Ident.Ident | QualifiedName | SelectorExpr |
@@ -62,12 +69,14 @@
6269

6370
Let's also narrow by argument name
6471

72+
#+BEGIN_SRC java
6573
from CallExpr c, string funcName, QualifiedName qn, Expr arg
6674
where funcName = c.getCalleeName()
6775
and funcName = "Get"
6876
and c.getCalleeExpr() = qn
6977
and arg = c.getAnArgument()
7078
select funcName, c, qn, arg
79+
#+END_SRC
7180

7281
arg has type Expr, view results, see quoted strings, check ast reference for ",
7382
find
@@ -76,12 +85,14 @@
7685
introduce the cast operator, which now gives access to StringLit member
7786
predicates -- especially .getValue()
7887

88+
#+BEGIN_SRC java
7989
from CallExpr c, string funcName, QualifiedName qn, Expr arg
8090
where funcName = c.getCalleeName()
8191
and funcName = "Get"
8292
and c.getCalleeExpr() = qn
8393
and arg = c.getAnArgument()
8494
select funcName, c, qn, arg, arg.(StringLit).getValue()
95+
#+END_SRC
8596

8697
Turn into predicate
8798

@@ -99,11 +110,6 @@
99110
select funcName, c, qn, arg
100111
#+END_SRC
101112

102-
103-
104-
105-
106-
107113
======================
108114
Query done. Now generalize sources.
109115
Look under semmle/go/security for general-purpose APIs
@@ -112,15 +118,298 @@
112118

113119
Hacky way: look for "Header" in the libraries.
114120

115-
116121
======================
117122

118123
For advanced session / later:
119124

120125
UntrustedFlowSource
121126

127+
#+BEGIN_SRC java
122128
from CallExpr c, string funcName, Expr e
123129
where funcName = c.getCalleeName() and
124130
e = c.getAnArgument()
125131
and e.(StringLit).getStringValue().matches("X-%")
126132
select funcName, c, e
133+
#+END_SRC
134+
135+
136+
137+
* SINK
138+
139+
ask this - a data sink with the structure
140+
strings.HasPrefix(redirect, "/") && !strings.HasPrefix(redirect, "//")
141+
142+
you can show that strings.HasPrefix(redirect, "/") is CallExpr by looking at AST viewer
143+
144+
add where clause - demo the chaining of the predicates. i.e .getTarget() returns Function but we want string "HasPrefix" so we . again and go through member predicates and see if there's anything that suits what we're looking for.
145+
146+
We are matching just this - _.HasPrefix(_, _)
147+
148+
#+BEGIN_SRC text
149+
from CallExpr call
150+
where
151+
call.getTarget().getName() = "HasPrefix"
152+
select call
153+
#+END_SRC
154+
155+
156+
Explain that strings.HasPrefix checkes that a given string argumenet begins with a
157+
certain prefix
158+
159+
For prefix check do the chaining of the member predicates (oo principles), first
160+
put it in the select and then move it down to the where
161+
162+
163+
We are matching just this - _.HasPrefix(checked, _)
164+
#+BEGIN_SRC java
165+
import go
166+
167+
from CallExpr call, Expr checked,
168+
where
169+
call.getTarget().getName() = "HasPrefix" and
170+
call.getArgument(0) = checked and
171+
select call, checked
172+
173+
#+END_SRC
174+
175+
- .getStringValue will always work i.e if int it gets changed to string.
176+
We dont want this. We want the prefix to be a string so we cast. It's not a cast is just restricts our set
177+
178+
#+BEGIN_SRC java
179+
import go
180+
from CallExpr call, Expr checked, string prefix
181+
where
182+
call.getTarget().getName() = "HasPrefix" and
183+
checked = call.getArgument(0) and
184+
prefix = call.getArgument(1).(StringLit).getStringValue()
185+
select call, checked, prefix
186+
187+
#+END_SRC
188+
189+
- Write class for HasPrefix
190+
- Mention that a class is a type
191+
- Inheritence
192+
- Characteristic predicate
193+
- the this value - similar to O-O constructors
194+
- Replace CallExpr in from to HasPrefix
195+
- Mention that you can only refine the set not widen it
196+
197+
#+BEGIN_SRC java
198+
import go
199+
200+
class HasPrefix extends CallExpr {
201+
Expr checked;
202+
string prefix;
203+
204+
HasPrefix() {
205+
this.getTarget().getName() = "HasPrefix" and
206+
checked = this.getArgument(0) and
207+
prefix = this.getArgument(1).(StringLit).getStringValue()
208+
}
209+
}
210+
#+END_SRC
211+
212+
Characteristic predicate has to initialise field in the class. It produces a table with all the fields set
213+
214+
#+BEGIN_SRC java
215+
//strings.HasPrefix(redirect, "/") && //!strings.HasPrefix(redirect, "//")
216+
from HasPrefix call, Expr checked, string prefix
217+
where
218+
call.getArgument(0) = checked and
219+
call.getArgument(1).getStringValue() = prefix
220+
select call, checked, prefix
221+
222+
223+
class HasPrefix extends CallExpr {
224+
Expr checked;
225+
string prefix;
226+
227+
HasPrefix() {
228+
this.getTarget().getName() = "HasPrefix" and
229+
checked = this.getArgument(0) and
230+
prefix = this.getArgument(1).(StringLit).getStringValue()
231+
}
232+
233+
Expr getBaseString() { result = checked }
234+
235+
string getSubString() { result = prefix }
236+
}
237+
from HasPrefix call, Expr checked, string prefix
238+
where call.getBaseString() = checked and call.getSubString() = prefix
239+
select call, checked, prefix
240+
#+END_SRC
241+
242+
- introduce result, predicates with values
243+
- only reason we gave those names to the predicate is later compatibility
244+
245+
- Revisit what we are trying to find. We are looking for cases where the variable is checked against some prefixes but not others. This means we will have to reuse the logic of the previous query later, but with different string prefixes.
246+
247+
- We can use predicates!
248+
- Use variable decl in from to predicate params
249+
- Use where for predicate logic
250+
251+
: import go
252+
253+
- We have Variables and we have read and write accesses to them.
254+
- For write, a Control Flow node
255+
256+
#+BEGIN_SRC java
257+
from HasPrefix call, Expr checked, string prefix, Variable var
258+
where
259+
call.getBaseString() = checked and
260+
call.getSubString() = prefix and
261+
checked = var.getARead().asExpr()
262+
select call, checked, prefix, var
263+
#+END_SRC
264+
265+
266+
- A class is for modelling single logical items whilst predicates are good for connecting them.
267+
268+
#+BEGIN_SRC java
269+
predicate prefixCheck(HasPrefix call, Expr checked, string prefix, Variable var) {
270+
call.getBaseString() = checked and
271+
call.getSubString() = prefix and
272+
checked = var.getARead().asExpr()
273+
}
274+
275+
from HasPrefix call, Expr checked, string prefix, Variable var
276+
where prefixCheck(call, checked, prefix, var)
277+
select call, checked, prefix, var
278+
#+END_SRC
279+
280+
281+
//strings.HasPrefix(redirect, "/") && !strings.HasPrefix(redirect, "//")
282+
//We are matching just this - _.HasPrefix(checked, "prefix string")
283+
from HasPrefix call, Expr checked, Variable var
284+
where prefixCheck(call, checked, "/", var) and prefixCheck(_, _, "//", var)
285+
select call, checked, var
286+
- this finds one of the incomplete checks
287+
- the correct check is The string is prefix-checked against / but not both // and /\, suggesting it will eventually be used as a redirect (a sink).
288+
289+
we want / & // & /\\
290+
so logically / & (not // or not /\\)
291+
292+
#+BEGIN_SRC java
293+
predicate insufficientPrefixCheck(HasPrefix call, Expr checked, Variable var) {
294+
prefixCheck(call, checked, "/", var) and
295+
(not prefixCheck(_, _, "//", var) or not prefixCheck(_, _, "/\\", var))
296+
}
297+
298+
//strings.HasPrefix(redirect, "/") && !strings.HasPrefix(redirect, "//")
299+
// we want / & // & /\\
300+
// so logically / & (not // or not /\\)
301+
from HasPrefix call, Expr checked, Variable var
302+
where insufficientPrefixCheck(call, checked, var)
303+
select call, checked, var
304+
305+
#+END_SRC
306+
307+
GLOBAL FLOW
308+
309+
#+BEGIN_SRC java
310+
import go
311+
312+
class Config extends TaintTracking::Configuration {
313+
Config() { this = "Config" }
314+
315+
override predicate isSource(DataFlow::Node source) { xAuthSource(source.asExpr(), _, _, _) }
316+
317+
override predicate isSink(DataFlow::Node sink) { insufficientPrefixCheck(_, sink.asExpr(), _) }
318+
}
319+
320+
class HasPrefix extends CallExpr {
321+
Expr checked;
322+
string prefix;
323+
324+
HasPrefix() {
325+
this.getTarget().getName() = "HasPrefix" and
326+
checked = this.getArgument(0) and
327+
prefix = this.getArgument(1).(StringLit).getStringValue()
328+
}
329+
330+
Expr getBaseString() { result = checked }
331+
332+
string getSubString() { result = prefix }
333+
}
334+
335+
predicate prefixCheck(HasPrefix call, Expr checked, string prefix, Variable var) {
336+
call.getBaseString() = checked and
337+
call.getSubString() = prefix and
338+
checked = var.getARead().asExpr()
339+
}
340+
341+
predicate insufficientPrefixCheck(HasPrefix call, Expr checked, Variable var) {
342+
prefixCheck(call, checked, "/", var) and
343+
(not prefixCheck(_, _, "//", var) or not prefixCheck(_, _, "/\\", var))
344+
}
345+
346+
predicate xAuthSource(CallExpr c, string funcName, QualifiedName qn, Expr arg) {
347+
funcName = c.getCalleeName() and
348+
funcName = "Get" and
349+
c.getCalleeExpr() = qn and
350+
arg = c.getAnArgument() and
351+
arg.(StringLit).getValue() = "X-Auth-Request-Redirect"
352+
}
353+
354+
//strings.HasPrefix(redirect, "/") && !strings.HasPrefix(redirect, "//")
355+
// we want / & // & /\\
356+
// so logically / & (not // or not /\\)
357+
// from HasPrefix call, Expr checked, Variable var
358+
// where insufficientPrefixCheck(call, checked, var)
359+
// select call, checked, var
360+
from DataFlow::Node source, DataFlow::Node sink, Config c
361+
where c.hasFlow(source, sink)
362+
select sink, source, sink, "Untrusted value reaches insufficient redirect check"
363+
364+
#+END_SRC
365+
366+
OPTIONAL
367+
- Mention that there could be other ways of searching for string prefixes in Go.
368+
- Take strings.HasPrefix(redirect, "/") and search for it in vscode
369+
- Explain how you don't want to reinvent the wheel, and that it's always good to check the qll libraries to see what's already provided out of the box
370+
- Go through the StringOps.qll and notice how the HasPrefix class extends DataFlow::Node and that the return types of the interesting predicates are also DataFlow::Node
371+
- Change your query and arrive at this
372+
373+
#+BEGIN_SRC
374+
import go
375+
376+
class HasPrefix extends CallExpr {
377+
HasPrefix() { this.getTarget().getName() = "HasPrefix" }
378+
}
379+
380+
//strings.HasPrefix(redirect, "/") && !strings.HasPrefix(redirect, "//")
381+
from StringOps::HasPrefix call, DataFlow::Node checked, DataFlow::Node prefix
382+
where
383+
call.getBaseString() = checked and
384+
call.getSubstring() = prefix
385+
select call, checked, prefix
386+
#+END_SRC
387+
388+
- Notice that the first result is selection of ProxyPrefix which you're not
389+
interested in, you're interested in String values '/' or '//'
390+
391+
- THIS IS A BIT OF A STRETCH BUT
392+
find this through exploration call.getSubstring().asExpr().getStringValue() = prefix
393+
Technically, it can be justified, because you've already shown that it was an Expr you just want the equivalent of the old query
394+
395+
- Run query. Notice the second result. That wouldn't have been there if you didn't use StringOps::HasPrefix. Re-emphasise the need to have exploration mindset when writing queries. Try to leverage the libraries as much as possible
396+
397+
- Notice that all the checked results correspond to a Variable. Model this. First do checked = v and then .getARead
398+
399+
#+BEGIN_SRC
400+
import go
401+
402+
class HasPrefix extends CallExpr {
403+
HasPrefix() { this.getTarget().getName() = "HasPrefix" }
404+
}
405+
406+
//strings.HasPrefix(redirect, "/") && !strings.HasPrefix(redirect, "//")
407+
from StringOps::HasPrefix call, DataFlow::Node checked, string prefix, Variable v
408+
where
409+
call.getBaseString() = checked and
410+
checked = v.getARead() and
411+
call.getSubstring().asExpr().getStringValue() = prefix
412+
select call, checked, prefix
413+
#+END_SRC
414+
415+

0 commit comments

Comments
 (0)