Thanks to visit codestin.com
Credit goes to github.com

Skip to content

Commit d8bfa35

Browse files
committed
Python: Simple port of URL redirect query
Still have not added sanitizer, but seems like old sanitizer was a bit too broad (also covering %-formatting)
1 parent 9d8925a commit d8bfa35

6 files changed

Lines changed: 122 additions & 80 deletions

File tree

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,2 @@
1+
lgtm,codescanning
2+
* Ported URL redirection (`py/url-redirection`) query to use new data-flow library. This might result in different results, but overall a more robust and accurate analysis.

python/ql/src/Security/CWE-601/UrlRedirect.ql

Lines changed: 6 additions & 26 deletions
Original file line numberDiff line numberDiff line change
@@ -12,30 +12,10 @@
1212
*/
1313

1414
import python
15-
import semmle.python.security.Paths
16-
import semmle.python.web.HttpRedirect
17-
import semmle.python.web.HttpRequest
18-
import semmle.python.security.strings.Untrusted
15+
import semmle.python.security.dataflow.UrlRedirect
16+
import DataFlow::PathGraph
1917

20-
/** Url redirection is a problem only if the user controls the prefix of the URL */
21-
class UntrustedPrefixStringKind extends UntrustedStringKind {
22-
override TaintKind getTaintForFlowStep(ControlFlowNode fromnode, ControlFlowNode tonode) {
23-
result = UntrustedStringKind.super.getTaintForFlowStep(fromnode, tonode) and
24-
not tonode.(BinaryExprNode).getRight() = fromnode
25-
}
26-
}
27-
28-
class UrlRedirectConfiguration extends TaintTracking::Configuration {
29-
UrlRedirectConfiguration() { this = "URL redirect configuration" }
30-
31-
override predicate isSource(TaintTracking::Source source) {
32-
source instanceof HttpRequestTaintSource
33-
}
34-
35-
override predicate isSink(TaintTracking::Sink sink) { sink instanceof HttpRedirectTaintSink }
36-
}
37-
38-
from UrlRedirectConfiguration config, TaintedPathSource src, TaintedPathSink sink
39-
where config.hasFlowPath(src, sink)
40-
select sink.getSink(), src, sink, "Untrusted URL redirection due to $@.", src.getSource(),
41-
"a user-provided value"
18+
from UrlRedirectConfiguration config, DataFlow::PathNode source, DataFlow::PathNode sink
19+
where config.hasFlowPath(source, sink)
20+
select sink.getNode(), source, sink, "Untrusted URL redirection due to $@.", source.getNode(),
21+
"A user-provided value"
Lines changed: 41 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,41 @@
1+
/**
2+
* @name URL redirection from remote source
3+
* @description URL redirection based on unvalidated user input
4+
* may cause redirection to malicious web sites.
5+
* @kind path-problem
6+
* @problem.severity error
7+
* @sub-severity low
8+
* @id py/url-redirection
9+
* @tags security
10+
* external/cwe/cwe-601
11+
* @precision high
12+
*/
13+
14+
import python
15+
import semmle.python.security.Paths
16+
import semmle.python.web.HttpRedirect
17+
import semmle.python.web.HttpRequest
18+
import semmle.python.security.strings.Untrusted
19+
20+
/** Url redirection is a problem only if the user controls the prefix of the URL */
21+
class UntrustedPrefixStringKind extends UntrustedStringKind {
22+
override TaintKind getTaintForFlowStep(ControlFlowNode fromnode, ControlFlowNode tonode) {
23+
result = UntrustedStringKind.super.getTaintForFlowStep(fromnode, tonode) and
24+
not tonode.(BinaryExprNode).getRight() = fromnode
25+
}
26+
}
27+
28+
class UrlRedirectConfiguration extends TaintTracking::Configuration {
29+
UrlRedirectConfiguration() { this = "URL redirect configuration" }
30+
31+
override predicate isSource(TaintTracking::Source source) {
32+
source instanceof HttpRequestTaintSource
33+
}
34+
35+
override predicate isSink(TaintTracking::Sink sink) { sink instanceof HttpRedirectTaintSink }
36+
}
37+
38+
from UrlRedirectConfiguration config, TaintedPathSource src, TaintedPathSink sink
39+
where config.hasFlowPath(src, sink)
40+
select sink.getSink(), src, sink, "Untrusted URL redirection due to $@.", src.getSource(),
41+
"a user-provided value"
Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,28 @@
1+
/**
2+
* Provides a taint-tracking configuration for detecting URL redirection
3+
* vulnerabilities.
4+
*/
5+
6+
import python
7+
import semmle.python.dataflow.new.DataFlow
8+
import semmle.python.dataflow.new.TaintTracking
9+
import semmle.python.Concepts
10+
import semmle.python.dataflow.new.RemoteFlowSources
11+
import semmle.python.dataflow.new.BarrierGuards
12+
13+
/**
14+
* A taint-tracking configuration for detecting URL redirection vulnerabilities.
15+
*/
16+
class UrlRedirectConfiguration extends TaintTracking::Configuration {
17+
UrlRedirectConfiguration() { this = "UrlRedirectConfiguration" }
18+
19+
override predicate isSource(DataFlow::Node source) { source instanceof RemoteFlowSource }
20+
21+
override predicate isSink(DataFlow::Node sink) {
22+
sink = any(HTTP::Server::HttpRedirectResponse e).getRedirectLocation()
23+
}
24+
25+
override predicate isSanitizerGuard(DataFlow::BarrierGuard guard) {
26+
guard instanceof StringConstCompare
27+
}
28+
}
Lines changed: 41 additions & 50 deletions
Original file line numberDiff line numberDiff line change
@@ -1,52 +1,43 @@
11
edges
2-
| test.py:7:14:7:25 | dict of externally controlled string | test.py:7:14:7:43 | externally controlled string |
3-
| test.py:7:14:7:25 | dict of externally controlled string | test.py:7:14:7:43 | externally controlled string |
4-
| test.py:7:14:7:43 | externally controlled string | test.py:8:21:8:26 | externally controlled string |
5-
| test.py:7:14:7:43 | externally controlled string | test.py:8:21:8:26 | externally controlled string |
6-
| test.py:30:17:30:28 | dict of externally controlled string | test.py:30:17:30:46 | externally controlled string |
7-
| test.py:30:17:30:28 | dict of externally controlled string | test.py:30:17:30:46 | externally controlled string |
8-
| test.py:30:17:30:46 | externally controlled string | test.py:31:41:31:49 | externally controlled string |
9-
| test.py:30:17:30:46 | externally controlled string | test.py:31:41:31:49 | externally controlled string |
10-
| test.py:31:12:31:50 | externally controlled string | test.py:32:21:32:24 | externally controlled string |
11-
| test.py:31:12:31:50 | externally controlled string | test.py:32:21:32:24 | externally controlled string |
12-
| test.py:31:41:31:49 | externally controlled string | test.py:31:12:31:50 | externally controlled string |
13-
| test.py:31:41:31:49 | externally controlled string | test.py:31:12:31:50 | externally controlled string |
14-
| test.py:37:17:37:28 | dict of externally controlled string | test.py:37:17:37:46 | externally controlled string |
15-
| test.py:37:17:37:28 | dict of externally controlled string | test.py:37:17:37:46 | externally controlled string |
16-
| test.py:37:17:37:46 | externally controlled string | test.py:38:32:38:40 | externally controlled string |
17-
| test.py:37:17:37:46 | externally controlled string | test.py:38:32:38:40 | externally controlled string |
18-
| test.py:38:12:38:42 | externally controlled string | test.py:39:21:39:24 | externally controlled string |
19-
| test.py:38:12:38:42 | externally controlled string | test.py:39:21:39:24 | externally controlled string |
20-
| test.py:38:32:38:40 | externally controlled string | test.py:38:12:38:42 | externally controlled string |
21-
| test.py:38:32:38:40 | externally controlled string | test.py:38:12:38:42 | externally controlled string |
22-
| test.py:53:17:53:28 | dict of externally controlled string | test.py:53:17:53:46 | externally controlled string |
23-
| test.py:53:17:53:28 | dict of externally controlled string | test.py:53:17:53:46 | externally controlled string |
24-
| test.py:53:17:53:46 | externally controlled string | test.py:54:14:54:22 | externally controlled string |
25-
| test.py:53:17:53:46 | externally controlled string | test.py:54:14:54:22 | externally controlled string |
26-
| test.py:54:14:54:22 | externally controlled string | test.py:54:14:54:41 | externally controlled string |
27-
| test.py:54:14:54:22 | externally controlled string | test.py:54:14:54:41 | externally controlled string |
28-
| test.py:54:14:54:41 | externally controlled string | test.py:55:21:55:26 | externally controlled string |
29-
| test.py:54:14:54:41 | externally controlled string | test.py:55:21:55:26 | externally controlled string |
30-
| test.py:60:17:60:28 | dict of externally controlled string | test.py:60:17:60:46 | externally controlled string |
31-
| test.py:60:17:60:28 | dict of externally controlled string | test.py:60:17:60:46 | externally controlled string |
32-
| test.py:60:17:60:46 | externally controlled string | test.py:61:40:61:48 | externally controlled string |
33-
| test.py:60:17:60:46 | externally controlled string | test.py:61:40:61:48 | externally controlled string |
34-
| test.py:61:14:61:49 | externally controlled string | test.py:62:21:62:26 | externally controlled string |
35-
| test.py:61:14:61:49 | externally controlled string | test.py:62:21:62:26 | externally controlled string |
36-
| test.py:61:40:61:48 | externally controlled string | test.py:61:14:61:49 | externally controlled string |
37-
| test.py:61:40:61:48 | externally controlled string | test.py:61:14:61:49 | externally controlled string |
38-
| test.py:67:17:67:28 | dict of externally controlled string | test.py:67:17:67:46 | externally controlled string |
39-
| test.py:67:17:67:28 | dict of externally controlled string | test.py:67:17:67:46 | externally controlled string |
40-
| test.py:67:17:67:46 | externally controlled string | test.py:68:17:68:25 | externally controlled string |
41-
| test.py:67:17:67:46 | externally controlled string | test.py:68:17:68:25 | externally controlled string |
42-
| test.py:68:14:68:41 | externally controlled string | test.py:69:21:69:26 | externally controlled string |
43-
| test.py:68:14:68:41 | externally controlled string | test.py:69:21:69:26 | externally controlled string |
44-
| test.py:68:17:68:25 | externally controlled string | test.py:68:14:68:41 | externally controlled string |
45-
| test.py:68:17:68:25 | externally controlled string | test.py:68:14:68:41 | externally controlled string |
2+
| test.py:7:14:7:25 | ControlFlowNode for Attribute | test.py:8:21:8:26 | ControlFlowNode for target |
3+
| test.py:15:17:15:28 | ControlFlowNode for Attribute | test.py:18:21:18:24 | ControlFlowNode for safe |
4+
| test.py:23:17:23:28 | ControlFlowNode for Attribute | test.py:25:21:25:24 | ControlFlowNode for safe |
5+
| test.py:30:17:30:28 | ControlFlowNode for Attribute | test.py:32:21:32:24 | ControlFlowNode for safe |
6+
| test.py:37:17:37:28 | ControlFlowNode for Attribute | test.py:39:21:39:24 | ControlFlowNode for safe |
7+
| test.py:44:17:44:28 | ControlFlowNode for Attribute | test.py:46:21:46:24 | ControlFlowNode for safe |
8+
| test.py:53:17:53:28 | ControlFlowNode for Attribute | test.py:55:21:55:26 | ControlFlowNode for unsafe |
9+
| test.py:60:17:60:28 | ControlFlowNode for Attribute | test.py:62:21:62:26 | ControlFlowNode for unsafe |
10+
| test.py:67:17:67:28 | ControlFlowNode for Attribute | test.py:69:21:69:26 | ControlFlowNode for unsafe |
11+
| test.py:74:17:74:28 | ControlFlowNode for Attribute | test.py:76:21:76:26 | ControlFlowNode for unsafe |
12+
nodes
13+
| test.py:7:14:7:25 | ControlFlowNode for Attribute | semmle.label | ControlFlowNode for Attribute |
14+
| test.py:8:21:8:26 | ControlFlowNode for target | semmle.label | ControlFlowNode for target |
15+
| test.py:15:17:15:28 | ControlFlowNode for Attribute | semmle.label | ControlFlowNode for Attribute |
16+
| test.py:18:21:18:24 | ControlFlowNode for safe | semmle.label | ControlFlowNode for safe |
17+
| test.py:23:17:23:28 | ControlFlowNode for Attribute | semmle.label | ControlFlowNode for Attribute |
18+
| test.py:25:21:25:24 | ControlFlowNode for safe | semmle.label | ControlFlowNode for safe |
19+
| test.py:30:17:30:28 | ControlFlowNode for Attribute | semmle.label | ControlFlowNode for Attribute |
20+
| test.py:32:21:32:24 | ControlFlowNode for safe | semmle.label | ControlFlowNode for safe |
21+
| test.py:37:17:37:28 | ControlFlowNode for Attribute | semmle.label | ControlFlowNode for Attribute |
22+
| test.py:39:21:39:24 | ControlFlowNode for safe | semmle.label | ControlFlowNode for safe |
23+
| test.py:44:17:44:28 | ControlFlowNode for Attribute | semmle.label | ControlFlowNode for Attribute |
24+
| test.py:46:21:46:24 | ControlFlowNode for safe | semmle.label | ControlFlowNode for safe |
25+
| test.py:53:17:53:28 | ControlFlowNode for Attribute | semmle.label | ControlFlowNode for Attribute |
26+
| test.py:55:21:55:26 | ControlFlowNode for unsafe | semmle.label | ControlFlowNode for unsafe |
27+
| test.py:60:17:60:28 | ControlFlowNode for Attribute | semmle.label | ControlFlowNode for Attribute |
28+
| test.py:62:21:62:26 | ControlFlowNode for unsafe | semmle.label | ControlFlowNode for unsafe |
29+
| test.py:67:17:67:28 | ControlFlowNode for Attribute | semmle.label | ControlFlowNode for Attribute |
30+
| test.py:69:21:69:26 | ControlFlowNode for unsafe | semmle.label | ControlFlowNode for unsafe |
31+
| test.py:74:17:74:28 | ControlFlowNode for Attribute | semmle.label | ControlFlowNode for Attribute |
32+
| test.py:76:21:76:26 | ControlFlowNode for unsafe | semmle.label | ControlFlowNode for unsafe |
4633
#select
47-
| test.py:8:21:8:26 | target | test.py:7:14:7:25 | dict of externally controlled string | test.py:8:21:8:26 | externally controlled string | Untrusted URL redirection due to $@. | test.py:7:14:7:25 | Attribute | a user-provided value |
48-
| test.py:32:21:32:24 | safe | test.py:30:17:30:28 | dict of externally controlled string | test.py:32:21:32:24 | externally controlled string | Untrusted URL redirection due to $@. | test.py:30:17:30:28 | Attribute | a user-provided value |
49-
| test.py:39:21:39:24 | safe | test.py:37:17:37:28 | dict of externally controlled string | test.py:39:21:39:24 | externally controlled string | Untrusted URL redirection due to $@. | test.py:37:17:37:28 | Attribute | a user-provided value |
50-
| test.py:55:21:55:26 | unsafe | test.py:53:17:53:28 | dict of externally controlled string | test.py:55:21:55:26 | externally controlled string | Untrusted URL redirection due to $@. | test.py:53:17:53:28 | Attribute | a user-provided value |
51-
| test.py:62:21:62:26 | unsafe | test.py:60:17:60:28 | dict of externally controlled string | test.py:62:21:62:26 | externally controlled string | Untrusted URL redirection due to $@. | test.py:60:17:60:28 | Attribute | a user-provided value |
52-
| test.py:69:21:69:26 | unsafe | test.py:67:17:67:28 | dict of externally controlled string | test.py:69:21:69:26 | externally controlled string | Untrusted URL redirection due to $@. | test.py:67:17:67:28 | Attribute | a user-provided value |
34+
| test.py:8:21:8:26 | ControlFlowNode for target | test.py:7:14:7:25 | ControlFlowNode for Attribute | test.py:8:21:8:26 | ControlFlowNode for target | Untrusted URL redirection due to $@. | test.py:7:14:7:25 | ControlFlowNode for Attribute | A user-provided value |
35+
| test.py:18:21:18:24 | ControlFlowNode for safe | test.py:15:17:15:28 | ControlFlowNode for Attribute | test.py:18:21:18:24 | ControlFlowNode for safe | Untrusted URL redirection due to $@. | test.py:15:17:15:28 | ControlFlowNode for Attribute | A user-provided value |
36+
| test.py:25:21:25:24 | ControlFlowNode for safe | test.py:23:17:23:28 | ControlFlowNode for Attribute | test.py:25:21:25:24 | ControlFlowNode for safe | Untrusted URL redirection due to $@. | test.py:23:17:23:28 | ControlFlowNode for Attribute | A user-provided value |
37+
| test.py:32:21:32:24 | ControlFlowNode for safe | test.py:30:17:30:28 | ControlFlowNode for Attribute | test.py:32:21:32:24 | ControlFlowNode for safe | Untrusted URL redirection due to $@. | test.py:30:17:30:28 | ControlFlowNode for Attribute | A user-provided value |
38+
| test.py:39:21:39:24 | ControlFlowNode for safe | test.py:37:17:37:28 | ControlFlowNode for Attribute | test.py:39:21:39:24 | ControlFlowNode for safe | Untrusted URL redirection due to $@. | test.py:37:17:37:28 | ControlFlowNode for Attribute | A user-provided value |
39+
| test.py:46:21:46:24 | ControlFlowNode for safe | test.py:44:17:44:28 | ControlFlowNode for Attribute | test.py:46:21:46:24 | ControlFlowNode for safe | Untrusted URL redirection due to $@. | test.py:44:17:44:28 | ControlFlowNode for Attribute | A user-provided value |
40+
| test.py:55:21:55:26 | ControlFlowNode for unsafe | test.py:53:17:53:28 | ControlFlowNode for Attribute | test.py:55:21:55:26 | ControlFlowNode for unsafe | Untrusted URL redirection due to $@. | test.py:53:17:53:28 | ControlFlowNode for Attribute | A user-provided value |
41+
| test.py:62:21:62:26 | ControlFlowNode for unsafe | test.py:60:17:60:28 | ControlFlowNode for Attribute | test.py:62:21:62:26 | ControlFlowNode for unsafe | Untrusted URL redirection due to $@. | test.py:60:17:60:28 | ControlFlowNode for Attribute | A user-provided value |
42+
| test.py:69:21:69:26 | ControlFlowNode for unsafe | test.py:67:17:67:28 | ControlFlowNode for Attribute | test.py:69:21:69:26 | ControlFlowNode for unsafe | Untrusted URL redirection due to $@. | test.py:67:17:67:28 | ControlFlowNode for Attribute | A user-provided value |
43+
| test.py:76:21:76:26 | ControlFlowNode for unsafe | test.py:74:17:74:28 | ControlFlowNode for Attribute | test.py:76:21:76:26 | ControlFlowNode for unsafe | Untrusted URL redirection due to $@. | test.py:74:17:74:28 | ControlFlowNode for Attribute | A user-provided value |

