Skip to content

Commit 3486372

Browse files
committed
Python: Model cgi.FieldStorage
1 parent 4368871 commit 3486372

File tree

2 files changed

+214
-15
lines changed

2 files changed

+214
-15
lines changed

python/ql/src/semmle/python/frameworks/Stdlib.qll

Lines changed: 199 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1032,6 +1032,205 @@ private module Stdlib {
10321032

10331033
override string getFormat() { result = "JSON" }
10341034
}
1035+
1036+
// ---------------------------------------------------------------------------
1037+
// cgi
1038+
// ---------------------------------------------------------------------------
1039+
/** Gets a reference to the `cgi` module. */
1040+
private DataFlow::Node cgi(DataFlow::TypeTracker t) {
1041+
t.start() and
1042+
result = DataFlow::importNode("cgi")
1043+
or
1044+
exists(DataFlow::TypeTracker t2 | result = cgi(t2).track(t2, t))
1045+
}
1046+
1047+
/** Gets a reference to the `cgi` module. */
1048+
DataFlow::Node cgi() { result = cgi(DataFlow::TypeTracker::end()) }
1049+
1050+
/** Provides models for the `cgi` module. */
1051+
module cgi {
1052+
/**
1053+
* Provides models for the `cgi.FieldStorage` class
1054+
*
1055+
* See https://docs.python.org/3/library/cgi.html.
1056+
*/
1057+
module FieldStorage {
1058+
/** Gets a reference to the `cgi.FieldStorage` class. */
1059+
private DataFlow::Node classRef(DataFlow::TypeTracker t) {
1060+
t.startInAttr("FieldStorage") and
1061+
result = cgi()
1062+
or
1063+
exists(DataFlow::TypeTracker t2 | result = classRef(t2).track(t2, t))
1064+
}
1065+
1066+
/** Gets a reference to the `cgi.FieldStorage` class. */
1067+
DataFlow::Node classRef() { result = classRef(DataFlow::TypeTracker::end()) }
1068+
1069+
/**
1070+
* A source of an instance of `cgi.FieldStorage`.
1071+
*
1072+
* This can include instantiation of the class, return value from function
1073+
* calls, or a special parameter that will be set when functions are call by external
1074+
* library.
1075+
*
1076+
* Use `FieldStorage::instance()` predicate to get references to instances of `cgi.FieldStorage`.
1077+
*/
1078+
abstract class InstanceSource extends DataFlow::Node { }
1079+
1080+
/**
1081+
* A direct instantiation of `cgi.FieldStorage`.
1082+
*
1083+
* We currently consider ALL instantiations to be `RemoteFlowSource`. This seems
1084+
* reasonable since it's used to parse form data for incoming POST requests, but
1085+
* if it turns out to be a problem, we'll have to refine.
1086+
*/
1087+
private class ClassInstantiation extends InstanceSource, RemoteFlowSource::Range,
1088+
DataFlow::CfgNode {
1089+
override CallNode node;
1090+
1091+
ClassInstantiation() { node.getFunction() = classRef().asCfgNode() }
1092+
1093+
override string getSourceType() { result = "cgi.FieldStorage" }
1094+
}
1095+
1096+
/** Gets a reference to an instance of `cgi.FieldStorage`. */
1097+
private DataFlow::Node instance(DataFlow::TypeTracker t) {
1098+
t.start() and
1099+
result instanceof InstanceSource
1100+
or
1101+
exists(DataFlow::TypeTracker t2 | result = instance(t2).track(t2, t))
1102+
}
1103+
1104+
/** Gets a reference to an instance of `cgi.FieldStorage`. */
1105+
DataFlow::Node instance() { result = instance(DataFlow::TypeTracker::end()) }
1106+
1107+
/** Gets a reference to the `getvalue` method on a `cgi.FieldStorage` instance. */
1108+
private DataFlow::Node getvalueRef(DataFlow::TypeTracker t) {
1109+
t.startInAttr("getvalue") and
1110+
result = instance()
1111+
or
1112+
exists(DataFlow::TypeTracker t2 | result = getvalueRef(t2).track(t2, t))
1113+
}
1114+
1115+
/** Gets a reference to the `getvalue` method on a `cgi.FieldStorage` instance. */
1116+
DataFlow::Node getvalueRef() { result = getvalueRef(DataFlow::TypeTracker::end()) }
1117+
1118+
/** Gets a reference to the result of calling the `getvalue` method on a `cgi.FieldStorage` instance. */
1119+
private DataFlow::Node getvalueResult(DataFlow::TypeTracker t) {
1120+
t.start() and
1121+
result.asCfgNode().(CallNode).getFunction() = getvalueRef().asCfgNode()
1122+
or
1123+
exists(DataFlow::TypeTracker t2 | result = getvalueResult(t2).track(t2, t))
1124+
}
1125+
1126+
/** Gets a reference to the result of calling the `getvalue` method on a `cgi.FieldStorage` instance. */
1127+
DataFlow::Node getvalueResult() { result = getvalueResult(DataFlow::TypeTracker::end()) }
1128+
1129+
/** Gets a reference to the `getfirst` method on a `cgi.FieldStorage` instance. */
1130+
private DataFlow::Node getfirstRef(DataFlow::TypeTracker t) {
1131+
t.startInAttr("getfirst") and
1132+
result = instance()
1133+
or
1134+
exists(DataFlow::TypeTracker t2 | result = getfirstRef(t2).track(t2, t))
1135+
}
1136+
1137+
/** Gets a reference to the `getfirst` method on a `cgi.FieldStorage` instance. */
1138+
DataFlow::Node getfirstRef() { result = getfirstRef(DataFlow::TypeTracker::end()) }
1139+
1140+
/** Gets a reference to the result of calling the `getfirst` method on a `cgi.FieldStorage` instance. */
1141+
private DataFlow::Node getfirstResult(DataFlow::TypeTracker t) {
1142+
t.start() and
1143+
result.asCfgNode().(CallNode).getFunction() = getfirstRef().asCfgNode()
1144+
or
1145+
exists(DataFlow::TypeTracker t2 | result = getfirstResult(t2).track(t2, t))
1146+
}
1147+
1148+
/** Gets a reference to the result of calling the `getfirst` method on a `cgi.FieldStorage` instance. */
1149+
DataFlow::Node getfirstResult() { result = getfirstResult(DataFlow::TypeTracker::end()) }
1150+
1151+
/** Gets a reference to the `getlist` method on a `cgi.FieldStorage` instance. */
1152+
private DataFlow::Node getlistRef(DataFlow::TypeTracker t) {
1153+
t.startInAttr("getlist") and
1154+
result = instance()
1155+
or
1156+
exists(DataFlow::TypeTracker t2 | result = getlistRef(t2).track(t2, t))
1157+
}
1158+
1159+
/** Gets a reference to the `getlist` method on a `cgi.FieldStorage` instance. */
1160+
DataFlow::Node getlistRef() { result = getlistRef(DataFlow::TypeTracker::end()) }
1161+
1162+
/** Gets a reference to the result of calling the `getlist` method on a `cgi.FieldStorage` instance. */
1163+
private DataFlow::Node getlistResult(DataFlow::TypeTracker t) {
1164+
t.start() and
1165+
result.asCfgNode().(CallNode).getFunction() = getlistRef().asCfgNode()
1166+
or
1167+
exists(DataFlow::TypeTracker t2 | result = getlistResult(t2).track(t2, t))
1168+
}
1169+
1170+
/** Gets a reference to the result of calling the `getlist` method on a `cgi.FieldStorage` instance. */
1171+
DataFlow::Node getlistResult() { result = getlistResult(DataFlow::TypeTracker::end()) }
1172+
1173+
/** Gets a reference to a list of fields. */
1174+
private DataFlow::Node fieldList(DataFlow::TypeTracker t) {
1175+
t.start() and
1176+
(
1177+
result = getlistResult()
1178+
or
1179+
result = getvalueResult()
1180+
or
1181+
// TODO: Should have better handling of subscripting
1182+
result.asCfgNode().(SubscriptNode).getObject() = instance().asCfgNode()
1183+
)
1184+
or
1185+
exists(DataFlow::TypeTracker t2 | result = fieldList(t2).track(t2, t))
1186+
}
1187+
1188+
/** Gets a reference to a list of fields. */
1189+
DataFlow::Node fieldList() { result = fieldList(DataFlow::TypeTracker::end()) }
1190+
1191+
/** Gets a reference to a field. */
1192+
private DataFlow::Node field(DataFlow::TypeTracker t) {
1193+
t.start() and
1194+
(
1195+
result = getfirstResult()
1196+
or
1197+
result = getvalueResult()
1198+
or
1199+
// TODO: Should have better handling of subscripting
1200+
result.asCfgNode().(SubscriptNode).getObject() = [instance(), fieldList()].asCfgNode()
1201+
)
1202+
or
1203+
exists(DataFlow::TypeTracker t2 | result = field(t2).track(t2, t))
1204+
}
1205+
1206+
/** Gets a reference to a field. */
1207+
DataFlow::Node field() { result = field(DataFlow::TypeTracker::end()) }
1208+
1209+
private class AdditionalTaintStep extends TaintTracking::AdditionalTaintStep {
1210+
override predicate step(DataFlow::Node nodeFrom, DataFlow::Node nodeTo) {
1211+
// Methods
1212+
nodeFrom = instance() and
1213+
nodeTo in [getvalueRef(), getfirstRef(), getlistRef()]
1214+
or
1215+
nodeFrom = getvalueRef() and nodeTo = getvalueResult()
1216+
or
1217+
nodeFrom = getfirstRef() and nodeTo = getfirstResult()
1218+
or
1219+
nodeFrom = getlistRef() and nodeTo = getlistResult()
1220+
or
1221+
// Indexing
1222+
nodeFrom in [instance(), fieldList()] and
1223+
nodeTo.asCfgNode().(SubscriptNode).getObject() = nodeFrom.asCfgNode()
1224+
or
1225+
// Attributes on Field
1226+
nodeFrom = field() and
1227+
exists(DataFlow::AttrRead read | nodeTo = read and read.getObject() = nodeFrom |
1228+
read.getAttributeName() in ["value", "file", "filename"]
1229+
)
1230+
}
1231+
}
1232+
}
1233+
}
10351234
}
10361235

