Skip to content

Commit a519132

Browse files
author
Porcupiney Hairs
committed
add support for libxml2
1 parent ce1f0a3 commit a519132

File tree

7 files changed

+90
-26
lines changed

7 files changed

+90
-26
lines changed

python/ql/src/experimental/semmle/python/security/injection/Xpath.qll

Lines changed: 29 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,9 @@ module XpathInjection {
1515
/** Returns a class value which refers to `lxml.etree` */
1616
Value etree() { result = Value::named("lxml.etree") }
1717

18+
/** Returns a class value which refers to `lxml.etree` */
19+
Value libxml2parseFile() { result = Value::named("libxml2.parseFile") }
20+
1821
/** A generic taint sink that is vulnerable to Xpath injection. */
1922
abstract class XpathInjectionSink extends TaintSink { }
2023

@@ -83,4 +86,30 @@ module XpathInjection {
8386

8487
override predicate sinks(TaintKind kind) { kind instanceof ExternalStringKind }
8588
}
89+
90+
/**
91+
* A Sink representing an argument to the `xpathEval` call to a parsed libxml2 document.
92+
*
93+
* import libxml2
94+
* tree = libxml2.parseFile("file.xml")
95+
* r = tree.xpathEval('`sink`')
96+
*/
97+
private class ParseFileXpathEvalArgument extends XpathInjectionSink {
98+
override string toString() { result = "libxml2.parseFile.xpathEval" }
99+
100+
ParseFileXpathEvalArgument() {
101+
exists(
102+
CallNode parseCall, CallNode xpathCall, ControlFlowNode obj, Variable var, AssignStmt assign
103+
|
104+
parseCall.getFunction().(AttrNode).pointsTo(libxml2parseFile()) and
105+
assign.getValue().(Call).getAFlowNode() = parseCall and
106+
xpathCall.getFunction().(AttrNode).getObject("xpathEval") = obj and
107+
var.getAUse() = obj and
108+
assign.getATarget() = var.getAStore() and
109+
xpathCall.getArg(0) = this
110+
)
111+
}
112+
113+
override predicate sinks(TaintKind kind) { kind instanceof ExternalStringKind }
114+
}
86115
}

python/ql/test/experimental/CWE-643/xpath.expected

