diff --git a/python/ql/lib/semmle/python/security/dataflow/ServerSideRequestForgery.qll b/python/ql/lib/semmle/python/security/dataflow/ServerSideRequestForgery.qll new file mode 100644 index 00000000000..70239f42ff5 --- /dev/null +++ b/python/ql/lib/semmle/python/security/dataflow/ServerSideRequestForgery.qll @@ -0,0 +1,69 @@ +/** + * Provides a taint-tracking configuration for detecting "Server-side request forgery" vulnerabilities. + * + * Note, for performance reasons: only import this file if + * `ServerSideRequestForgery::Configuration` is needed, otherwise + * `ServerSideRequestForgeryCustomizations` should be imported instead. + */ + +private import python +import semmle.python.dataflow.new.DataFlow +import semmle.python.dataflow.new.TaintTracking + +/** + * Provides a taint-tracking configuration for detecting "Server-side request forgery" vulnerabilities. + * + * This configuration has a sanitizer to limit results to cases where attacker has full control of URL. + * See `PartialServerSideRequestForgery` for a variant without this requirement. + */ +module FullServerSideRequestForgery { + import ServerSideRequestForgeryCustomizations::ServerSideRequestForgery + + /** + * A taint-tracking configuration for detecting "Server-side request forgery" vulnerabilities. + */ + class Configuration extends TaintTracking::Configuration { + Configuration() { this = "FullServerSideRequestForgery" } + + override predicate isSource(DataFlow::Node source) { source instanceof Source } + + override predicate isSink(DataFlow::Node sink) { sink instanceof Sink } + + override predicate isSanitizer(DataFlow::Node node) { + node instanceof Sanitizer + or + node instanceof FullUrlControlSanitizer + } + + override predicate isSanitizerGuard(DataFlow::BarrierGuard guard) { + guard instanceof SanitizerGuard + } + } +} + +/** + * Provides a taint-tracking configuration for detecting "Server-side request forgery" vulnerabilities. + * + * This configuration has results, even when the attacker does not have full control over the URL. + * See `FullServerSideRequestForgery` for variant that has this requirement. + */ +module PartialServerSideRequestForgery { + import ServerSideRequestForgeryCustomizations::ServerSideRequestForgery + + /** + * A taint-tracking configuration for detecting "Server-side request forgery" vulnerabilities. + */ + class Configuration extends TaintTracking::Configuration { + Configuration() { this = "PartialServerSideRequestForgery" } + + override predicate isSource(DataFlow::Node source) { source instanceof Source } + + override predicate isSink(DataFlow::Node sink) { sink instanceof Sink } + + override predicate isSanitizer(DataFlow::Node node) { node instanceof Sanitizer } + + override predicate isSanitizerGuard(DataFlow::BarrierGuard guard) { + guard instanceof SanitizerGuard + } + } +} diff --git a/python/ql/lib/semmle/python/security/dataflow/ServerSideRequestForgeryCustomizations.qll b/python/ql/lib/semmle/python/security/dataflow/ServerSideRequestForgeryCustomizations.qll new file mode 100644 index 00000000000..bd7b4e6e507 --- /dev/null +++ b/python/ql/lib/semmle/python/security/dataflow/ServerSideRequestForgeryCustomizations.qll @@ -0,0 +1,94 @@ +/** + * Provides default sources, sinks and sanitizers for detecting + * "Server-side request forgery" + * vulnerabilities, as well as extension points for adding your own. + */ + +private import python +private import semmle.python.dataflow.new.DataFlow +private import semmle.python.Concepts +private import semmle.python.dataflow.new.RemoteFlowSources +private import semmle.python.dataflow.new.BarrierGuards + +/** + * Provides default sources, sinks and sanitizers for detecting + * "Server-side request forgery" + * vulnerabilities, as well as extension points for adding your own. + */ +module ServerSideRequestForgery { + /** + * A data flow source for "Server-side request forgery" vulnerabilities. + */ + abstract class Source extends DataFlow::Node { } + + /** + * A data flow sink for "Server-side request forgery" vulnerabilities. + */ + abstract class Sink extends DataFlow::Node { } + + /** + * A sanitizer for "Server-side request forgery" vulnerabilities. + */ + abstract class Sanitizer extends DataFlow::Node { } + + /** + * A sanitizer for "Server-side request forgery" vulnerabilities, + * that ensures the attacker does not have full control of the URL. (that is, might + * still be able to control path or query parameters). + */ + abstract class FullUrlControlSanitizer extends DataFlow::Node { } + + /** + * A sanitizer guard for "Server-side request forgery" vulnerabilities. + */ + abstract class SanitizerGuard extends DataFlow::BarrierGuard { } + + /** + * A source of remote user input, considered as a flow source. + */ + class RemoteFlowSourceAsSource extends Source, RemoteFlowSource { } + + /** The URL of an HTTP request, considered as a sink. */ + class HttpRequestUrlAsSink extends Sink { + HttpRequestUrlAsSink() { + exists(HTTP::Client::Request req | req.getAUrlPart() = this) and + // Since we find sinks inside stdlib, we need to exclude them manually. See + // comment for command injection sinks for more details. + not this.getScope().getEnclosingModule().getName() in ["http.client", "httplib"] + } + } + + /** + * A comparison with a constant string, considered as a sanitizer-guard. + */ + class StringConstCompareAsSanitizerGuard extends SanitizerGuard, StringConstCompare { } + + /** + * A string construction (concat, format, f-string) where the left side is not + * user-controlled. + */ + class StringConstructioneAsFullUrlControlSanitizer extends FullUrlControlSanitizer { + StringConstructioneAsFullUrlControlSanitizer() { + // string concat + exists(BinaryExprNode add | + add.getOp() instanceof Add and + add.getRight() = this.asCfgNode() + ) + or + // % formatting + exists(BinaryExprNode fmt | + fmt.getOp() instanceof Mod and + fmt.getRight() = this.asCfgNode() + ) + or + // arguments to a format call + exists(DataFlow::MethodCallNode call | + call.getMethodName() = "format" and + this in [call.getArg(_), call.getArgByName(_)] + ) + or + // f-string + exists(Fstring fstring | fstring.getValue(any(int i | i > 0)) = this.asExpr()) + } + } +} diff --git a/python/ql/src/Security/CWE-918/FullServerSideRequestForgery.ql b/python/ql/src/Security/CWE-918/FullServerSideRequestForgery.ql new file mode 100644 index 00000000000..72c5ac9f70f --- /dev/null +++ b/python/ql/src/Security/CWE-918/FullServerSideRequestForgery.ql @@ -0,0 +1,22 @@ +/** + * @name Full server-side request forgery + * @description Making a network request to a URL that is fully user-controlled allows for request forgery attacks. + * @kind path-problem + * @problem.severity error + * @security-severity 9.1 + * @precision high + * @id py/full-ssrf + * @tags security + * external/cwe/cwe-918 + */ + +import python +import semmle.python.security.dataflow.ServerSideRequestForgery +import DataFlow::PathGraph + +from + FullServerSideRequestForgery::Configuration config, DataFlow::PathNode source, + DataFlow::PathNode sink +where config.hasFlowPath(source, sink) +select sink.getNode(), source, sink, "The full URL of this request depends on $@.", + source.getNode(), "a user-provided value" diff --git a/python/ql/src/Security/CWE-918/PartialServerSideRequestForgery.ql b/python/ql/src/Security/CWE-918/PartialServerSideRequestForgery.ql new file mode 100644 index 00000000000..cd59a710a78 --- /dev/null +++ b/python/ql/src/Security/CWE-918/PartialServerSideRequestForgery.ql @@ -0,0 +1,25 @@ +/** + * @name Partial server-side request forgery + * @description Making a network request to a URL that is partially user-controlled allows for request forgery attacks. + * @kind path-problem + * @problem.severity error + * @security-severity 9.1 + * @precision medium + * @id py/partial-ssrf + * @tags security + * external/cwe/cwe-918 + */ + +import python +import semmle.python.security.dataflow.ServerSideRequestForgery +import DataFlow::PathGraph + +from + FullServerSideRequestForgery::Configuration fullConfig, + PartialServerSideRequestForgery::Configuration partialConfig, DataFlow::PathNode source, + DataFlow::PathNode sink +where + partialConfig.hasFlowPath(source, sink) and + not fullConfig.hasFlow(source.getNode(), sink.getNode()) +select sink.getNode(), source, sink, "Part of the URL of this request depends on $@.", + source.getNode(), "a user-provided value" diff --git a/python/ql/test/query-tests/Security/CWE-918-ServerSideRequestForgery/FullServerSideRequestForgery.expected b/python/ql/test/query-tests/Security/CWE-918-ServerSideRequestForgery/FullServerSideRequestForgery.expected new file mode 100644 index 00000000000..fb24c3ebdaf --- /dev/null +++ b/python/ql/test/query-tests/Security/CWE-918-ServerSideRequestForgery/FullServerSideRequestForgery.expected @@ -0,0 +1,147 @@ +edges +| full_partial_test.py:7:18:7:24 | ControlFlowNode for request | full_partial_test.py:7:18:7:29 | ControlFlowNode for Attribute | +| full_partial_test.py:7:18:7:24 | ControlFlowNode for request | full_partial_test.py:7:18:7:29 | ControlFlowNode for Attribute | +| full_partial_test.py:7:18:7:24 | ControlFlowNode for request | full_partial_test.py:8:17:8:23 | ControlFlowNode for request | +| full_partial_test.py:7:18:7:24 | ControlFlowNode for request | full_partial_test.py:8:17:8:28 | ControlFlowNode for Attribute | +| full_partial_test.py:7:18:7:29 | ControlFlowNode for Attribute | full_partial_test.py:7:18:7:48 | ControlFlowNode for Subscript | +| full_partial_test.py:7:18:7:29 | ControlFlowNode for Attribute | full_partial_test.py:7:18:7:48 | ControlFlowNode for Subscript | +| full_partial_test.py:7:18:7:48 | ControlFlowNode for Subscript | full_partial_test.py:10:18:10:27 | ControlFlowNode for user_input | +| full_partial_test.py:7:18:7:48 | ControlFlowNode for Subscript | full_partial_test.py:10:18:10:27 | ControlFlowNode for user_input | +| full_partial_test.py:7:18:7:48 | ControlFlowNode for Subscript | full_partial_test.py:13:18:13:20 | ControlFlowNode for url | +| full_partial_test.py:7:18:7:48 | ControlFlowNode for Subscript | full_partial_test.py:19:18:19:20 | ControlFlowNode for url | +| full_partial_test.py:7:18:7:48 | ControlFlowNode for Subscript | full_partial_test.py:23:18:23:20 | ControlFlowNode for url | +| full_partial_test.py:7:18:7:48 | ControlFlowNode for Subscript | full_partial_test.py:28:18:28:20 | ControlFlowNode for url | +| full_partial_test.py:7:18:7:48 | ControlFlowNode for Subscript | full_partial_test.py:31:18:31:20 | ControlFlowNode for url | +| full_partial_test.py:7:18:7:48 | ControlFlowNode for Subscript | full_partial_test.py:34:18:34:20 | ControlFlowNode for url | +| full_partial_test.py:7:18:7:48 | ControlFlowNode for Subscript | full_partial_test.py:39:18:39:20 | ControlFlowNode for url | +| full_partial_test.py:7:18:7:48 | ControlFlowNode for Subscript | full_partial_test.py:42:18:42:20 | ControlFlowNode for url | +| full_partial_test.py:7:18:7:48 | ControlFlowNode for Subscript | full_partial_test.py:44:38:44:58 | ControlFlowNode for Tuple | +| full_partial_test.py:7:18:7:48 | ControlFlowNode for Subscript | full_partial_test.py:50:18:50:20 | ControlFlowNode for url | +| full_partial_test.py:7:18:7:48 | ControlFlowNode for Subscript | full_partial_test.py:53:18:53:20 | ControlFlowNode for url | +| full_partial_test.py:7:18:7:48 | ControlFlowNode for Subscript | full_partial_test.py:56:18:56:20 | ControlFlowNode for url | +| full_partial_test.py:8:17:8:23 | ControlFlowNode for request | full_partial_test.py:8:17:8:28 | ControlFlowNode for Attribute | +| full_partial_test.py:8:17:8:28 | ControlFlowNode for Attribute | full_partial_test.py:8:17:8:41 | ControlFlowNode for Subscript | +| full_partial_test.py:8:17:8:41 | ControlFlowNode for Subscript | full_partial_test.py:23:18:23:20 | ControlFlowNode for url | +| full_partial_test.py:8:17:8:41 | ControlFlowNode for Subscript | full_partial_test.py:34:18:34:20 | ControlFlowNode for url | +| full_partial_test.py:8:17:8:41 | ControlFlowNode for Subscript | full_partial_test.py:44:38:44:58 | ControlFlowNode for Tuple | +| full_partial_test.py:8:17:8:41 | ControlFlowNode for Subscript | full_partial_test.py:56:18:56:20 | ControlFlowNode for url | +| full_partial_test.py:44:38:44:58 | ControlFlowNode for Tuple | full_partial_test.py:45:18:45:20 | ControlFlowNode for url | +| full_partial_test.py:60:18:60:24 | ControlFlowNode for request | full_partial_test.py:60:18:60:29 | ControlFlowNode for Attribute | +| full_partial_test.py:60:18:60:29 | ControlFlowNode for Attribute | full_partial_test.py:60:18:60:48 | ControlFlowNode for Subscript | +| full_partial_test.py:60:18:60:48 | ControlFlowNode for Subscript | full_partial_test.py:63:18:63:20 | ControlFlowNode for url | +| full_partial_test.py:60:18:60:48 | ControlFlowNode for Subscript | full_partial_test.py:66:18:66:20 | ControlFlowNode for url | +| full_partial_test.py:60:18:60:48 | ControlFlowNode for Subscript | full_partial_test.py:69:18:69:20 | ControlFlowNode for url | +| full_partial_test.py:60:18:60:48 | ControlFlowNode for Subscript | full_partial_test.py:72:18:72:20 | ControlFlowNode for url | +| full_partial_test.py:60:18:60:48 | ControlFlowNode for Subscript | full_partial_test.py:78:18:78:20 | ControlFlowNode for url | +| full_partial_test.py:60:18:60:48 | ControlFlowNode for Subscript | full_partial_test.py:81:18:81:20 | ControlFlowNode for url | +| test_http_client.py:9:19:9:25 | ControlFlowNode for request | test_http_client.py:9:19:9:30 | ControlFlowNode for Attribute | +| test_http_client.py:9:19:9:25 | ControlFlowNode for request | test_http_client.py:9:19:9:30 | ControlFlowNode for Attribute | +| test_http_client.py:9:19:9:25 | ControlFlowNode for request | test_http_client.py:10:19:10:25 | ControlFlowNode for request | +| test_http_client.py:9:19:9:25 | ControlFlowNode for request | test_http_client.py:10:19:10:25 | ControlFlowNode for request | +| test_http_client.py:9:19:9:25 | ControlFlowNode for request | test_http_client.py:10:19:10:30 | ControlFlowNode for Attribute | +| test_http_client.py:9:19:9:25 | ControlFlowNode for request | test_http_client.py:10:19:10:30 | ControlFlowNode for Attribute | +| test_http_client.py:9:19:9:25 | ControlFlowNode for request | test_http_client.py:11:18:11:24 | ControlFlowNode for request | +| test_http_client.py:9:19:9:25 | ControlFlowNode for request | test_http_client.py:11:18:11:29 | ControlFlowNode for Attribute | +| test_http_client.py:9:19:9:30 | ControlFlowNode for Attribute | test_http_client.py:9:19:9:38 | ControlFlowNode for Subscript | +| test_http_client.py:9:19:9:30 | ControlFlowNode for Attribute | test_http_client.py:9:19:9:38 | ControlFlowNode for Subscript | +| test_http_client.py:9:19:9:38 | ControlFlowNode for Subscript | test_http_client.py:13:27:13:37 | ControlFlowNode for unsafe_host | +| test_http_client.py:9:19:9:38 | ControlFlowNode for Subscript | test_http_client.py:13:27:13:37 | ControlFlowNode for unsafe_host | +| test_http_client.py:9:19:9:38 | ControlFlowNode for Subscript | test_http_client.py:17:27:17:37 | ControlFlowNode for unsafe_host | +| test_http_client.py:9:19:9:38 | ControlFlowNode for Subscript | test_http_client.py:17:27:17:37 | ControlFlowNode for unsafe_host | +| test_http_client.py:10:19:10:25 | ControlFlowNode for request | test_http_client.py:10:19:10:30 | ControlFlowNode for Attribute | +| test_http_client.py:10:19:10:25 | ControlFlowNode for request | test_http_client.py:10:19:10:30 | ControlFlowNode for Attribute | +| test_http_client.py:10:19:10:25 | ControlFlowNode for request | test_http_client.py:11:18:11:24 | ControlFlowNode for request | +| test_http_client.py:10:19:10:25 | ControlFlowNode for request | test_http_client.py:11:18:11:29 | ControlFlowNode for Attribute | +| test_http_client.py:10:19:10:30 | ControlFlowNode for Attribute | test_http_client.py:10:19:10:38 | ControlFlowNode for Subscript | +| test_http_client.py:10:19:10:30 | ControlFlowNode for Attribute | test_http_client.py:10:19:10:38 | ControlFlowNode for Subscript | +| test_http_client.py:10:19:10:38 | ControlFlowNode for Subscript | test_http_client.py:14:25:14:35 | ControlFlowNode for unsafe_path | +| test_http_client.py:10:19:10:38 | ControlFlowNode for Subscript | test_http_client.py:14:25:14:35 | ControlFlowNode for unsafe_path | +| test_http_client.py:10:19:10:38 | ControlFlowNode for Subscript | test_http_client.py:21:25:21:35 | ControlFlowNode for unsafe_path | +| test_http_client.py:10:19:10:38 | ControlFlowNode for Subscript | test_http_client.py:21:25:21:35 | ControlFlowNode for unsafe_path | +| test_http_client.py:11:18:11:24 | ControlFlowNode for request | test_http_client.py:11:18:11:29 | ControlFlowNode for Attribute | +| test_http_client.py:11:18:11:29 | ControlFlowNode for Attribute | test_http_client.py:11:18:11:48 | ControlFlowNode for Subscript | +| test_http_client.py:11:18:11:48 | ControlFlowNode for Subscript | test_http_client.py:25:25:25:28 | ControlFlowNode for path | +| test_http_client.py:11:18:11:48 | ControlFlowNode for Subscript | test_http_client.py:29:25:29:28 | ControlFlowNode for path | +| test_requests.py:6:18:6:24 | ControlFlowNode for request | test_requests.py:6:18:6:29 | ControlFlowNode for Attribute | +| test_requests.py:6:18:6:24 | ControlFlowNode for request | test_requests.py:6:18:6:29 | ControlFlowNode for Attribute | +| test_requests.py:6:18:6:29 | ControlFlowNode for Attribute | test_requests.py:6:18:6:48 | ControlFlowNode for Subscript | +| test_requests.py:6:18:6:29 | ControlFlowNode for Attribute | test_requests.py:6:18:6:48 | ControlFlowNode for Subscript | +| test_requests.py:6:18:6:48 | ControlFlowNode for Subscript | test_requests.py:8:18:8:27 | ControlFlowNode for user_input | +| test_requests.py:6:18:6:48 | ControlFlowNode for Subscript | test_requests.py:8:18:8:27 | ControlFlowNode for user_input | +nodes +| full_partial_test.py:7:18:7:24 | ControlFlowNode for request | semmle.label | ControlFlowNode for request | +| full_partial_test.py:7:18:7:24 | ControlFlowNode for request | semmle.label | ControlFlowNode for request | +| full_partial_test.py:7:18:7:29 | ControlFlowNode for Attribute | semmle.label | ControlFlowNode for Attribute | +| full_partial_test.py:7:18:7:29 | ControlFlowNode for Attribute | semmle.label | ControlFlowNode for Attribute | +| full_partial_test.py:7:18:7:48 | ControlFlowNode for Subscript | semmle.label | ControlFlowNode for Subscript | +| full_partial_test.py:7:18:7:48 | ControlFlowNode for Subscript | semmle.label | ControlFlowNode for Subscript | +| full_partial_test.py:8:17:8:23 | ControlFlowNode for request | semmle.label | ControlFlowNode for request | +| full_partial_test.py:8:17:8:28 | ControlFlowNode for Attribute | semmle.label | ControlFlowNode for Attribute | +| full_partial_test.py:8:17:8:41 | ControlFlowNode for Subscript | semmle.label | ControlFlowNode for Subscript | +| full_partial_test.py:10:18:10:27 | ControlFlowNode for user_input | semmle.label | ControlFlowNode for user_input | +| full_partial_test.py:10:18:10:27 | ControlFlowNode for user_input | semmle.label | ControlFlowNode for user_input | +| full_partial_test.py:13:18:13:20 | ControlFlowNode for url | semmle.label | ControlFlowNode for url | +| full_partial_test.py:19:18:19:20 | ControlFlowNode for url | semmle.label | ControlFlowNode for url | +| full_partial_test.py:23:18:23:20 | ControlFlowNode for url | semmle.label | ControlFlowNode for url | +| full_partial_test.py:28:18:28:20 | ControlFlowNode for url | semmle.label | ControlFlowNode for url | +| full_partial_test.py:31:18:31:20 | ControlFlowNode for url | semmle.label | ControlFlowNode for url | +| full_partial_test.py:34:18:34:20 | ControlFlowNode for url | semmle.label | ControlFlowNode for url | +| full_partial_test.py:39:18:39:20 | ControlFlowNode for url | semmle.label | ControlFlowNode for url | +| full_partial_test.py:42:18:42:20 | ControlFlowNode for url | semmle.label | ControlFlowNode for url | +| full_partial_test.py:44:38:44:58 | ControlFlowNode for Tuple | semmle.label | ControlFlowNode for Tuple | +| full_partial_test.py:45:18:45:20 | ControlFlowNode for url | semmle.label | ControlFlowNode for url | +| full_partial_test.py:50:18:50:20 | ControlFlowNode for url | semmle.label | ControlFlowNode for url | +| full_partial_test.py:53:18:53:20 | ControlFlowNode for url | semmle.label | ControlFlowNode for url | +| full_partial_test.py:56:18:56:20 | ControlFlowNode for url | semmle.label | ControlFlowNode for url | +| full_partial_test.py:60:18:60:24 | ControlFlowNode for request | semmle.label | ControlFlowNode for request | +| full_partial_test.py:60:18:60:29 | ControlFlowNode for Attribute | semmle.label | ControlFlowNode for Attribute | +| full_partial_test.py:60:18:60:48 | ControlFlowNode for Subscript | semmle.label | ControlFlowNode for Subscript | +| full_partial_test.py:63:18:63:20 | ControlFlowNode for url | semmle.label | ControlFlowNode for url | +| full_partial_test.py:66:18:66:20 | ControlFlowNode for url | semmle.label | ControlFlowNode for url | +| full_partial_test.py:69:18:69:20 | ControlFlowNode for url | semmle.label | ControlFlowNode for url | +| full_partial_test.py:72:18:72:20 | ControlFlowNode for url | semmle.label | ControlFlowNode for url | +| full_partial_test.py:78:18:78:20 | ControlFlowNode for url | semmle.label | ControlFlowNode for url | +| full_partial_test.py:81:18:81:20 | ControlFlowNode for url | semmle.label | ControlFlowNode for url | +| test_http_client.py:9:19:9:25 | ControlFlowNode for request | semmle.label | ControlFlowNode for request | +| test_http_client.py:9:19:9:25 | ControlFlowNode for request | semmle.label | ControlFlowNode for request | +| test_http_client.py:9:19:9:30 | ControlFlowNode for Attribute | semmle.label | ControlFlowNode for Attribute | +| test_http_client.py:9:19:9:30 | ControlFlowNode for Attribute | semmle.label | ControlFlowNode for Attribute | +| test_http_client.py:9:19:9:38 | ControlFlowNode for Subscript | semmle.label | ControlFlowNode for Subscript | +| test_http_client.py:9:19:9:38 | ControlFlowNode for Subscript | semmle.label | ControlFlowNode for Subscript | +| test_http_client.py:10:19:10:25 | ControlFlowNode for request | semmle.label | ControlFlowNode for request | +| test_http_client.py:10:19:10:25 | ControlFlowNode for request | semmle.label | ControlFlowNode for request | +| test_http_client.py:10:19:10:30 | ControlFlowNode for Attribute | semmle.label | ControlFlowNode for Attribute | +| test_http_client.py:10:19:10:30 | ControlFlowNode for Attribute | semmle.label | ControlFlowNode for Attribute | +| test_http_client.py:10:19:10:38 | ControlFlowNode for Subscript | semmle.label | ControlFlowNode for Subscript | +| test_http_client.py:10:19:10:38 | ControlFlowNode for Subscript | semmle.label | ControlFlowNode for Subscript | +| test_http_client.py:11:18:11:24 | ControlFlowNode for request | semmle.label | ControlFlowNode for request | +| test_http_client.py:11:18:11:29 | ControlFlowNode for Attribute | semmle.label | ControlFlowNode for Attribute | +| test_http_client.py:11:18:11:48 | ControlFlowNode for Subscript | semmle.label | ControlFlowNode for Subscript | +| test_http_client.py:13:27:13:37 | ControlFlowNode for unsafe_host | semmle.label | ControlFlowNode for unsafe_host | +| test_http_client.py:13:27:13:37 | ControlFlowNode for unsafe_host | semmle.label | ControlFlowNode for unsafe_host | +| test_http_client.py:14:25:14:35 | ControlFlowNode for unsafe_path | semmle.label | ControlFlowNode for unsafe_path | +| test_http_client.py:14:25:14:35 | ControlFlowNode for unsafe_path | semmle.label | ControlFlowNode for unsafe_path | +| test_http_client.py:17:27:17:37 | ControlFlowNode for unsafe_host | semmle.label | ControlFlowNode for unsafe_host | +| test_http_client.py:17:27:17:37 | ControlFlowNode for unsafe_host | semmle.label | ControlFlowNode for unsafe_host | +| test_http_client.py:21:25:21:35 | ControlFlowNode for unsafe_path | semmle.label | ControlFlowNode for unsafe_path | +| test_http_client.py:21:25:21:35 | ControlFlowNode for unsafe_path | semmle.label | ControlFlowNode for unsafe_path | +| test_http_client.py:25:25:25:28 | ControlFlowNode for path | semmle.label | ControlFlowNode for path | +| test_http_client.py:29:25:29:28 | ControlFlowNode for path | semmle.label | ControlFlowNode for path | +| test_requests.py:6:18:6:24 | ControlFlowNode for request | semmle.label | ControlFlowNode for request | +| test_requests.py:6:18:6:24 | ControlFlowNode for request | semmle.label | ControlFlowNode for request | +| test_requests.py:6:18:6:29 | ControlFlowNode for Attribute | semmle.label | ControlFlowNode for Attribute | +| test_requests.py:6:18:6:29 | ControlFlowNode for Attribute | semmle.label | ControlFlowNode for Attribute | +| test_requests.py:6:18:6:48 | ControlFlowNode for Subscript | semmle.label | ControlFlowNode for Subscript | +| test_requests.py:6:18:6:48 | ControlFlowNode for Subscript | semmle.label | ControlFlowNode for Subscript | +| test_requests.py:8:18:8:27 | ControlFlowNode for user_input | semmle.label | ControlFlowNode for user_input | +| test_requests.py:8:18:8:27 | ControlFlowNode for user_input | semmle.label | ControlFlowNode for user_input | +subpaths +#select +| full_partial_test.py:10:18:10:27 | ControlFlowNode for user_input | full_partial_test.py:7:18:7:24 | ControlFlowNode for request | full_partial_test.py:10:18:10:27 | ControlFlowNode for user_input | The full URL of this request depends on $@. | full_partial_test.py:7:18:7:24 | ControlFlowNode for request | a user-provided value | +| test_http_client.py:13:27:13:37 | ControlFlowNode for unsafe_host | test_http_client.py:9:19:9:25 | ControlFlowNode for request | test_http_client.py:13:27:13:37 | ControlFlowNode for unsafe_host | The full URL of this request depends on $@. | test_http_client.py:9:19:9:25 | ControlFlowNode for request | a user-provided value | +| test_http_client.py:14:25:14:35 | ControlFlowNode for unsafe_path | test_http_client.py:9:19:9:25 | ControlFlowNode for request | test_http_client.py:14:25:14:35 | ControlFlowNode for unsafe_path | The full URL of this request depends on $@. | test_http_client.py:9:19:9:25 | ControlFlowNode for request | a user-provided value | +| test_http_client.py:14:25:14:35 | ControlFlowNode for unsafe_path | test_http_client.py:10:19:10:25 | ControlFlowNode for request | test_http_client.py:14:25:14:35 | ControlFlowNode for unsafe_path | The full URL of this request depends on $@. | test_http_client.py:10:19:10:25 | ControlFlowNode for request | a user-provided value | +| test_http_client.py:17:27:17:37 | ControlFlowNode for unsafe_host | test_http_client.py:9:19:9:25 | ControlFlowNode for request | test_http_client.py:17:27:17:37 | ControlFlowNode for unsafe_host | The full URL of this request depends on $@. | test_http_client.py:9:19:9:25 | ControlFlowNode for request | a user-provided value | +| test_http_client.py:21:25:21:35 | ControlFlowNode for unsafe_path | test_http_client.py:9:19:9:25 | ControlFlowNode for request | test_http_client.py:21:25:21:35 | ControlFlowNode for unsafe_path | The full URL of this request depends on $@. | test_http_client.py:9:19:9:25 | ControlFlowNode for request | a user-provided value | +| test_http_client.py:21:25:21:35 | ControlFlowNode for unsafe_path | test_http_client.py:10:19:10:25 | ControlFlowNode for request | test_http_client.py:21:25:21:35 | ControlFlowNode for unsafe_path | The full URL of this request depends on $@. | test_http_client.py:10:19:10:25 | ControlFlowNode for request | a user-provided value | +| test_requests.py:8:18:8:27 | ControlFlowNode for user_input | test_requests.py:6:18:6:24 | ControlFlowNode for request | test_requests.py:8:18:8:27 | ControlFlowNode for user_input | The full URL of this request depends on $@. | test_requests.py:6:18:6:24 | ControlFlowNode for request | a user-provided value | diff --git a/python/ql/test/query-tests/Security/CWE-918-ServerSideRequestForgery/FullServerSideRequestForgery.qlref b/python/ql/test/query-tests/Security/CWE-918-ServerSideRequestForgery/FullServerSideRequestForgery.qlref new file mode 100644 index 00000000000..50d53b5f47e --- /dev/null +++ b/python/ql/test/query-tests/Security/CWE-918-ServerSideRequestForgery/FullServerSideRequestForgery.qlref @@ -0,0 +1 @@ +Security/CWE-918/FullServerSideRequestForgery.ql diff --git a/python/ql/test/query-tests/Security/CWE-918-ServerSideRequestForgery/PartialServerSideRequestForgery.expected b/python/ql/test/query-tests/Security/CWE-918-ServerSideRequestForgery/PartialServerSideRequestForgery.expected new file mode 100644 index 00000000000..26485e9b6a8 --- /dev/null +++ b/python/ql/test/query-tests/Security/CWE-918-ServerSideRequestForgery/PartialServerSideRequestForgery.expected @@ -0,0 +1,167 @@ +edges +| full_partial_test.py:7:18:7:24 | ControlFlowNode for request | full_partial_test.py:7:18:7:29 | ControlFlowNode for Attribute | +| full_partial_test.py:7:18:7:24 | ControlFlowNode for request | full_partial_test.py:7:18:7:29 | ControlFlowNode for Attribute | +| full_partial_test.py:7:18:7:24 | ControlFlowNode for request | full_partial_test.py:8:17:8:23 | ControlFlowNode for request | +| full_partial_test.py:7:18:7:24 | ControlFlowNode for request | full_partial_test.py:8:17:8:28 | ControlFlowNode for Attribute | +| full_partial_test.py:7:18:7:29 | ControlFlowNode for Attribute | full_partial_test.py:7:18:7:48 | ControlFlowNode for Subscript | +| full_partial_test.py:7:18:7:29 | ControlFlowNode for Attribute | full_partial_test.py:7:18:7:48 | ControlFlowNode for Subscript | +| full_partial_test.py:7:18:7:48 | ControlFlowNode for Subscript | full_partial_test.py:10:18:10:27 | ControlFlowNode for user_input | +| full_partial_test.py:7:18:7:48 | ControlFlowNode for Subscript | full_partial_test.py:10:18:10:27 | ControlFlowNode for user_input | +| full_partial_test.py:7:18:7:48 | ControlFlowNode for Subscript | full_partial_test.py:13:18:13:20 | ControlFlowNode for url | +| full_partial_test.py:7:18:7:48 | ControlFlowNode for Subscript | full_partial_test.py:19:18:19:20 | ControlFlowNode for url | +| full_partial_test.py:7:18:7:48 | ControlFlowNode for Subscript | full_partial_test.py:23:18:23:20 | ControlFlowNode for url | +| full_partial_test.py:7:18:7:48 | ControlFlowNode for Subscript | full_partial_test.py:28:18:28:20 | ControlFlowNode for url | +| full_partial_test.py:7:18:7:48 | ControlFlowNode for Subscript | full_partial_test.py:31:18:31:20 | ControlFlowNode for url | +| full_partial_test.py:7:18:7:48 | ControlFlowNode for Subscript | full_partial_test.py:34:18:34:20 | ControlFlowNode for url | +| full_partial_test.py:7:18:7:48 | ControlFlowNode for Subscript | full_partial_test.py:39:18:39:20 | ControlFlowNode for url | +| full_partial_test.py:7:18:7:48 | ControlFlowNode for Subscript | full_partial_test.py:42:18:42:20 | ControlFlowNode for url | +| full_partial_test.py:7:18:7:48 | ControlFlowNode for Subscript | full_partial_test.py:44:38:44:58 | ControlFlowNode for Tuple | +| full_partial_test.py:7:18:7:48 | ControlFlowNode for Subscript | full_partial_test.py:50:18:50:20 | ControlFlowNode for url | +| full_partial_test.py:7:18:7:48 | ControlFlowNode for Subscript | full_partial_test.py:53:18:53:20 | ControlFlowNode for url | +| full_partial_test.py:7:18:7:48 | ControlFlowNode for Subscript | full_partial_test.py:56:18:56:20 | ControlFlowNode for url | +| full_partial_test.py:8:17:8:23 | ControlFlowNode for request | full_partial_test.py:8:17:8:28 | ControlFlowNode for Attribute | +| full_partial_test.py:8:17:8:28 | ControlFlowNode for Attribute | full_partial_test.py:8:17:8:41 | ControlFlowNode for Subscript | +| full_partial_test.py:8:17:8:41 | ControlFlowNode for Subscript | full_partial_test.py:23:18:23:20 | ControlFlowNode for url | +| full_partial_test.py:8:17:8:41 | ControlFlowNode for Subscript | full_partial_test.py:34:18:34:20 | ControlFlowNode for url | +| full_partial_test.py:8:17:8:41 | ControlFlowNode for Subscript | full_partial_test.py:44:38:44:58 | ControlFlowNode for Tuple | +| full_partial_test.py:8:17:8:41 | ControlFlowNode for Subscript | full_partial_test.py:56:18:56:20 | ControlFlowNode for url | +| full_partial_test.py:44:38:44:58 | ControlFlowNode for Tuple | full_partial_test.py:45:18:45:20 | ControlFlowNode for url | +| full_partial_test.py:60:18:60:24 | ControlFlowNode for request | full_partial_test.py:60:18:60:29 | ControlFlowNode for Attribute | +| full_partial_test.py:60:18:60:29 | ControlFlowNode for Attribute | full_partial_test.py:60:18:60:48 | ControlFlowNode for Subscript | +| full_partial_test.py:60:18:60:48 | ControlFlowNode for Subscript | full_partial_test.py:63:18:63:20 | ControlFlowNode for url | +| full_partial_test.py:60:18:60:48 | ControlFlowNode for Subscript | full_partial_test.py:66:18:66:20 | ControlFlowNode for url | +| full_partial_test.py:60:18:60:48 | ControlFlowNode for Subscript | full_partial_test.py:69:18:69:20 | ControlFlowNode for url | +| full_partial_test.py:60:18:60:48 | ControlFlowNode for Subscript | full_partial_test.py:72:18:72:20 | ControlFlowNode for url | +| full_partial_test.py:60:18:60:48 | ControlFlowNode for Subscript | full_partial_test.py:78:18:78:20 | ControlFlowNode for url | +| full_partial_test.py:60:18:60:48 | ControlFlowNode for Subscript | full_partial_test.py:81:18:81:20 | ControlFlowNode for url | +| test_http_client.py:9:19:9:25 | ControlFlowNode for request | test_http_client.py:9:19:9:30 | ControlFlowNode for Attribute | +| test_http_client.py:9:19:9:25 | ControlFlowNode for request | test_http_client.py:9:19:9:30 | ControlFlowNode for Attribute | +| test_http_client.py:9:19:9:25 | ControlFlowNode for request | test_http_client.py:10:19:10:25 | ControlFlowNode for request | +| test_http_client.py:9:19:9:25 | ControlFlowNode for request | test_http_client.py:10:19:10:25 | ControlFlowNode for request | +| test_http_client.py:9:19:9:25 | ControlFlowNode for request | test_http_client.py:10:19:10:30 | ControlFlowNode for Attribute | +| test_http_client.py:9:19:9:25 | ControlFlowNode for request | test_http_client.py:10:19:10:30 | ControlFlowNode for Attribute | +| test_http_client.py:9:19:9:25 | ControlFlowNode for request | test_http_client.py:11:18:11:24 | ControlFlowNode for request | +| test_http_client.py:9:19:9:25 | ControlFlowNode for request | test_http_client.py:11:18:11:29 | ControlFlowNode for Attribute | +| test_http_client.py:9:19:9:30 | ControlFlowNode for Attribute | test_http_client.py:9:19:9:38 | ControlFlowNode for Subscript | +| test_http_client.py:9:19:9:30 | ControlFlowNode for Attribute | test_http_client.py:9:19:9:38 | ControlFlowNode for Subscript | +| test_http_client.py:9:19:9:38 | ControlFlowNode for Subscript | test_http_client.py:13:27:13:37 | ControlFlowNode for unsafe_host | +| test_http_client.py:9:19:9:38 | ControlFlowNode for Subscript | test_http_client.py:13:27:13:37 | ControlFlowNode for unsafe_host | +| test_http_client.py:9:19:9:38 | ControlFlowNode for Subscript | test_http_client.py:17:27:17:37 | ControlFlowNode for unsafe_host | +| test_http_client.py:9:19:9:38 | ControlFlowNode for Subscript | test_http_client.py:17:27:17:37 | ControlFlowNode for unsafe_host | +| test_http_client.py:10:19:10:25 | ControlFlowNode for request | test_http_client.py:10:19:10:30 | ControlFlowNode for Attribute | +| test_http_client.py:10:19:10:25 | ControlFlowNode for request | test_http_client.py:10:19:10:30 | ControlFlowNode for Attribute | +| test_http_client.py:10:19:10:25 | ControlFlowNode for request | test_http_client.py:11:18:11:24 | ControlFlowNode for request | +| test_http_client.py:10:19:10:25 | ControlFlowNode for request | test_http_client.py:11:18:11:29 | ControlFlowNode for Attribute | +| test_http_client.py:10:19:10:30 | ControlFlowNode for Attribute | test_http_client.py:10:19:10:38 | ControlFlowNode for Subscript | +| test_http_client.py:10:19:10:30 | ControlFlowNode for Attribute | test_http_client.py:10:19:10:38 | ControlFlowNode for Subscript | +| test_http_client.py:10:19:10:38 | ControlFlowNode for Subscript | test_http_client.py:14:25:14:35 | ControlFlowNode for unsafe_path | +| test_http_client.py:10:19:10:38 | ControlFlowNode for Subscript | test_http_client.py:14:25:14:35 | ControlFlowNode for unsafe_path | +| test_http_client.py:10:19:10:38 | ControlFlowNode for Subscript | test_http_client.py:21:25:21:35 | ControlFlowNode for unsafe_path | +| test_http_client.py:10:19:10:38 | ControlFlowNode for Subscript | test_http_client.py:21:25:21:35 | ControlFlowNode for unsafe_path | +| test_http_client.py:11:18:11:24 | ControlFlowNode for request | test_http_client.py:11:18:11:29 | ControlFlowNode for Attribute | +| test_http_client.py:11:18:11:29 | ControlFlowNode for Attribute | test_http_client.py:11:18:11:48 | ControlFlowNode for Subscript | +| test_http_client.py:11:18:11:48 | ControlFlowNode for Subscript | test_http_client.py:25:25:25:28 | ControlFlowNode for path | +| test_http_client.py:11:18:11:48 | ControlFlowNode for Subscript | test_http_client.py:29:25:29:28 | ControlFlowNode for path | +| test_requests.py:6:18:6:24 | ControlFlowNode for request | test_requests.py:6:18:6:29 | ControlFlowNode for Attribute | +| test_requests.py:6:18:6:24 | ControlFlowNode for request | test_requests.py:6:18:6:29 | ControlFlowNode for Attribute | +| test_requests.py:6:18:6:29 | ControlFlowNode for Attribute | test_requests.py:6:18:6:48 | ControlFlowNode for Subscript | +| test_requests.py:6:18:6:29 | ControlFlowNode for Attribute | test_requests.py:6:18:6:48 | ControlFlowNode for Subscript | +| test_requests.py:6:18:6:48 | ControlFlowNode for Subscript | test_requests.py:8:18:8:27 | ControlFlowNode for user_input | +| test_requests.py:6:18:6:48 | ControlFlowNode for Subscript | test_requests.py:8:18:8:27 | ControlFlowNode for user_input | +nodes +| full_partial_test.py:7:18:7:24 | ControlFlowNode for request | semmle.label | ControlFlowNode for request | +| full_partial_test.py:7:18:7:24 | ControlFlowNode for request | semmle.label | ControlFlowNode for request | +| full_partial_test.py:7:18:7:29 | ControlFlowNode for Attribute | semmle.label | ControlFlowNode for Attribute | +| full_partial_test.py:7:18:7:29 | ControlFlowNode for Attribute | semmle.label | ControlFlowNode for Attribute | +| full_partial_test.py:7:18:7:48 | ControlFlowNode for Subscript | semmle.label | ControlFlowNode for Subscript | +| full_partial_test.py:7:18:7:48 | ControlFlowNode for Subscript | semmle.label | ControlFlowNode for Subscript | +| full_partial_test.py:8:17:8:23 | ControlFlowNode for request | semmle.label | ControlFlowNode for request | +| full_partial_test.py:8:17:8:28 | ControlFlowNode for Attribute | semmle.label | ControlFlowNode for Attribute | +| full_partial_test.py:8:17:8:41 | ControlFlowNode for Subscript | semmle.label | ControlFlowNode for Subscript | +| full_partial_test.py:10:18:10:27 | ControlFlowNode for user_input | semmle.label | ControlFlowNode for user_input | +| full_partial_test.py:10:18:10:27 | ControlFlowNode for user_input | semmle.label | ControlFlowNode for user_input | +| full_partial_test.py:13:18:13:20 | ControlFlowNode for url | semmle.label | ControlFlowNode for url | +| full_partial_test.py:19:18:19:20 | ControlFlowNode for url | semmle.label | ControlFlowNode for url | +| full_partial_test.py:23:18:23:20 | ControlFlowNode for url | semmle.label | ControlFlowNode for url | +| full_partial_test.py:28:18:28:20 | ControlFlowNode for url | semmle.label | ControlFlowNode for url | +| full_partial_test.py:31:18:31:20 | ControlFlowNode for url | semmle.label | ControlFlowNode for url | +| full_partial_test.py:34:18:34:20 | ControlFlowNode for url | semmle.label | ControlFlowNode for url | +| full_partial_test.py:39:18:39:20 | ControlFlowNode for url | semmle.label | ControlFlowNode for url | +| full_partial_test.py:42:18:42:20 | ControlFlowNode for url | semmle.label | ControlFlowNode for url | +| full_partial_test.py:44:38:44:58 | ControlFlowNode for Tuple | semmle.label | ControlFlowNode for Tuple | +| full_partial_test.py:45:18:45:20 | ControlFlowNode for url | semmle.label | ControlFlowNode for url | +| full_partial_test.py:50:18:50:20 | ControlFlowNode for url | semmle.label | ControlFlowNode for url | +| full_partial_test.py:53:18:53:20 | ControlFlowNode for url | semmle.label | ControlFlowNode for url | +| full_partial_test.py:56:18:56:20 | ControlFlowNode for url | semmle.label | ControlFlowNode for url | +| full_partial_test.py:60:18:60:24 | ControlFlowNode for request | semmle.label | ControlFlowNode for request | +| full_partial_test.py:60:18:60:29 | ControlFlowNode for Attribute | semmle.label | ControlFlowNode for Attribute | +| full_partial_test.py:60:18:60:48 | ControlFlowNode for Subscript | semmle.label | ControlFlowNode for Subscript | +| full_partial_test.py:63:18:63:20 | ControlFlowNode for url | semmle.label | ControlFlowNode for url | +| full_partial_test.py:66:18:66:20 | ControlFlowNode for url | semmle.label | ControlFlowNode for url | +| full_partial_test.py:69:18:69:20 | ControlFlowNode for url | semmle.label | ControlFlowNode for url | +| full_partial_test.py:72:18:72:20 | ControlFlowNode for url | semmle.label | ControlFlowNode for url | +| full_partial_test.py:78:18:78:20 | ControlFlowNode for url | semmle.label | ControlFlowNode for url | +| full_partial_test.py:81:18:81:20 | ControlFlowNode for url | semmle.label | ControlFlowNode for url | +| test_http_client.py:9:19:9:25 | ControlFlowNode for request | semmle.label | ControlFlowNode for request | +| test_http_client.py:9:19:9:25 | ControlFlowNode for request | semmle.label | ControlFlowNode for request | +| test_http_client.py:9:19:9:30 | ControlFlowNode for Attribute | semmle.label | ControlFlowNode for Attribute | +| test_http_client.py:9:19:9:30 | ControlFlowNode for Attribute | semmle.label | ControlFlowNode for Attribute | +| test_http_client.py:9:19:9:38 | ControlFlowNode for Subscript | semmle.label | ControlFlowNode for Subscript | +| test_http_client.py:9:19:9:38 | ControlFlowNode for Subscript | semmle.label | ControlFlowNode for Subscript | +| test_http_client.py:10:19:10:25 | ControlFlowNode for request | semmle.label | ControlFlowNode for request | +| test_http_client.py:10:19:10:25 | ControlFlowNode for request | semmle.label | ControlFlowNode for request | +| test_http_client.py:10:19:10:30 | ControlFlowNode for Attribute | semmle.label | ControlFlowNode for Attribute | +| test_http_client.py:10:19:10:30 | ControlFlowNode for Attribute | semmle.label | ControlFlowNode for Attribute | +| test_http_client.py:10:19:10:38 | ControlFlowNode for Subscript | semmle.label | ControlFlowNode for Subscript | +| test_http_client.py:10:19:10:38 | ControlFlowNode for Subscript | semmle.label | ControlFlowNode for Subscript | +| test_http_client.py:11:18:11:24 | ControlFlowNode for request | semmle.label | ControlFlowNode for request | +| test_http_client.py:11:18:11:29 | ControlFlowNode for Attribute | semmle.label | ControlFlowNode for Attribute | +| test_http_client.py:11:18:11:48 | ControlFlowNode for Subscript | semmle.label | ControlFlowNode for Subscript | +| test_http_client.py:13:27:13:37 | ControlFlowNode for unsafe_host | semmle.label | ControlFlowNode for unsafe_host | +| test_http_client.py:13:27:13:37 | ControlFlowNode for unsafe_host | semmle.label | ControlFlowNode for unsafe_host | +| test_http_client.py:14:25:14:35 | ControlFlowNode for unsafe_path | semmle.label | ControlFlowNode for unsafe_path | +| test_http_client.py:14:25:14:35 | ControlFlowNode for unsafe_path | semmle.label | ControlFlowNode for unsafe_path | +| test_http_client.py:17:27:17:37 | ControlFlowNode for unsafe_host | semmle.label | ControlFlowNode for unsafe_host | +| test_http_client.py:17:27:17:37 | ControlFlowNode for unsafe_host | semmle.label | ControlFlowNode for unsafe_host | +| test_http_client.py:21:25:21:35 | ControlFlowNode for unsafe_path | semmle.label | ControlFlowNode for unsafe_path | +| test_http_client.py:21:25:21:35 | ControlFlowNode for unsafe_path | semmle.label | ControlFlowNode for unsafe_path | +| test_http_client.py:25:25:25:28 | ControlFlowNode for path | semmle.label | ControlFlowNode for path | +| test_http_client.py:29:25:29:28 | ControlFlowNode for path | semmle.label | ControlFlowNode for path | +| test_requests.py:6:18:6:24 | ControlFlowNode for request | semmle.label | ControlFlowNode for request | +| test_requests.py:6:18:6:24 | ControlFlowNode for request | semmle.label | ControlFlowNode for request | +| test_requests.py:6:18:6:29 | ControlFlowNode for Attribute | semmle.label | ControlFlowNode for Attribute | +| test_requests.py:6:18:6:29 | ControlFlowNode for Attribute | semmle.label | ControlFlowNode for Attribute | +| test_requests.py:6:18:6:48 | ControlFlowNode for Subscript | semmle.label | ControlFlowNode for Subscript | +| test_requests.py:6:18:6:48 | ControlFlowNode for Subscript | semmle.label | ControlFlowNode for Subscript | +| test_requests.py:8:18:8:27 | ControlFlowNode for user_input | semmle.label | ControlFlowNode for user_input | +| test_requests.py:8:18:8:27 | ControlFlowNode for user_input | semmle.label | ControlFlowNode for user_input | +subpaths +#select +| full_partial_test.py:13:18:13:20 | ControlFlowNode for url | full_partial_test.py:7:18:7:24 | ControlFlowNode for request | full_partial_test.py:13:18:13:20 | ControlFlowNode for url | Part of the URL of this request depends on $@. | full_partial_test.py:7:18:7:24 | ControlFlowNode for request | a user-provided value | +| full_partial_test.py:19:18:19:20 | ControlFlowNode for url | full_partial_test.py:7:18:7:24 | ControlFlowNode for request | full_partial_test.py:19:18:19:20 | ControlFlowNode for url | Part of the URL of this request depends on $@. | full_partial_test.py:7:18:7:24 | ControlFlowNode for request | a user-provided value | +| full_partial_test.py:23:18:23:20 | ControlFlowNode for url | full_partial_test.py:7:18:7:24 | ControlFlowNode for request | full_partial_test.py:23:18:23:20 | ControlFlowNode for url | Part of the URL of this request depends on $@. | full_partial_test.py:7:18:7:24 | ControlFlowNode for request | a user-provided value | +| full_partial_test.py:23:18:23:20 | ControlFlowNode for url | full_partial_test.py:8:17:8:23 | ControlFlowNode for request | full_partial_test.py:23:18:23:20 | ControlFlowNode for url | Part of the URL of this request depends on $@. | full_partial_test.py:8:17:8:23 | ControlFlowNode for request | a user-provided value | +| full_partial_test.py:28:18:28:20 | ControlFlowNode for url | full_partial_test.py:7:18:7:24 | ControlFlowNode for request | full_partial_test.py:28:18:28:20 | ControlFlowNode for url | Part of the URL of this request depends on $@. | full_partial_test.py:7:18:7:24 | ControlFlowNode for request | a user-provided value | +| full_partial_test.py:31:18:31:20 | ControlFlowNode for url | full_partial_test.py:7:18:7:24 | ControlFlowNode for request | full_partial_test.py:31:18:31:20 | ControlFlowNode for url | Part of the URL of this request depends on $@. | full_partial_test.py:7:18:7:24 | ControlFlowNode for request | a user-provided value | +| full_partial_test.py:34:18:34:20 | ControlFlowNode for url | full_partial_test.py:7:18:7:24 | ControlFlowNode for request | full_partial_test.py:34:18:34:20 | ControlFlowNode for url | Part of the URL of this request depends on $@. | full_partial_test.py:7:18:7:24 | ControlFlowNode for request | a user-provided value | +| full_partial_test.py:34:18:34:20 | ControlFlowNode for url | full_partial_test.py:8:17:8:23 | ControlFlowNode for request | full_partial_test.py:34:18:34:20 | ControlFlowNode for url | Part of the URL of this request depends on $@. | full_partial_test.py:8:17:8:23 | ControlFlowNode for request | a user-provided value | +| full_partial_test.py:39:18:39:20 | ControlFlowNode for url | full_partial_test.py:7:18:7:24 | ControlFlowNode for request | full_partial_test.py:39:18:39:20 | ControlFlowNode for url | Part of the URL of this request depends on $@. | full_partial_test.py:7:18:7:24 | ControlFlowNode for request | a user-provided value | +| full_partial_test.py:42:18:42:20 | ControlFlowNode for url | full_partial_test.py:7:18:7:24 | ControlFlowNode for request | full_partial_test.py:42:18:42:20 | ControlFlowNode for url | Part of the URL of this request depends on $@. | full_partial_test.py:7:18:7:24 | ControlFlowNode for request | a user-provided value | +| full_partial_test.py:45:18:45:20 | ControlFlowNode for url | full_partial_test.py:7:18:7:24 | ControlFlowNode for request | full_partial_test.py:45:18:45:20 | ControlFlowNode for url | Part of the URL of this request depends on $@. | full_partial_test.py:7:18:7:24 | ControlFlowNode for request | a user-provided value | +| full_partial_test.py:45:18:45:20 | ControlFlowNode for url | full_partial_test.py:8:17:8:23 | ControlFlowNode for request | full_partial_test.py:45:18:45:20 | ControlFlowNode for url | Part of the URL of this request depends on $@. | full_partial_test.py:8:17:8:23 | ControlFlowNode for request | a user-provided value | +| full_partial_test.py:50:18:50:20 | ControlFlowNode for url | full_partial_test.py:7:18:7:24 | ControlFlowNode for request | full_partial_test.py:50:18:50:20 | ControlFlowNode for url | Part of the URL of this request depends on $@. | full_partial_test.py:7:18:7:24 | ControlFlowNode for request | a user-provided value | +| full_partial_test.py:53:18:53:20 | ControlFlowNode for url | full_partial_test.py:7:18:7:24 | ControlFlowNode for request | full_partial_test.py:53:18:53:20 | ControlFlowNode for url | Part of the URL of this request depends on $@. | full_partial_test.py:7:18:7:24 | ControlFlowNode for request | a user-provided value | +| full_partial_test.py:56:18:56:20 | ControlFlowNode for url | full_partial_test.py:7:18:7:24 | ControlFlowNode for request | full_partial_test.py:56:18:56:20 | ControlFlowNode for url | Part of the URL of this request depends on $@. | full_partial_test.py:7:18:7:24 | ControlFlowNode for request | a user-provided value | +| full_partial_test.py:56:18:56:20 | ControlFlowNode for url | full_partial_test.py:8:17:8:23 | ControlFlowNode for request | full_partial_test.py:56:18:56:20 | ControlFlowNode for url | Part of the URL of this request depends on $@. | full_partial_test.py:8:17:8:23 | ControlFlowNode for request | a user-provided value | +| full_partial_test.py:63:18:63:20 | ControlFlowNode for url | full_partial_test.py:60:18:60:24 | ControlFlowNode for request | full_partial_test.py:63:18:63:20 | ControlFlowNode for url | Part of the URL of this request depends on $@. | full_partial_test.py:60:18:60:24 | ControlFlowNode for request | a user-provided value | +| full_partial_test.py:66:18:66:20 | ControlFlowNode for url | full_partial_test.py:60:18:60:24 | ControlFlowNode for request | full_partial_test.py:66:18:66:20 | ControlFlowNode for url | Part of the URL of this request depends on $@. | full_partial_test.py:60:18:60:24 | ControlFlowNode for request | a user-provided value | +| full_partial_test.py:69:18:69:20 | ControlFlowNode for url | full_partial_test.py:60:18:60:24 | ControlFlowNode for request | full_partial_test.py:69:18:69:20 | ControlFlowNode for url | Part of the URL of this request depends on $@. | full_partial_test.py:60:18:60:24 | ControlFlowNode for request | a user-provided value | +| full_partial_test.py:72:18:72:20 | ControlFlowNode for url | full_partial_test.py:60:18:60:24 | ControlFlowNode for request | full_partial_test.py:72:18:72:20 | ControlFlowNode for url | Part of the URL of this request depends on $@. | full_partial_test.py:60:18:60:24 | ControlFlowNode for request | a user-provided value | +| full_partial_test.py:78:18:78:20 | ControlFlowNode for url | full_partial_test.py:60:18:60:24 | ControlFlowNode for request | full_partial_test.py:78:18:78:20 | ControlFlowNode for url | Part of the URL of this request depends on $@. | full_partial_test.py:60:18:60:24 | ControlFlowNode for request | a user-provided value | +| full_partial_test.py:81:18:81:20 | ControlFlowNode for url | full_partial_test.py:60:18:60:24 | ControlFlowNode for request | full_partial_test.py:81:18:81:20 | ControlFlowNode for url | Part of the URL of this request depends on $@. | full_partial_test.py:60:18:60:24 | ControlFlowNode for request | a user-provided value | +| test_http_client.py:25:25:25:28 | ControlFlowNode for path | test_http_client.py:9:19:9:25 | ControlFlowNode for request | test_http_client.py:25:25:25:28 | ControlFlowNode for path | Part of the URL of this request depends on $@. | test_http_client.py:9:19:9:25 | ControlFlowNode for request | a user-provided value | +| test_http_client.py:25:25:25:28 | ControlFlowNode for path | test_http_client.py:10:19:10:25 | ControlFlowNode for request | test_http_client.py:25:25:25:28 | ControlFlowNode for path | Part of the URL of this request depends on $@. | test_http_client.py:10:19:10:25 | ControlFlowNode for request | a user-provided value | +| test_http_client.py:25:25:25:28 | ControlFlowNode for path | test_http_client.py:11:18:11:24 | ControlFlowNode for request | test_http_client.py:25:25:25:28 | ControlFlowNode for path | Part of the URL of this request depends on $@. | test_http_client.py:11:18:11:24 | ControlFlowNode for request | a user-provided value | +| test_http_client.py:29:25:29:28 | ControlFlowNode for path | test_http_client.py:9:19:9:25 | ControlFlowNode for request | test_http_client.py:29:25:29:28 | ControlFlowNode for path | Part of the URL of this request depends on $@. | test_http_client.py:9:19:9:25 | ControlFlowNode for request | a user-provided value | +| test_http_client.py:29:25:29:28 | ControlFlowNode for path | test_http_client.py:10:19:10:25 | ControlFlowNode for request | test_http_client.py:29:25:29:28 | ControlFlowNode for path | Part of the URL of this request depends on $@. | test_http_client.py:10:19:10:25 | ControlFlowNode for request | a user-provided value | +| test_http_client.py:29:25:29:28 | ControlFlowNode for path | test_http_client.py:11:18:11:24 | ControlFlowNode for request | test_http_client.py:29:25:29:28 | ControlFlowNode for path | Part of the URL of this request depends on $@. | test_http_client.py:11:18:11:24 | ControlFlowNode for request | a user-provided value | diff --git a/python/ql/test/query-tests/Security/CWE-918-ServerSideRequestForgery/PartialServerSideRequestForgery.qlref b/python/ql/test/query-tests/Security/CWE-918-ServerSideRequestForgery/PartialServerSideRequestForgery.qlref new file mode 100644 index 00000000000..88de9285e5e --- /dev/null +++ b/python/ql/test/query-tests/Security/CWE-918-ServerSideRequestForgery/PartialServerSideRequestForgery.qlref @@ -0,0 +1 @@ +Security/CWE-918/PartialServerSideRequestForgery.ql diff --git a/python/ql/test/query-tests/Security/CWE-918-ServerSideRequestForgery/full_partial_test.py b/python/ql/test/query-tests/Security/CWE-918-ServerSideRequestForgery/full_partial_test.py new file mode 100644 index 00000000000..4b3dacdb372 --- /dev/null +++ b/python/ql/test/query-tests/Security/CWE-918-ServerSideRequestForgery/full_partial_test.py @@ -0,0 +1,81 @@ +from flask import request + +import requests + + +def full_ssrf(): + user_input = request.args['untrusted_input'] + query_val = request.args['query_val'] + + requests.get(user_input) # NOT OK -- user has full control + + url = "https://" + user_input + requests.get(url) # NOT OK -- user has full control + + # although the path `/foo` is added here, this can be circumvented such that the + # final URL is `https://evil.com/#/foo" -- since the fragment (#) is not sent to the + # server. + url = "https://" + user_input + "/foo" + requests.get(url) # NOT OK -- user has full control + + # this might seem like a dummy test, but it serves to check how our sanitizers work. + url = "https://" + user_input + "/foo?key=" + query_val + requests.get(url) # NOT OK -- user has full control + + + # using .format + url = "https://{}".format(user_input) + requests.get(url) # NOT OK -- user has full control + + url = "https://{}/foo".format(user_input) + requests.get(url) # NOT OK -- user has full control + + url = "https://{}/foo?key={}".format(user_input, query_val) + requests.get(url) # NOT OK -- user has full control + + + # using %-formatting + url = "https://%s" % user_input + requests.get(url) # NOT OK -- user has full control + + url = "https://%s/foo" % user_input + requests.get(url) # NOT OK -- user has full control + + url = "https://%s/foo/key=%s" % (user_input, query_val) + requests.get(url) # NOT OK -- user has full control + + + # using f-strings + url = f"https://{user_input}" + requests.get(url) # NOT OK -- user has full control + + url = f"https://{user_input}/foo" + requests.get(url) # NOT OK -- user has full control + + url = f"https://{user_input}/foo?key={query_val}" + requests.get(url) # NOT OK -- user has full control + + +def partial_ssrf(): + user_input = request.args['untrusted_input'] + + url = "https://example.com/foo?" + user_input + requests.get(url) # NOT OK -- user controls query parameters + + url = "https://example.com/" + user_input + requests.get(url) # NOT OK -- user controls path + + url = "https://example.com/" + user_input + requests.get(url) # NOT OK -- user controls path + + url = "https://example.com/foo#{}".format(user_input) + requests.get(url) # NOT OK -- user contollred fragment + + # this is probably the least interesting one, since it's only the fragment that is + # controlled + + url = "https://example.com/foo#%s" % user_input + requests.get(url) # NOT OK -- user contollred fragment + + url = f"https://example.com/foo#{user_input}" + requests.get(url) # NOT OK -- user only controlled fragment diff --git a/python/ql/test/query-tests/Security/CWE-918-ServerSideRequestForgery/options b/python/ql/test/query-tests/Security/CWE-918-ServerSideRequestForgery/options new file mode 100644 index 00000000000..89369a90996 --- /dev/null +++ b/python/ql/test/query-tests/Security/CWE-918-ServerSideRequestForgery/options @@ -0,0 +1 @@ +semmle-extractor-options: --lang=3 --max-import-depth=1 diff --git a/python/ql/test/query-tests/Security/CWE-918-ServerSideRequestForgery/test_http_client.py b/python/ql/test/query-tests/Security/CWE-918-ServerSideRequestForgery/test_http_client.py new file mode 100644 index 00000000000..52774fec59a --- /dev/null +++ b/python/ql/test/query-tests/Security/CWE-918-ServerSideRequestForgery/test_http_client.py @@ -0,0 +1,29 @@ +from flask import Flask, request + +from http.client import HTTPConnection + +app = Flask(__name__) + +@app.route("/ssrf_test") # $ routeSetup="/ssrf_test" +def ssrf_test(): + unsafe_host = request.args["host"] + unsafe_path = request.args["path"] + user_input = request.args['untrusted_input'] + + conn = HTTPConnection(unsafe_host) + conn.request("GET", unsafe_path) # NOT OK -- user has full control + + # the rest are partial SSRF + conn = HTTPConnection(unsafe_host) + conn.request("GET", "/foo") # NOT OK -- user controlled domain + + conn = HTTPConnection("example.com") + conn.request("GET", unsafe_path) # NOT OK -- user controlled path + + path = "foo?" + user_input + conn = HTTPConnection("example.com") + conn.request("GET", path) # NOT OK -- user controlled query parameters + + path = "foo#" + user_input + conn = HTTPConnection("example.com") + conn.request("GET", path) # NOT OK -- user controlled fragment diff --git a/python/ql/test/query-tests/Security/CWE-918-ServerSideRequestForgery/test_requests.py b/python/ql/test/query-tests/Security/CWE-918-ServerSideRequestForgery/test_requests.py new file mode 100644 index 00000000000..443f5c3b81f --- /dev/null +++ b/python/ql/test/query-tests/Security/CWE-918-ServerSideRequestForgery/test_requests.py @@ -0,0 +1,11 @@ +from flask import request + +import requests + +def ssrf_test(): + user_input = request.args['untrusted_input'] + + requests.get(user_input) # NOT OK -- user has full control + + # since `requests`` always uses complete URLs, it's not interesting to test more of + # the framework directly. See `full_partial_test.py` for different ways to do SSRF.