Add initial query for CWE-942

This commit is contained in:
Maiky
2023-09-29 18:25:58 +02:00
parent 67a0112fcb
commit e171123589
8 changed files with 231 additions and 0 deletions

View File

@@ -71,6 +71,7 @@ import semmle.javascript.frameworks.ActionsLib
import semmle.javascript.frameworks.Angular2
import semmle.javascript.frameworks.AngularJS
import semmle.javascript.frameworks.Anser
import semmle.javascript.frameworks.ApolloGraphQL
import semmle.javascript.frameworks.AsyncPackage
import semmle.javascript.frameworks.AWS
import semmle.javascript.frameworks.Azure

View File

@@ -0,0 +1,59 @@
/**
* Provides classes for working with Apollo GraphQL connectors.
*/
import javascript
/** Provides classes modeling concepts of Apollo GraphQL. */
module ApolloGraphQL {
/** A string-valued expression that is interpreted as a Apollo GraphQL query. */
abstract class GraphQLString extends DataFlow::Node { }
/** A string-valued expression that is interpreted as a Apollo GraphQL query. */
abstract class ApolloGraphQLServer extends DataFlow::Node { }
}
/**
* Provides classes modeling the apollo packages [@apollo/server](https://npmjs.com/package/@apollo/server`)
*/
private module Apollo {
/** Get an instanceof of `Apollo` */
private API::Node apollo() {
result =
API::moduleImport([
"@apollo/server", "apollo/server", "@apollo/apollo-server-express",
"@apollo/apollo-server-core", "apollo-server", "apollo-server-express"
]).getMember("ApolloServer")
}
/** Get an instanceof of `gql` */
private API::Node gql() {
result =
API::moduleImport([
"@apollo/server", "apollo/server", "@apollo/apollo-server-express",
"@apollo/apollo-server-core", "apollo-server", "apollo-server-express"
]).getMember("gql")
}
/** A string that is interpreted as a GraphQL query by a `octokit` package. */
private class ApolloGraphQLString extends GraphQL::GraphQLString {
ApolloGraphQLString() { this = gql().getACall() }
}
/** A string that is interpreted as a GraphQL query by a `graphql` package. */
private class ApolloServer extends ApolloGraphQL::ApolloGraphQLServer {
ApolloServer() {
this = apollo().getAnInstantiation()
// or this = apollo().getAnInstantiation().getOptionArgument(0, "cors")
}
predicate isPermissive() {
this.(DataFlow::NewNode)
.getOptionArgument(0, "cors")
.getALocalSource()
.getAPropertyWrite("origin")
.getRhs()
.mayHaveBooleanValue(true)
}
}
}

View File

@@ -0,0 +1,50 @@
/**
* Provides default sources, sinks and sanitizers for reasoning about
* overly permissive CORS configurations, as well as
* extension points for adding your own.
*/
import javascript
module CorsPermissiveConfiguration {
/**
* A data flow source for permissive CORS configuration.
*/
abstract class Source extends DataFlow::Node { }
/**
* A data flow sink for permissive CORS configuration.
*/
abstract class Sink extends DataFlow::Node { }
/**
* A sanitizer for permissive CORS configuration.
*/
abstract class Sanitizer extends DataFlow::Node { }
/** A source of remote user input, considered as a flow source for CORS misconfiguration. */
class RemoteFlowSourceAsSource extends Source instanceof RemoteFlowSource {
RemoteFlowSourceAsSource() { not this instanceof ClientSideRemoteFlowSource }
}
/** true and null are considered bad values */
class BadValues extends Source instanceof DataFlow::Node {
BadValues() { this.mayHaveBooleanValue(true) or this.asExpr() instanceof NullLiteral }
}
/**
* The value of cors origin when initializing the application.
*/
class CorsApolloServer extends Sink, DataFlow::ValueNode {
CorsApolloServer() {
exists(ApolloGraphQL::ApolloGraphQLServer agql |
this =
agql.(DataFlow::NewNode)
.getOptionArgument(0, "cors")
.getALocalSource()
.getAPropertyWrite("origin")
.getRhs()
)
}
}
}

View File

@@ -0,0 +1,28 @@
/**
* Provides a dataflow taint tracking configuration for reasoning
* about overly permissive CORS configurations.
*
* Note, for performance reasons: only import this file if
* `CorsPermissiveConfiguration::Configuration` is needed,
* otherwise `CorsPermissiveConfigurationCustomizations` should
* be imported instead.
*/
import javascript
import CorsPermissiveConfigurationCustomizations::CorsPermissiveConfiguration
/**
* A data flow configuration for overly permissive CORS configuration.
*/
class Configuration extends TaintTracking::Configuration {
Configuration() { this = "CorsPermissiveConfiguration" }
override predicate isSource(DataFlow::Node source) { source instanceof Source }
override predicate isSink(DataFlow::Node sink) { sink instanceof Sink }
override predicate isSanitizer(DataFlow::Node node) {
super.isSanitizer(node) or
node instanceof Sanitizer
}
}

View File

