diff --git a/go/ql/lib/change-notes/2025-10-09-deprecate-sqlinjection-numericorbooleansanitizer.md b/go/ql/lib/change-notes/2025-10-09-deprecate-sqlinjection-numericorbooleansanitizer.md new file mode 100644 index 000000000000..647d9a4332cd --- /dev/null +++ b/go/ql/lib/change-notes/2025-10-09-deprecate-sqlinjection-numericorbooleansanitizer.md @@ -0,0 +1,4 @@ +--- +category: deprecated +--- +* The class `SqlInjection::NumericOrBooleanSanitizer` has been deprecated. Use `SimpleTypeSanitizer` from `semmle.go.security.Sanitizers` instead. diff --git a/go/ql/lib/change-notes/2025-10-09-sanitize-simple-types-request-forgery.md b/go/ql/lib/change-notes/2025-10-09-sanitize-simple-types-request-forgery.md new file mode 100644 index 000000000000..1bbf8c7f88a9 --- /dev/null +++ b/go/ql/lib/change-notes/2025-10-09-sanitize-simple-types-request-forgery.md @@ -0,0 +1,4 @@ +--- +category: minorAnalysis +--- +* The query `go/request-forgery` will no longer report alerts when the user input is of a simple type, like a number or a boolean. diff --git a/go/ql/lib/semmle/go/security/RequestForgeryCustomizations.qll b/go/ql/lib/semmle/go/security/RequestForgeryCustomizations.qll index 1298785b726c..82f9df4b5064 100644 --- a/go/ql/lib/semmle/go/security/RequestForgeryCustomizations.qll +++ b/go/ql/lib/semmle/go/security/RequestForgeryCustomizations.qll @@ -9,6 +9,7 @@ import semmle.go.dataflow.barrierguardutil.RedirectCheckBarrierGuard import semmle.go.dataflow.barrierguardutil.RegexpCheck import semmle.go.dataflow.barrierguardutil.UrlCheck import semmle.go.dataflow.ExternalFlow +private import semmle.go.security.Sanitizers /** Provides classes and predicates for the request forgery query. */ module RequestForgery { @@ -114,6 +115,11 @@ module RequestForgery { * considered a barrier guard for `url`. */ class UrlCheckAsBarrierGuard extends UrlCheckBarrier, Sanitizer { } + + /** + * A simple-typed node, considered a sanitizer for request forgery. + */ + private class DefaultSanitizer extends Sanitizer instanceof SimpleTypeSanitizer { } } /** A sink for request forgery, considered as a sink for safe URL flow. */ diff --git a/go/ql/lib/semmle/go/security/Sanitizers.qll b/go/ql/lib/semmle/go/security/Sanitizers.qll new file mode 100644 index 000000000000..4391e12c25a0 --- /dev/null +++ b/go/ql/lib/semmle/go/security/Sanitizers.qll @@ -0,0 +1,16 @@ +/** + * Classes to represent sanitizers commonly used in dataflow and taint tracking + * configurations. + */ + +import go + +/** + * A node whose type is a simple type unlikely to carry taint, such as a + * numeric or boolean type. + */ +class SimpleTypeSanitizer extends DataFlow::Node { + SimpleTypeSanitizer() { + this.getType() instanceof NumericType or this.getType() instanceof BoolType + } +} diff --git a/go/ql/lib/semmle/go/security/SqlInjectionCustomizations.qll b/go/ql/lib/semmle/go/security/SqlInjectionCustomizations.qll index 0b2f96a92838..f26168ad1d77 100644 --- a/go/ql/lib/semmle/go/security/SqlInjectionCustomizations.qll +++ b/go/ql/lib/semmle/go/security/SqlInjectionCustomizations.qll @@ -4,6 +4,7 @@ */ import go +private import semmle.go.security.Sanitizers /** * Provides extension points for customizing the taint tracking configuration for reasoning about @@ -39,12 +40,11 @@ module SqlInjection { /** A NoSql query, considered as a taint sink for SQL injection. */ class NoSqlQueryAsSink extends Sink instanceof NoSql::Query { } + /** DEPRECATED: Use `SimpleTypeSanitizer` from semmle.go.security.Sanitizers instead. */ + deprecated class NumericOrBooleanSanitizer = SimpleTypeSanitizer; + /** * A numeric- or boolean-typed node, considered a sanitizer for sql injection. */ - class NumericOrBooleanSanitizer extends Sanitizer { - NumericOrBooleanSanitizer() { - this.getType() instanceof NumericType or this.getType() instanceof BoolType - } - } + private class DefaultSanitizer extends Sanitizer instanceof SimpleTypeSanitizer { } } diff --git a/go/ql/test/query-tests/Security/CWE-918/RequestForgery.expected b/go/ql/test/query-tests/Security/CWE-918/RequestForgery.expected index 129613a3f1ab..15b0e179e983 100644 --- a/go/ql/test/query-tests/Security/CWE-918/RequestForgery.expected +++ b/go/ql/test/query-tests/Security/CWE-918/RequestForgery.expected @@ -1,18 +1,18 @@ #select | RequestForgery.go:11:15:11:66 | call to Get | RequestForgery.go:8:12:8:34 | call to FormValue | RequestForgery.go:11:24:11:65 | ...+... | The $@ of this request depends on a $@. | RequestForgery.go:11:24:11:65 | ...+... | URL | RequestForgery.go:8:12:8:34 | call to FormValue | user-provided value | -| tst.go:18:2:18:18 | call to Get | tst.go:10:13:10:35 | call to FormValue | tst.go:18:11:18:17 | tainted | The $@ of this request depends on a $@. | tst.go:18:11:18:17 | tainted | URL | tst.go:10:13:10:35 | call to FormValue | user-provided value | -| tst.go:20:2:20:19 | call to Head | tst.go:10:13:10:35 | call to FormValue | tst.go:20:12:20:18 | tainted | The $@ of this request depends on a $@. | tst.go:20:12:20:18 | tainted | URL | tst.go:10:13:10:35 | call to FormValue | user-provided value | -| tst.go:22:2:22:38 | call to Post | tst.go:10:13:10:35 | call to FormValue | tst.go:22:12:22:18 | tainted | The $@ of this request depends on a $@. | tst.go:22:12:22:18 | tainted | URL | tst.go:10:13:10:35 | call to FormValue | user-provided value | -| tst.go:24:2:24:28 | call to PostForm | tst.go:10:13:10:35 | call to FormValue | tst.go:24:16:24:22 | tainted | The $@ of this request depends on a $@. | tst.go:24:16:24:22 | tainted | URL | tst.go:10:13:10:35 | call to FormValue | user-provided value | -| tst.go:28:2:28:15 | call to Do | tst.go:10:13:10:35 | call to FormValue | tst.go:27:35:27:41 | tainted | The $@ of this request depends on a $@. | tst.go:27:35:27:41 | tainted | URL | tst.go:10:13:10:35 | call to FormValue | user-provided value | -| tst.go:31:2:31:15 | call to Do | tst.go:10:13:10:35 | call to FormValue | tst.go:30:68:30:74 | tainted | The $@ of this request depends on a $@. | tst.go:30:68:30:74 | tainted | URL | tst.go:10:13:10:35 | call to FormValue | user-provided value | -| tst.go:33:2:33:20 | call to Get | tst.go:10:13:10:35 | call to FormValue | tst.go:33:13:33:19 | tainted | The $@ of this request depends on a $@. | tst.go:33:13:33:19 | tainted | URL | tst.go:10:13:10:35 | call to FormValue | user-provided value | -| tst.go:34:2:34:21 | call to Head | tst.go:10:13:10:35 | call to FormValue | tst.go:34:14:34:20 | tainted | The $@ of this request depends on a $@. | tst.go:34:14:34:20 | tainted | URL | tst.go:10:13:10:35 | call to FormValue | user-provided value | -| tst.go:35:2:35:40 | call to Post | tst.go:10:13:10:35 | call to FormValue | tst.go:35:14:35:20 | tainted | The $@ of this request depends on a $@. | tst.go:35:14:35:20 | tainted | URL | tst.go:10:13:10:35 | call to FormValue | user-provided value | -| tst.go:36:2:36:30 | call to PostForm | tst.go:10:13:10:35 | call to FormValue | tst.go:36:18:36:24 | tainted | The $@ of this request depends on a $@. | tst.go:36:18:36:24 | tainted | URL | tst.go:10:13:10:35 | call to FormValue | user-provided value | -| tst.go:38:2:38:30 | call to Get | tst.go:10:13:10:35 | call to FormValue | tst.go:38:11:38:29 | ...+... | The $@ of this request depends on a $@. | tst.go:38:11:38:29 | ...+... | URL | tst.go:10:13:10:35 | call to FormValue | user-provided value | -| tst.go:40:2:40:41 | call to Get | tst.go:10:13:10:35 | call to FormValue | tst.go:40:11:40:40 | ...+... | The $@ of this request depends on a $@. | tst.go:40:11:40:40 | ...+... | URL | tst.go:10:13:10:35 | call to FormValue | user-provided value | -| tst.go:48:2:48:21 | call to Get | tst.go:10:13:10:35 | call to FormValue | tst.go:48:11:48:20 | call to String | The $@ of this request depends on a $@. | tst.go:48:11:48:20 | call to String | URL | tst.go:10:13:10:35 | call to FormValue | user-provided value | +| tst.go:19:2:19:18 | call to Get | tst.go:11:13:11:35 | call to FormValue | tst.go:19:11:19:17 | tainted | The $@ of this request depends on a $@. | tst.go:19:11:19:17 | tainted | URL | tst.go:11:13:11:35 | call to FormValue | user-provided value | +| tst.go:21:2:21:19 | call to Head | tst.go:11:13:11:35 | call to FormValue | tst.go:21:12:21:18 | tainted | The $@ of this request depends on a $@. | tst.go:21:12:21:18 | tainted | URL | tst.go:11:13:11:35 | call to FormValue | user-provided value | +| tst.go:23:2:23:38 | call to Post | tst.go:11:13:11:35 | call to FormValue | tst.go:23:12:23:18 | tainted | The $@ of this request depends on a $@. | tst.go:23:12:23:18 | tainted | URL | tst.go:11:13:11:35 | call to FormValue | user-provided value | +| tst.go:25:2:25:28 | call to PostForm | tst.go:11:13:11:35 | call to FormValue | tst.go:25:16:25:22 | tainted | The $@ of this request depends on a $@. | tst.go:25:16:25:22 | tainted | URL | tst.go:11:13:11:35 | call to FormValue | user-provided value | +| tst.go:29:2:29:15 | call to Do | tst.go:11:13:11:35 | call to FormValue | tst.go:28:35:28:41 | tainted | The $@ of this request depends on a $@. | tst.go:28:35:28:41 | tainted | URL | tst.go:11:13:11:35 | call to FormValue | user-provided value | +| tst.go:32:2:32:15 | call to Do | tst.go:11:13:11:35 | call to FormValue | tst.go:31:68:31:74 | tainted | The $@ of this request depends on a $@. | tst.go:31:68:31:74 | tainted | URL | tst.go:11:13:11:35 | call to FormValue | user-provided value | +| tst.go:34:2:34:20 | call to Get | tst.go:11:13:11:35 | call to FormValue | tst.go:34:13:34:19 | tainted | The $@ of this request depends on a $@. | tst.go:34:13:34:19 | tainted | URL | tst.go:11:13:11:35 | call to FormValue | user-provided value | +| tst.go:35:2:35:21 | call to Head | tst.go:11:13:11:35 | call to FormValue | tst.go:35:14:35:20 | tainted | The $@ of this request depends on a $@. | tst.go:35:14:35:20 | tainted | URL | tst.go:11:13:11:35 | call to FormValue | user-provided value | +| tst.go:36:2:36:40 | call to Post | tst.go:11:13:11:35 | call to FormValue | tst.go:36:14:36:20 | tainted | The $@ of this request depends on a $@. | tst.go:36:14:36:20 | tainted | URL | tst.go:11:13:11:35 | call to FormValue | user-provided value | +| tst.go:37:2:37:30 | call to PostForm | tst.go:11:13:11:35 | call to FormValue | tst.go:37:18:37:24 | tainted | The $@ of this request depends on a $@. | tst.go:37:18:37:24 | tainted | URL | tst.go:11:13:11:35 | call to FormValue | user-provided value | +| tst.go:39:2:39:30 | call to Get | tst.go:11:13:11:35 | call to FormValue | tst.go:39:11:39:29 | ...+... | The $@ of this request depends on a $@. | tst.go:39:11:39:29 | ...+... | URL | tst.go:11:13:11:35 | call to FormValue | user-provided value | +| tst.go:41:2:41:41 | call to Get | tst.go:11:13:11:35 | call to FormValue | tst.go:41:11:41:40 | ...+... | The $@ of this request depends on a $@. | tst.go:41:11:41:40 | ...+... | URL | tst.go:11:13:11:35 | call to FormValue | user-provided value | +| tst.go:49:2:49:21 | call to Get | tst.go:11:13:11:35 | call to FormValue | tst.go:49:11:49:20 | call to String | The $@ of this request depends on a $@. | tst.go:49:11:49:20 | call to String | URL | tst.go:11:13:11:35 | call to FormValue | user-provided value | | websocket.go:65:12:65:53 | call to Dial | websocket.go:60:21:60:31 | call to Referer | websocket.go:65:27:65:40 | untrustedInput | The $@ of this request depends on a $@. | websocket.go:65:27:65:40 | untrustedInput | WebSocket URL | websocket.go:60:21:60:31 | call to Referer | user-provided value | | websocket.go:79:13:79:40 | call to DialConfig | websocket.go:74:21:74:31 | call to Referer | websocket.go:78:36:78:49 | untrustedInput | The $@ of this request depends on a $@. | websocket.go:78:36:78:49 | untrustedInput | WebSocket URL | websocket.go:74:21:74:31 | call to Referer | user-provided value | | websocket.go:91:3:91:50 | call to Dial | websocket.go:88:21:88:31 | call to Referer | websocket.go:91:31:91:44 | untrustedInput | The $@ of this request depends on a $@. | websocket.go:91:31:91:44 | untrustedInput | WebSocket URL | websocket.go:88:21:88:31 | call to Referer | user-provided value | @@ -24,24 +24,24 @@ | websocket.go:204:7:204:29 | call to New | websocket.go:202:21:202:31 | call to Referer | websocket.go:204:15:204:28 | untrustedInput | The $@ of this request depends on a $@. | websocket.go:204:15:204:28 | untrustedInput | WebSocket URL | websocket.go:202:21:202:31 | call to Referer | user-provided value | edges | RequestForgery.go:8:12:8:34 | call to FormValue | RequestForgery.go:11:24:11:65 | ...+... | provenance | Src:MaD:1 | -| tst.go:10:13:10:35 | call to FormValue | tst.go:18:11:18:17 | tainted | provenance | Src:MaD:1 | -| tst.go:10:13:10:35 | call to FormValue | tst.go:20:12:20:18 | tainted | provenance | Src:MaD:1 | -| tst.go:10:13:10:35 | call to FormValue | tst.go:22:12:22:18 | tainted | provenance | Src:MaD:1 | -| tst.go:10:13:10:35 | call to FormValue | tst.go:24:16:24:22 | tainted | provenance | Src:MaD:1 | -| tst.go:10:13:10:35 | call to FormValue | tst.go:27:35:27:41 | tainted | provenance | Src:MaD:1 | -| tst.go:10:13:10:35 | call to FormValue | tst.go:30:68:30:74 | tainted | provenance | Src:MaD:1 | -| tst.go:10:13:10:35 | call to FormValue | tst.go:33:13:33:19 | tainted | provenance | Src:MaD:1 | -| tst.go:10:13:10:35 | call to FormValue | tst.go:34:14:34:20 | tainted | provenance | Src:MaD:1 | -| tst.go:10:13:10:35 | call to FormValue | tst.go:35:14:35:20 | tainted | provenance | Src:MaD:1 | -| tst.go:10:13:10:35 | call to FormValue | tst.go:36:18:36:24 | tainted | provenance | Src:MaD:1 | -| tst.go:10:13:10:35 | call to FormValue | tst.go:38:11:38:29 | ...+... | provenance | Src:MaD:1 | -| tst.go:10:13:10:35 | call to FormValue | tst.go:40:11:40:40 | ...+... | provenance | Src:MaD:1 | -| tst.go:10:13:10:35 | call to FormValue | tst.go:47:11:47:18 | tainted2 | provenance | Src:MaD:1 | -| tst.go:47:2:47:2 | implicit dereference [postupdate] | tst.go:47:2:47:2 | u [postupdate] | provenance | | -| tst.go:47:2:47:2 | u [postupdate] | tst.go:48:11:48:11 | u | provenance | | -| tst.go:47:11:47:18 | tainted2 | tst.go:47:2:47:2 | implicit dereference [postupdate] | provenance | Config | -| tst.go:47:11:47:18 | tainted2 | tst.go:47:2:47:2 | u [postupdate] | provenance | Config | -| tst.go:48:11:48:11 | u | tst.go:48:11:48:20 | call to String | provenance | MaD:3 | +| tst.go:11:13:11:35 | call to FormValue | tst.go:19:11:19:17 | tainted | provenance | Src:MaD:1 | +| tst.go:11:13:11:35 | call to FormValue | tst.go:21:12:21:18 | tainted | provenance | Src:MaD:1 | +| tst.go:11:13:11:35 | call to FormValue | tst.go:23:12:23:18 | tainted | provenance | Src:MaD:1 | +| tst.go:11:13:11:35 | call to FormValue | tst.go:25:16:25:22 | tainted | provenance | Src:MaD:1 | +| tst.go:11:13:11:35 | call to FormValue | tst.go:28:35:28:41 | tainted | provenance | Src:MaD:1 | +| tst.go:11:13:11:35 | call to FormValue | tst.go:31:68:31:74 | tainted | provenance | Src:MaD:1 | +| tst.go:11:13:11:35 | call to FormValue | tst.go:34:13:34:19 | tainted | provenance | Src:MaD:1 | +| tst.go:11:13:11:35 | call to FormValue | tst.go:35:14:35:20 | tainted | provenance | Src:MaD:1 | +| tst.go:11:13:11:35 | call to FormValue | tst.go:36:14:36:20 | tainted | provenance | Src:MaD:1 | +| tst.go:11:13:11:35 | call to FormValue | tst.go:37:18:37:24 | tainted | provenance | Src:MaD:1 | +| tst.go:11:13:11:35 | call to FormValue | tst.go:39:11:39:29 | ...+... | provenance | Src:MaD:1 | +| tst.go:11:13:11:35 | call to FormValue | tst.go:41:11:41:40 | ...+... | provenance | Src:MaD:1 | +| tst.go:11:13:11:35 | call to FormValue | tst.go:48:11:48:18 | tainted2 | provenance | Src:MaD:1 | +| tst.go:48:2:48:2 | implicit dereference [postupdate] | tst.go:48:2:48:2 | u [postupdate] | provenance | | +| tst.go:48:2:48:2 | u [postupdate] | tst.go:49:11:49:11 | u | provenance | | +| tst.go:48:11:48:18 | tainted2 | tst.go:48:2:48:2 | implicit dereference [postupdate] | provenance | Config | +| tst.go:48:11:48:18 | tainted2 | tst.go:48:2:48:2 | u [postupdate] | provenance | Config | +| tst.go:49:11:49:11 | u | tst.go:49:11:49:20 | call to String | provenance | MaD:3 | | websocket.go:60:21:60:31 | call to Referer | websocket.go:65:27:65:40 | untrustedInput | provenance | Src:MaD:2 | | websocket.go:74:21:74:31 | call to Referer | websocket.go:78:36:78:49 | untrustedInput | provenance | Src:MaD:2 | | websocket.go:88:21:88:31 | call to Referer | websocket.go:91:31:91:44 | untrustedInput | provenance | Src:MaD:2 | @@ -58,24 +58,24 @@ models nodes | RequestForgery.go:8:12:8:34 | call to FormValue | semmle.label | call to FormValue | | RequestForgery.go:11:24:11:65 | ...+... | semmle.label | ...+... | -| tst.go:10:13:10:35 | call to FormValue | semmle.label | call to FormValue | -| tst.go:18:11:18:17 | tainted | semmle.label | tainted | -| tst.go:20:12:20:18 | tainted | semmle.label | tainted | -| tst.go:22:12:22:18 | tainted | semmle.label | tainted | -| tst.go:24:16:24:22 | tainted | semmle.label | tainted | -| tst.go:27:35:27:41 | tainted | semmle.label | tainted | -| tst.go:30:68:30:74 | tainted | semmle.label | tainted | -| tst.go:33:13:33:19 | tainted | semmle.label | tainted | -| tst.go:34:14:34:20 | tainted | semmle.label | tainted | +| tst.go:11:13:11:35 | call to FormValue | semmle.label | call to FormValue | +| tst.go:19:11:19:17 | tainted | semmle.label | tainted | +| tst.go:21:12:21:18 | tainted | semmle.label | tainted | +| tst.go:23:12:23:18 | tainted | semmle.label | tainted | +| tst.go:25:16:25:22 | tainted | semmle.label | tainted | +| tst.go:28:35:28:41 | tainted | semmle.label | tainted | +| tst.go:31:68:31:74 | tainted | semmle.label | tainted | +| tst.go:34:13:34:19 | tainted | semmle.label | tainted | | tst.go:35:14:35:20 | tainted | semmle.label | tainted | -| tst.go:36:18:36:24 | tainted | semmle.label | tainted | -| tst.go:38:11:38:29 | ...+... | semmle.label | ...+... | -| tst.go:40:11:40:40 | ...+... | semmle.label | ...+... | -| tst.go:47:2:47:2 | implicit dereference [postupdate] | semmle.label | implicit dereference [postupdate] | -| tst.go:47:2:47:2 | u [postupdate] | semmle.label | u [postupdate] | -| tst.go:47:11:47:18 | tainted2 | semmle.label | tainted2 | -| tst.go:48:11:48:11 | u | semmle.label | u | -| tst.go:48:11:48:20 | call to String | semmle.label | call to String | +| tst.go:36:14:36:20 | tainted | semmle.label | tainted | +| tst.go:37:18:37:24 | tainted | semmle.label | tainted | +| tst.go:39:11:39:29 | ...+... | semmle.label | ...+... | +| tst.go:41:11:41:40 | ...+... | semmle.label | ...+... | +| tst.go:48:2:48:2 | implicit dereference [postupdate] | semmle.label | implicit dereference [postupdate] | +| tst.go:48:2:48:2 | u [postupdate] | semmle.label | u [postupdate] | +| tst.go:48:11:48:18 | tainted2 | semmle.label | tainted2 | +| tst.go:49:11:49:11 | u | semmle.label | u | +| tst.go:49:11:49:20 | call to String | semmle.label | call to String | | websocket.go:60:21:60:31 | call to Referer | semmle.label | call to Referer | | websocket.go:65:27:65:40 | untrustedInput | semmle.label | untrustedInput | | websocket.go:74:21:74:31 | call to Referer | semmle.label | call to Referer | diff --git a/go/ql/test/query-tests/Security/CWE-918/RequestForgery.ext.yml b/go/ql/test/query-tests/Security/CWE-918/RequestForgery.ext.yml new file mode 100644 index 000000000000..1fc92a07144b --- /dev/null +++ b/go/ql/test/query-tests/Security/CWE-918/RequestForgery.ext.yml @@ -0,0 +1,6 @@ +extensions: + - addsTo: + pack: codeql/go-all + extensible: sourceModel + data: + - ["main", "", False, "intSource", "", "", "ReturnValue", "remote", "manual"] diff --git a/go/ql/test/query-tests/Security/CWE-918/tst.go b/go/ql/test/query-tests/Security/CWE-918/tst.go index 02f65507abd5..e237f5f30e75 100644 --- a/go/ql/test/query-tests/Security/CWE-918/tst.go +++ b/go/ql/test/query-tests/Security/CWE-918/tst.go @@ -2,6 +2,7 @@ package main import ( "context" + "fmt" "net/http" "net/url" ) @@ -46,8 +47,16 @@ func handler2(w http.ResponseWriter, req *http.Request) { u, _ := url.Parse("http://example.com/relative-path") u.Host = tainted2 http.Get(u.String()) // $ Alert + + // Simple types are considered sanitized. + url := fmt.Sprintf("%s/%d", "some-url", intSource()) + http.Get("http://" + url) } func main() { } + +func intSource() int64 { + return 0 +}