Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Javascript Taint Tracking #18765

Open
DSimsek000 opened this issue Feb 13, 2025 · 5 comments
Open

Javascript Taint Tracking #18765

DSimsek000 opened this issue Feb 13, 2025 · 5 comments
Labels
question Further information is requested

Comments

@DSimsek000
Copy link

I have the following code:

source.js:

function id(mod) {
    return mod;
}

function __importDefault(mod) {
    return mod && mod.__esModule
        ? mod
        : {
            default: mod,
        }
}
var sinkMod0 = __importDefault(require("./sink"))
var sinkMod1 = require("./sink")
var sinkMod2 = id(require("./sink"))
var sinkMod3 = unknown(require("./sink"))

function source(s) {
    sinkMod0.default(s)
    sinkMod1(s)
    sinkMod2(s)
    sinkMod3(s)
}

sink.js:

module.exports = function (data) {
    sink(data)
}

I am using the following query to get all calls to sink from the source function:

/**
 * @kind path-problem
 */

import javascript
import semmle.javascript.dataflow.TaintTracking

module Config implements DataFlow::ConfigSig {
  DataFlow::FlowFeature getAFeature() { result instanceof DataFlow::FeatureHasSourceCallContext }

  predicate isSource(DataFlow::Node source) {
    exists(Function f |
      f.getName() = "source" and
      source.asExpr() = f.getAParameter()
    )
  }

  predicate isSink(DataFlow::Node node) {
    exists(DataFlow::CallNode cn |
      cn.getAnArgument() = node and
      cn.getCalleeName() = "sink"
    )
  }
}

module Flow = DataFlow::Global<Config>;

import Flow::PathGraph

from Flow::PathNode source, Flow::PathNode sink
where Flow::flowPath(source, sink)
select sink.getNode(), source, sink, ""

The above query doesnt find the flow through sinkMod3(s) . Is there a way to get codeQL to treat the unknown(..) function as an identity function?

@DSimsek000 DSimsek000 added the question Further information is requested label Feb 13, 2025
@jketema
Copy link
Contributor

jketema commented Feb 13, 2025

Hi @DSimsek000,

To do this you could add a isAdditionalFlowStep(DataFlow::Node pred, DataFlow::Node succ) predicate to your configuration that looks for calls to unknown functions where pred is an argument of the function and succ is its result.

@DSimsek000
Copy link
Author

DSimsek000 commented Feb 13, 2025

Please correct me if the below is not what you suggested, but what I attempted was:

predicate isAdditionalFlowStep(DataFlow::Node pred, DataFlow::Node succ) {
    exists(DataFlow::InvokeNode call |
      call.getAnArgument() = pred and
      call = succ
    )
  }

which didnt solve the issue.

I also encountered an issue when changing the definition of __importDefault to:

var __importDefault =
    (this && this.__importDefault) ||
    function (mod) {
        return mod && mod.__esModule
            ? mod
            : {
                default: mod,
            }
    }

@jketema
Copy link
Contributor

jketema commented Feb 14, 2025

predicate isAdditionalFlowStep(DataFlow::Node pred, DataFlow::Node succ) {
    exists(DataFlow::InvokeNode call |
      call.getAnArgument() = pred and
      call = succ
    )
  }

Did you look at the AST and confirm that unknown is actually an invoke?

@DSimsek000
Copy link
Author

Hi @jketema,

Yes, take a look at the attached image. Wouldn't this only cause the taint to flow to var sinkMod3 = unknown(require("./sink")), whereas I want sinkMod3 to be treated simply as require("./sink")?

Image

@jketema
Copy link
Contributor

jketema commented Feb 17, 2025

Hi @DSimsek000,

predicate isAdditionalFlowStep(DataFlow::Node pred, DataFlow::Node succ) {
    exists(DataFlow::InvokeNode call |
      call.getAnArgument() = pred and
      call = succ
    )
  }

If I quick evaluate this predicate on a test database build from your original example, I see a step from require("./sink") to unknown(require("./sink")), as expected.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
question Further information is requested
Projects
None yet
Development

No branches or pull requests

2 participants