Compare commits

...

8 Commits

Author SHA1 Message Date
tombolton
08678a990f delete actual test files 2022-04-26 14:03:16 +01:00
tombolton
3c6b9f223b update @name, @tags, and alert message 2022-04-26 11:32:34 +01:00
tombolton
d7ea0b54d3 update actual and expected test output 2022-04-25 15:39:41 +01:00
tombolton
15ac1f2642 modify acronyms in class names to address code scanning warnings 2022-04-25 15:16:04 +01:00
tombolton
59b439fe87 update test output to include new queries 2022-04-25 14:16:48 +01:00
tombolton
7fdb670405 explicitly include individual ATM queries in pack 2022-04-22 14:32:30 +01:00
tombolton
afd4fbbcc0 add new Xss queries to extraction code 2022-04-22 14:28:58 +01:00
Esben Sparre Andreasen
e5bfb94403 Boost StoredXss and XssThroughDomATM
Produced with:
```
javascript/ql$tb boost src/Security/CWE-079/StoredXss.ql XssSink
javascript/ql$ tb boost src/Security/CWE-079/XssThroughDom.ql XssSink
```
2022-04-22 14:28:39 +01:00
14 changed files with 633190 additions and 27 deletions

View File

@@ -0,0 +1,72 @@
/**
* Provides a taint-tracking configuration for reasoning about stored
* cross-site scripting vulnerabilities.
* Is boosted by ATM.
*/
import javascript
import AdaptiveThreatModeling
import semmle.javascript.security.dataflow.Xss::StoredXss
/**
* This module provides logic to filter candidate sinks to those which are likely XSS sinks.
*/
module SinkEndpointFilter {
private import StandardEndpointFilters as StandardEndpointFilters
/**
* Provides a set of reasons why a given data flow node should be excluded as a sink candidate.
*
* If this predicate has no results for a sink candidate `n`, then we should treat `n` as an
* effective sink.
*/
string getAReasonSinkExcluded(DataFlow::Node sinkCandidate) {
result = StandardEndpointFilters::getAReasonSinkExcluded(sinkCandidate)
}
}
class StoredXssAtmConfig extends ATMConfig {
StoredXssAtmConfig() { this = "StoredXssAtmConfig" }
override predicate isKnownSource(DataFlow::Node source) { source instanceof Source }
override predicate isKnownSink(DataFlow::Node sink) { sink instanceof Sink }
override predicate isEffectiveSink(DataFlow::Node sinkCandidate) {
not exists(SinkEndpointFilter::getAReasonSinkExcluded(sinkCandidate))
}
override EndpointType getASinkEndpointType() { result instanceof XssSinkType }
}
/**
* A taint-tracking configuration for reasoning about XSS.
*/
class Configuration extends TaintTracking::Configuration {
Configuration() { this = "StoredXssAtmConfig" }
override predicate isSource(DataFlow::Node source) { source instanceof Source }
override predicate isSink(DataFlow::Node sink) {
(sink instanceof Sink or any(StoredXssAtmConfig cfg).isEffectiveSink(sink))
}
override predicate isSanitizer(DataFlow::Node node) {
super.isSanitizer(node) or
node instanceof Sanitizer
}
override predicate isSanitizerGuard(TaintTracking::SanitizerGuardNode guard) {
guard instanceof SanitizerGuard
}
}
/** A file name, considered as a flow source for stored XSS. */
class FileNameSourceAsSource extends Source {
FileNameSourceAsSource() { this instanceof FileNameSource }
}
/** An instance of user-controlled torrent information, considered as a flow source for stored XSS. */
class UserControlledTorrentInfoAsSource extends Source {
UserControlledTorrentInfoAsSource() { this instanceof ParseTorrent::UserControlledTorrentInfo }
}

View File