10371236
// ---------------------------------------------------------------------------

python/ql/test/experimental/library-tests/frameworks/stdlib/TestTaint.expected

Lines changed: 15 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -2,21 +2,21 @@
22
| CodeExecution.py:36 | ok | test_additional_taint | cmd1 |
33
| CodeExecution.py:37 | ok | test_additional_taint | cmd2 |
44
| CodeExecution.py:38 | ok | test_additional_taint | cmd3 |
5-
| http_server.py:22 | fail | test_cgi_FieldStorage_taint | form |
6-
| http_server.py:24 | fail | test_cgi_FieldStorage_taint | form['key'] |
7-
| http_server.py:25 | fail | test_cgi_FieldStorage_taint | form['key'].value |
8-
| http_server.py:26 | fail | test_cgi_FieldStorage_taint | form['key'].file |
9-
| http_server.py:27 | fail | test_cgi_FieldStorage_taint | form['key'].filename |
10-
| http_server.py:28 | fail | test_cgi_FieldStorage_taint | form['key'][0] |
11-
| http_server.py:29 | fail | test_cgi_FieldStorage_taint | form['key'][0].value |
12-
| http_server.py:30 | fail | test_cgi_FieldStorage_taint | form['key'][0].file |
13-
| http_server.py:31 | fail | test_cgi_FieldStorage_taint | form['key'][0].filename |
5+
| http_server.py:22 | ok | test_cgi_FieldStorage_taint | form |
6+
| http_server.py:24 | ok | test_cgi_FieldStorage_taint | form['key'] |
7+
| http_server.py:25 | ok | test_cgi_FieldStorage_taint | form['key'].value |
8+
| http_server.py:26 | ok | test_cgi_FieldStorage_taint | form['key'].file |
9+
| http_server.py:27 | ok | test_cgi_FieldStorage_taint | form['key'].filename |
10+
| http_server.py:28 | ok | test_cgi_FieldStorage_taint | form['key'][0] |
11+
| http_server.py:29 | ok | test_cgi_FieldStorage_taint | form['key'][0].value |
12+
| http_server.py:30 | ok | test_cgi_FieldStorage_taint | form['key'][0].file |
13+
| http_server.py:31 | ok | test_cgi_FieldStorage_taint | form['key'][0].filename |
1414
| http_server.py:32 | fail | test_cgi_FieldStorage_taint | ListComp |
15-
| http_server.py:34 | fail | test_cgi_FieldStorage_taint | form.getvalue(..) |
16-
| http_server.py:35 | fail | test_cgi_FieldStorage_taint | form.getvalue(..)[0] |
17-
| http_server.py:37 | fail | test_cgi_FieldStorage_taint | form.getfirst(..) |
18-
| http_server.py:39 | fail | test_cgi_FieldStorage_taint | form.getlist(..) |
19-
| http_server.py:40 | fail | test_cgi_FieldStorage_taint | form.getlist(..)[0] |
15+
| http_server.py:34 | ok | test_cgi_FieldStorage_taint | form.getvalue(..) |
16+
| http_server.py:35 | ok | test_cgi_FieldStorage_taint | form.getvalue(..)[0] |
17+
| http_server.py:37 | ok | test_cgi_FieldStorage_taint | form.getfirst(..) |
18+
| http_server.py:39 | ok | test_cgi_FieldStorage_taint | form.getlist(..) |
19+
| http_server.py:40 | ok | test_cgi_FieldStorage_taint | form.getlist(..)[0] |
2020
| http_server.py:41 | fail | test_cgi_FieldStorage_taint | ListComp |
2121
| http_server.py:50 | fail | taint_sources | self |
2222
| http_server.py:52 | fail | taint_sources | self.requestline |
@@ -34,4 +34,4 @@
3434
| http_server.py:66 | fail | taint_sources | bytes(..) |
3535
| http_server.py:68 | fail | taint_sources | self.rfile |
3636
| http_server.py:69 | fail | taint_sources | self.rfile.read() |
37-
| http_server.py:78 | fail | taint_sources | form |
37+
| http_server.py:78 | ok | taint_sources | form |

0 commit comments

Comments
 (0)