From 8bf8893307da1314158fdfbf3c24100cbeb5902f Mon Sep 17 00:00:00 2001 From: Kevin Stubbings Date: Mon, 26 Aug 2024 21:30:48 -0700 Subject: [PATCH 01/10] Add support for vulnerable CORS middlewares --- python/ql/lib/semmle/python/Concepts.qll | 50 ++++++++++++ .../lib/semmle/python/frameworks/FastApi.qll | 26 ++++++ .../semmle/python/frameworks/Starlette.qll | 81 +++++++++++++++++++ .../CorsMisconfigurationMiddleware.qhelp | 64 +++++++++++++++ .../CWE-942/CorsMisconfigurationMiddleware.ql | 36 +++++++++ .../CorsMisconfigurationMiddlewareBad.py | 21 +++++ .../CorsMisconfigurationMiddlewareGood.py | 24 ++++++ .../CorsMisconfigurationMiddleware.expected | 2 + .../CorsMisconfigurationMiddleware.qlref | 1 + .../fastapi.py | 21 +++++ .../starlette.py | 11 +++ 11 files changed, 337 insertions(+) create mode 100644 python/ql/src/experimental/Security/CWE-942/CorsMisconfigurationMiddleware.qhelp create mode 100644 python/ql/src/experimental/Security/CWE-942/CorsMisconfigurationMiddleware.ql create mode 100644 python/ql/src/experimental/Security/CWE-942/CorsMisconfigurationMiddlewareBad.py create mode 100644 python/ql/src/experimental/Security/CWE-942/CorsMisconfigurationMiddlewareGood.py create mode 100644 python/ql/test/query-tests/Security/CWE-942-CorsMisconfigurationMiddleware/CorsMisconfigurationMiddleware.expected create mode 100644 python/ql/test/query-tests/Security/CWE-942-CorsMisconfigurationMiddleware/CorsMisconfigurationMiddleware.qlref create mode 100644 python/ql/test/query-tests/Security/CWE-942-CorsMisconfigurationMiddleware/fastapi.py create mode 100644 python/ql/test/query-tests/Security/CWE-942-CorsMisconfigurationMiddleware/starlette.py diff --git a/python/ql/lib/semmle/python/Concepts.qll b/python/ql/lib/semmle/python/Concepts.qll index 75b884a9dd4..920fbca3553 100644 --- a/python/ql/lib/semmle/python/Concepts.qll +++ b/python/ql/lib/semmle/python/Concepts.qll @@ -1411,6 +1411,56 @@ module Http { override DataFlow::Node getValueArg() { none() } } + /** + * A data-flow node that enables or disables CORS + * in a global manner. + * + * Extend this class to refine existing API models. If you want to model new APIs, + * extend `CorsMiddleware::Range` instead. + */ + class CorsMiddleware extends DataFlow::Node instanceof CorsMiddleware::Range { + /** + * Gets the string corresponding to the middleware + */ + string middleware_name() { result = super.middleware_name() } + + /** + * Gets the boolean value corresponding to if CORS credentials is enabled + * (`true`) or disabled (`false`) by this node. + */ + DataFlow::Node allowed_origins() { result = super.allowed_origins() } + + DataFlow::Node allowed_credentials() { result = super.allowed_credentials() } + } + + /** Provides a class for modeling new CORS middleware APIs. */ + module CorsMiddleware { + /** + * A data-flow node that enables or disables Cross-site request forgery protection + * in a global manner. + * + * Extend this class to model new APIs. If you want to refine existing API models, + * extend `CorsMiddleware` instead. + */ + abstract class Range extends DataFlow::Node { + /** + * Gets the string corresponding to the middleware + */ + abstract string middleware_name(); + + /** + * Gets the boolean value corresponding to if CORS credentials is enabled + * (`true`) or disabled (`false`) by this node. + */ + abstract DataFlow::Node allowed_credentials(); + + /** + * Gets the strings corresponding to the origins allowed by the cors policy + */ + abstract DataFlow::Node allowed_origins(); + } + } + /** * A data-flow node that enables or disables Cross-site request forgery protection * in a global manner. diff --git a/python/ql/lib/semmle/python/frameworks/FastApi.qll b/python/ql/lib/semmle/python/frameworks/FastApi.qll index 3a15ca6fbcc..7927dac5a24 100644 --- a/python/ql/lib/semmle/python/frameworks/FastApi.qll +++ b/python/ql/lib/semmle/python/frameworks/FastApi.qll @@ -30,6 +30,32 @@ module FastApi { API::Node instance() { result = cls().getReturn() } } + /** + * A call to `app.add_middleware` adding a generic middleware. + */ + private class AddMiddlewareCall extends DataFlow::CallCfgNode { + AddMiddlewareCall() { this = App::instance().getMember("add_middleware").getACall() } + + string middleware_name() { result = this.getArg(0).asExpr().(Name).toString() } + } + + /** + * A call to `app.add_middleware` adding CORSMiddleware. + */ + class AddCorsMiddlewareCall extends Http::Server::CorsMiddleware::Range, AddMiddlewareCall { + override string middleware_name() { result = this.getArg(0).asExpr().(Name).toString() } + + override DataFlow::Node allowed_origins() { result = this.getArgByName("allow_origins") } + + override DataFlow::Node allowed_credentials() { + result = this.getArgByName("allow_credentials") + } + + DataFlow::Node allowed_methods() { result = this.getArgByName("allow_methods") } + + DataFlow::Node allowed_headers() { result = this.getArgByName("allow_headers") } + } + /** * Provides models for the `fastapi.APIRouter` class * diff --git a/python/ql/lib/semmle/python/frameworks/Starlette.qll b/python/ql/lib/semmle/python/frameworks/Starlette.qll index ec62888ecb0..d54fc267b41 100644 --- a/python/ql/lib/semmle/python/frameworks/Starlette.qll +++ b/python/ql/lib/semmle/python/frameworks/Starlette.qll @@ -25,6 +25,87 @@ private import semmle.python.frameworks.data.ModelsAsData * - https://www.starlette.io/ */ module Starlette { + /** + * Provides models for the `starlette.app` class + * + * See https://www.starlette.io/websockets/. + */ + module App { + API::Node cls() { result = API::moduleImport("starlette").getMember("app") } + + /** Gets a reference to a FastAPI application (an instance of `fastapi.FastAPI`). */ + API::Node instance() { result = cls().getReturn() } + } + + /** + * A call to any of the execute methods on a `app.add_middleware`. + */ + class AddMiddlewareCall extends DataFlow::CallCfgNode { + AddMiddlewareCall() { + this = [App::instance().getMember("add_middleware").getACall(), Middleware::instance()] + } + + string middleware_name() { result = this.getArg(0).asExpr().(Name).toString() } + } + + /** + * A call to any of the execute methods on a `app.add_middleware` with CORSMiddleware. + */ + class AddCorsMiddlewareCall extends AddMiddlewareCall, Http::Server::CorsMiddleware::Range { + override string middleware_name() { result = this.getArg(0).asExpr().(Name).toString() } + + override DataFlow::Node allowed_origins() { result = this.getArgByName("allow_origins") } + + override DataFlow::Node allowed_credentials() { + result = this.getArgByName("allow_credentials") + } + + DataFlow::Node allowed_methods() { result = this.getArgByName("allow_methods") } + + DataFlow::Node allowed_headers() { result = this.getArgByName("allow_headers") } + } + + /** + * Provides models for the `starlette.middleware.Middleware` class + * + * See https://www.starlette.io/. + */ + module Middleware { + /** Gets a reference to the `starlette.middleware.Middleware` class. */ + API::Node classRef() { + result = API::moduleImport("starlette").getMember("middleware").getMember("Middleware") + or + result = ModelOutput::getATypeNode("starlette.middleware.Middleware~Subclass").getASubclass*() + } + + /** + * A source of instances of `starlette.middleware.Middleware`, extend this class to model new instances. + * + * This can include instantiations of the class, return values from function + * calls, or a special parameter that will be set when functions are called by an external + * library. + * + * Use the predicate `Middleware::instance()` to get references to instances of `starlette.middleware.middleware`. + */ + abstract class InstanceSource extends DataFlow::LocalSourceNode { } + + /** A direct instantiation of `starlette.middleware.Middleware`. */ + class ClassInstantiation extends InstanceSource, DataFlow::CallCfgNode { + ClassInstantiation() { this = classRef().getACall() } + } + + /** Gets a reference to an instance of `starlette.middleware.Middleware`. */ + private DataFlow::TypeTrackingNode instance(DataFlow::TypeTracker t) { + t.start() and + result instanceof InstanceSource + or + exists(DataFlow::TypeTracker t2 | result = instance(t2).track(t2, t)) + } + + /** Gets a reference to an instance of `starlette.middleware.Middleware`. */ + DataFlow::Node instance() { instance(DataFlow::TypeTracker::end()).flowsTo(result) } + } + /** * Provides models for the `starlette.websockets.WebSocket` class * diff --git a/python/ql/src/experimental/Security/CWE-942/CorsMisconfigurationMiddleware.qhelp b/python/ql/src/experimental/Security/CWE-942/CorsMisconfigurationMiddleware.qhelp new file mode 100644 index 00000000000..43f800f03a5 --- /dev/null +++ b/python/ql/src/experimental/Security/CWE-942/CorsMisconfigurationMiddleware.qhelp @@ -0,0 +1,64 @@ + + + +

+ Web browsers, by default, disallow cross-origin resource sharing via direct HTTP requests (i.e. using a JavaScript HTTP client). + Still, to satisfy some needs that arose with the growth of the web, an expedient was created to make exceptions possible. + CORS (Cross-origin resource sharing) is a mechanism that allows resources of a web endpoint (let's call it "Peer A") + to be accessed from another web page belonging to a different domain ("Peer B"). +

+

+ For that to happen, Peer A needs to make available its CORS configuration via special headers on the desired endpoint + via the OPTIONS method. +

+

+ This configuration can also allow the inclusion of cookies on the cross-origin request, + (i.e. when the Access-Control-Allow-Credentials header is set to true) + meaning that Peer B can send a request to Peer A that will include the cookies as if the request was executed by the user. +

+

+ That can have dangerous effects if Peer B origin is not restricted correctly. + An example of a dangerous scenario is when Access-Control-Allow-Origin header is set to a value gotten from the Peer B's request + (and not correctly validated), or is set to special values such as * or null. + The above values can allow any Peer B to send requests to the misconfigured Peer A on behalf of the user. +

+

+ Example scenario: + User is client of a bank that has its API misconfigured to accept CORS requests from any domain. + When the user loads an evil page, the evil page sends a request to the bank's API to transfer all funds + to evil party's account. + Given that the user was already logged in to the bank website, and had its session cookies set, + the evil party's request succeeds. +

+
+ +

+ When configuring CORS that allow credentials passing, + it's best not to use user-provided values for the allowed origins response header, + especially if the cookies grant session permissions on the user's account. +

+

+ It also can be very dangerous to set the allowed origins to null (which can be bypassed). +

+
+ +

+ The first example shows a few possible CORS misconfiguration cases: +

+ +

+ The second example show better configurations: +

+ +
+ +
  • + Reference 1: PortSwigger Web Security Academy on CORS. +
  • +
  • + Reference 2: AppSec EU 2017 Exploiting CORS Misconfigurations For Bitcoins And Bounties by James Kettle. +
  • +
    +
    \ No newline at end of file diff --git a/python/ql/src/experimental/Security/CWE-942/CorsMisconfigurationMiddleware.ql b/python/ql/src/experimental/Security/CWE-942/CorsMisconfigurationMiddleware.ql new file mode 100644 index 00000000000..ddbd0766207 --- /dev/null +++ b/python/ql/src/experimental/Security/CWE-942/CorsMisconfigurationMiddleware.ql @@ -0,0 +1,36 @@ +/** + * @name SOP protection weak with credentials + * @description Disabling or weakening SOP protection may make the application + * vulnerable to a CORS attack. + * @kind problem + * @problem.severity warning + * @security-severity 8.8 + * @precision high + * @id py/insecure-cors-setting + * @tags security + * external/cwe/cwe-352 + */ + + import python + import semmle.python.Concepts + private import semmle.python.dataflow.new.DataFlow + predicate containsStar(DataFlow::Node array){ + (array.asExpr() instanceof List and + array.asExpr().getASubExpression().(StringLiteral).getText().matches("*")) or + (array.asExpr().(StringLiteral).getText().matches(["*", "null"])) + + } + + predicate isCorsMiddleware(Http::Server::CorsMiddleware middleware){ + middleware.middleware_name().matches("CORSMiddleware") + } + + predicate credentialsAllowed(Http::Server::CorsMiddleware middleware){ + middleware.allowed_credentials().asExpr() instanceof True + } + + from Http::Server::CorsMiddleware a + where credentialsAllowed(a) and + containsStar(a.allowed_origins().getALocalSource()) and + isCorsMiddleware(a) + select a, "This CORS middleware uses a vulnerable configuration that leaves it open to attacks from arbitrary websites" \ No newline at end of file diff --git a/python/ql/src/experimental/Security/CWE-942/CorsMisconfigurationMiddlewareBad.py b/python/ql/src/experimental/Security/CWE-942/CorsMisconfigurationMiddlewareBad.py new file mode 100644 index 00000000000..2b1c3c58a7e --- /dev/null +++ b/python/ql/src/experimental/Security/CWE-942/CorsMisconfigurationMiddlewareBad.py @@ -0,0 +1,21 @@ +from fastapi import FastAPI +from fastapi.middleware.cors import CORSMiddleware + +app = FastAPI() + +origins = [ + "*" +] + +app.add_middleware( + CORSMiddleware, + allow_origins=origins, + allow_credentials=True, + allow_methods=["*"], + allow_headers=["*"], +) + + +@app.get("/") +async def main(): + return {"message": "Hello World"} \ No newline at end of file diff --git a/python/ql/src/experimental/Security/CWE-942/CorsMisconfigurationMiddlewareGood.py b/python/ql/src/experimental/Security/CWE-942/CorsMisconfigurationMiddlewareGood.py new file mode 100644 index 00000000000..bfcb559feb2 --- /dev/null +++ b/python/ql/src/experimental/Security/CWE-942/CorsMisconfigurationMiddlewareGood.py @@ -0,0 +1,24 @@ +from fastapi import FastAPI +from fastapi.middleware.cors import CORSMiddleware + +app = FastAPI() + +origins = [ + "http://localhost.tiangolo.com", + "https://localhost.tiangolo.com", + "http://localhost", + "http://localhost:8080", +] + +app.add_middleware( + CORSMiddleware, + allow_origins=origins, + allow_credentials=True, + allow_methods=["*"], + allow_headers=["*"], +) + + +@app.get("/") +async def main(): + return {"message": "Hello World"} \ No newline at end of file diff --git a/python/ql/test/query-tests/Security/CWE-942-CorsMisconfigurationMiddleware/CorsMisconfigurationMiddleware.expected b/python/ql/test/query-tests/Security/CWE-942-CorsMisconfigurationMiddleware/CorsMisconfigurationMiddleware.expected new file mode 100644 index 00000000000..cc27e83a644 --- /dev/null +++ b/python/ql/test/query-tests/Security/CWE-942-CorsMisconfigurationMiddleware/CorsMisconfigurationMiddleware.expected @@ -0,0 +1,2 @@ +| fastapi.py:10:1:16:1 | ControlFlowNode for Attribute() | This CORS middleware uses a vulnerable configuration that leaves it open to attacks from arbitrary websites | +| starlette.py:8:5:8:75 | ControlFlowNode for Middleware() | This CORS middleware uses a vulnerable configuration that leaves it open to attacks from arbitrary websites | diff --git a/python/ql/test/query-tests/Security/CWE-942-CorsMisconfigurationMiddleware/CorsMisconfigurationMiddleware.qlref b/python/ql/test/query-tests/Security/CWE-942-CorsMisconfigurationMiddleware/CorsMisconfigurationMiddleware.qlref new file mode 100644 index 00000000000..f7017ada0d8 --- /dev/null +++ b/python/ql/test/query-tests/Security/CWE-942-CorsMisconfigurationMiddleware/CorsMisconfigurationMiddleware.qlref @@ -0,0 +1 @@ +experimental/Security/CWE-942/CorsMisconfigurationMiddleware.ql \ No newline at end of file diff --git a/python/ql/test/query-tests/Security/CWE-942-CorsMisconfigurationMiddleware/fastapi.py b/python/ql/test/query-tests/Security/CWE-942-CorsMisconfigurationMiddleware/fastapi.py new file mode 100644 index 00000000000..2b1c3c58a7e --- /dev/null +++ b/python/ql/test/query-tests/Security/CWE-942-CorsMisconfigurationMiddleware/fastapi.py @@ -0,0 +1,21 @@ +from fastapi import FastAPI +from fastapi.middleware.cors import CORSMiddleware + +app = FastAPI() + +origins = [ + "*" +] + +app.add_middleware( + CORSMiddleware, + allow_origins=origins, + allow_credentials=True, + allow_methods=["*"], + allow_headers=["*"], +) + + +@app.get("/") +async def main(): + return {"message": "Hello World"} \ No newline at end of file diff --git a/python/ql/test/query-tests/Security/CWE-942-CorsMisconfigurationMiddleware/starlette.py b/python/ql/test/query-tests/Security/CWE-942-CorsMisconfigurationMiddleware/starlette.py new file mode 100644 index 00000000000..1d6e7705369 --- /dev/null +++ b/python/ql/test/query-tests/Security/CWE-942-CorsMisconfigurationMiddleware/starlette.py @@ -0,0 +1,11 @@ +from starlette.applications import Starlette +from starlette.middleware import Middleware +from starlette.middleware.cors import CORSMiddleware + +routes = ... + +middleware = [ + Middleware(CORSMiddleware, allow_origins=['*'], allow_credentials=True) +] + +app = Starlette(routes=routes, middleware=middleware) \ No newline at end of file From 1db7865d49b904c3a01667c31c3f774dd92ddd2f Mon Sep 17 00:00:00 2001 From: Kevin Stubbings Date: Mon, 26 Aug 2024 22:06:12 -0700 Subject: [PATCH 02/10] Corrections --- python/ql/lib/semmle/python/Concepts.qll | 7 ++- .../lib/semmle/python/frameworks/FastApi.qll | 18 ++++++- .../semmle/python/frameworks/Starlette.qll | 16 +++++-- .../CorsMisconfigurationMiddleware.qhelp | 2 +- .../CWE-942/CorsMisconfigurationMiddleware.ql | 47 ++++++++++--------- 5 files changed, 60 insertions(+), 30 deletions(-) diff --git a/python/ql/lib/semmle/python/Concepts.qll b/python/ql/lib/semmle/python/Concepts.qll index 920fbca3553..5d9ac3d9e12 100644 --- a/python/ql/lib/semmle/python/Concepts.qll +++ b/python/ql/lib/semmle/python/Concepts.qll @@ -1425,11 +1425,14 @@ module Http { string middleware_name() { result = super.middleware_name() } /** - * Gets the boolean value corresponding to if CORS credentials is enabled - * (`true`) or disabled (`false`) by this node. + * Gets the dataflow node corresponding to the allowed CORS origins */ DataFlow::Node allowed_origins() { result = super.allowed_origins() } + /** + * Gets the boolean value corresponding to if CORS credentials is enabled + * (`true`) or disabled (`false`) by this node. + */ DataFlow::Node allowed_credentials() { result = super.allowed_credentials() } } diff --git a/python/ql/lib/semmle/python/frameworks/FastApi.qll b/python/ql/lib/semmle/python/frameworks/FastApi.qll index 7927dac5a24..c3bf25d43e9 100644 --- a/python/ql/lib/semmle/python/frameworks/FastApi.qll +++ b/python/ql/lib/semmle/python/frameworks/FastApi.qll @@ -43,16 +43,30 @@ module FastApi { * A call to `app.add_middleware` adding CORSMiddleware. */ class AddCorsMiddlewareCall extends Http::Server::CorsMiddleware::Range, AddMiddlewareCall { + /** + * Gets the string corresponding to the middleware + */ override string middleware_name() { result = this.getArg(0).asExpr().(Name).toString() } + /** + * Gets the dataflow node corresponding to the allowed CORS origins + */ override DataFlow::Node allowed_origins() { result = this.getArgByName("allow_origins") } - + /** + * Gets the boolean value corresponding to if CORS credentials is enabled + * (`true`) or disabled (`false`) by this node. + */ override DataFlow::Node allowed_credentials() { result = this.getArgByName("allow_credentials") } - + /** + * Gets the dataflow node corresponding to the allowed CORS methods + */ DataFlow::Node allowed_methods() { result = this.getArgByName("allow_methods") } + /** + * Gets the dataflow node corresponding to the allowed CORS headers + */ DataFlow::Node allowed_headers() { result = this.getArgByName("allow_headers") } } diff --git a/python/ql/lib/semmle/python/frameworks/Starlette.qll b/python/ql/lib/semmle/python/frameworks/Starlette.qll index d54fc267b41..404084c3e36 100644 --- a/python/ql/lib/semmle/python/frameworks/Starlette.qll +++ b/python/ql/lib/semmle/python/frameworks/Starlette.qll @@ -28,12 +28,13 @@ module Starlette { /** * Provides models for the `starlette.app` class * - * See https://www.starlette.io/websockets/. + * */ module App { + /** Gets import of `starlette.app`. */ API::Node cls() { result = API::moduleImport("starlette").getMember("app") } - /** Gets a reference to a FastAPI application (an instance of `fastapi.FastAPI`). */ + /** Gets a reference to a Starlette application (an instance of `starlette.app`). */ API::Node instance() { result = cls().getReturn() } } @@ -52,6 +53,10 @@ module Starlette { * A call to any of the execute methods on a `app.add_middleware` with CORSMiddleware. */ class AddCorsMiddlewareCall extends AddMiddlewareCall, Http::Server::CorsMiddleware::Range { + + /** + * Gets the string corresponding to the middleware + */ override string middleware_name() { result = this.getArg(0).asExpr().(Name).toString() } override DataFlow::Node allowed_origins() { result = this.getArgByName("allow_origins") } @@ -59,9 +64,14 @@ module Starlette { override DataFlow::Node allowed_credentials() { result = this.getArgByName("allow_credentials") } - + /** + * Gets the dataflow node corresponding to the allowed CORS methods + */ DataFlow::Node allowed_methods() { result = this.getArgByName("allow_methods") } + /** + * Gets the dataflow node corresponding to the allowed CORS headers + */ DataFlow::Node allowed_headers() { result = this.getArgByName("allow_headers") } } diff --git a/python/ql/src/experimental/Security/CWE-942/CorsMisconfigurationMiddleware.qhelp b/python/ql/src/experimental/Security/CWE-942/CorsMisconfigurationMiddleware.qhelp index 43f800f03a5..9c4524a726c 100644 --- a/python/ql/src/experimental/Security/CWE-942/CorsMisconfigurationMiddleware.qhelp +++ b/python/ql/src/experimental/Security/CWE-942/CorsMisconfigurationMiddleware.qhelp @@ -4,7 +4,7 @@

    - Web browsers, by default, disallow cross-origin resource sharing via direct HTTP requests (i.e. using a JavaScript HTTP client). + Web browsers, by default, disallow cross-origin resource sharing via direct HTTP requests. Still, to satisfy some needs that arose with the growth of the web, an expedient was created to make exceptions possible. CORS (Cross-origin resource sharing) is a mechanism that allows resources of a web endpoint (let's call it "Peer A") to be accessed from another web page belonging to a different domain ("Peer B"). diff --git a/python/ql/src/experimental/Security/CWE-942/CorsMisconfigurationMiddleware.ql b/python/ql/src/experimental/Security/CWE-942/CorsMisconfigurationMiddleware.ql index ddbd0766207..1016836cf21 100644 --- a/python/ql/src/experimental/Security/CWE-942/CorsMisconfigurationMiddleware.ql +++ b/python/ql/src/experimental/Security/CWE-942/CorsMisconfigurationMiddleware.ql @@ -11,26 +11,29 @@ * external/cwe/cwe-352 */ - import python - import semmle.python.Concepts - private import semmle.python.dataflow.new.DataFlow - predicate containsStar(DataFlow::Node array){ - (array.asExpr() instanceof List and - array.asExpr().getASubExpression().(StringLiteral).getText().matches("*")) or - (array.asExpr().(StringLiteral).getText().matches(["*", "null"])) +import python +import semmle.python.Concepts +private import semmle.python.dataflow.new.DataFlow - } - - predicate isCorsMiddleware(Http::Server::CorsMiddleware middleware){ - middleware.middleware_name().matches("CORSMiddleware") - } - - predicate credentialsAllowed(Http::Server::CorsMiddleware middleware){ - middleware.allowed_credentials().asExpr() instanceof True - } - - from Http::Server::CorsMiddleware a - where credentialsAllowed(a) and - containsStar(a.allowed_origins().getALocalSource()) and - isCorsMiddleware(a) - select a, "This CORS middleware uses a vulnerable configuration that leaves it open to attacks from arbitrary websites" \ No newline at end of file +predicate containsStar(DataFlow::Node array) { + array.asExpr() instanceof List and + array.asExpr().getASubExpression().(StringLiteral).getText() = ["*", "null"] + or + array.asExpr().(StringLiteral).getText() = ["*", "null"] +} + +predicate isCorsMiddleware(Http::Server::CorsMiddleware middleware) { + middleware.middleware_name().matches("CORSMiddleware") +} + +predicate credentialsAllowed(Http::Server::CorsMiddleware middleware) { + middleware.allowed_credentials().asExpr() instanceof True +} + +from Http::Server::CorsMiddleware a +where + credentialsAllowed(a) and + containsStar(a.allowed_origins().getALocalSource()) and + isCorsMiddleware(a) +select a, + "This CORS middleware uses a vulnerable configuration that leaves it open to attacks from arbitrary websites" From 0420d25c13ebe80bf267ed9b7cd8d698c68ff4d9 Mon Sep 17 00:00:00 2001 From: Kevin Stubbings Date: Mon, 26 Aug 2024 22:09:24 -0700 Subject: [PATCH 03/10] refactor --- python/ql/lib/semmle/python/Concepts.qll | 2 +- .../lib/semmle/python/frameworks/FastApi.qll | 32 ++++++++++--------- .../semmle/python/frameworks/Starlette.qll | 22 ++++++------- 3 files changed, 28 insertions(+), 28 deletions(-) diff --git a/python/ql/lib/semmle/python/Concepts.qll b/python/ql/lib/semmle/python/Concepts.qll index 5d9ac3d9e12..d981f82c1bc 100644 --- a/python/ql/lib/semmle/python/Concepts.qll +++ b/python/ql/lib/semmle/python/Concepts.qll @@ -1425,7 +1425,7 @@ module Http { string middleware_name() { result = super.middleware_name() } /** - * Gets the dataflow node corresponding to the allowed CORS origins + * Gets the dataflow node corresponding to the allowed CORS origins */ DataFlow::Node allowed_origins() { result = super.allowed_origins() } diff --git a/python/ql/lib/semmle/python/frameworks/FastApi.qll b/python/ql/lib/semmle/python/frameworks/FastApi.qll index c3bf25d43e9..24a25b86751 100644 --- a/python/ql/lib/semmle/python/frameworks/FastApi.qll +++ b/python/ql/lib/semmle/python/frameworks/FastApi.qll @@ -43,30 +43,32 @@ module FastApi { * A call to `app.add_middleware` adding CORSMiddleware. */ class AddCorsMiddlewareCall extends Http::Server::CorsMiddleware::Range, AddMiddlewareCall { - /** - * Gets the string corresponding to the middleware - */ + /** + * Gets the string corresponding to the middleware + */ override string middleware_name() { result = this.getArg(0).asExpr().(Name).toString() } /** - * Gets the dataflow node corresponding to the allowed CORS origins - */ + * Gets the dataflow node corresponding to the allowed CORS origins + */ override DataFlow::Node allowed_origins() { result = this.getArgByName("allow_origins") } - /** - * Gets the boolean value corresponding to if CORS credentials is enabled - * (`true`) or disabled (`false`) by this node. - */ + + /** + * Gets the boolean value corresponding to if CORS credentials is enabled + * (`true`) or disabled (`false`) by this node. + */ override DataFlow::Node allowed_credentials() { result = this.getArgByName("allow_credentials") } - /** - * Gets the dataflow node corresponding to the allowed CORS methods - */ + + /** + * Gets the dataflow node corresponding to the allowed CORS methods + */ DataFlow::Node allowed_methods() { result = this.getArgByName("allow_methods") } - /** - * Gets the dataflow node corresponding to the allowed CORS headers - */ + /** + * Gets the dataflow node corresponding to the allowed CORS headers + */ DataFlow::Node allowed_headers() { result = this.getArgByName("allow_headers") } } diff --git a/python/ql/lib/semmle/python/frameworks/Starlette.qll b/python/ql/lib/semmle/python/frameworks/Starlette.qll index 404084c3e36..bd963fa7fe2 100644 --- a/python/ql/lib/semmle/python/frameworks/Starlette.qll +++ b/python/ql/lib/semmle/python/frameworks/Starlette.qll @@ -27,8 +27,6 @@ private import semmle.python.frameworks.data.ModelsAsData module Starlette { /** * Provides models for the `starlette.app` class - * - * */ module App { /** Gets import of `starlette.app`. */ @@ -53,10 +51,9 @@ module Starlette { * A call to any of the execute methods on a `app.add_middleware` with CORSMiddleware. */ class AddCorsMiddlewareCall extends AddMiddlewareCall, Http::Server::CorsMiddleware::Range { - - /** - * Gets the string corresponding to the middleware - */ + /** + * Gets the string corresponding to the middleware + */ override string middleware_name() { result = this.getArg(0).asExpr().(Name).toString() } override DataFlow::Node allowed_origins() { result = this.getArgByName("allow_origins") } @@ -64,14 +61,15 @@ module Starlette { override DataFlow::Node allowed_credentials() { result = this.getArgByName("allow_credentials") } - /** - * Gets the dataflow node corresponding to the allowed CORS methods - */ + + /** + * Gets the dataflow node corresponding to the allowed CORS methods + */ DataFlow::Node allowed_methods() { result = this.getArgByName("allow_methods") } - /** - * Gets the dataflow node corresponding to the allowed CORS headers - */ + /** + * Gets the dataflow node corresponding to the allowed CORS headers + */ DataFlow::Node allowed_headers() { result = this.getArgByName("allow_headers") } } From 812abea0dedd181012ab35f3bfe007f532687438 Mon Sep 17 00:00:00 2001 From: Kevin Stubbings Date: Mon, 26 Aug 2024 22:25:00 -0700 Subject: [PATCH 04/10] change-notes --- python/ql/lib/semmle/python/frameworks/FastApi.qll | 3 +++ python/ql/lib/semmle/python/frameworks/Starlette.qll | 3 +++ .../2024-08-26-Cors-misconfiguration-middleware.md | 4 ++++ 3 files changed, 10 insertions(+) create mode 100644 python/ql/src/change-notes/2024-08-26-Cors-misconfiguration-middleware.md diff --git a/python/ql/lib/semmle/python/frameworks/FastApi.qll b/python/ql/lib/semmle/python/frameworks/FastApi.qll index 24a25b86751..e29bf06ec7a 100644 --- a/python/ql/lib/semmle/python/frameworks/FastApi.qll +++ b/python/ql/lib/semmle/python/frameworks/FastApi.qll @@ -36,6 +36,9 @@ module FastApi { private class AddMiddlewareCall extends DataFlow::CallCfgNode { AddMiddlewareCall() { this = App::instance().getMember("add_middleware").getACall() } + /** + * Gets the string corresponding to the middleware + */ string middleware_name() { result = this.getArg(0).asExpr().(Name).toString() } } diff --git a/python/ql/lib/semmle/python/frameworks/Starlette.qll b/python/ql/lib/semmle/python/frameworks/Starlette.qll index bd963fa7fe2..337324f2c66 100644 --- a/python/ql/lib/semmle/python/frameworks/Starlette.qll +++ b/python/ql/lib/semmle/python/frameworks/Starlette.qll @@ -44,6 +44,9 @@ module Starlette { this = [App::instance().getMember("add_middleware").getACall(), Middleware::instance()] } + /** + * Gets the string corresponding to the middleware + */ string middleware_name() { result = this.getArg(0).asExpr().(Name).toString() } } diff --git a/python/ql/src/change-notes/2024-08-26-Cors-misconfiguration-middleware.md b/python/ql/src/change-notes/2024-08-26-Cors-misconfiguration-middleware.md new file mode 100644 index 00000000000..2576b2e89dc --- /dev/null +++ b/python/ql/src/change-notes/2024-08-26-Cors-misconfiguration-middleware.md @@ -0,0 +1,4 @@ +--- +category: newQuery +--- +* The `py/insecure-cors-setting` query, which finds insecure CORS middleware configurations. \ No newline at end of file From c60f4595302a5969d2a5f455eed664e7bce4810c Mon Sep 17 00:00:00 2001 From: Kevin Stubbings Date: Mon, 26 Aug 2024 23:57:19 -0700 Subject: [PATCH 05/10] Grammar --- .../Security/CWE-942/CorsMisconfigurationMiddleware.qhelp | 4 ++-- .../Security/CWE-942/CorsMisconfigurationMiddleware.ql | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/python/ql/src/experimental/Security/CWE-942/CorsMisconfigurationMiddleware.qhelp b/python/ql/src/experimental/Security/CWE-942/CorsMisconfigurationMiddleware.qhelp index 9c4524a726c..76bcecf6012 100644 --- a/python/ql/src/experimental/Security/CWE-942/CorsMisconfigurationMiddleware.qhelp +++ b/python/ql/src/experimental/Security/CWE-942/CorsMisconfigurationMiddleware.qhelp @@ -45,11 +45,11 @@

    - The first example shows a few possible CORS misconfiguration cases: + The first example shows a possible CORS misconfiguration case:

    - The second example show better configurations: + The second example shows a better configuration:

    diff --git a/python/ql/src/experimental/Security/CWE-942/CorsMisconfigurationMiddleware.ql b/python/ql/src/experimental/Security/CWE-942/CorsMisconfigurationMiddleware.ql index 1016836cf21..3ec8ea509fd 100644 --- a/python/ql/src/experimental/Security/CWE-942/CorsMisconfigurationMiddleware.ql +++ b/python/ql/src/experimental/Security/CWE-942/CorsMisconfigurationMiddleware.ql @@ -8,7 +8,7 @@ * @precision high * @id py/insecure-cors-setting * @tags security - * external/cwe/cwe-352 + * external/cwe/cwe-942 */ import python From 831d522025fbe32c11ec59e6c4ede4e11edb8ce6 Mon Sep 17 00:00:00 2001 From: Kevin Stubbings Date: Thu, 12 Sep 2024 20:49:10 -0700 Subject: [PATCH 06/10] First round feedback --- python/ql/lib/semmle/python/Concepts.qll | 14 ++++---- .../lib/semmle/python/frameworks/FastApi.qll | 8 ++--- .../semmle/python/frameworks/Starlette.qll | 36 ++++--------------- .../CorsMisconfigurationMiddleware.qhelp | 4 +-- .../CWE-942/CorsMisconfigurationMiddleware.ql | 14 ++++---- 5 files changed, 26 insertions(+), 50 deletions(-) diff --git a/python/ql/lib/semmle/python/Concepts.qll b/python/ql/lib/semmle/python/Concepts.qll index d981f82c1bc..cd8f888eb54 100644 --- a/python/ql/lib/semmle/python/Concepts.qll +++ b/python/ql/lib/semmle/python/Concepts.qll @@ -1422,18 +1422,18 @@ module Http { /** * Gets the string corresponding to the middleware */ - string middleware_name() { result = super.middleware_name() } + string getMiddlewareName() { result = super.getMiddlewareName() } /** * Gets the dataflow node corresponding to the allowed CORS origins */ - DataFlow::Node allowed_origins() { result = super.allowed_origins() } + DataFlow::Node getOrigins() { result = super.getOrigins() } /** * Gets the boolean value corresponding to if CORS credentials is enabled * (`true`) or disabled (`false`) by this node. */ - DataFlow::Node allowed_credentials() { result = super.allowed_credentials() } + DataFlow::Node getCredentialsAllowed() { result = super.getCredentialsAllowed() } } /** Provides a class for modeling new CORS middleware APIs. */ @@ -1447,20 +1447,20 @@ module Http { */ abstract class Range extends DataFlow::Node { /** - * Gets the string corresponding to the middleware + * Gets the name corresponding to the middleware */ - abstract string middleware_name(); + abstract string getMiddlewareName(); /** * Gets the boolean value corresponding to if CORS credentials is enabled * (`true`) or disabled (`false`) by this node. */ - abstract DataFlow::Node allowed_credentials(); + abstract DataFlow::Node getCredentialsAllowed(); /** * Gets the strings corresponding to the origins allowed by the cors policy */ - abstract DataFlow::Node allowed_origins(); + abstract DataFlow::Node getOrigins(); } } diff --git a/python/ql/lib/semmle/python/frameworks/FastApi.qll b/python/ql/lib/semmle/python/frameworks/FastApi.qll index e29bf06ec7a..e0f994491ae 100644 --- a/python/ql/lib/semmle/python/frameworks/FastApi.qll +++ b/python/ql/lib/semmle/python/frameworks/FastApi.qll @@ -39,7 +39,7 @@ module FastApi { /** * Gets the string corresponding to the middleware */ - string middleware_name() { result = this.getArg(0).asExpr().(Name).toString() } + string getMiddlewareName() { result = this.getArg(0).asExpr().(Name).getId() } } /** @@ -49,18 +49,18 @@ module FastApi { /** * Gets the string corresponding to the middleware */ - override string middleware_name() { result = this.getArg(0).asExpr().(Name).toString() } + override string getMiddlewareName() { result = this.getArg(0).asExpr().(Name).getId() } /** * Gets the dataflow node corresponding to the allowed CORS origins */ - override DataFlow::Node allowed_origins() { result = this.getArgByName("allow_origins") } + override DataFlow::Node getOrigins() { result = this.getArgByName("allow_origins") } /** * Gets the boolean value corresponding to if CORS credentials is enabled * (`true`) or disabled (`false`) by this node. */ - override DataFlow::Node allowed_credentials() { + override DataFlow::Node getCredentialsAllowed() { result = this.getArgByName("allow_credentials") } diff --git a/python/ql/lib/semmle/python/frameworks/Starlette.qll b/python/ql/lib/semmle/python/frameworks/Starlette.qll index 337324f2c66..0dc1260af19 100644 --- a/python/ql/lib/semmle/python/frameworks/Starlette.qll +++ b/python/ql/lib/semmle/python/frameworks/Starlette.qll @@ -33,7 +33,7 @@ module Starlette { API::Node cls() { result = API::moduleImport("starlette").getMember("app") } /** Gets a reference to a Starlette application (an instance of `starlette.app`). */ - API::Node instance() { result = cls().getReturn() } + API::Node instance() { result = cls().getAnInstance() } } /** @@ -47,7 +47,7 @@ module Starlette { /** * Gets the string corresponding to the middleware */ - string middleware_name() { result = this.getArg(0).asExpr().(Name).toString() } + string getMiddlewareName() { result = this.getArg(0).asExpr().(Name).getId() } } /** @@ -57,11 +57,11 @@ module Starlette { /** * Gets the string corresponding to the middleware */ - override string middleware_name() { result = this.getArg(0).asExpr().(Name).toString() } + override string getMiddlewareName() { result = this.getArg(0).asExpr().(Name).getId() } - override DataFlow::Node allowed_origins() { result = this.getArgByName("allow_origins") } + override DataFlow::Node getOrigins() { result = this.getArgByName("allow_origins") } - override DataFlow::Node allowed_credentials() { + override DataFlow::Node getCredentialsAllowed() { result = this.getArgByName("allow_credentials") } @@ -89,32 +89,8 @@ module Starlette { result = ModelOutput::getATypeNode("starlette.middleware.Middleware~Subclass").getASubclass*() } - /** - * A source of instances of `starlette.middleware.Middleware`, extend this class to model new instances. - * - * This can include instantiations of the class, return values from function - * calls, or a special parameter that will be set when functions are called by an external - * library. - * - * Use the predicate `Middleware::instance()` to get references to instances of `starlette.middleware.middleware`. - */ - abstract class InstanceSource extends DataFlow::LocalSourceNode { } - - /** A direct instantiation of `starlette.middleware.Middleware`. */ - class ClassInstantiation extends InstanceSource, DataFlow::CallCfgNode { - ClassInstantiation() { this = classRef().getACall() } - } - /** Gets a reference to an instance of `starlette.middleware.Middleware`. */ - private DataFlow::TypeTrackingNode instance(DataFlow::TypeTracker t) { - t.start() and - result instanceof InstanceSource - or - exists(DataFlow::TypeTracker t2 | result = instance(t2).track(t2, t)) - } - - /** Gets a reference to an instance of `starlette.middleware.Middleware`. */ - DataFlow::Node instance() { instance(DataFlow::TypeTracker::end()).flowsTo(result) } + DataFlow::Node instance() { result = classRef().getACall() } } /** diff --git a/python/ql/src/experimental/Security/CWE-942/CorsMisconfigurationMiddleware.qhelp b/python/ql/src/experimental/Security/CWE-942/CorsMisconfigurationMiddleware.qhelp index 76bcecf6012..21a670019c3 100644 --- a/python/ql/src/experimental/Security/CWE-942/CorsMisconfigurationMiddleware.qhelp +++ b/python/ql/src/experimental/Security/CWE-942/CorsMisconfigurationMiddleware.qhelp @@ -19,8 +19,8 @@ meaning that Peer B can send a request to Peer A that will include the cookies as if the request was executed by the user.

    - That can have dangerous effects if Peer B origin is not restricted correctly. - An example of a dangerous scenario is when Access-Control-Allow-Origin header is set to a value gotten from the Peer B's request + That can have dangerous effects if the origin of Peer B is not restricted correctly. + An example of a dangerous scenario is when Access-Control-Allow-Origin header is set to a value obtained from the request made by Peer B (and not correctly validated), or is set to special values such as * or null. The above values can allow any Peer B to send requests to the misconfigured Peer A on behalf of the user.

    diff --git a/python/ql/src/experimental/Security/CWE-942/CorsMisconfigurationMiddleware.ql b/python/ql/src/experimental/Security/CWE-942/CorsMisconfigurationMiddleware.ql index 3ec8ea509fd..465bc2a3af9 100644 --- a/python/ql/src/experimental/Security/CWE-942/CorsMisconfigurationMiddleware.ql +++ b/python/ql/src/experimental/Security/CWE-942/CorsMisconfigurationMiddleware.ql @@ -6,7 +6,7 @@ * @problem.severity warning * @security-severity 8.8 * @precision high - * @id py/insecure-cors-setting + * @id py/cors-misconfiguration-with-credentials * @tags security * external/cwe/cwe-942 */ @@ -17,23 +17,23 @@ private import semmle.python.dataflow.new.DataFlow predicate containsStar(DataFlow::Node array) { array.asExpr() instanceof List and - array.asExpr().getASubExpression().(StringLiteral).getText() = ["*", "null"] + array.asExpr().getASubExpression().(StringLiteral).getText() in ["*", "null"] or - array.asExpr().(StringLiteral).getText() = ["*", "null"] + array.asExpr().(StringLiteral).getText() in ["*", "null"] } predicate isCorsMiddleware(Http::Server::CorsMiddleware middleware) { - middleware.middleware_name().matches("CORSMiddleware") + middleware.getMiddlewareName() = "CORSMiddleware" } predicate credentialsAllowed(Http::Server::CorsMiddleware middleware) { - middleware.allowed_credentials().asExpr() instanceof True + middleware.getCredentialsAllowed().asExpr() instanceof True } from Http::Server::CorsMiddleware a where credentialsAllowed(a) and - containsStar(a.allowed_origins().getALocalSource()) and + containsStar(a.getOrigins().getALocalSource()) and isCorsMiddleware(a) select a, - "This CORS middleware uses a vulnerable configuration that leaves it open to attacks from arbitrary websites" + "This CORS middleware uses a vulnerable configuration that allows arbitrary websites to make authenticated cross-site requests" From 7657b3e115dc314628d8e89fe0c3954b6cce5937 Mon Sep 17 00:00:00 2001 From: Kevin Stubbings Date: Thu, 12 Sep 2024 21:30:32 -0700 Subject: [PATCH 07/10] Fix tests --- .../CorsMisconfigurationMiddleware.expected | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/python/ql/test/query-tests/Security/CWE-942-CorsMisconfigurationMiddleware/CorsMisconfigurationMiddleware.expected b/python/ql/test/query-tests/Security/CWE-942-CorsMisconfigurationMiddleware/CorsMisconfigurationMiddleware.expected index cc27e83a644..520cf9ab0d2 100644 --- a/python/ql/test/query-tests/Security/CWE-942-CorsMisconfigurationMiddleware/CorsMisconfigurationMiddleware.expected +++ b/python/ql/test/query-tests/Security/CWE-942-CorsMisconfigurationMiddleware/CorsMisconfigurationMiddleware.expected @@ -1,2 +1,2 @@ -| fastapi.py:10:1:16:1 | ControlFlowNode for Attribute() | This CORS middleware uses a vulnerable configuration that leaves it open to attacks from arbitrary websites | -| starlette.py:8:5:8:75 | ControlFlowNode for Middleware() | This CORS middleware uses a vulnerable configuration that leaves it open to attacks from arbitrary websites | +| fastapi.py:10:1:16:1 | ControlFlowNode for Attribute() | This CORS middleware uses a vulnerable configuration that allows arbitrary websites to make authenticated cross-site requests | +| starlette.py:8:5:8:75 | ControlFlowNode for Middleware() | This CORS middleware uses a vulnerable configuration that allows arbitrary websites to make authenticated cross-site requests | From 03f375e43610ac1d471f0a4d67cfcad3b890dd8b Mon Sep 17 00:00:00 2001 From: Kevin Stubbings Date: Fri, 13 Sep 2024 00:21:33 -0700 Subject: [PATCH 08/10] missed some --- python/ql/lib/semmle/python/frameworks/FastApi.qll | 4 ++-- python/ql/lib/semmle/python/frameworks/Starlette.qll | 4 ++-- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/python/ql/lib/semmle/python/frameworks/FastApi.qll b/python/ql/lib/semmle/python/frameworks/FastApi.qll index e0f994491ae..ed28f18a85a 100644 --- a/python/ql/lib/semmle/python/frameworks/FastApi.qll +++ b/python/ql/lib/semmle/python/frameworks/FastApi.qll @@ -67,12 +67,12 @@ module FastApi { /** * Gets the dataflow node corresponding to the allowed CORS methods */ - DataFlow::Node allowed_methods() { result = this.getArgByName("allow_methods") } + DataFlow::Node getMethods() { result = this.getArgByName("allow_methods") } /** * Gets the dataflow node corresponding to the allowed CORS headers */ - DataFlow::Node allowed_headers() { result = this.getArgByName("allow_headers") } + DataFlow::Node getHeaders() { result = this.getArgByName("allow_headers") } } /** diff --git a/python/ql/lib/semmle/python/frameworks/Starlette.qll b/python/ql/lib/semmle/python/frameworks/Starlette.qll index 0dc1260af19..7ced137fcfc 100644 --- a/python/ql/lib/semmle/python/frameworks/Starlette.qll +++ b/python/ql/lib/semmle/python/frameworks/Starlette.qll @@ -68,12 +68,12 @@ module Starlette { /** * Gets the dataflow node corresponding to the allowed CORS methods */ - DataFlow::Node allowed_methods() { result = this.getArgByName("allow_methods") } + DataFlow::Node getMethods() { result = this.getArgByName("allow_methods") } /** * Gets the dataflow node corresponding to the allowed CORS headers */ - DataFlow::Node allowed_headers() { result = this.getArgByName("allow_headers") } + DataFlow::Node getHeaders() { result = this.getArgByName("allow_headers") } } /** From c30332818f4263fd4af1f43eae5be2163120d0ee Mon Sep 17 00:00:00 2001 From: Kevin Stubbings Date: Fri, 13 Sep 2024 00:41:55 -0700 Subject: [PATCH 09/10] Reorder and rename --- python/ql/lib/semmle/python/Concepts.qll | 10 +++++----- .../2024-08-26-Cors-misconfiguration-middleware.md | 2 +- .../Security/CWE-942/CorsMisconfigurationMiddleware.ql | 2 +- 3 files changed, 7 insertions(+), 7 deletions(-) diff --git a/python/ql/lib/semmle/python/Concepts.qll b/python/ql/lib/semmle/python/Concepts.qll index cd8f888eb54..b6f540373a5 100644 --- a/python/ql/lib/semmle/python/Concepts.qll +++ b/python/ql/lib/semmle/python/Concepts.qll @@ -1451,16 +1451,16 @@ module Http { */ abstract string getMiddlewareName(); + /** + * Gets the strings corresponding to the origins allowed by the cors policy + */ + abstract DataFlow::Node getOrigins(); + /** * Gets the boolean value corresponding to if CORS credentials is enabled * (`true`) or disabled (`false`) by this node. */ abstract DataFlow::Node getCredentialsAllowed(); - - /** - * Gets the strings corresponding to the origins allowed by the cors policy - */ - abstract DataFlow::Node getOrigins(); } } diff --git a/python/ql/src/change-notes/2024-08-26-Cors-misconfiguration-middleware.md b/python/ql/src/change-notes/2024-08-26-Cors-misconfiguration-middleware.md index 2576b2e89dc..aa8bc7198b3 100644 --- a/python/ql/src/change-notes/2024-08-26-Cors-misconfiguration-middleware.md +++ b/python/ql/src/change-notes/2024-08-26-Cors-misconfiguration-middleware.md @@ -1,4 +1,4 @@ --- category: newQuery --- -* The `py/insecure-cors-setting` query, which finds insecure CORS middleware configurations. \ No newline at end of file +* The `py/cors-misconfiguration-with-credentials` query, which finds insecure CORS middleware configurations. \ No newline at end of file diff --git a/python/ql/src/experimental/Security/CWE-942/CorsMisconfigurationMiddleware.ql b/python/ql/src/experimental/Security/CWE-942/CorsMisconfigurationMiddleware.ql index 465bc2a3af9..9d2b461b986 100644 --- a/python/ql/src/experimental/Security/CWE-942/CorsMisconfigurationMiddleware.ql +++ b/python/ql/src/experimental/Security/CWE-942/CorsMisconfigurationMiddleware.ql @@ -1,5 +1,5 @@ /** - * @name SOP protection weak with credentials + * @name Cors misconfiguration with credentials * @description Disabling or weakening SOP protection may make the application * vulnerable to a CORS attack. * @kind problem From 01aa63e17070e1cb7f0cd705ec885125ce2068f2 Mon Sep 17 00:00:00 2001 From: Kevin Stubbings Date: Mon, 23 Sep 2024 16:47:10 -0700 Subject: [PATCH 10/10] Add tests --- .../ql/test/experimental/meta/ConceptsTest.qll | 16 +++++++++++++++- .../frameworks/fastapi/middleware.py | 10 ++++++++++ .../frameworks/starlette/Middleware.py | 11 +++++++++++ 3 files changed, 36 insertions(+), 1 deletion(-) create mode 100644 python/ql/test/library-tests/frameworks/fastapi/middleware.py create mode 100644 python/ql/test/library-tests/frameworks/starlette/Middleware.py diff --git a/python/ql/test/experimental/meta/ConceptsTest.qll b/python/ql/test/experimental/meta/ConceptsTest.qll index a53171de88a..05d60f92e21 100644 --- a/python/ql/test/experimental/meta/ConceptsTest.qll +++ b/python/ql/test/experimental/meta/ConceptsTest.qll @@ -632,13 +632,27 @@ module XmlParsingTest implements TestSig { } } +module CorsMiddlewareTest implements TestSig { + string getARelevantTag() { result = "CorsMiddleware" } + + predicate hasActualResult(Location location, string element, string tag, string value) { + exists(location.getFile().getRelativePath()) and + exists(Http::Server::CorsMiddleware cm | + location = cm.getLocation() and + element = cm.toString() and + value = cm.getMiddlewareName().toString() and + tag = "CorsMiddleware" + ) + } +} + import MakeTest, MergeTests5, MergeTests5>, + MergeTests3>, MergeTests5, MergeTests5