Merge pull request #5908 from erik-krogh/protoLib

JS: Add library input as source to js/prototype-polluting-assignment
This commit is contained in:
Erik Krogh Kristensen
2021-11-11 12:04:05 +01:00
committed by GitHub
13 changed files with 520 additions and 63 deletions

View File

@@ -0,0 +1,3 @@
lgtm,codescanning
* The `js/prototype-polluting-assignment` query now flags assignments that may modify
the built-in Object prototype where the property name originates from library input.

View File

@@ -11,11 +11,36 @@ private import semmle.javascript.internal.CachedStages
* Gets a parameter that is a library input to a top-level package.
*/
cached
DataFlow::ParameterNode getALibraryInputParameter() {
DataFlow::SourceNode getALibraryInputParameter() {
Stages::Taint::ref() and
exists(int bound, DataFlow::FunctionNode func |
func = getAValueExportedByPackage().getABoundFunctionValue(bound) and
func = getAValueExportedByPackage().getABoundFunctionValue(bound)
|
result = func.getParameter(any(int arg | arg >= bound))
or
result = getAnArgumentsRead(func.getFunction())
)
}
private DataFlow::SourceNode getAnArgumentsRead(Function func) {
exists(DataFlow::PropRead read |
not read.getPropertyName() = "length" and
result = read
|
read.getBase() = func.getArgumentsVariable().getAnAccess().flow()
or
exists(DataFlow::MethodCallNode call |
call =
DataFlow::globalVarRef("Array")
.getAPropertyRead("prototype")
.getAPropertyRead("slice")
.getAMethodCall("call")
or
call = DataFlow::globalVarRef("Array").getAMethodCall("from")
|
call.getArgument(0) = func.getArgumentsVariable().getAnAccess().flow() and
call.flowsTo(read.getBase())
)
)
}

View File

@@ -45,6 +45,49 @@ private predicate looksLikeExterns(TopLevel tl) {
)
}
/**
* Holds if `f` contains generated or minified code.
*/
predicate isGeneratedCodeFile(File f) { isGenerated(f.getATopLevel()) }
/**
* Holds if `f` contains test code.
*/
predicate isTestFile(File f) {
exists(Test t | t.getFile() = f)
or
exists(string stemExt | stemExt = "test" or stemExt = "spec" |
f = getTestFile(any(File orig), stemExt)
)
or
f.getAbsolutePath().regexpMatch(".*/__(mocks|tests)__/.*")
}
/**
* Holds if `f` contains externs declarations.
*/
predicate isExternsFile(File f) {
(f.getATopLevel().isExterns() or looksLikeExterns(f.getATopLevel()))
}
/**
* Holds if `f` contains library code.
*/
predicate isLibaryFile(File f) { f.getATopLevel() instanceof FrameworkLibraryInstance }
/**
* Holds if `f` contains template code.
*/
predicate isTemplateFile(File f) {
exists(JSParseError err | maybeCausedByTemplate(err) | f = err.getFile())
or
// Polymer templates
exists(HTML::Element elt | elt.getName() = "template" |
f = elt.getFile() and
not f.getExtension() = "vue"
)
}
/**
* Holds if `f` is classified as belonging to `category`.
*
@@ -55,33 +98,15 @@ private predicate looksLikeExterns(TopLevel tl) {
* - `"library"`: `f` contains library code;
* - `"template"`: `f` contains template code.
*/
pragma[inline]
predicate classify(File f, string category) {
isGenerated(f.getATopLevel()) and category = "generated"
isGeneratedCodeFile(f) and category = "generated"
or
(
exists(Test t | t.getFile() = f)
or
exists(string stemExt | stemExt = "test" or stemExt = "spec" |
f = getTestFile(any(File orig), stemExt)
)
or
f.getAbsolutePath().regexpMatch(".*/__(mocks|tests)__/.*")
) and
category = "test"
isTestFile(f) and category = "test"
or
(f.getATopLevel().isExterns() or looksLikeExterns(f.getATopLevel())) and
category = "externs"
isExternsFile(f) and category = "externs"
or
f.getATopLevel() instanceof FrameworkLibraryInstance and category = "library"
isLibaryFile(f) and category = "library"
or
exists(JSParseError err | maybeCausedByTemplate(err) |
f = err.getFile() and category = "template"
)
or
// Polymer templates
exists(HTML::Element elt | elt.getName() = "template" |
f = elt.getFile() and
category = "template" and
not f.getExtension() = "vue"
)
isTemplateFile(f) and category = "template"
}

View File

@@ -13,7 +13,12 @@ module PrototypePollutingAssignment {
/**
* A data flow source for untrusted data from which the special `__proto__` property name may be arise.
*/
abstract class Source extends DataFlow::Node { }
abstract class Source extends DataFlow::Node {
/**
* Gets a string that describes the type of source.
*/
abstract string describe();
}
/**
* A data flow sink for prototype-polluting assignments or untrusted property names.
@@ -44,6 +49,8 @@ module PrototypePollutingAssignment {
this = any(DataFlow::PropWrite write).getBase()
or
this = any(ExtendCall c).getDestinationOperand()
or
this = any(DeleteExpr del).getOperand().flow().(DataFlow::PropRef).getBase()
}
override DataFlow::FlowLabel getAFlowLabel() { result instanceof ObjectPrototype }
@@ -52,5 +59,18 @@ module PrototypePollutingAssignment {
/** A remote flow source or location.{hash,search} as a taint source. */
private class DefaultSource extends Source {
DefaultSource() { this instanceof RemoteFlowSource }
override string describe() { result = "user controlled input" }
}
import semmle.javascript.PackageExports as Exports
/**
* A parameter of an exported function, seen as a source prototype-polluting assignment.
*/
class ExternalInputSource extends Source, DataFlow::SourceNode {
ExternalInputSource() { this = Exports::getALibraryInputParameter() }
override string describe() { result = "library input" }
}
}