@@ -0,0 +1,20 @@
/**
* @name overly CORS configuration
* @description Misconfiguration of CORS HTTP headers allows CSRF attacks.
* @kind path-problem
* @problem.severity error
* @security-severity 7.5
* @precision high
* @id js/cors-misconfiguration
* @tags security
* external/cwe/cwe-942
*/
import javascript
import semmle.javascript.security.dataflow.CorsPermissiveConfigurationQuery
import DataFlow::PathGraph
from Configuration cfg, DataFlow::PathNode source, DataFlow::PathNode sink
where cfg.hasFlowPath(source, sink)
select sink.getNode(), source, sink, "$@ misconfiguration due to a $@.", sink.getNode(),
"CORS Origin", source.getNode(), "too permissive or user controlled value"

View File

@@ -0,0 +1,39 @@
nodes
| tst.js:8:9:8:59 | user_origin |
| tst.js:8:23:8:46 | url.par ... , true) |
| tst.js:8:23:8:52 | url.par ... ).query |
| tst.js:8:23:8:59 | url.par ... .origin |
| tst.js:8:33:8:39 | req.url |
| tst.js:8:33:8:39 | req.url |
| tst.js:8:42:8:45 | true |
| tst.js:8:42:8:45 | true |
| tst.js:11:25:11:28 | true |
| tst.js:11:25:11:28 | true |
| tst.js:11:25:11:28 | true |
| tst.js:16:25:16:28 | true |
| tst.js:16:25:16:28 | true |
| tst.js:16:25:16:28 | true |
| tst.js:26:25:26:28 | null |
| tst.js:26:25:26:28 | null |
| tst.js:26:25:26:28 | null |
| tst.js:31:25:31:35 | user_origin |
| tst.js:31:25:31:35 | user_origin |
edges
| tst.js:8:9:8:59 | user_origin | tst.js:31:25:31:35 | user_origin |
| tst.js:8:9:8:59 | user_origin | tst.js:31:25:31:35 | user_origin |
| tst.js:8:23:8:46 | url.par ... , true) | tst.js:8:23:8:52 | url.par ... ).query |
| tst.js:8:23:8:52 | url.par ... ).query | tst.js:8:23:8:59 | url.par ... .origin |
| tst.js:8:23:8:59 | url.par ... .origin | tst.js:8:9:8:59 | user_origin |
| tst.js:8:33:8:39 | req.url | tst.js:8:23:8:46 | url.par ... , true) |
| tst.js:8:33:8:39 | req.url | tst.js:8:23:8:46 | url.par ... , true) |
| tst.js:8:42:8:45 | true | tst.js:8:23:8:46 | url.par ... , true) |
| tst.js:8:42:8:45 | true | tst.js:8:23:8:46 | url.par ... , true) |
| tst.js:11:25:11:28 | true | tst.js:11:25:11:28 | true |
| tst.js:16:25:16:28 | true | tst.js:16:25:16:28 | true |
| tst.js:26:25:26:28 | null | tst.js:26:25:26:28 | null |
#select
| tst.js:11:25:11:28 | true | tst.js:11:25:11:28 | true | tst.js:11:25:11:28 | true | $@ misconfiguration due to a $@. | tst.js:11:25:11:28 | true | CORS Origin | tst.js:11:25:11:28 | true | too permissive or user controlled value |
| tst.js:16:25:16:28 | true | tst.js:16:25:16:28 | true | tst.js:16:25:16:28 | true | $@ misconfiguration due to a $@. | tst.js:16:25:16:28 | true | CORS Origin | tst.js:16:25:16:28 | true | too permissive or user controlled value |
| tst.js:26:25:26:28 | null | tst.js:26:25:26:28 | null | tst.js:26:25:26:28 | null | $@ misconfiguration due to a $@. | tst.js:26:25:26:28 | null | CORS Origin | tst.js:26:25:26:28 | null | too permissive or user controlled value |
| tst.js:31:25:31:35 | user_origin | tst.js:8:33:8:39 | req.url | tst.js:31:25:31:35 | user_origin | $@ misconfiguration due to a $@. | tst.js:31:25:31:35 | user_origin | CORS Origin | tst.js:8:33:8:39 | req.url | too permissive or user controlled value |
| tst.js:31:25:31:35 | user_origin | tst.js:8:42:8:45 | true | tst.js:31:25:31:35 | user_origin | $@ misconfiguration due to a $@. | tst.js:31:25:31:35 | user_origin | CORS Origin | tst.js:8:42:8:45 | true | too permissive or user controlled value |

View File

@@ -0,0 +1 @@
Security/CWE-942/CorsPermissiveConfiguration.ql

View File

@@ -0,0 +1,33 @@
import { ApolloServer } from 'apollo-server';
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: attacker can choose the value of origin
const server_1 = new ApolloServer({
cors: { origin: true }
});
// BAD: CORS too permissive
const server_2 = new ApolloServer({
cors: { origin: true }
});
// GOOD: restrictive CORS
const server_3 = new ApolloServer({
cors: false
});
// BAD: CORS too permissive
const server_4 = new ApolloServer({
cors: { origin: null }
});
// BAD: CORS is controlled by user
const server_5 = new ApolloServer({
cors: { origin: user_origin }
});
});