@@ -0,0 +1,72 @@
/**
* Provides a taint-tracking configuration for reasoning about
* cross-site scripting vulnerabilities through the DOM.
* Is boosted by ATM.
*/
import javascript
import AdaptiveThreatModeling
private import semmle.javascript.dataflow.InferredTypes
import semmle.javascript.security.dataflow.Xss::XssThroughDom
private import semmle.javascript.security.dataflow.XssThroughDomCustomizations::XssThroughDom
private import semmle.javascript.security.dataflow.Xss::DomBasedXss as DomBasedXss
private import semmle.javascript.security.dataflow.UnsafeJQueryPluginCustomizations::UnsafeJQueryPlugin as UnsafeJQuery
/**
* This module provides logic to filter candidate sinks to those which are likely XSS sinks.
*/
module SinkEndpointFilter {
private import StandardEndpointFilters as StandardEndpointFilters
/**
* Provides a set of reasons why a given data flow node should be excluded as a sink candidate.
*
* If this predicate has no results for a sink candidate `n`, then we should treat `n` as an
* effective sink.
*/
string getAReasonSinkExcluded(DataFlow::Node sinkCandidate) {
result = StandardEndpointFilters::getAReasonSinkExcluded(sinkCandidate)
}
}
class XssThroughDomAtmConfig extends ATMConfig {
XssThroughDomAtmConfig() { this = "XssThroughDomAtmConfig" }
override predicate isKnownSource(DataFlow::Node source) { source instanceof Source }
override predicate isKnownSink(DataFlow::Node sink) { sink instanceof DomBasedXss::Sink }
override predicate isEffectiveSink(DataFlow::Node sinkCandidate) {
not exists(SinkEndpointFilter::getAReasonSinkExcluded(sinkCandidate))
}
override EndpointType getASinkEndpointType() { result instanceof XssSinkType }
}
/**
* A taint-tracking configuration for reasoning about XSS through the DOM.
*/
class Configuration extends TaintTracking::Configuration {
Configuration() { this = "XssThroughDomAtmConfig" }
override predicate isSource(DataFlow::Node source) { source instanceof Source }
override predicate isSink(DataFlow::Node sink) {
(sink instanceof DomBasedXss::Sink or any(XssThroughDomAtmConfig cfg).isEffectiveSink(sink))
}
override predicate isSanitizer(DataFlow::Node node) {
super.isSanitizer(node) or
node instanceof DomBasedXss::Sanitizer
}
override predicate isSanitizerGuard(TaintTracking::SanitizerGuardNode guard) {
guard instanceof TypeTestGuard or
guard instanceof UnsafeJQuery::PropertyPresenceSanitizer or
guard instanceof DomBasedXss::SanitizerGuard
}
override predicate isSanitizerEdge(DataFlow::Node pred, DataFlow::Node succ) {
DomBasedXss::isOptionallySanitizedEdge(pred, succ)
}
}

View File

@@ -16,8 +16,10 @@ import experimental.adaptivethreatmodeling.EndpointTypes
import experimental.adaptivethreatmodeling.FilteringReasons
import experimental.adaptivethreatmodeling.NosqlInjectionATM as NosqlInjectionATM
import experimental.adaptivethreatmodeling.SqlInjectionATM as SqlInjectionATM
import experimental.adaptivethreatmodeling.StoredXssATM as StoredXssATM
import experimental.adaptivethreatmodeling.TaintedPathATM as TaintedPathATM
import experimental.adaptivethreatmodeling.XssATM as XssATM
import experimental.adaptivethreatmodeling.XssThroughDomATM as XssThroughDomATM
import Labels
import NoFeaturizationRestrictionsConfig
import Queries
@@ -29,9 +31,13 @@ AtmConfig getAtmCfg(Query query) {
or
query instanceof SqlInjectionQuery and result instanceof SqlInjectionATM::SqlInjectionAtmConfig
or
query instanceof TaintedPathQuery and result instanceof TaintedPathATM::TaintedPathAtmConfig
query instanceof StoredXssQuery and result instanceof StoredXssATM::StoredXssAtmConfig
or
query instanceof XssQuery and result instanceof XssATM::DomBasedXssAtmConfig
query instanceof TaintedPathQuery and result instanceof TaintedPathATM::TaintedPathATMConfig
or
query instanceof XssQuery and result instanceof XssATM::DomBasedXssATMConfig
or
query instanceof XssThroughDomQuery and result instanceof XssThroughDomATM::XssThroughDomAtmConfig
}
/** DEPRECATED: Alias for getAtmCfg */
@@ -46,6 +52,10 @@ DataFlow::Configuration getDataFlowCfg(Query query) {
query instanceof TaintedPathQuery and result instanceof TaintedPathATM::Configuration
or
query instanceof XssQuery and result instanceof XssATM::Configuration
or
query instanceof StoredXssQuery and result instanceof StoredXssATM::Configuration
or
query instanceof XssThroughDomQuery and result instanceof XssThroughDomATM::Configuration
}
/** Gets a known sink for the specified query. */

View File

