diff --git a/javascript/ql/lib/semmle/javascript/frameworks/Cors.qll b/javascript/ql/lib/semmle/javascript/frameworks/Cors.qll new file mode 100644 index 00000000000..57ec67b1f38 --- /dev/null +++ b/javascript/ql/lib/semmle/javascript/frameworks/Cors.qll @@ -0,0 +1,24 @@ +/** + * Provides classes for working with Cors connectors. + */ + +import javascript + +/** Provides classes modeling [cors package](https://npmjs.com/package/cors) */ +module Cors { + class Cors extends DataFlow::CallNode { + /** Get an instanceof of `cors` */ + Cors() { this = DataFlow::moduleImport("cors").getAnInvocation() } + + /** Get Cors configuration */ + DataFlow::Node getCorsArgument() { result = this.getArgument(0) } + + /** Holds if cors is using default configuration */ + predicate isDefault() { this.getNumArgument() = 0 } + + /** The value of origin */ + DataFlow::Node getOrigin() { + result = this.getCorsArgument().getALocalSource().getAPropertyWrite("origin").getRhs() + } + } +} diff --git a/javascript/ql/lib/semmle/javascript/frameworks/Express.qll b/javascript/ql/lib/semmle/javascript/frameworks/Express.qll index c39d19d4375..dadebb31485 100644 --- a/javascript/ql/lib/semmle/javascript/frameworks/Express.qll +++ b/javascript/ql/lib/semmle/javascript/frameworks/Express.qll @@ -5,6 +5,7 @@ import javascript import semmle.javascript.frameworks.HTTP import semmle.javascript.frameworks.ExpressModules +import semmle.javascript.frameworks.Cors private import semmle.javascript.dataflow.InferredTypes private import semmle.javascript.frameworks.ConnectExpressShared::ConnectExpressShared @@ -1071,4 +1072,23 @@ module Express { override predicate definitelyResumesDispatch() { none() } } + + class CorsConfiguration extends DataFlow::MethodCallNode { + /** Get an `app.use` with a cors object as argument */ + CorsConfiguration() { + this = appCreation().getAMethodCall("use") and this.getArgument(0) instanceof Cors::Cors + } + + /** Get Cors */ + private Cors::Cors cors() { result = this.getArgument(0).(Cors::Cors) } + + /** Get Cors configuration */ + DataFlow::Node getCorsArgument() { result = cors().getCorsArgument() } + + /** Holds if cors is using default configuration */ + predicate isDefault() { cors().isDefault() } + + /** Get Cors origin value */ + DataFlow::Node getOrigin() { result = cors().getOrigin() } + } } diff --git a/javascript/ql/lib/semmle/javascript/security/dataflow/CorsPermissiveConfigurationCustomizations.qll b/javascript/ql/lib/semmle/javascript/security/dataflow/CorsPermissiveConfigurationCustomizations.qll index 14031e75a29..a05573ece74 100644 --- a/javascript/ql/lib/semmle/javascript/security/dataflow/CorsPermissiveConfigurationCustomizations.qll +++ b/javascript/ql/lib/semmle/javascript/security/dataflow/CorsPermissiveConfigurationCustomizations.qll @@ -44,4 +44,11 @@ module CorsPermissiveConfiguration { ) } } + + /** + * The value of cors origin when initializing the application. + */ + class ExpressCors extends Sink, DataFlow::ValueNode { + ExpressCors() { exists(Express::CorsConfiguration config | this = config.getOrigin()) } + } } diff --git a/javascript/ql/test/experimental/Security/CWE-942/tst.js b/javascript/ql/test/experimental/Security/CWE-942/apollo-test.js similarity index 100% rename from javascript/ql/test/experimental/Security/CWE-942/tst.js rename to javascript/ql/test/experimental/Security/CWE-942/apollo-test.js diff --git a/javascript/ql/test/experimental/Security/CWE-942/express-test b/javascript/ql/test/experimental/Security/CWE-942/express-test new file mode 100644 index 00000000000..3ad31a6a31a --- /dev/null +++ b/javascript/ql/test/experimental/Security/CWE-942/express-test @@ -0,0 +1,36 @@ +const cors = require('cors'); +var express = require('express'); + +var https = require('https'), + url = require('url'); + +var server = https.createServer(function () { }); + +server.on('request', function (req, res) { + let user_origin = url.parse(req.url, true).query.origin; + + // BAD: CORS too permissive, default value is * + var app1 = express(); + app1.use(cors()); + + // GOOD: restrictive CORS + var app2 = express(); + var corsOptions2 = { + origin: ["https://example1.com", "https://example2.com"], + }; + app2.use(cors(corsOptions2)); + + // BAD: CORS too permissive + var app3 = express(); + var corsOption3 = { + origin: '*' + }; + app3.use(cors(corsOption3)); + + // BAD: CORS is controlled by user + var app4 = express(); + var corsOption4 = { + origin: user_origin + }; + app4.use(cors(corsOption4)); +}); \ No newline at end of file