-
Notifications
You must be signed in to change notification settings - Fork 146
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
Add script engine injection sanitizer with real life example #531
Add script engine injection sanitizer with real life example #531
Conversation
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Thanks for the great contribution, we certainly don't want to miss detection capabilities for this type of issue.
I will read up on scripting engines and comment on the actual sanitizer a bit more. For the meantime, I left some basic review comments.
sanitizers/src/main/java/com/code_intelligence/jazzer/sanitizers/ScriptEngineInjection.java
Outdated
Show resolved
Hide resolved
sanitizers/src/test/java/com/example/ScriptEngineInjection.java
Outdated
Show resolved
Hide resolved
sanitizers/src/main/java/com/code_intelligence/jazzer/sanitizers/ScriptEngineInjection.java
Outdated
Show resolved
Hide resolved
sanitizers/src/main/java/com/code_intelligence/jazzer/sanitizers/ScriptEngineInjection.java
Outdated
Show resolved
Hide resolved
Hey @gdemarcsek, did you get some time to adapt @fmeum comments? It would be great if we could finish this PR, as it's really valuable and could uncover many bugs. |
@bertschneider Got a bit drifted away from this, thx for the reminder, will give it a go this weekend. :) |
d311fbb
to
24cc432
Compare
I thought about the false positive rate of raising a finding whenever the scripting engine is invoked and ways to reduce that possibility. As I understand the documentation and other blog posts one would restrict the script engine to only access allowed classes. Using a properly secured script engine would still be fine. To take that into account we could try to invoke But it should still be possible to guide the fuzzer to invoke the internal What do you think about this approach? |
I could also give it a try, if you like 😄 |
I don't want to annoy, but any update on this sanitizer? I think it could be a great addition to the project. |
Sorry about the delay. Some thoughts (tl;dr I would ignore sandboxing and would avoid relying on any specific script language or engine but some smaller changes might make sense like checking containment instead of equality and randomizing the injected string to avoid detecting constants that happen to be the same):
If this raises a finding whenever the scripting engine is invoked, then I think I misunderstood the entire project. :( I thought the point of this is to prove that user-supplied input flows into the script engine sink, i.e. no constant string would trigger the detector provided it doesn't happen to be the test script - having said that, it might be better to randomize it, as it can be any string really given we are not checking for actual execution). In fact, it's already quite low FP IMHO because it checks for equality, not even containment, so it detects only if you can control the entire script - maybe
As far as I understand, such sandboxing capabilities would be quite engine-specific and rely on engine-specific APIs (like the Rhino blog post you linked or this guide for Nashorn: https://docs.oracle.com/javase/10/nashorn/nashorn-java-api.htm#JSNUG125). This feature would only detect injections via the Java Scripting API (JSR 223) as it would be quite difficult to write a difficult to every single script engine out there. (Similarly how you already detect SQLi by hooking JDBC classes, not specific SQL driver classes.) On a more principled note, it's impossible to reason about the environment of the application from the vantage point of the detector IMHO. (Sure, there might be a sandbox, but is it really secure? How "far" should I look for it, for example the Java process might run in a container which may be quite restricted. Similarly, should I not report a confirmed command injection if AppArmor/SELinux/Seccomp/Capabilities might be making it difficult (impossible?) to spawn child processes?) You guys know better, but I think the vulnerability is still there - sandboxes mitigate (reduce risk), but do not remediate (remove root cause). I think the only way to avoid script injection while allowing the scripts to access user-input is via bindings maybe, but even then, user-controlled input should not flow to
I am not sure I fully understand this part, but indeed, my initial approach was to actually execute the script and confirm evaluation, e,g. execute "1 + 1" with the script engine and check that I get 2 or similar POCs (like |
I misphrased that, sorry, and your understanding is correct! I wondered if raising a finding whenever the script engine is invoked with the fixed string would be sufficient or not. The argument that we would need script engine specific inputs to trigger findings from within the script engines themselves is quite convincing and I would also try to avoid that. I only thought about JS/Nashorn, as that mainly caused problems in the past, but there are plenty of other script engines out there as well. I agree that user controlled input should not be part of the script content directly and could potentially circumvent the sandbox. Verifying that behavior, should be sufficient for this bug detector. If only user controlled input in the script content should be detected, the check could be done before the actual invocation. Then the hooks |
Friendly ping 😃 |
3c02253
to
2733872
Compare
@gdemarcsek I took the liberty to rebase your PR an |
sanitizers/src/main/java/com/code_intelligence/jazzer/sanitizers/ScriptEngineInjection.java
Outdated
Show resolved
Hide resolved
sanitizers/src/main/java/com/code_intelligence/jazzer/sanitizers/ScriptEngineInjection.java
Outdated
Show resolved
Hide resolved
2733872
to
d51403d
Compare
sanitizers/src/main/java/com/code_intelligence/jazzer/sanitizers/ScriptEngineInjection.java
Show resolved
Hide resolved
d51403d
to
9f17243
Compare
@gdemarcsek Do you want to take another look? Otherwise we will merge this PR in a few days. Further changes are always possible, though. |
…ion test because the reproducer / verifier seems to be either faulty or not fully supporting hooks yet?
…s Text example somewhat slower)
…zers/ScriptEngineInjection.java Co-authored-by: Fabian Meumertzheim <[email protected]>
Check for containment of payload in script content in all overloads of eval.
9f17243
to
af3ae4d
Compare
Merged the PR now. @gdemarcsek, thanks again for your contribution! |
This merge request adds a sanitiser to detect data flows of user input into the
javax.script.ScriptEngine#eval
sink which often results in arbitrary code execution (CWE-94) using a registered scripting engine.JVM script engines (JSR-223 implementations) can be registered under multiple arbitrary names and they can implement any language (although ECMAScript is probably the most widely used one in real applications).
This sanitizer implementation only confirms data flow, it does not require successful script execution with a registered script engine (similarly to how the
OsCommandInjection
sanitizer does not guide the fuzzer into running the actual command). As soon as an input produces an execution that reaches the evaluation of the “1+1” expression by ajavax.script.ScriptEngine
implementation, we report a finding.I have added a test that demonstrates the sanitizer by rediscovering the CVE-2022-42889 in Apache Commons Text using minimal assumptions of the library and the finding. (It does however limit the length of the input taken from the fuzzer and it adds a hook to avoid the
${const:…}
interpolation which can be used to read public static members of any loadable class - not remotely as promising as the${script:…}
payload.)I have managed to reproduce this a couple of times in reasonable running times on a laptop.