Lines changed: 25 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -9,25 +9,30 @@ edges
99
| xpathBad.py:10:13:10:32 | externally controlled string | xpathBad.py:13:39:13:43 | externally controlled string |
1010
| xpathBad.py:13:39:13:43 | externally controlled string | xpathBad.py:13:20:13:43 | externally controlled string |
1111
| xpathBad.py:13:39:13:43 | externally controlled string | xpathBad.py:13:20:13:43 | externally controlled string |
12-
| xpathFlow.py:10:18:10:29 | dict of externally controlled string | xpathFlow.py:10:18:10:44 | externally controlled string |
13-
| xpathFlow.py:10:18:10:29 | dict of externally controlled string | xpathFlow.py:10:18:10:44 | externally controlled string |
14-
| xpathFlow.py:10:18:10:44 | externally controlled string | xpathFlow.py:13:20:13:29 | externally controlled string |
15-
| xpathFlow.py:10:18:10:44 | externally controlled string | xpathFlow.py:13:20:13:29 | externally controlled string |
16-
| xpathFlow.py:18:18:18:29 | dict of externally controlled string | xpathFlow.py:18:18:18:44 | externally controlled string |
17-
| xpathFlow.py:18:18:18:29 | dict of externally controlled string | xpathFlow.py:18:18:18:44 | externally controlled string |
18-
| xpathFlow.py:18:18:18:44 | externally controlled string | xpathFlow.py:21:29:21:38 | externally controlled string |
19-
| xpathFlow.py:18:18:18:44 | externally controlled string | xpathFlow.py:21:29:21:38 | externally controlled string |
20-
| xpathFlow.py:27:18:27:29 | dict of externally controlled string | xpathFlow.py:27:18:27:44 | externally controlled string |
21-
| xpathFlow.py:27:18:27:29 | dict of externally controlled string | xpathFlow.py:27:18:27:44 | externally controlled string |
22-
| xpathFlow.py:27:18:27:44 | externally controlled string | xpathFlow.py:29:29:29:38 | externally controlled string |
23-
| xpathFlow.py:27:18:27:44 | externally controlled string | xpathFlow.py:29:29:29:38 | externally controlled string |
24-
| xpathFlow.py:35:18:35:29 | dict of externally controlled string | xpathFlow.py:35:18:35:44 | externally controlled string |
25-
| xpathFlow.py:35:18:35:29 | dict of externally controlled string | xpathFlow.py:35:18:35:44 | externally controlled string |
26-
| xpathFlow.py:35:18:35:44 | externally controlled string | xpathFlow.py:37:31:37:40 | externally controlled string |
27-
| xpathFlow.py:35:18:35:44 | externally controlled string | xpathFlow.py:37:31:37:40 | externally controlled string |
12+
| xpathFlow.py:11:18:11:29 | dict of externally controlled string | xpathFlow.py:11:18:11:44 | externally controlled string |
13+
| xpathFlow.py:11:18:11:29 | dict of externally controlled string | xpathFlow.py:11:18:11:44 | externally controlled string |
14+
| xpathFlow.py:11:18:11:44 | externally controlled string | xpathFlow.py:14:20:14:29 | externally controlled string |
15+
| xpathFlow.py:11:18:11:44 | externally controlled string | xpathFlow.py:14:20:14:29 | externally controlled string |
16+
| xpathFlow.py:20:18:20:29 | dict of externally controlled string | xpathFlow.py:20:18:20:44 | externally controlled string |
17+
| xpathFlow.py:20:18:20:29 | dict of externally controlled string | xpathFlow.py:20:18:20:44 | externally controlled string |
18+
| xpathFlow.py:20:18:20:44 | externally controlled string | xpathFlow.py:23:29:23:38 | externally controlled string |
19+
| xpathFlow.py:20:18:20:44 | externally controlled string | xpathFlow.py:23:29:23:38 | externally controlled string |
20+
| xpathFlow.py:30:18:30:29 | dict of externally controlled string | xpathFlow.py:30:18:30:44 | externally controlled string |
21+
| xpathFlow.py:30:18:30:29 | dict of externally controlled string | xpathFlow.py:30:18:30:44 | externally controlled string |
22+
| xpathFlow.py:30:18:30:44 | externally controlled string | xpathFlow.py:32:29:32:38 | externally controlled string |
23+
| xpathFlow.py:30:18:30:44 | externally controlled string | xpathFlow.py:32:29:32:38 | externally controlled string |
24+
| xpathFlow.py:39:18:39:29 | dict of externally controlled string | xpathFlow.py:39:18:39:44 | externally controlled string |
25+
| xpathFlow.py:39:18:39:29 | dict of externally controlled string | xpathFlow.py:39:18:39:44 | externally controlled string |
26+
| xpathFlow.py:39:18:39:44 | externally controlled string | xpathFlow.py:41:31:41:40 | externally controlled string |
27+
| xpathFlow.py:39:18:39:44 | externally controlled string | xpathFlow.py:41:31:41:40 | externally controlled string |
28+
| xpathFlow.py:47:18:47:29 | dict of externally controlled string | xpathFlow.py:47:18:47:44 | externally controlled string |
29+
| xpathFlow.py:47:18:47:29 | dict of externally controlled string | xpathFlow.py:47:18:47:44 | externally controlled string |
30+
| xpathFlow.py:47:18:47:44 | externally controlled string | xpathFlow.py:49:29:49:38 | externally controlled string |
31+
| xpathFlow.py:47:18:47:44 | externally controlled string | xpathFlow.py:49:29:49:38 | externally controlled string |
2832
#select
2933
| xpathBad.py:13:20:13:43 | BinaryExpr | xpathBad.py:9:7:9:13 | django.request.HttpRequest | xpathBad.py:13:20:13:43 | externally controlled string | This Xpath query depends on $@. | xpathBad.py:9:7:9:13 | request | a user-provided value |
30-
| xpathFlow.py:13:20:13:29 | xpathQuery | xpathFlow.py:10:18:10:29 | dict of externally controlled string | xpathFlow.py:13:20:13:29 | externally controlled string | This Xpath query depends on $@. | xpathFlow.py:10:18:10:29 | Attribute | a user-provided value |
31-
| xpathFlow.py:21:29:21:38 | xpathQuery | xpathFlow.py:18:18:18:29 | dict of externally controlled string | xpathFlow.py:21:29:21:38 | externally controlled string | This Xpath query depends on $@. | xpathFlow.py:18:18:18:29 | Attribute | a user-provided value |
32-
| xpathFlow.py:29:29:29:38 | xpathQuery | xpathFlow.py:27:18:27:29 | dict of externally controlled string | xpathFlow.py:29:29:29:38 | externally controlled string | This Xpath query depends on $@. | xpathFlow.py:27:18:27:29 | Attribute | a user-provided value |
33-
| xpathFlow.py:37:31:37:40 | xpathQuery | xpathFlow.py:35:18:35:29 | dict of externally controlled string | xpathFlow.py:37:31:37:40 | externally controlled string | This Xpath query depends on $@. | xpathFlow.py:35:18:35:29 | Attribute | a user-provided value |
34+
| xpathFlow.py:14:20:14:29 | xpathQuery | xpathFlow.py:11:18:11:29 | dict of externally controlled string | xpathFlow.py:14:20:14:29 | externally controlled string | This Xpath query depends on $@. | xpathFlow.py:11:18:11:29 | Attribute | a user-provided value |
35+
| xpathFlow.py:23:29:23:38 | xpathQuery | xpathFlow.py:20:18:20:29 | dict of externally controlled string | xpathFlow.py:23:29:23:38 | externally controlled string | This Xpath query depends on $@. | xpathFlow.py:20:18:20:29 | Attribute | a user-provided value |
36+
| xpathFlow.py:32:29:32:38 | xpathQuery | xpathFlow.py:30:18:30:29 | dict of externally controlled string | xpathFlow.py:32:29:32:38 | externally controlled string | This Xpath query depends on $@. | xpathFlow.py:30:18:30:29 | Attribute | a user-provided value |
37+
| xpathFlow.py:41:31:41:40 | xpathQuery | xpathFlow.py:39:18:39:29 | dict of externally controlled string | xpathFlow.py:41:31:41:40 | externally controlled string | This Xpath query depends on $@. | xpathFlow.py:39:18:39:29 | Attribute | a user-provided value |
38+
| xpathFlow.py:49:29:49:38 | xpathQuery | xpathFlow.py:47:18:47:29 | dict of externally controlled string | xpathFlow.py:49:29:49:38 | externally controlled string | This Xpath query depends on $@. | xpathFlow.py:47:18:47:29 | Attribute | a user-provided value |