View File

@@ -10,7 +10,8 @@
private import javascript
private import semmle.javascript.DynamicPropertyAccess
private import semmle.javascript.dataflow.InferredTypes
private import PrototypePollutingAssignmentCustomizations::PrototypePollutingAssignment
import PrototypePollutingAssignmentCustomizations::PrototypePollutingAssignment
private import semmle.javascript.filters.ClassifyFiles as ClassifyFiles
// Materialize flow labels
private class ConcreteObjectPrototype extends ObjectPrototype {
@@ -31,7 +32,10 @@ class Configuration extends TaintTracking::Configuration {
node instanceof Sanitizer
or
// Concatenating with a string will in practice prevent the string `__proto__` from arising.
node instanceof StringOps::ConcatenationRoot
exists(StringOps::ConcatenationRoot root | node = root |
// Exclude the string coercion `"" + node` from this filter.
not node.(StringOps::ConcatenationNode).isCoercion()
)
or
node instanceof DataFlow::ThisNode
or
@@ -79,6 +83,29 @@ class Configuration extends TaintTracking::Configuration {
inlbl.isTaint() and
outlbl instanceof ObjectPrototype
)
or
DataFlow::localFieldStep(pred, succ) and inlbl = outlbl
}
override predicate hasFlowPath(DataFlow::SourcePathNode source, DataFlow::SinkPathNode sink) {
super.hasFlowPath(source, sink) and
// require that there is a path without unmatched return steps
DataFlow::hasPathWithoutUnmatchedReturn(source, sink) and
// filter away paths that start with library inputs and end with a write to a fixed property.
not exists(ExternalInputSource src, Sink snk, DataFlow::PropWrite write |
source.getNode() = src and sink.getNode() = snk
|
snk = write.getBase() and
(
// fixed property name
exists(write.getPropertyName())
or
// non-string property name (likely number)
exists(Expr prop | prop = write.getPropertyNameExpr() |
not prop.analyze().getAType() = TTString()
)
)
)
}
override predicate isLabeledBarrier(DataFlow::Node node, DataFlow::FlowLabel lbl) {
@@ -95,7 +122,8 @@ class Configuration extends TaintTracking::Configuration {
guard instanceof InstanceofCheck or
guard instanceof IsArrayCheck or
guard instanceof TypeofCheck or
guard instanceof EqualityCheck
guard instanceof EqualityCheck or
guard instanceof IncludesCheck
}
}
@@ -108,7 +136,8 @@ private DataFlow::SourceNode prototypeLessObject(DataFlow::TypeTracker t) {
t.start() and
// We assume the argument to Object.create is not Object.prototype, since most
// users wouldn't bother to call Object.create in that case.
result = DataFlow::globalVarRef("Object").getAMemberCall("create")
result = DataFlow::globalVarRef("Object").getAMemberCall("create") and
not result.getFile() instanceof TestFile
or
// Allow use of SharedFlowSteps to track a bit further
exists(DataFlow::Node mid |
@@ -119,6 +148,14 @@ private DataFlow::SourceNode prototypeLessObject(DataFlow::TypeTracker t) {
exists(DataFlow::TypeTracker t2 | result = prototypeLessObject(t2).track(t2, t))
}
/**
* A test file.
* Objects created in such files are ignored in the `prototypeLessObject` predicate.
*/
private class TestFile extends File {
TestFile() { ClassifyFiles::isTestFile(this) }
}
/** Holds if `Object.prototype` has a member named `prop`. */
private predicate isPropertyPresentOnObjectPrototype(string prop) {
exists(ExternalInstanceMemberDecl decl |
@@ -215,3 +252,15 @@ private class EqualityCheck extends TaintTracking::SanitizerGuardNode, DataFlow:
outcome = astNode.getPolarity().booleanNot()
}
}
/**
* Sanitizer guard of the form `x.includes("__proto__")`.
*/
private class IncludesCheck extends TaintTracking::LabeledSanitizerGuardNode, InclusionTest {
IncludesCheck() { this.getContainedNode().mayHaveStringValue("__proto__") }
override predicate sanitizes(boolean outcome, Expr e) {
e = getContainerNode().asExpr() and
outcome = getPolarity().booleanNot()
}
}

View File

@@ -50,14 +50,14 @@ module UnsafeShellCommandConstruction {
/**
* A parameter of an exported function, seen as a source for shell command constructed from library input.
*/
class ExternalInputSource extends Source, DataFlow::ParameterNode {
class ExternalInputSource extends Source, DataFlow::SourceNode {
ExternalInputSource() {
this = Exports::getALibraryInputParameter() and
not (
// looks to be on purpose.
this.getName() = ["cmd", "command"]
this.(DataFlow::ParameterNode).getName() = ["cmd", "command"]
or
this.getName().regexpMatch(".*(Cmd|Command)$") // ends with "Cmd" or "Command"
this.(DataFlow::ParameterNode).getName().regexpMatch(".*(Cmd|Command)$") // ends with "Cmd" or "Command"
)
}
}

View File

@@ -124,7 +124,7 @@ module PolynomialReDoS {
/**
* A parameter of an exported function, seen as a source for polynomial-redos.
*/
class ExternalInputSource extends Source, DataFlow::ParameterNode {
class ExternalInputSource extends Source, DataFlow::SourceNode {
ExternalInputSource() { this = Exports::getALibraryInputParameter() }
override string getKind() { result = "library" }

View File

@@ -24,4 +24,4 @@ from Configuration cfg, DataFlow::PathNode source, DataFlow::PathNode sink
where cfg.hasFlowPath(source, sink)
select sink, source, sink,
"This assignment may alter Object.prototype if a malicious '__proto__' string is injected from $@.",
source.getNode(), "here"
source.getNode(), source.getNode().(Source).describe()

View File

@@ -1,4 +1,93 @@
nodes
| lib.js:1:38:1:40 | obj |
| lib.js:1:43:1:46 | path |
| lib.js:1:43:1:46 | path |
| lib.js:1:43:1:46 | path |
| lib.js:2:7:2:27 | currentPath |
| lib.js:2:7:2:27 | currentPath |
| lib.js:2:21:2:24 | path |
| lib.js:2:21:2:24 | path |
| lib.js:2:21:2:27 | path[0] |
| lib.js:2:21:2:27 | path[0] |
| lib.js:6:7:6:9 | obj |
| lib.js:6:7:6:9 | obj |
| lib.js:11:17:11:32 | obj[currentPath] |
| lib.js:11:17:11:32 | obj[currentPath] |
| lib.js:11:21:11:31 | currentPath |
| lib.js:11:21:11:31 | currentPath |
| lib.js:11:35:11:38 | path |
| lib.js:11:35:11:38 | path |
| lib.js:11:35:11:47 | path.slice(1) |
| lib.js:11:35:11:47 | path.slice(1) |
| lib.js:14:38:14:41 | path |
| lib.js:14:38:14:41 | path |
| lib.js:15:3:15:14 | obj[path[0]] |
| lib.js:15:3:15:14 | obj[path[0]] |
| lib.js:15:7:15:10 | path |
| lib.js:15:7:15:13 | path[0] |
| lib.js:20:7:20:25 | path |
| lib.js:20:14:20:25 | arguments[1] |
| lib.js:20:14:20:25 | arguments[1] |
| lib.js:22:3:22:14 | obj[path[0]] |
| lib.js:22:3:22:14 | obj[path[0]] |
| lib.js:22:7:22:10 | path |
| lib.js:22:7:22:13 | path[0] |
| lib.js:25:44:25:47 | path |
| lib.js:25:44:25:47 | path |
| lib.js:26:10:26:21 | obj[path[0]] |
| lib.js:26:10:26:21 | obj[path[0]] |
| lib.js:26:14:26:17 | path |
| lib.js:26:14:26:20 | path[0] |
| lib.js:32:7:32:20 | path |
| lib.js:32:14:32:20 | args[1] |
| lib.js:32:14:32:20 | args[1] |
| lib.js:34:3:34:14 | obj[path[0]] |
| lib.js:34:3:34:14 | obj[path[0]] |
| lib.js:34:7:34:10 | path |
| lib.js:34:7:34:13 | path[0] |
| lib.js:40:7:40:20 | path |
| lib.js:40:14:40:20 | args[1] |
| lib.js:40:14:40:20 | args[1] |
| lib.js:42:3:42:14 | obj[path[0]] |
| lib.js:42:3:42:14 | obj[path[0]] |
| lib.js:42:7:42:10 | path |
| lib.js:42:7:42:13 | path[0] |
| lib.js:45:13:45:13 | s |
| lib.js:45:13:45:13 | s |
| lib.js:46:10:46:10 | s |
| lib.js:52:9:52:22 | path |
| lib.js:52:16:52:22 | id("x") |
| lib.js:55:11:55:22 | obj[path[0]] |
| lib.js:55:11:55:22 | obj[path[0]] |
| lib.js:55:15:55:18 | path |
| lib.js:55:15:55:21 | path[0] |
| lib.js:59:18:59:18 | s |
| lib.js:59:18:59:18 | s |
| lib.js:61:17:61:17 | s |
| lib.js:68:11:68:26 | path |
| lib.js:68:18:68:26 | this.path |
| lib.js:70:13:70:24 | obj[path[0]] |
| lib.js:70:13:70:24 | obj[path[0]] |
| lib.js:70:17:70:20 | path |
| lib.js:70:17:70:23 | path[0] |
| lib.js:83:7:83:25 | path |
| lib.js:83:14:83:25 | arguments[1] |
| lib.js:83:14:83:25 | arguments[1] |
| lib.js:86:7:86:26 | proto |
| lib.js:86:15:86:26 | obj[path[0]] |
| lib.js:86:19:86:22 | path |
| lib.js:86:19:86:25 | path[0] |
| lib.js:87:10:87:14 | proto |
| lib.js:87:10:87:14 | proto |
| lib.js:90:43:90:46 | path |
| lib.js:90:43:90:46 | path |
| lib.js:91:7:91:28 | maybeProto |
| lib.js:91:20:91:28 | obj[path] |
| lib.js:91:24:91:27 | path |
| lib.js:92:3:92:12 | maybeProto |
| lib.js:92:3:92:12 | maybeProto |
| lib.js:95:3:95:12 | maybeProto |
| lib.js:95:3:95:12 | maybeProto |
| tst.js:5:9:5:38 | taint |
| tst.js:5:17:5:38 | String( ... y.data) |
| tst.js:5:24:5:37 | req.query.data |
@@ -23,17 +112,113 @@ nodes
| tst.js:45:9:45:11 | obj |
| tst.js:48:9:48:11 | obj |
| tst.js:48:9:48:11 | obj |
| tst.js:78:5:78:37 | obj[req ... ', '')] |
| tst.js:78:5:78:37 | obj[req ... ', '')] |
| tst.js:78:9:78:19 | req.query.x |
| tst.js:78:9:78:19 | req.query.x |
| tst.js:78:9:78:36 | req.que ... _', '') |
| tst.js:81:5:81:46 | obj[req ... g, '')] |
| tst.js:81:5:81:46 | obj[req ... g, '')] |
| tst.js:81:9:81:19 | req.query.x |
| tst.js:81:9:81:19 | req.query.x |
| tst.js:81:9:81:45 | req.que ... /g, '') |
| tst.js:77:9:77:38 | taint |
| tst.js:77:17:77:38 | String( ... y.data) |
| tst.js:77:24:77:37 | req.query.data |
| tst.js:77:24:77:37 | req.query.data |
| tst.js:80:5:80:17 | object[taint] |
| tst.js:80:5:80:17 | object[taint] |
| tst.js:80:12:80:16 | taint |
| tst.js:82:5:82:22 | object["" + taint] |
| tst.js:82:5:82:22 | object["" + taint] |
| tst.js:82:12:82:21 | "" + taint |
| tst.js:82:17:82:21 | taint |
| tst.js:87:9:87:21 | object[taint] |
| tst.js:87:9:87:21 | object[taint] |
| tst.js:87:16:87:20 | taint |
| tst.js:94:5:94:37 | obj[req ... ', '')] |
| tst.js:94:5:94:37 | obj[req ... ', '')] |
| tst.js:94:9:94:19 | req.query.x |
| tst.js:94:9:94:19 | req.query.x |
| tst.js:94:9:94:36 | req.que ... _', '') |
| tst.js:97:5:97:46 | obj[req ... g, '')] |
| tst.js:97:5:97:46 | obj[req ... g, '')] |
| tst.js:97:9:97:19 | req.query.x |
| tst.js:97:9:97:19 | req.query.x |
| tst.js:97:9:97:45 | req.que ... /g, '') |
edges
| lib.js:1:38:1:40 | obj | lib.js:6:7:6:9 | obj |
| lib.js:1:38:1:40 | obj | lib.js:6:7:6:9 | obj |
| lib.js:1:43:1:46 | path | lib.js:2:21:2:24 | path |
| lib.js:1:43:1:46 | path | lib.js:2:21:2:24 | path |
| lib.js:1:43:1:46 | path | lib.js:2:21:2:24 | path |
| lib.js:1:43:1:46 | path | lib.js:11:35:11:38 | path |
| lib.js:1:43:1:46 | path | lib.js:11:35:11:38 | path |
| lib.js:1:43:1:46 | path | lib.js:11:35:11:38 | path |
| lib.js:2:7:2:27 | currentPath | lib.js:11:21:11:31 | currentPath |
| lib.js:2:7:2:27 | currentPath | lib.js:11:21:11:31 | currentPath |
| lib.js:2:21:2:24 | path | lib.js:2:21:2:27 | path[0] |
| lib.js:2:21:2:24 | path | lib.js:2:21:2:27 | path[0] |
| lib.js:2:21:2:27 | path[0] | lib.js:2:7:2:27 | currentPath |
| lib.js:2:21:2:27 | path[0] | lib.js:2:7:2:27 | currentPath |
| lib.js:11:17:11:32 | obj[currentPath] | lib.js:1:38:1:40 | obj |
| lib.js:11:17:11:32 | obj[currentPath] | lib.js:1:38:1:40 | obj |
| lib.js:11:21:11:31 | currentPath | lib.js:11:17:11:32 | obj[currentPath] |
| lib.js:11:21:11:31 | currentPath | lib.js:11:17:11:32 | obj[currentPath] |
| lib.js:11:35:11:38 | path | lib.js:11:35:11:47 | path.slice(1) |
| lib.js:11:35:11:38 | path | lib.js:11:35:11:47 | path.slice(1) |
| lib.js:11:35:11:47 | path.slice(1) | lib.js:1:43:1:46 | path |
| lib.js:11:35:11:47 | path.slice(1) | lib.js:1:43:1:46 | path |
| lib.js:14:38:14:41 | path | lib.js:15:7:15:10 | path |
| lib.js:14:38:14:41 | path | lib.js:15:7:15:10 | path |
| lib.js:15:7:15:10 | path | lib.js:15:7:15:13 | path[0] |
| lib.js:15:7:15:13 | path[0] | lib.js:15:3:15:14 | obj[path[0]] |
| lib.js:15:7:15:13 | path[0] | lib.js:15:3:15:14 | obj[path[0]] |
| lib.js:20:7:20:25 | path | lib.js:22:7:22:10 | path |
| lib.js:20:14:20:25 | arguments[1] | lib.js:20:7:20:25 | path |
| lib.js:20:14:20:25 | arguments[1] | lib.js:20:7:20:25 | path |
| lib.js:22:7:22:10 | path | lib.js:22:7:22:13 | path[0] |
| lib.js:22:7:22:13 | path[0] | lib.js:22:3:22:14 | obj[path[0]] |
| lib.js:22:7:22:13 | path[0] | lib.js:22:3:22:14 | obj[path[0]] |
| lib.js:25:44:25:47 | path | lib.js:26:14:26:17 | path |
| lib.js:25:44:25:47 | path | lib.js:26:14:26:17 | path |
| lib.js:26:14:26:17 | path | lib.js:26:14:26:20 | path[0] |
| lib.js:26:14:26:20 | path[0] | lib.js:26:10:26:21 | obj[path[0]] |
| lib.js:26:14:26:20 | path[0] | lib.js:26:10:26:21 | obj[path[0]] |
| lib.js:32:7:32:20 | path | lib.js:34:7:34:10 | path |
| lib.js:32:14:32:20 | args[1] | lib.js:32:7:32:20 | path |
| lib.js:32:14:32:20 | args[1] | lib.js:32:7:32:20 | path |
| lib.js:34:7:34:10 | path | lib.js:34:7:34:13 | path[0] |
| lib.js:34:7:34:13 | path[0] | lib.js:34:3:34:14 | obj[path[0]] |
| lib.js:34:7:34:13 | path[0] | lib.js:34:3:34:14 | obj[path[0]] |
| lib.js:40:7:40:20 | path | lib.js:42:7:42:10 | path |
| lib.js:40:14:40:20 | args[1] | lib.js:40:7:40:20 | path |
| lib.js:40:14:40:20 | args[1] | lib.js:40:7:40:20 | path |
| lib.js:42:7:42:10 | path | lib.js:42:7:42:13 | path[0] |
| lib.js:42:7:42:13 | path[0] | lib.js:42:3:42:14 | obj[path[0]] |
| lib.js:42:7:42:13 | path[0] | lib.js:42:3:42:14 | obj[path[0]] |
| lib.js:45:13:45:13 | s | lib.js:46:10:46:10 | s |
| lib.js:45:13:45:13 | s | lib.js:46:10:46:10 | s |
| lib.js:46:10:46:10 | s | lib.js:52:16:52:22 | id("x") |
| lib.js:52:9:52:22 | path | lib.js:55:15:55:18 | path |
| lib.js:52:16:52:22 | id("x") | lib.js:52:9:52:22 | path |
| lib.js:55:15:55:18 | path | lib.js:55:15:55:21 | path[0] |
| lib.js:55:15:55:21 | path[0] | lib.js:55:11:55:22 | obj[path[0]] |
| lib.js:55:15:55:21 | path[0] | lib.js:55:11:55:22 | obj[path[0]] |
| lib.js:59:18:59:18 | s | lib.js:61:17:61:17 | s |
| lib.js:59:18:59:18 | s | lib.js:61:17:61:17 | s |
| lib.js:61:17:61:17 | s | lib.js:68:18:68:26 | this.path |
| lib.js:68:11:68:26 | path | lib.js:70:17:70:20 | path |
| lib.js:68:18:68:26 | this.path | lib.js:68:11:68:26 | path |
| lib.js:70:17:70:20 | path | lib.js:70:17:70:23 | path[0] |
| lib.js:70:17:70:23 | path[0] | lib.js:70:13:70:24 | obj[path[0]] |
| lib.js:70:17:70:23 | path[0] | lib.js:70:13:70:24 | obj[path[0]] |
| lib.js:83:7:83:25 | path | lib.js:86:19:86:22 | path |
| lib.js:83:14:83:25 | arguments[1] | lib.js:83:7:83:25 | path |
| lib.js:83:14:83:25 | arguments[1] | lib.js:83:7:83:25 | path |
| lib.js:86:7:86:26 | proto | lib.js:87:10:87:14 | proto |
| lib.js:86:7:86:26 | proto | lib.js:87:10:87:14 | proto |
| lib.js:86:15:86:26 | obj[path[0]] | lib.js:86:7:86:26 | proto |
| lib.js:86:19:86:22 | path | lib.js:86:19:86:25 | path[0] |
| lib.js:86:19:86:25 | path[0] | lib.js:86:15:86:26 | obj[path[0]] |
| lib.js:90:43:90:46 | path | lib.js:91:24:91:27 | path |
| lib.js:90:43:90:46 | path | lib.js:91:24:91:27 | path |
| lib.js:91:7:91:28 | maybeProto | lib.js:92:3:92:12 | maybeProto |
| lib.js:91:7:91:28 | maybeProto | lib.js:92:3:92:12 | maybeProto |
| lib.js:91:7:91:28 | maybeProto | lib.js:95:3:95:12 | maybeProto |
| lib.js:91:7:91:28 | maybeProto | lib.js:95:3:95:12 | maybeProto |
| lib.js:91:20:91:28 | obj[path] | lib.js:91:7:91:28 | maybeProto |
| lib.js:91:24:91:27 | path | lib.js:91:20:91:28 | obj[path] |
| tst.js:5:9:5:38 | taint | tst.js:8:12:8:16 | taint |
| tst.js:5:9:5:38 | taint | tst.js:9:12:9:16 | taint |
| tst.js:5:9:5:38 | taint | tst.js:12:25:12:29 | taint |
@@ -57,21 +242,45 @@ edges
| tst.js:33:23:33:25 | obj | tst.js:45:9:45:11 | obj |
| tst.js:33:23:33:25 | obj | tst.js:48:9:48:11 | obj |
| tst.js:33:23:33:25 | obj | tst.js:48:9:48:11 | obj |
| tst.js:78:9:78:19 | req.query.x | tst.js:78:9:78:36 | req.que ... _', '') |
| tst.js:78:9:78:19 | req.query.x | tst.js:78:9:78:36 | req.que ... _', '') |
| tst.js:78:9:78:36 | req.que ... _', '') | tst.js:78:5:78:37 | obj[req ... ', '')] |
| tst.js:78:9:78:36 | req.que ... _', '') | tst.js:78:5:78:37 | obj[req ... ', '')] |
| tst.js:81:9:81:19 | req.query.x | tst.js:81:9:81:45 | req.que ... /g, '') |
| tst.js:81:9:81:19 | req.query.x | tst.js:81:9:81:45 | req.que ... /g, '') |
| tst.js:81:9:81:45 | req.que ... /g, '') | tst.js:81:5:81:46 | obj[req ... g, '')] |
| tst.js:81:9:81:45 | req.que ... /g, '') | tst.js:81:5:81:46 | obj[req ... g, '')] |
| tst.js:77:9:77:38 | taint | tst.js:80:12:80:16 | taint |
| tst.js:77:9:77:38 | taint | tst.js:82:17:82:21 | taint |
| tst.js:77:9:77:38 | taint | tst.js:87:16:87:20 | taint |
| tst.js:77:17:77:38 | String( ... y.data) | tst.js:77:9:77:38 | taint |
| tst.js:77:24:77:37 | req.query.data | tst.js:77:17:77:38 | String( ... y.data) |
| tst.js:77:24:77:37 | req.query.data | tst.js:77:17:77:38 | String( ... y.data) |
| tst.js:80:12:80:16 | taint | tst.js:80:5:80:17 | object[taint] |
| tst.js:80:12:80:16 | taint | tst.js:80:5:80:17 | object[taint] |
| tst.js:82:12:82:21 | "" + taint | tst.js:82:5:82:22 | object["" + taint] |
| tst.js:82:12:82:21 | "" + taint | tst.js:82:5:82:22 | object["" + taint] |
| tst.js:82:17:82:21 | taint | tst.js:82:12:82:21 | "" + taint |
| tst.js:87:16:87:20 | taint | tst.js:87:9:87:21 | object[taint] |
| tst.js:87:16:87:20 | taint | tst.js:87:9:87:21 | object[taint] |
| tst.js:94:9:94:19 | req.query.x | tst.js:94:9:94:36 | req.que ... _', '') |
| tst.js:94:9:94:19 | req.query.x | tst.js:94:9:94:36 | req.que ... _', '') |
| tst.js:94:9:94:36 | req.que ... _', '') | tst.js:94:5:94:37 | obj[req ... ', '')] |
| tst.js:94:9:94:36 | req.que ... _', '') | tst.js:94:5:94:37 | obj[req ... ', '')] |
| tst.js:97:9:97:19 | req.query.x | tst.js:97:9:97:45 | req.que ... /g, '') |
| tst.js:97:9:97:19 | req.query.x | tst.js:97:9:97:45 | req.que ... /g, '') |
| tst.js:97:9:97:45 | req.que ... /g, '') | tst.js:97:5:97:46 | obj[req ... g, '')] |
| tst.js:97:9:97:45 | req.que ... /g, '') | tst.js:97:5:97:46 | obj[req ... g, '')] |
#select
| tst.js:8:5:8:17 | object[taint] | tst.js:5:24:5:37 | req.query.data | tst.js:8:5:8:17 | object[taint] | This assignment may alter Object.prototype if a malicious '__proto__' string is injected from $@. | tst.js:5:24:5:37 | req.query.data | here |
| tst.js:9:5:9:17 | object[taint] | tst.js:5:24:5:37 | req.query.data | tst.js:9:5:9:17 | object[taint] | This assignment may alter Object.prototype if a malicious '__proto__' string is injected from $@. | tst.js:5:24:5:37 | req.query.data | here |
| tst.js:14:5:14:32 | unsafeG ... taint) | tst.js:5:24:5:37 | req.query.data | tst.js:14:5:14:32 | unsafeG ... taint) | This assignment may alter Object.prototype if a malicious '__proto__' string is injected from $@. | tst.js:5:24:5:37 | req.query.data | here |
| tst.js:34:5:34:7 | obj | tst.js:5:24:5:37 | req.query.data | tst.js:34:5:34:7 | obj | This assignment may alter Object.prototype if a malicious '__proto__' string is injected from $@. | tst.js:5:24:5:37 | req.query.data | here |
| tst.js:39:9:39:11 | obj | tst.js:5:24:5:37 | req.query.data | tst.js:39:9:39:11 | obj | This assignment may alter Object.prototype if a malicious '__proto__' string is injected from $@. | tst.js:5:24:5:37 | req.query.data | here |
| tst.js:45:9:45:11 | obj | tst.js:5:24:5:37 | req.query.data | tst.js:45:9:45:11 | obj | This assignment may alter Object.prototype if a malicious '__proto__' string is injected from $@. | tst.js:5:24:5:37 | req.query.data | here |
| tst.js:48:9:48:11 | obj | tst.js:5:24:5:37 | req.query.data | tst.js:48:9:48:11 | obj | This assignment may alter Object.prototype if a malicious '__proto__' string is injected from $@. | tst.js:5:24:5:37 | req.query.data | here |
| tst.js:78:5:78:37 | obj[req ... ', '')] | tst.js:78:9:78:19 | req.query.x | tst.js:78:5:78:37 | obj[req ... ', '')] | This assignment may alter Object.prototype if a malicious '__proto__' string is injected from $@. | tst.js:78:9:78:19 | req.query.x | here |
| tst.js:81:5:81:46 | obj[req ... g, '')] | tst.js:81:9:81:19 | req.query.x | tst.js:81:5:81:46 | obj[req ... g, '')] | This assignment may alter Object.prototype if a malicious '__proto__' string is injected from $@. | tst.js:81:9:81:19 | req.query.x | here |
| lib.js:6:7:6:9 | obj | lib.js:1:43:1:46 | path | lib.js:6:7:6:9 | obj | This assignment may alter Object.prototype if a malicious '__proto__' string is injected from $@. | lib.js:1:43:1:46 | path | library input |
| lib.js:15:3:15:14 | obj[path[0]] | lib.js:14:38:14:41 | path | lib.js:15:3:15:14 | obj[path[0]] | This assignment may alter Object.prototype if a malicious '__proto__' string is injected from $@. | lib.js:14:38:14:41 | path | library input |
| lib.js:22:3:22:14 | obj[path[0]] | lib.js:20:14:20:25 | arguments[1] | lib.js:22:3:22:14 | obj[path[0]] | This assignment may alter Object.prototype if a malicious '__proto__' string is injected from $@. | lib.js:20:14:20:25 | arguments[1] | library input |
| lib.js:26:10:26:21 | obj[path[0]] | lib.js:25:44:25:47 | path | lib.js:26:10:26:21 | obj[path[0]] | This assignment may alter Object.prototype if a malicious '__proto__' string is injected from $@. | lib.js:25:44:25:47 | path | library input |
| lib.js:34:3:34:14 | obj[path[0]] | lib.js:32:14:32:20 | args[1] | lib.js:34:3:34:14 | obj[path[0]] | This assignment may alter Object.prototype if a malicious '__proto__' string is injected from $@. | lib.js:32:14:32:20 | args[1] | library input |
| lib.js:42:3:42:14 | obj[path[0]] | lib.js:40:14:40:20 | args[1] | lib.js:42:3:42:14 | obj[path[0]] | This assignment may alter Object.prototype if a malicious '__proto__' string is injected from $@. | lib.js:40:14:40:20 | args[1] | library input |
| lib.js:70:13:70:24 | obj[path[0]] | lib.js:59:18:59:18 | s | lib.js:70:13:70:24 | obj[path[0]] | This assignment may alter Object.prototype if a malicious '__proto__' string is injected from $@. | lib.js:59:18:59:18 | s | library input |
| lib.js:87:10:87:14 | proto | lib.js:83:14:83:25 | arguments[1] | lib.js:87:10:87:14 | proto | This assignment may alter Object.prototype if a malicious '__proto__' string is injected from $@. | lib.js:83:14:83:25 | arguments[1] | library input |
| tst.js:8:5:8:17 | object[taint] | tst.js:5:24:5:37 | req.query.data | tst.js:8:5:8:17 | object[taint] | This assignment may alter Object.prototype if a malicious '__proto__' string is injected from $@. | tst.js:5:24:5:37 | req.query.data | user controlled input |
| tst.js:9:5:9:17 | object[taint] | tst.js:5:24:5:37 | req.query.data | tst.js:9:5:9:17 | object[taint] | This assignment may alter Object.prototype if a malicious '__proto__' string is injected from $@. | tst.js:5:24:5:37 | req.query.data | user controlled input |
| tst.js:14:5:14:32 | unsafeG ... taint) | tst.js:5:24:5:37 | req.query.data | tst.js:14:5:14:32 | unsafeG ... taint) | This assignment may alter Object.prototype if a malicious '__proto__' string is injected from $@. | tst.js:5:24:5:37 | req.query.data | user controlled input |
| tst.js:34:5:34:7 | obj | tst.js:5:24:5:37 | req.query.data | tst.js:34:5:34:7 | obj | This assignment may alter Object.prototype if a malicious '__proto__' string is injected from $@. | tst.js:5:24:5:37 | req.query.data | user controlled input |
| tst.js:39:9:39:11 | obj | tst.js:5:24:5:37 | req.query.data | tst.js:39:9:39:11 | obj | This assignment may alter Object.prototype if a malicious '__proto__' string is injected from $@. | tst.js:5:24:5:37 | req.query.data | user controlled input |
| tst.js:45:9:45:11 | obj | tst.js:5:24:5:37 | req.query.data | tst.js:45:9:45:11 | obj | This assignment may alter Object.prototype if a malicious '__proto__' string is injected from $@. | tst.js:5:24:5:37 | req.query.data | user controlled input |
| tst.js:48:9:48:11 | obj | tst.js:5:24:5:37 | req.query.data | tst.js:48:9:48:11 | obj | This assignment may alter Object.prototype if a malicious '__proto__' string is injected from $@. | tst.js:5:24:5:37 | req.query.data | user controlled input |
| tst.js:80:5:80:17 | object[taint] | tst.js:77:24:77:37 | req.query.data | tst.js:80:5:80:17 | object[taint] | This assignment may alter Object.prototype if a malicious '__proto__' string is injected from $@. | tst.js:77:24:77:37 | req.query.data | user controlled input |
| tst.js:82:5:82:22 | object["" + taint] | tst.js:77:24:77:37 | req.query.data | tst.js:82:5:82:22 | object["" + taint] | This assignment may alter Object.prototype if a malicious '__proto__' string is injected from $@. | tst.js:77:24:77:37 | req.query.data | user controlled input |
| tst.js:87:9:87:21 | object[taint] | tst.js:77:24:77:37 | req.query.data | tst.js:87:9:87:21 | object[taint] | This assignment may alter Object.prototype if a malicious '__proto__' string is injected from $@. | tst.js:77:24:77:37 | req.query.data | user controlled input |
| tst.js:94:5:94:37 | obj[req ... ', '')] | tst.js:94:9:94:19 | req.query.x | tst.js:94:5:94:37 | obj[req ... ', '')] | This assignment may alter Object.prototype if a malicious '__proto__' string is injected from $@. | tst.js:94:9:94:19 | req.query.x | user controlled input |
| tst.js:97:5:97:46 | obj[req ... g, '')] | tst.js:97:9:97:19 | req.query.x | tst.js:97:5:97:46 | obj[req ... g, '')] | This assignment may alter Object.prototype if a malicious '__proto__' string is injected from $@. | tst.js:97:9:97:19 | req.query.x | user controlled input |

