Skip to content

Commit 7c3b68b

Browse files
authored
Merge pull request #7091 from RasmusWL/port-request-without-validation
Python: Port `py/request-without-cert-validation` to use API graphs
2 parents 4bbfa51 + 1f90dca commit 7c3b68b

File tree

4 files changed

+48
-16
lines changed

4 files changed

+48
-16
lines changed

python/ql/src/Security/CWE-295/RequestWithoutValidation.ql

Lines changed: 39 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -11,17 +11,46 @@
1111
*/
1212

1313
import python
14-
import semmle.python.web.Http
14+
private import semmle.python.dataflow.new.DataFlow
15+
private import semmle.python.Concepts
16+
private import semmle.python.ApiGraphs
1517

16-
FunctionValue requestFunction() { result = Module::named("requests").attr(httpVerbLower()) }
18+
/**
19+
* Gets a call to a method that makes an outgoing request using the `requests` module,
20+
* such as `requests.get` or `requests.put`, with the specified HTTP verb `verb`
21+
*/
22+
DataFlow::CallCfgNode outgoingRequestCall(string verb) {
23+
verb = HTTP::httpVerbLower() and
24+
result = API::moduleImport("requests").getMember(verb).getACall()
25+
}
26+
27+
/** Gets the "verfiy" argument to a outgoingRequestCall. */
28+
DataFlow::Node verifyArg(DataFlow::CallCfgNode call) {
29+
call = outgoingRequestCall(_) and
30+
result = call.getArgByName("verify")
31+
}
32+
33+
/** Gets a back-reference to the verify argument `arg`. */
34+
private DataFlow::TypeTrackingNode verifyArgBacktracker(
35+
DataFlow::TypeBackTracker t, DataFlow::Node arg
36+
) {
37+
t.start() and
38+
arg = verifyArg(_) and
39+
result = arg.getALocalSource()
40+
or
41+
exists(DataFlow::TypeBackTracker t2 | result = verifyArgBacktracker(t2, arg).backtrack(t2, t))
42+
}
1743

18-
/** requests treats None as the default and all other "falsey" values as False */
19-
predicate falseNotNone(Value v) { v.getDefiniteBooleanValue() = false and not v = Value::none_() }
44+
/** Gets a back-reference to the verify argument `arg`. */
45+
DataFlow::LocalSourceNode verifyArgBacktracker(DataFlow::Node arg) {
46+
result = verifyArgBacktracker(DataFlow::TypeBackTracker::end(), arg)
47+
}
2048

21-
from CallNode call, FunctionValue func, Value falsey, ControlFlowNode origin
49+
from DataFlow::CallCfgNode call, DataFlow::Node falseyOrigin, string verb
2250
where
23-
func = requestFunction() and
24-
func.getACall() = call and
25-
falseNotNone(falsey) and
26-
call.getArgByName("verify").pointsTo(falsey, origin)
27-
select call, "Call to $@ with verify=$@", func, "requests." + func.getName(), origin, "False"
51+
call = outgoingRequestCall(verb) and
52+
falseyOrigin = verifyArgBacktracker(verifyArg(call)) and
53+
// requests treats `None` as the default and all other "falsey" values as `False`.
54+
falseyOrigin.asExpr().(ImmutableLiteral).booleanValue() = false and
55+
not falseyOrigin.asExpr() instanceof None
56+
select call, "Call to requests." + verb + " with verify=$@", falseyOrigin, "False"
Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
1-
| make_request.py:5:1:5:48 | ControlFlowNode for Attribute() | Call to $@ with verify=$@ | ../lib/requests.py:2:1:2:36 | Function get | requests.get | make_request.py:5:43:5:47 | ControlFlowNode for False | False |
2-
| make_request.py:7:1:7:49 | ControlFlowNode for Attribute() | Call to $@ with verify=$@ | ../lib/requests.py:11:1:11:46 | Function post | requests.post | make_request.py:7:44:7:48 | ControlFlowNode for False | False |
3-
| make_request.py:12:1:12:39 | ControlFlowNode for put() | Call to $@ with verify=$@ | ../lib/requests.py:14:1:14:34 | Function put | requests.put | make_request.py:12:34:12:38 | ControlFlowNode for False | False |
4-
| make_request.py:28:5:28:46 | ControlFlowNode for patch() | Call to $@ with verify=$@ | ../lib/requests.py:17:1:17:36 | Function patch | requests.patch | make_request.py:30:6:30:10 | ControlFlowNode for False | False |
5-
| make_request.py:34:1:34:45 | ControlFlowNode for Attribute() | Call to $@ with verify=$@ | ../lib/requests.py:11:1:11:46 | Function post | requests.post | make_request.py:34:44:34:44 | ControlFlowNode for IntegerLiteral | False |
1+
| make_request.py:5:1:5:48 | ControlFlowNode for Attribute() | Call to requests.get with verify=$@ | make_request.py:5:43:5:47 | ControlFlowNode for False | False |
2+
| make_request.py:7:1:7:49 | ControlFlowNode for Attribute() | Call to requests.post with verify=$@ | make_request.py:7:44:7:48 | ControlFlowNode for False | False |
3+
| make_request.py:12:1:12:39 | ControlFlowNode for put() | Call to requests.put with verify=$@ | make_request.py:12:34:12:38 | ControlFlowNode for False | False |
4+
| make_request.py:28:5:28:46 | ControlFlowNode for patch() | Call to requests.patch with verify=$@ | make_request.py:30:6:30:10 | ControlFlowNode for False | False |
5+
| make_request.py:34:1:34:45 | ControlFlowNode for Attribute() | Call to requests.post with verify=$@ | make_request.py:34:44:34:44 | ControlFlowNode for IntegerLiteral | False |

python/ql/test/query-tests/Security/CWE-295-RequestWithoutValidation/make_request.py

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -32,3 +32,7 @@ def req2(verify):
3232

3333
#Falsey value
3434
requests.post('https://semmle.com', verify=0) # BAD
35+
36+
# requests treat `None` as default value, which means it is turned on
37+
requests.get('https://semmle.com') # OK
38+
requests.get('https://semmle.com', verify=None) # OK

python/ql/test/query-tests/Security/CWE-295-RequestWithoutValidation/options

Lines changed: 0 additions & 1 deletion
This file was deleted.

0 commit comments

Comments
 (0)