python/ql/test/experimental/CWE-643/xpath.py

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -26,8 +26,15 @@ def d():
2626
text = find_text(root)[0]
2727

2828

29+
def e():
30+
import libxml2
31+
doc = libxml2.parseFile('xpath_injection/credential.xml')
32+
results = doc.xpathEval('sink')
33+
34+
2935
if __name__ == "__main__":
3036
a()
3137
b()
3238
c()
3339
d()
40+
e()

python/ql/test/experimental/CWE-643/xpathFlow.py

Lines changed: 12 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,3 @@
1-
from lxml import etree
21
from io import StringIO
32
from flask import Flask, request
43

@@ -7,6 +6,8 @@
76

87
@app.route("/xpath1")
98
def xpath1():
9+
from lxml import etree
10+
1011
xpathQuery = request.args.get('xml', '')
1112
f = StringIO('<foo><bar></bar></foo>')
1213
tree = etree.parse(f)
@@ -15,6 +16,7 @@ def xpath1():
1516

1617
@app.route("/xpath2")
1718
def xpath2():
19+
from lxml import etree
1820
xpathQuery = request.args.get('xml', '')
1921

2022
root = etree.XML("<root><a>TEXT</a></root>")
@@ -24,6 +26,7 @@ def xpath2():
2426

2527
@app.route("/xpath3")
2628
def xpath3():
29+
from lxml import etree
2730
xpathQuery = request.args.get('xml', '')
2831
root = etree.XML("<root><a>TEXT</a></root>")
2932
find_text = etree.XPath(xpathQuery, smart_strings=False)
@@ -32,7 +35,15 @@ def xpath3():
3235