View File

@@ -0,0 +1,96 @@
module.exports.set = function recSet(obj, path, value) {
var currentPath = path[0];
var currentValue = obj[currentPath];
if (path.length === 1) {
if (currentValue === void 0) {
obj[currentPath] = value; // NOT OK
}
return currentValue;
}
return recSet(obj[currentPath], path.slice(1), value);
}
module.exports.set2 = function (obj, path, value) {
obj[path[0]][path[1]] = value; // NOT OK
}
module.exports.setWithArgs = function() {
var obj = arguments[0];
var path = arguments[1];
var value = arguments[2];
obj[path[0]][path[1]] = value; // NOT OK
}
module.exports.usedInTest = function (obj, path, value) {
return obj[path[0]][path[1]] = value; // NOT OK
}
module.exports.setWithArgs2 = function() {
const args = Array.prototype.slice.call(arguments);
var obj = args[0];
var path = args[1];
var value = args[2];
obj[path[0]][path[1]] = value; // NOT OK
}
module.exports.setWithArgs3 = function() {
const args = Array.from(arguments);
var obj = args[0];
var path = args[1];
var value = args[2];
obj[path[0]][path[1]] = value; // NOT OK
}
function id(s) {
return s;
}
module.exports.id = id;
module.exports.notVulnerable = function () {
const path = id("x");
const value = id("y");
const obj = id("z");
return (obj[path[0]][path[1]] = value); // OK
}
class Foo {
constructor(o, s, v) {
this.obj = o;
this.path = s;
this.value = v;
}
doXss() {
// not called here, but still bad.
const obj = this.obj;
const path = this.path;
const value = this.value;
return (obj[path[0]][path[1]] = value); // NOT OK
}
safe() {
const obj = this.obj;
obj[path[0]] = this.value; // OK
}
}
module.exports.Foo = Foo;
module.exports.delete = function() {
var obj = arguments[0];
var path = arguments[1];
delete obj[path[0]]; // OK
var prop = arguments[2];
var proto = obj[path[0]];
delete proto[prop]; // NOT OK
}
module.exports.fixedProp = function (obj, path, value) {
var maybeProto = obj[path];
maybeProto.foo = value; // OK - fixed properties from library inputs are OK.
var i = 0;
maybeProto[i + 2] = value; // OK - number properties are OK.
}

View File

@@ -0,0 +1,9 @@
const lib = require("./lib");
describe("lib", () => {
it("should work", () => {
const obj = Object.create(null);
lib.usedInTest(obj, "foo", "my-value");
});
});

View File

@@ -0,0 +1,5 @@
{
"name": "my-lib",
"version": "0.0.7",
"main": "./lib.js"
}

View File

@@ -72,6 +72,22 @@ class Box {
}
}
app.get('/', (req, res) => {
let taint = String(req.query.data);
let object = {};
object[taint][taint] = taint; // NOT OK
object["" + taint]["" + taint] = taint; // NOT OK
if (!taint.includes("__proto__")) {
object[taint][taint] = taint; // OK
} else {
object[taint][taint] = taint; // NOT OK
}
});
app.get('/foo', (req, res) => {
let obj = {};
obj[req.query.x.replace('_', '-')].x = 'foo'; // OK