python/ql/test/query-tests/Security/CWE-601/test.py

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -15,14 +15,14 @@ def ok():
1515
untrusted = request.args.get('target', '')
1616
safe = "https://safe.com/"
1717
safe += untrusted
18-
return redirect(safe, code=302)
18+
return redirect(safe, code=302) # FP
1919

2020

2121
@app.route('/ok2')
2222
def ok2():
2323
untrusted = request.args.get('target', '')
2424
safe = "https://safe.com/" + untrusted
25-
return redirect(safe, code=302)
25+
return redirect(safe, code=302) # FP
2626

2727

2828
@app.route('/ok3')
@@ -43,7 +43,7 @@ def ok4():
4343
def ok5():
4444
untrusted = request.args.get('target', '')
4545
safe = "https://safe.com/%s" % untrusted
46-
return redirect(safe, code=302)
46+
return redirect(safe, code=302) # FP
4747

4848

4949
# Check that our sanitizer is not too broad
@@ -73,4 +73,4 @@ def not_ok3():
7373
def not_ok4():
7474
untrusted = request.args.get('target', '')
7575
unsafe = "%s?login=success" % untrusted
76-
return redirect(unsafe, code=302) # Missing result
76+
return redirect(unsafe, code=302)

0 commit comments

Comments
 (0)