mirror of
https://github.com/github/codeql.git
synced 2026-05-03 04:39:29 +02:00
Merge pull request #5908 from erik-krogh/protoLib
JS: Add library input as source to js/prototype-polluting-assignment
This commit is contained in:
@@ -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.
|
||||
@@ -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())
|
||||
)
|
||||
)
|
||||
}
|
||||
|
||||
|
||||
@@ -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"
|
||||
}
|
||||
|
||||
@@ -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" }
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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()
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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"
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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" }
|
||||
|
||||
@@ -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()
|
||||
|
||||
@@ -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 |
|
||||
|
||||
@@ -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.
|
||||
}
|
||||
@@ -0,0 +1,9 @@
|
||||
const lib = require("./lib");
|
||||
|
||||
describe("lib", () => {
|
||||
it("should work", () => {
|
||||
const obj = Object.create(null);
|
||||
|
||||
lib.usedInTest(obj, "foo", "my-value");
|
||||
});
|
||||
});
|
||||
@@ -0,0 +1,5 @@
|
||||
{
|
||||
"name": "my-lib",
|
||||
"version": "0.0.7",
|
||||
"main": "./lib.js"
|
||||
}
|
||||
@@ -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
|
||||
|
||||
Reference in New Issue
Block a user