@@ -8,6 +8,8 @@ import experimental.adaptivethreatmodeling.SqlInjectionATM as SqlInjectionATM
import experimental.adaptivethreatmodeling.NosqlInjectionATM as NosqlInjectionATM
import experimental.adaptivethreatmodeling.TaintedPathATM as TaintedPathATM
import experimental.adaptivethreatmodeling.XssATM as XssATM
import experimental.adaptivethreatmodeling.StoredXssATM as StoredXssATM
import experimental.adaptivethreatmodeling.XssThroughDomATM as XssThroughDomATM
import experimental.adaptivethreatmodeling.AdaptiveThreatModeling
from string queryName, AtmConfig c, EndpointType e
@@ -23,6 +25,12 @@ where
c instanceof TaintedPathATM::TaintedPathAtmConfig
or
queryName = "XssATM.ql" and c instanceof XssATM::DomBasedXssAtmConfig
or
queryName = "StoredXssATM.ql" and
c instanceof StoredXssATM::StoredXssAtmConfig
or
queryName = "XssThroughDomATM.ql" and
c instanceof XssThroughDomATM::XssThroughDomAtmConfig
) and
e = c.getASinkEndpointType()
select queryName, e.getEncoding() as endpointTypeEncoded

View File

@@ -8,7 +8,9 @@ newtype TQuery =
TNosqlInjectionQuery() or
TSqlInjectionQuery() or
TTaintedPathQuery() or
TXssQuery()
TXssQuery() or
TStoredXssQuery() or
TXssThroughDomQuery()
abstract class Query extends TQuery {
abstract string getName();
@@ -24,6 +26,10 @@ class SqlInjectionQuery extends Query, TSqlInjectionQuery {
override string getName() { result = "SqlInjection" }
}
class StoredXssQuery extends Query, TStoredXssQuery {
override string getName() { result = "StoredXss" }
}
class TaintedPathQuery extends Query, TTaintedPathQuery {
override string getName() { result = "TaintedPath" }
}
@@ -31,3 +37,7 @@ class TaintedPathQuery extends Query, TTaintedPathQuery {
class XssQuery extends Query, TXssQuery {
override string getName() { result = "Xss" }
}
class XssThroughDomQuery extends Query, TXssThroughDomQuery {
override string getName() { result = "XssThroughDom" }
}

View File

@@ -0,0 +1,29 @@
/**
* For internal use only.
*
* @name Stored cross-site scripting (experimental)
* @description Using uncontrolled stored values in HTML allows for a stored cross-site scripting vulnerability.
* @kind path-problem
* @scored
* @problem.severity error
* @security-severity 6.1
* @id adaptive-threat-modeling/js/stored-xss
* @tags experimental security external/cwe/cwe-079 external/cwe/cwe-116
*/
import experimental.adaptivethreatmodeling.StoredXssATM
import ATM::ResultsInfo
import DataFlow::PathGraph
from
DataFlow::Configuration cfg, DataFlow::PathNode source, DataFlow::PathNode sink, float score,
string scoreString
where
cfg.hasFlowPath(source, sink) and
not isFlowLikelyInBaseQuery(source.getNode(), sink.getNode()) and
score = getScoreForFlow(source.getNode(), sink.getNode()) and
scoreString = getScoreStringForFlow(source.getNode(), sink.getNode())
select sink.getNode(), source, sink,
"(Experimental) This may be a js/stored-xss result due to $@ " +
getAdditionalAlertInfo(source.getNode(), sink.getNode()), source.getNode(),
" Identified using machine learning"

View File

@@ -0,0 +1,29 @@
/**
* For internal use only.
*
* @name DOM text reinterpreted as HTML (experimental)
* @description Reinterpreting text from the DOM as HTML can lead to a cross-site scripting vulnerability.
* @kind path-problem
* @scored
* @problem.severity warning
* @security-severity 6.1
* @id adaptive-threat-modeling/js/xss-through-dom
* @tags experimental security external/cwe/cwe-079 external/cwe/cwe-116
*/
import experimental.adaptivethreatmodeling.XssThroughDomATM
import ATM::ResultsInfo
import DataFlow::PathGraph
from
DataFlow::Configuration cfg, DataFlow::PathNode source, DataFlow::PathNode sink, float score,
string scoreString
where
cfg.hasFlowPath(source, sink) and
not isFlowLikelyInBaseQuery(source.getNode(), sink.getNode()) and
score = getScoreForFlow(source.getNode(), sink.getNode()) and
scoreString = getScoreStringForFlow(source.getNode(), sink.getNode())
select sink.getNode(), source, sink,
"(Experimental) This may be a js/xss-through-dom result due to $@ " +
getAdditionalAlertInfo(source.getNode(), sink.getNode()), source.getNode(),
" Identified using machine learning."

View File

@@ -1,2 +1,7 @@
- description: ATM boosted Code Scanning queries for JavaScript
- queries: .
- include:
query filename: NosqlInjectionATM.ql
query filename: SqlInjectionATM.ql
query filename: TaintedPathATM.ql
query filename: XssATM.ql

View File

@@ -13,6 +13,11 @@ endpoints
| index.js:15:17:15:32 | req.body.isAdmin | SqlInjection | isExcludedFromEndToEndEvaluation | false | boolean |
| index.js:15:17:15:32 | req.body.isAdmin | SqlInjection | notASinkReason | LoggerMethod | string |
| index.js:15:17:15:32 | req.body.isAdmin | SqlInjection | sinkLabel | NotASink | string |
| index.js:15:17:15:32 | req.body.isAdmin | StoredXss | hasFlowFromSource | false | boolean |
| index.js:15:17:15:32 | req.body.isAdmin | StoredXss | isConstantExpression | false | boolean |
| index.js:15:17:15:32 | req.body.isAdmin | StoredXss | isExcludedFromEndToEndEvaluation | false | boolean |
| index.js:15:17:15:32 | req.body.isAdmin | StoredXss | notASinkReason | LoggerMethod | string |
| index.js:15:17:15:32 | req.body.isAdmin | StoredXss | sinkLabel | NotASink | string |
| index.js:15:17:15:32 | req.body.isAdmin | TaintedPath | hasFlowFromSource | true | boolean |
| index.js:15:17:15:32 | req.body.isAdmin | TaintedPath | isConstantExpression | false | boolean |
| index.js:15:17:15:32 | req.body.isAdmin | TaintedPath | isExcludedFromEndToEndEvaluation | false | boolean |
@@ -23,6 +28,11 @@ endpoints
| index.js:15:17:15:32 | req.body.isAdmin | Xss | isExcludedFromEndToEndEvaluation | false | boolean |
| index.js:15:17:15:32 | req.body.isAdmin | Xss | notASinkReason | LoggerMethod | string |
| index.js:15:17:15:32 | req.body.isAdmin | Xss | sinkLabel | NotASink | string |
| index.js:15:17:15:32 | req.body.isAdmin | XssThroughDom | hasFlowFromSource | false | boolean |
| index.js:15:17:15:32 | req.body.isAdmin | XssThroughDom | isConstantExpression | false | boolean |
| index.js:15:17:15:32 | req.body.isAdmin | XssThroughDom | isExcludedFromEndToEndEvaluation | false | boolean |
| index.js:15:17:15:32 | req.body.isAdmin | XssThroughDom | notASinkReason | LoggerMethod | string |
| index.js:15:17:15:32 | req.body.isAdmin | XssThroughDom | sinkLabel | NotASink | string |
| index.js:20:13:20:31 | { 'isAdmin': true } | NosqlInjection | hasFlowFromSource | false | boolean |
| index.js:20:13:20:31 | { 'isAdmin': true } | NosqlInjection | isConstantExpression | false | boolean |
| index.js:20:13:20:31 | { 'isAdmin': true } | NosqlInjection | isExcludedFromEndToEndEvaluation | false | boolean |
@@ -43,6 +53,12 @@ endpoints
| index.js:83:10:85:3 | {\\n " ... ar,\\n } | SqlInjection | notASinkReason | ClientRequest | string |
| index.js:83:10:85:3 | {\\n " ... ar,\\n } | SqlInjection | notASinkReason | JQueryArgument | string |
| index.js:83:10:85:3 | {\\n " ... ar,\\n } | SqlInjection | sinkLabel | NotASink | string |
| index.js:83:10:85:3 | {\\n " ... ar,\\n } | StoredXss | hasFlowFromSource | false | boolean |
| index.js:83:10:85:3 | {\\n " ... ar,\\n } | StoredXss | isConstantExpression | false | boolean |
| index.js:83:10:85:3 | {\\n " ... ar,\\n } | StoredXss | isExcludedFromEndToEndEvaluation | false | boolean |
| index.js:83:10:85:3 | {\\n " ... ar,\\n } | StoredXss | notASinkReason | ClientRequest | string |
| index.js:83:10:85:3 | {\\n " ... ar,\\n } | StoredXss | notASinkReason | JQueryArgument | string |
| index.js:83:10:85:3 | {\\n " ... ar,\\n } | StoredXss | sinkLabel | NotASink | string |
| index.js:83:10:85:3 | {\\n " ... ar,\\n } | TaintedPath | hasFlowFromSource | false | boolean |
| index.js:83:10:85:3 | {\\n " ... ar,\\n } | TaintedPath | isConstantExpression | false | boolean |
| index.js:83:10:85:3 | {\\n " ... ar,\\n } | TaintedPath | isExcludedFromEndToEndEvaluation | false | boolean |
@@ -55,6 +71,12 @@ endpoints
| index.js:83:10:85:3 | {\\n " ... ar,\\n } | Xss | notASinkReason | ClientRequest | string |
| index.js:83:10:85:3 | {\\n " ... ar,\\n } | Xss | notASinkReason | JQueryArgument | string |
| index.js:83:10:85:3 | {\\n " ... ar,\\n } | Xss | sinkLabel | NotASink | string |
| index.js:83:10:85:3 | {\\n " ... ar,\\n } | XssThroughDom | hasFlowFromSource | false | boolean |
| index.js:83:10:85:3 | {\\n " ... ar,\\n } | XssThroughDom | isConstantExpression | false | boolean |
| index.js:83:10:85:3 | {\\n " ... ar,\\n } | XssThroughDom | isExcludedFromEndToEndEvaluation | false | boolean |
| index.js:83:10:85:3 | {\\n " ... ar,\\n } | XssThroughDom | notASinkReason | ClientRequest | string |
| index.js:83:10:85:3 | {\\n " ... ar,\\n } | XssThroughDom | notASinkReason | JQueryArgument | string |
| index.js:83:10:85:3 | {\\n " ... ar,\\n } | XssThroughDom | sinkLabel | NotASink | string |
| index.js:84:12:84:18 | foo.bar | NosqlInjection | hasFlowFromSource | false | boolean |
| index.js:84:12:84:18 | foo.bar | NosqlInjection | isConstantExpression | false | boolean |
| index.js:84:12:84:18 | foo.bar | NosqlInjection | isExcludedFromEndToEndEvaluation | false | boolean |
@@ -65,6 +87,11 @@ endpoints
| index.js:84:12:84:18 | foo.bar | SqlInjection | isExcludedFromEndToEndEvaluation | false | boolean |
| index.js:84:12:84:18 | foo.bar | SqlInjection | notASinkReason | ClientRequest | string |
| index.js:84:12:84:18 | foo.bar | SqlInjection | sinkLabel | NotASink | string |
| index.js:84:12:84:18 | foo.bar | StoredXss | hasFlowFromSource | false | boolean |
| index.js:84:12:84:18 | foo.bar | StoredXss | isConstantExpression | false | boolean |
| index.js:84:12:84:18 | foo.bar | StoredXss | isExcludedFromEndToEndEvaluation | false | boolean |
| index.js:84:12:84:18 | foo.bar | StoredXss | notASinkReason | ClientRequest | string |
| index.js:84:12:84:18 | foo.bar | StoredXss | sinkLabel | NotASink | string |
| index.js:84:12:84:18 | foo.bar | TaintedPath | hasFlowFromSource | false | boolean |
| index.js:84:12:84:18 | foo.bar | TaintedPath | isConstantExpression | false | boolean |
| index.js:84:12:84:18 | foo.bar | TaintedPath | isExcludedFromEndToEndEvaluation | false | boolean |
@@ -75,6 +102,11 @@ endpoints
| index.js:84:12:84:18 | foo.bar | Xss | isExcludedFromEndToEndEvaluation | false | boolean |
| index.js:84:12:84:18 | foo.bar | Xss | notASinkReason | ClientRequest | string |
| index.js:84:12:84:18 | foo.bar | Xss | sinkLabel | NotASink | string |
| index.js:84:12:84:18 | foo.bar | XssThroughDom | hasFlowFromSource | false | boolean |
| index.js:84:12:84:18 | foo.bar | XssThroughDom | isConstantExpression | false | boolean |
| index.js:84:12:84:18 | foo.bar | XssThroughDom | isExcludedFromEndToEndEvaluation | false | boolean |
| index.js:84:12:84:18 | foo.bar | XssThroughDom | notASinkReason | ClientRequest | string |
| index.js:84:12:84:18 | foo.bar | XssThroughDom | sinkLabel | NotASink | string |
tokenFeatures
| index.js:9:15:9:45 | { 'isAd ... Admin } | argumentIndex | 0 |
| index.js:9:15:9:45 | { 'isAd ... Admin } | calleeAccessPath | mongoose model find |