From 247cfc58a08b0ccd983579ac5f1036849afd72a6 Mon Sep 17 00:00:00 2001 From: Joe Farebrother Date: Mon, 8 Apr 2024 19:01:40 +0100 Subject: [PATCH] Add tests for instances of the header write concept --- .../Tests1/HeaderWriteTest.expected | 82 +++++++++++++++++++ .../Tests1/HeaderWriteTest.ql | 20 +++++ .../Tests2/HeaderWriteTest.expected | 17 ++++ .../Tests2/HeaderWriteTest.ql | 20 +++++ .../Tests2/wsgiref_tests.py | 4 +- 5 files changed, 141 insertions(+), 2 deletions(-) create mode 100644 python/ql/test/query-tests/Security/CWE-113-HeaderInjection/Tests1/HeaderWriteTest.expected create mode 100644 python/ql/test/query-tests/Security/CWE-113-HeaderInjection/Tests1/HeaderWriteTest.ql create mode 100644 python/ql/test/query-tests/Security/CWE-113-HeaderInjection/Tests2/HeaderWriteTest.expected create mode 100644 python/ql/test/query-tests/Security/CWE-113-HeaderInjection/Tests2/HeaderWriteTest.ql diff --git a/python/ql/test/query-tests/Security/CWE-113-HeaderInjection/Tests1/HeaderWriteTest.expected b/python/ql/test/query-tests/Security/CWE-113-HeaderInjection/Tests1/HeaderWriteTest.expected new file mode 100644 index 0000000000000..4621faec224fe --- /dev/null +++ b/python/ql/test/query-tests/Security/CWE-113-HeaderInjection/Tests1/HeaderWriteTest.expected @@ -0,0 +1,82 @@ +source +| flask_tests.py:1:29:1:35 | ControlFlowNode for ImportMember | +| wsgiref_tests.py:4:14:4:20 | ControlFlowNode for environ | +| wsgiref_tests.py:12:15:12:21 | ControlFlowNode for environ | +sink +| flask_tests.py:12:17:12:28 | ControlFlowNode for Str | +| flask_tests.py:13:17:13:26 | ControlFlowNode for rfs_header | +| flask_tests.py:22:22:22:33 | ControlFlowNode for Str | +| flask_tests.py:23:22:23:31 | ControlFlowNode for rfs_header | +| flask_tests.py:31:22:31:33 | ControlFlowNode for Str | +| flask_tests.py:32:22:32:31 | ControlFlowNode for rfs_header | +| flask_tests.py:41:10:41:21 | ControlFlowNode for Str | +| flask_tests.py:43:10:43:19 | ControlFlowNode for rfs_header | +| flask_tests.py:49:30:49:41 | ControlFlowNode for Str | +| flask_tests.py:49:72:49:97 | ControlFlowNode for Subscript | +| flask_tests.py:54:41:54:66 | ControlFlowNode for Subscript | +| flask_tests.py:60:36:60:61 | ControlFlowNode for Subscript | +| flask_tests.py:66:36:66:63 | ControlFlowNode for Attribute() | +| flask_tests.py:74:17:74:26 | ControlFlowNode for rfs_header | +| flask_tests.py:75:24:75:33 | ControlFlowNode for rfs_header | +| flask_tests.py:76:17:76:26 | ControlFlowNode for rfs_header | +| flask_tests.py:77:24:77:33 | ControlFlowNode for rfs_header | +| flask_tests.py:78:25:78:34 | ControlFlowNode for rfs_header | +| flask_tests.py:79:13:79:22 | ControlFlowNode for rfs_header | +| flask_tests.py:80:11:80:20 | ControlFlowNode for rfs_header | +| flask_tests.py:82:12:82:21 | ControlFlowNode for rfs_header | +| flask_tests.py:85:11:85:20 | ControlFlowNode for rfs_header | +| flask_tests.py:86:12:86:21 | ControlFlowNode for rfs_header | +| wsgiref_tests.py:8:17:8:22 | ControlFlowNode for h_name | +| wsgiref_tests.py:8:25:8:29 | ControlFlowNode for Str | +| wsgiref_tests.py:8:34:8:39 | ControlFlowNode for Str | +| wsgiref_tests.py:8:42:8:46 | ControlFlowNode for h_val | +| wsgiref_tests.py:16:25:16:30 | ControlFlowNode for h_name | +| wsgiref_tests.py:16:33:16:37 | ControlFlowNode for Str | +| wsgiref_tests.py:16:42:16:47 | ControlFlowNode for Str | +| wsgiref_tests.py:16:50:16:54 | ControlFlowNode for h_val | +| wsgiref_tests.py:17:24:17:29 | ControlFlowNode for h_name | +| wsgiref_tests.py:17:32:17:36 | ControlFlowNode for h_val | +| wsgiref_tests.py:18:24:18:29 | ControlFlowNode for h_name | +| wsgiref_tests.py:18:32:18:36 | ControlFlowNode for h_val | +| wsgiref_tests.py:19:25:19:30 | ControlFlowNode for h_name | +| wsgiref_tests.py:19:33:19:37 | ControlFlowNode for h_val | +| wsgiref_tests.py:20:13:20:18 | ControlFlowNode for h_name | +| wsgiref_tests.py:20:23:20:27 | ControlFlowNode for h_val | +headerWrite +| flask_tests.py:12:5:12:41 | ControlFlowNode for Attribute() | flask_tests.py:12:17:12:28 | ControlFlowNode for Str | flask_tests.py:12:31:12:40 | ControlFlowNode for rfs_header | true | false | +| flask_tests.py:13:5:13:42 | ControlFlowNode for Attribute() | flask_tests.py:13:17:13:26 | ControlFlowNode for rfs_header | flask_tests.py:13:29:13:41 | ControlFlowNode for Str | true | false | +| flask_tests.py:22:5:22:34 | ControlFlowNode for Subscript | flask_tests.py:22:22:22:33 | ControlFlowNode for Str | flask_tests.py:22:38:22:47 | ControlFlowNode for rfs_header | true | false | +| flask_tests.py:23:5:23:32 | ControlFlowNode for Subscript | flask_tests.py:23:22:23:31 | ControlFlowNode for rfs_header | flask_tests.py:23:36:23:48 | ControlFlowNode for Str | true | false | +| flask_tests.py:31:5:31:34 | ControlFlowNode for Subscript | flask_tests.py:31:22:31:33 | ControlFlowNode for Str | flask_tests.py:31:38:31:47 | ControlFlowNode for rfs_header | true | false | +| flask_tests.py:32:5:32:32 | ControlFlowNode for Subscript | flask_tests.py:32:22:32:31 | ControlFlowNode for rfs_header | flask_tests.py:32:36:32:48 | ControlFlowNode for Str | true | false | +| flask_tests.py:40:5:41:35 | ControlFlowNode for Attribute() | flask_tests.py:41:10:41:21 | ControlFlowNode for Str | flask_tests.py:41:24:41:33 | ControlFlowNode for rfs_header | true | false | +| flask_tests.py:42:5:43:36 | ControlFlowNode for Attribute() | flask_tests.py:43:10:43:19 | ControlFlowNode for rfs_header | flask_tests.py:43:22:43:34 | ControlFlowNode for Str | true | false | +| flask_tests.py:49:12:49:114 | ControlFlowNode for Response() | flask_tests.py:49:30:49:41 | ControlFlowNode for Str | flask_tests.py:49:44:49:69 | ControlFlowNode for Subscript | true | false | +| flask_tests.py:49:12:49:114 | ControlFlowNode for Response() | flask_tests.py:49:30:49:41 | ControlFlowNode for Str | flask_tests.py:49:100:49:112 | ControlFlowNode for Str | true | false | +| flask_tests.py:49:12:49:114 | ControlFlowNode for Response() | flask_tests.py:49:72:49:97 | ControlFlowNode for Subscript | flask_tests.py:49:44:49:69 | ControlFlowNode for Subscript | true | false | +| flask_tests.py:49:12:49:114 | ControlFlowNode for Response() | flask_tests.py:49:72:49:97 | ControlFlowNode for Subscript | flask_tests.py:49:100:49:112 | ControlFlowNode for Str | true | false | +| flask_tests.py:54:12:54:83 | ControlFlowNode for make_response() | flask_tests.py:54:41:54:66 | ControlFlowNode for Subscript | flask_tests.py:54:69:54:81 | ControlFlowNode for Str | true | false | +| flask_tests.py:60:12:60:78 | ControlFlowNode for make_response() | flask_tests.py:60:36:60:61 | ControlFlowNode for Subscript | flask_tests.py:60:64:60:76 | ControlFlowNode for Str | true | false | +| flask_tests.py:66:12:66:80 | ControlFlowNode for make_response() | flask_tests.py:66:36:66:63 | ControlFlowNode for Attribute() | flask_tests.py:66:66:66:78 | ControlFlowNode for Str | true | false | +| flask_tests.py:74:5:74:42 | ControlFlowNode for Attribute() | flask_tests.py:74:17:74:26 | ControlFlowNode for rfs_header | flask_tests.py:74:29:74:41 | ControlFlowNode for Str | true | false | +| flask_tests.py:75:5:75:49 | ControlFlowNode for Attribute() | flask_tests.py:75:24:75:33 | ControlFlowNode for rfs_header | flask_tests.py:75:36:75:48 | ControlFlowNode for Str | true | false | +| flask_tests.py:76:5:76:42 | ControlFlowNode for Attribute() | flask_tests.py:76:17:76:26 | ControlFlowNode for rfs_header | flask_tests.py:76:29:76:41 | ControlFlowNode for Str | true | false | +| flask_tests.py:77:5:77:49 | ControlFlowNode for Attribute() | flask_tests.py:77:24:77:33 | ControlFlowNode for rfs_header | flask_tests.py:77:36:77:48 | ControlFlowNode for Str | true | false | +| flask_tests.py:78:5:78:50 | ControlFlowNode for Attribute() | flask_tests.py:78:25:78:34 | ControlFlowNode for rfs_header | flask_tests.py:78:37:78:49 | ControlFlowNode for Str | true | false | +| flask_tests.py:79:5:79:23 | ControlFlowNode for Subscript | flask_tests.py:79:13:79:22 | ControlFlowNode for rfs_header | flask_tests.py:79:27:79:39 | ControlFlowNode for Str | true | false | +| flask_tests.py:81:5:81:22 | ControlFlowNode for Attribute() | flask_tests.py:80:11:80:20 | ControlFlowNode for rfs_header | flask_tests.py:80:23:80:35 | ControlFlowNode for Str | true | false | +| flask_tests.py:83:5:83:22 | ControlFlowNode for Attribute() | flask_tests.py:82:12:82:21 | ControlFlowNode for rfs_header | flask_tests.py:82:24:82:36 | ControlFlowNode for Str | true | false | +| flask_tests.py:87:13:87:35 | ControlFlowNode for make_response() | flask_tests.py:85:11:85:20 | ControlFlowNode for rfs_header | flask_tests.py:85:23:85:35 | ControlFlowNode for Str | true | false | +| flask_tests.py:88:13:88:35 | ControlFlowNode for make_response() | flask_tests.py:86:12:86:21 | ControlFlowNode for rfs_header | flask_tests.py:86:24:86:36 | ControlFlowNode for Str | true | false | +| wsgiref_tests.py:9:5:9:35 | ControlFlowNode for start_response() | wsgiref_tests.py:8:17:8:22 | ControlFlowNode for h_name | wsgiref_tests.py:8:25:8:29 | ControlFlowNode for Str | true | true | +| wsgiref_tests.py:9:5:9:35 | ControlFlowNode for start_response() | wsgiref_tests.py:8:17:8:22 | ControlFlowNode for h_name | wsgiref_tests.py:8:42:8:46 | ControlFlowNode for h_val | true | true | +| wsgiref_tests.py:9:5:9:35 | ControlFlowNode for start_response() | wsgiref_tests.py:8:34:8:39 | ControlFlowNode for Str | wsgiref_tests.py:8:25:8:29 | ControlFlowNode for Str | true | true | +| wsgiref_tests.py:9:5:9:35 | ControlFlowNode for start_response() | wsgiref_tests.py:8:34:8:39 | ControlFlowNode for Str | wsgiref_tests.py:8:42:8:46 | ControlFlowNode for h_val | true | true | +| wsgiref_tests.py:16:15:16:57 | ControlFlowNode for Headers() | wsgiref_tests.py:16:25:16:30 | ControlFlowNode for h_name | wsgiref_tests.py:16:33:16:37 | ControlFlowNode for Str | true | true | +| wsgiref_tests.py:16:15:16:57 | ControlFlowNode for Headers() | wsgiref_tests.py:16:25:16:30 | ControlFlowNode for h_name | wsgiref_tests.py:16:50:16:54 | ControlFlowNode for h_val | true | true | +| wsgiref_tests.py:16:15:16:57 | ControlFlowNode for Headers() | wsgiref_tests.py:16:42:16:47 | ControlFlowNode for Str | wsgiref_tests.py:16:33:16:37 | ControlFlowNode for Str | true | true | +| wsgiref_tests.py:16:15:16:57 | ControlFlowNode for Headers() | wsgiref_tests.py:16:42:16:47 | ControlFlowNode for Str | wsgiref_tests.py:16:50:16:54 | ControlFlowNode for h_val | true | true | +| wsgiref_tests.py:17:5:17:37 | ControlFlowNode for Attribute() | wsgiref_tests.py:17:24:17:29 | ControlFlowNode for h_name | wsgiref_tests.py:17:32:17:36 | ControlFlowNode for h_val | true | true | +| wsgiref_tests.py:18:5:18:37 | ControlFlowNode for Attribute() | wsgiref_tests.py:18:24:18:29 | ControlFlowNode for h_name | wsgiref_tests.py:18:32:18:36 | ControlFlowNode for h_val | true | true | +| wsgiref_tests.py:19:5:19:38 | ControlFlowNode for Attribute() | wsgiref_tests.py:19:25:19:30 | ControlFlowNode for h_name | wsgiref_tests.py:19:33:19:37 | ControlFlowNode for h_val | true | true | +| wsgiref_tests.py:20:5:20:19 | ControlFlowNode for Subscript | wsgiref_tests.py:20:13:20:18 | ControlFlowNode for h_name | wsgiref_tests.py:20:23:20:27 | ControlFlowNode for h_val | true | true | diff --git a/python/ql/test/query-tests/Security/CWE-113-HeaderInjection/Tests1/HeaderWriteTest.ql b/python/ql/test/query-tests/Security/CWE-113-HeaderInjection/Tests1/HeaderWriteTest.ql new file mode 100644 index 0000000000000..a46d46e89c9e3 --- /dev/null +++ b/python/ql/test/query-tests/Security/CWE-113-HeaderInjection/Tests1/HeaderWriteTest.ql @@ -0,0 +1,20 @@ +import python +import semmle.python.security.dataflow.HttpHeaderInjectionCustomizations +import semmle.python.dataflow.new.DataFlow +import semmle.python.Concepts + +query predicate source(HttpHeaderInjection::Source src) { + src.getLocation().getFile().getBaseName() in ["wsgiref_tests.py", "flask_tests.py"] +} + +query predicate sink(HttpHeaderInjection::Sink sink) { any() } + +query predicate headerWrite( + Http::Server::ResponseHeaderWrite write, DataFlow::Node name, DataFlow::Node val, + boolean nameVuln, boolean valVuln +) { + name = write.getNameArg() and + val = write.getValueArg() and + (if write.nameAllowsNewline() then nameVuln = true else nameVuln = false) and + (if write.valueAllowsNewline() then valVuln = true else valVuln = false) +} diff --git a/python/ql/test/query-tests/Security/CWE-113-HeaderInjection/Tests2/HeaderWriteTest.expected b/python/ql/test/query-tests/Security/CWE-113-HeaderInjection/Tests2/HeaderWriteTest.expected new file mode 100644 index 0000000000000..6a4bf6d2e008f --- /dev/null +++ b/python/ql/test/query-tests/Security/CWE-113-HeaderInjection/Tests2/HeaderWriteTest.expected @@ -0,0 +1,17 @@ +source +| wsgiref_tests.py:5:14:5:20 | ControlFlowNode for environ | +| wsgiref_tests.py:13:15:13:21 | ControlFlowNode for environ | +sink +headerWrite +| wsgiref_tests.py:10:5:10:35 | ControlFlowNode for start_response() | wsgiref_tests.py:9:17:9:22 | ControlFlowNode for h_name | wsgiref_tests.py:9:25:9:29 | ControlFlowNode for Str | false | false | +| wsgiref_tests.py:10:5:10:35 | ControlFlowNode for start_response() | wsgiref_tests.py:9:17:9:22 | ControlFlowNode for h_name | wsgiref_tests.py:9:42:9:46 | ControlFlowNode for h_val | false | false | +| wsgiref_tests.py:10:5:10:35 | ControlFlowNode for start_response() | wsgiref_tests.py:9:34:9:39 | ControlFlowNode for Str | wsgiref_tests.py:9:25:9:29 | ControlFlowNode for Str | false | false | +| wsgiref_tests.py:10:5:10:35 | ControlFlowNode for start_response() | wsgiref_tests.py:9:34:9:39 | ControlFlowNode for Str | wsgiref_tests.py:9:42:9:46 | ControlFlowNode for h_val | false | false | +| wsgiref_tests.py:17:15:17:57 | ControlFlowNode for Headers() | wsgiref_tests.py:17:25:17:30 | ControlFlowNode for h_name | wsgiref_tests.py:17:33:17:37 | ControlFlowNode for Str | false | false | +| wsgiref_tests.py:17:15:17:57 | ControlFlowNode for Headers() | wsgiref_tests.py:17:25:17:30 | ControlFlowNode for h_name | wsgiref_tests.py:17:50:17:54 | ControlFlowNode for h_val | false | false | +| wsgiref_tests.py:17:15:17:57 | ControlFlowNode for Headers() | wsgiref_tests.py:17:42:17:47 | ControlFlowNode for Str | wsgiref_tests.py:17:33:17:37 | ControlFlowNode for Str | false | false | +| wsgiref_tests.py:17:15:17:57 | ControlFlowNode for Headers() | wsgiref_tests.py:17:42:17:47 | ControlFlowNode for Str | wsgiref_tests.py:17:50:17:54 | ControlFlowNode for h_val | false | false | +| wsgiref_tests.py:18:5:18:37 | ControlFlowNode for Attribute() | wsgiref_tests.py:18:24:18:29 | ControlFlowNode for h_name | wsgiref_tests.py:18:32:18:36 | ControlFlowNode for h_val | false | false | +| wsgiref_tests.py:19:5:19:37 | ControlFlowNode for Attribute() | wsgiref_tests.py:19:24:19:29 | ControlFlowNode for h_name | wsgiref_tests.py:19:32:19:36 | ControlFlowNode for h_val | false | false | +| wsgiref_tests.py:20:5:20:38 | ControlFlowNode for Attribute() | wsgiref_tests.py:20:25:20:30 | ControlFlowNode for h_name | wsgiref_tests.py:20:33:20:37 | ControlFlowNode for h_val | false | false | +| wsgiref_tests.py:21:5:21:19 | ControlFlowNode for Subscript | wsgiref_tests.py:21:13:21:18 | ControlFlowNode for h_name | wsgiref_tests.py:21:23:21:27 | ControlFlowNode for h_val | false | false | diff --git a/python/ql/test/query-tests/Security/CWE-113-HeaderInjection/Tests2/HeaderWriteTest.ql b/python/ql/test/query-tests/Security/CWE-113-HeaderInjection/Tests2/HeaderWriteTest.ql new file mode 100644 index 0000000000000..a46d46e89c9e3 --- /dev/null +++ b/python/ql/test/query-tests/Security/CWE-113-HeaderInjection/Tests2/HeaderWriteTest.ql @@ -0,0 +1,20 @@ +import python +import semmle.python.security.dataflow.HttpHeaderInjectionCustomizations +import semmle.python.dataflow.new.DataFlow +import semmle.python.Concepts + +query predicate source(HttpHeaderInjection::Source src) { + src.getLocation().getFile().getBaseName() in ["wsgiref_tests.py", "flask_tests.py"] +} + +query predicate sink(HttpHeaderInjection::Sink sink) { any() } + +query predicate headerWrite( + Http::Server::ResponseHeaderWrite write, DataFlow::Node name, DataFlow::Node val, + boolean nameVuln, boolean valVuln +) { + name = write.getNameArg() and + val = write.getValueArg() and + (if write.nameAllowsNewline() then nameVuln = true else nameVuln = false) and + (if write.valueAllowsNewline() then valVuln = true else valVuln = false) +} diff --git a/python/ql/test/query-tests/Security/CWE-113-HeaderInjection/Tests2/wsgiref_tests.py b/python/ql/test/query-tests/Security/CWE-113-HeaderInjection/Tests2/wsgiref_tests.py index f13f7187b7267..7dbf3a0246457 100644 --- a/python/ql/test/query-tests/Security/CWE-113-HeaderInjection/Tests2/wsgiref_tests.py +++ b/python/ql/test/query-tests/Security/CWE-113-HeaderInjection/Tests2/wsgiref_tests.py @@ -23,11 +23,11 @@ def test_app2(environ, start_response): return [b"Hello"] def main1(): - with make_server('', 8000, validate(test_app)) as httpd: + with make_server('', 8000, validator(test_app)) as httpd: print("Serving on port 8000...") httpd.serve_forever() def main2(): - with make_server('', 8000, validate(test_app2)) as httpd: + with make_server('', 8000, validator(test_app2)) as httpd: print("Serving on port 8000...") httpd.serve_forever() \ No newline at end of file