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

JS: Dynamic import as code injection sink #14293

Draft
wants to merge 6 commits into
base: main
Choose a base branch
from

Conversation

amammad
Copy link
Contributor

@amammad amammad commented Sep 22, 2023

Dynamic import in nodejs support URLs starts with data: which is dangerous.
There is another nodejs API that accepts the data: URL which is:

const {Worker} = require('node:worker_threads');
new Worker(new URL('data:text/javascript,console.log("hello!");'))

but it needs to be a URL Type as input, not any string value that starts with data:, I'm not sure what is the best way to implement it.

@amammad
Copy link
Contributor Author

amammad commented Sep 28, 2023

Hi, I wanted to add two sinks, one of the sinks should have a new URL instance in one of the middle nodes, so I've used flow steps but it seems that I can't reach some sinks like this.

@pwntester
Copy link
Contributor

pwntester commented Oct 2, 2023

@amammad Not sure flow labels are needed for this use case. Can you try removing them and checking if the taint flows through the string concatenations and URL construction (keeping the existing flow step)?

@amammad
Copy link
Contributor Author

amammad commented Oct 2, 2023

@pwntester I'm using labels because dynamic import doesn't need a URL construction but worker needs.
do you want to simply create two queries for each one? Also, how can I indicate on the taint configuration that we must have a URL construction as a middle dataflow node?

@pwntester
Copy link
Contributor

pwntester commented Oct 2, 2023

Does the Worker constructor accept either a String or an URL and only the later is vulnerable? If thats the case, you need the Flow labels and affixing a label on the URL constructor that you can later check at the sink. Note that you can ignore that check for the imports sink but enforce it for the Worker one.

@amammad
Copy link
Contributor Author

amammad commented Oct 2, 2023

@pwntester
If the input is string then it should be a JS file path which then is not a code injection anymore, it can be arbitrary JS file execution.
If the input is a URL object then it can lead to code injection.

@pwntester
Copy link
Contributor

@amammad Ok, in that case you need to use flow labels as you were doing. Sorry for the confusion. You can start use the regular isSource(DataFlow::Node src) which should affix the default implicit label (just an empty string) and then at the URL constructor taint step affix a new label representing that the taint tracking flowed through this specific taint step. At the sink, you can use the isSink(DataFlow::Node sink, FlowLabel sinkLbl) and check the label only for the Worker sink. I think something like that should work but have not tried it:

  override predicate isSource(DataFlow::Node source) {
    source instanceof RemoteFlowSource
  }
  override predicate isAdditionalFlowStep(
    DataFlow::Node pred, DataFlow::Node succ, FlowLabel predlbl, FlowLabel succlbl
  ) {
    exists(DataFlow::NewNode newUrl | succ = newUrl |
      newUrl = DataFlow::globalVarRef(“URL”).getAnInstantiation() and
      pred = newUrl.getArgument(0)
    ) and
    predlbl = StandardFlowLabel and
    succlbl instanceof URLConstructorLabel
  }
  override predicate isSink(DataFlow::Node sink) {
    sink instanceof DynamicImport
  }
  override predicate isSink(DataFlow::Node sink, FlowLabel label) {
    sink instanceof WorkerThreads and label instanceof URLConstructorLabel
  }

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
Development

Successfully merging this pull request may close these issues.

None yet

2 participants