3336
@app.route("/xpath4")
3437
def xpath4():
38+
from lxml import etree
3539
xpathQuery = request.args.get('xml', '')
3640
root = etree.XML("<root><a>TEXT</a></root>")
3741
find_text = etree.ETXPath(xpathQuery)
3842
text = find_text(root)[0]
43+
44+
@app.route("/xpath5")
45+
def xpath5():
46+
import libxml2
47+
xpathQuery = request.args.get('xml', '')
48+
doc = libxml2.parseFile('xpath_injection/credential.xml')
49+
results = doc.xpathEval(xpathQuery)

python/ql/test/experimental/CWE-643/xpathSinks.expected

Lines changed: 6 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -2,9 +2,11 @@
22
| xpath.py:13:29:13:38 | lxml.etree.Xpath | externally controlled string |
33
| xpath.py:19:29:19:38 | lxml.etree.Xpath | externally controlled string |
44
| xpath.py:25:38:25:46 | lxml.etree.ETXpath | externally controlled string |
5+
| xpath.py:32:29:32:34 | libxml2.parseFile.xpathEval | externally controlled string |
56
| xpathBad.py:13:20:13:43 | lxml.etree.parse.xpath | externally controlled string |
6-
| xpathFlow.py:13:20:13:29 | lxml.etree.parse.xpath | externally controlled string |
7-
| xpathFlow.py:21:29:21:38 | lxml.etree.Xpath | externally controlled string |
8-
| xpathFlow.py:29:29:29:38 | lxml.etree.Xpath | externally controlled string |
9-
| xpathFlow.py:37:31:37:40 | lxml.etree.ETXpath | externally controlled string |
7+
| xpathFlow.py:14:20:14:29 | lxml.etree.parse.xpath | externally controlled string |
8+
| xpathFlow.py:23:29:23:38 | lxml.etree.Xpath | externally controlled string |
9+
| xpathFlow.py:32:29:32:38 | lxml.etree.Xpath | externally controlled string |
10+
| xpathFlow.py:41:31:41:40 | lxml.etree.ETXpath | externally controlled string |
11+
| xpathFlow.py:49:29:49:38 | libxml2.parseFile.xpathEval | externally controlled string |
1012
| xpathGood.py:13:20:13:37 | lxml.etree.parse.xpath | externally controlled string |

python/ql/test/experimental/CWE-643/xpathSinks.ql

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3,4 +3,4 @@ import experimental.semmle.python.security.injection.Xpath
33

44
from XpathInjection::XpathInjectionSink sink, TaintKind kind
55
where sink.sinks(kind)
6-
select sink, kind
6+
select sink, kind
Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,10 @@
1+
def parseFile(filename):
2+
return xmlDoc(_obj=None)
3+
4+
5+
class xmlDoc(Object):
6+
def __init__(self, _obj=None):
7+
pass
8+
9+
def xpathEval(self, expr):
10+
pass

0 commit comments

Comments
 (0)