|
| 1 | +/** Provides classes and predicates modelling aspects of the `d3` library. */ |
| 2 | + |
| 3 | +private import javascript |
| 4 | +private import semmle.javascript.security.dataflow.Xss |
| 5 | + |
| 6 | +/** Provides classes and predicates modelling aspects of the `d3` library. */ |
| 7 | +module D3 { |
| 8 | + /** The global variable `d3` as an entry point for API graphs. */ |
| 9 | + private class D3GlobalEntry extends API::EntryPoint { |
| 10 | + D3GlobalEntry() { this = "D3GlobalEntry" } |
| 11 | + |
| 12 | + override DataFlow::SourceNode getAUse() { result = DataFlow::globalVarRef("d3") } |
| 13 | + |
| 14 | + override DataFlow::Node getARhs() { none() } |
| 15 | + } |
| 16 | + |
| 17 | + /** Gets an API node referring to the `d3` module. */ |
| 18 | + API::Node d3() { |
| 19 | + result = API::moduleImport("d3") |
| 20 | + or |
| 21 | + // recognize copies of d3 in a scope |
| 22 | + result = API::moduleImport(any(string s | s.regexpMatch("@.*/d3(-\\w+)?"))) |
| 23 | + or |
| 24 | + result = API::moduleImport("d3-node").getInstance().getMember("d3") |
| 25 | + or |
| 26 | + result = API::root().getASuccessor(any(D3GlobalEntry i)) |
| 27 | + } |
| 28 | + |
| 29 | + /** |
| 30 | + * Gets an API node referring to the given module, or to `d3`. |
| 31 | + * |
| 32 | + * Useful for accessing modules that are re-exported by `d3`. |
| 33 | + */ |
| 34 | + bindingset[name] |
| 35 | + API::Node d3Module(string name) { |
| 36 | + result = d3() // all d3 modules are re-exported as part of 'd3' |
| 37 | + or |
| 38 | + result = API::moduleImport(name) |
| 39 | + } |
| 40 | + |
| 41 | + /** Gets an API node referring to a `d3` selection object, such as `d3.selection()`. */ |
| 42 | + API::Node d3Selection() { |
| 43 | + result = d3Module("d3-selection").getMember(["selection", "select", "selectAll"]).getReturn() |
| 44 | + or |
| 45 | + exists(API::CallNode call, string name | |
| 46 | + call = d3Selection().getMember(name).getACall() and |
| 47 | + result = call.getReturn() |
| 48 | + | |
| 49 | + name = |
| 50 | + [ |
| 51 | + "select", "selectAll", "filter", "merge", "selectChild", "selectChildren", "selection", |
| 52 | + "insert", "remove", "clone", "sort", "order", "raise", "lower", "append", "data", "join", |
| 53 | + "enter", "exit", "call" |
| 54 | + ] |
| 55 | + or |
| 56 | + name = ["text", "html", "datum"] and |
| 57 | + call.getNumArgument() > 0 // exclude 0-argument version, which returns the current value |
| 58 | + or |
| 59 | + name = ["attr", "classed", "style", "property", "on"] and |
| 60 | + call.getNumArgument() > 1 // exclude 1-argument version, which returns the current value |
| 61 | + or |
| 62 | + // Setting multiple things at once |
| 63 | + name = ["attr", "classed", "style", "property", "on"] and |
| 64 | + call.getArgument(0).getALocalSource() instanceof DataFlow::ObjectLiteralNode |
| 65 | + ) |
| 66 | + or |
| 67 | + result = d3Selection().getMember("call").getParameter(0).getParameter(0) |
| 68 | + } |
| 69 | + |
| 70 | + private class D3XssSink extends DomBasedXss::Sink { |
| 71 | + D3XssSink() { |
| 72 | + exists(API::Node htmlArg | |
| 73 | + htmlArg = d3Selection().getMember("html").getParameter(0) and |
| 74 | + this = [htmlArg, htmlArg.getReturn()].getARhs() |
| 75 | + ) |
| 76 | + } |
| 77 | + } |
| 78 | + |
| 79 | + private class D3DomValueSource extends DOM::DomValueSource::Range { |
| 80 | + D3DomValueSource() { |
| 81 | + this = d3Selection().getMember("each").getReceiver().getAnImmediateUse() |
| 82 | + or |
| 83 | + this = d3Selection().getMember("node").getReturn().getAnImmediateUse() |
| 84 | + or |
| 85 | + this = d3Selection().getMember("nodes").getReturn().getUnknownMember().getAnImmediateUse() |
| 86 | + } |
| 87 | + } |
| 88 | + |
| 89 | + private class D3AttributeDefinition extends DOM::AttributeDefinition { |
| 90 | + DataFlow::CallNode call; |
| 91 | + |
| 92 | + D3AttributeDefinition() { |
| 93 | + call = d3Selection().getMember("attr").getACall() and |
| 94 | + call.getNumArgument() > 1 and |
| 95 | + this = call.asExpr() |
| 96 | + } |
| 97 | + |
| 98 | + override string getName() { result = call.getArgument(0).getStringValue() } |
| 99 | + |
| 100 | + override DataFlow::Node getValueNode() { result = call.getArgument(1) } |
| 101 | + } |
| 102 | +} |
0 commit comments