add sources and sinks for typeahead.js

This commit is contained in:
Erik Krogh Kristensen
2019-11-21 13:31:05 +01:00
parent 8f3998915b
commit c7235bb372
10 changed files with 978 additions and 0 deletions

View File

@@ -75,6 +75,7 @@ import semmle.javascript.frameworks.DigitalOcean
import semmle.javascript.frameworks.Electron
import semmle.javascript.frameworks.Files
import semmle.javascript.frameworks.Firebase
import semmle.javascript.frameworks.typeahead
import semmle.javascript.frameworks.jQuery
import semmle.javascript.frameworks.LodashUnderscore
import semmle.javascript.frameworks.Logging

View File

@@ -0,0 +1,134 @@
/**
* Provides classes for working with typeahead.js code (https://www.npmjs.com/package/typeahead.js).
*/
import javascript
module Typeahead {
/**
* A reference to the Bloodhound class, which is a utility-class for generating auto-complete suggestions.
* Sometimes these suggestions can originate from remote sources.
*/
class Bloodhound extends DataFlow::SourceNode {
Bloodhound() {
this = DataFlow::moduleImport("typeahead.js/dist/bloodhound.js")
or
this.accessesGlobal("Bloodhound")
}
}
/**
* An instance of the Bloodhound class.
*/
class BloodhoundInstance extends DataFlow::NewNode {
BloodhoundInstance() { this = any(Bloodhound b).getAnInstantiation() }
}
/**
* An instance of of the Bloodhound class that is used to fetch data from a remote server.
*/
class RemoteBloodhoundClientRequest extends ClientRequest::Range, BloodhoundInstance {
string optionName;
DataFlow::ValueNode option;
RemoteBloodhoundClientRequest() {
(optionName = "remote" or optionName = "prefetch") and
option = this.getOptionArgument(0, optionName)
}
/**
* Gets the URL for this Bloodhound instance.
* The Bloodhound API specifies that the "remote" and "prefetch" options are either strings,
* or an object containing an "url" property.
*/
override DataFlow::Node getUrl() {
if exists(option.getALocalSource().getAPropertyWrite("url"))
then result = option.getALocalSource().getAPropertyWrite("url").getRhs()
else result = option
}
override DataFlow::Node getHost() { none() }
override DataFlow::Node getADataNode() { none() }
override DataFlow::Node getAResponseDataNode(string responseType, boolean promise) {
responseType = "json" and
promise = false and
/*
* exists(TypeAheadSuggestionTaintStep step |
* (
* this.flowsTo(step)
* or
* this.getAMethodCall("ttAdapter").flowsTo(step)
* ) and
* step.step(_, result)
* )
* or
*/
// the first occurrence of the responseDataNode can be very disconnected from the instantiation of Bloodhound
// So I do this trick to get a taint-path that is readable to a developer.
// The above (possibly with added type-tracking) would be the correct way, but which gives unhelpful feedback to developers.
result = this
}
}
/**
* A function that generates suggestions to typeahead.
*/
class TypeaheadSuggestionFunction extends DataFlow::FunctionNode {
DataFlow::CallNode typeaheadCall;
TypeaheadSuggestionFunction() {
typeaheadCall = JQuery::objectRef().getAMethodCall("typeahead") and
this = typeaheadCall
.getOptionArgument(1, "templates")
.getALocalSource()
.getAPropertySource("suggestion")
.getAFunctionValue()
}
/**
* Gets the call to typeahead.js where this suggestion function is used.
*/
DataFlow::CallNode getTypeaheadCall() { result = typeaheadCall }
}
/**
* A taint step that models that the `source` in typeahead is used to determine the input to the suggestion function.
*/
class TypeAheadSuggestionTaintStep extends TaintTracking::AdditionalTaintStep {
DataFlow::Node successor;
TypeaheadSuggestionFunction suggestionFunction;
TypeAheadSuggestionTaintStep() {
this = suggestionFunction.getTypeaheadCall().getOptionArgument(1, "source") and
successor = suggestionFunction.getParameter(0)
}
override predicate step(DataFlow::Node pred, DataFlow::Node succ) {
(
pred = this
or
pred = this.getAFunctionValue().getParameter(1).getACall().getAnArgument()
) and
succ = successor
}
}
/**
* A taint step that models a call to `.ttAdapter()` on an instance of Bloodhound.
*/
class BloodHoundAdapterStep extends TaintTracking::AdditionalTaintStep, BloodhoundInstance {
DataFlow::Node successor;
override predicate step(DataFlow::Node pred, DataFlow::Node succ) {
this.flowsTo(pred) and
exists(DataFlow::MethodCallNode call |
succ = call and
call.getReceiver() = pred and
call.getMethodName() = "ttAdapter"
)
}
}
}

View File

@@ -95,6 +95,8 @@ module DomBasedXss {
mcn.getMethodName() = m and
this = mcn.getArgument(1)
)
or
this = any(Typeahead::TypeaheadSuggestionFunction f).getAReturn()
}
}