-
Notifications
You must be signed in to change notification settings - Fork 1.6k
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
Java: QL Query to Detect Security Sensitive non-CSPRNG usage #2694
base: main
Are you sure you want to change the base?
Java: QL Query to Detect Security Sensitive non-CSPRNG usage #2694
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.
Just some initial thoughts and self-review
java/ql/src/Security/CWE/CWE-338/PredictableRandomNumberGenerator.java
Outdated
Show resolved
Hide resolved
java/ql/src/Security/CWE/CWE-338/PredictableRandomNumberGenerator.qhelp
Outdated
Show resolved
Hide resolved
) | ||
} | ||
|
||
private class PredictableJavaStdlibRandomMethodAcccess extends PredictableRandomMethodAccess { | ||
PredictableJavaStdlibRandomMethodAcccess() { | ||
this.getReceiverType() instanceof JavaStdlibInsecureRandomType and | ||
not isSecureRuntimeVarAccess(this.getQualifier()) | ||
} | ||
} | ||
|
||
/** | ||
* A byte array tainted by insecure RNG. | ||
* | ||
* Examples: | ||
* | ||
* ``` | ||
* byte[] array = new byte[]; | ||
* new Random().nextBytes(array); | ||
* ``` | ||
*/ | ||
private class TaintedByteArrayWrite extends VarAccess { | ||
TaintedByteArrayWrite() { | ||
exists(PredictableJavaStdlibRandomMethodAcccess insecureMethodAccess, MethodAccess ma | | ||
ma.getMethod().hasName("nextBytes") and | ||
ma = insecureMethodAccess and | ||
ma.getArgument(0) = this | ||
) | ||
} | ||
} | ||
|
||
class SecureRandomMethodAccess extends MethodAccess { | ||
SecureRandomMethodAccess() { this.getReceiverType() instanceof TypeSecureRandom } | ||
} | ||
|
||
/** | ||
* Intermediate tracking step to find instances of RandomStringGenerator that are "safely" created. | ||
* A "safely" created RandomStringGenerator is defined as having used a SecureRandom method in the | ||
* RandomStringGenerator.Builder::usingRandom lambda. | ||
*/ | ||
class SafeRandomStringGeneratorFlowConfig extends TaintTracking2::Configuration { | ||
SafeRandomStringGeneratorFlowConfig() { this = "RNG:SafeRandomStringGeneratorFlowConfig" } | ||
|
||
private predicate isUsingSafeUsingRandomSupplier(MethodAccess source) { | ||
exists( | ||
ApacheRandomStringGeneratorBuilderUsingRandomMethod usingRandomMethod, | ||
FunctionalExpr functionalExpr, SecureRandomMethodAccess secureRandomMethodAccess | ||
| | ||
usingRandomMethod = source.getMethod() and | ||
source.getArgument(0).getProperExpr() = functionalExpr and | ||
// Some SecureRandom method is used in the scope of the functional expression | ||
functionalExpr.asMethod() = secureRandomMethodAccess.getEnclosingCallable() | ||
) | ||
} | ||
|
||
override predicate isSource(DataFlow::Node src) { isUsingSafeUsingRandomSupplier(src.asExpr()) } | ||
|
||
/** | ||
* A sink is any RandomStringGenerator::generate method. | ||
*/ | ||
override predicate isSink(DataFlow::Node sink) { | ||
exists(ApacheRandomStringGeneratorType generatorType | | ||
sink.asExpr().(MethodAccess).getMethod() = generatorType.getGenerateMethod() | ||
) | ||
} | ||
|
||
private predicate isBuilderStep(MethodAccess previous, MethodAccess next) { | ||
exists(ApacheRandomStringGeneratorBuilderType builderType | | ||
builderType.getAMethod() = previous.getMethod() and | ||
previous = next.getQualifier() | ||
) | ||
} | ||
|
||
private predicate isGeneratorCallStep(Expr previous, MethodAccess next) { | ||
exists(ApacheRandomStringGeneratorType generatorType | | ||
generatorType.getGenerateMethod() = next.getMethod() and | ||
previous = next.getQualifier() | ||
) | ||
} | ||
|
||
override predicate isAdditionalTaintStep(DataFlow::Node previous, DataFlow::Node next) { | ||
isBuilderStep(previous.asExpr(), next.asExpr()) or | ||
isGeneratorCallStep(previous.asExpr(), next.asExpr()) | ||
} | ||
} | ||
|
||
private class TaintedRandomStringGeneratorMethodAccess extends PredictableRandomMethodAccess { | ||
TaintedRandomStringGeneratorMethodAccess() { | ||
this.getMethod().getDeclaringType() instanceof ApacheRandomStringGeneratorType and | ||
not exists(SafeRandomStringGeneratorFlowConfig safeFlowSource | | ||
safeFlowSource.hasFlowTo(DataFlow::exprNode(this)) | ||
) | ||
} | ||
} | ||
|
||
private class PredictableRandomTaintedMethodAccessSource extends PredictableRandomFlowSource { | ||
PredictableRandomTaintedMethodAccessSource() { | ||
this.asExpr() instanceof PredictableRandomMethodAccess or | ||
this.asExpr() instanceof TaintedByteArrayWrite | ||
} | ||
} |
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.
This file should probably live somewhere else (and maybe be broken up), but leaving it here for now allows me to continue to iterate on the query against LGTM.com
import java | ||
|
||
class UUIDCreationExp extends ClassInstanceExpr { | ||
UUIDCreationExp() { this.getConstructedType() instanceof TypeUUID } | ||
} | ||
|
||
class ApacheRandomStringUtilsType extends RefType { | ||
ApacheRandomStringUtilsType() { | ||
this.hasQualifiedName("org.apache.commons.lang", "RandomStringUtils") or | ||
this.hasQualifiedName("org.apache.commons.lang3", "RandomStringUtils") | ||
} | ||
} | ||
|
||
class ApacheRandomStringGeneratorType extends RefType { | ||
ApacheRandomStringGeneratorType() { | ||
this.hasQualifiedName("org.apache.commons.text", "RandomStringGenerator") | ||
} | ||
|
||
Method getGenerateMethod() { | ||
exists(Method m | | ||
m.getDeclaringType() = this and | ||
m.hasName("generate") and | ||
m = result | ||
) | ||
} | ||
} | ||
|
||
class ApacheRandomStringGeneratorBuilderType extends RefType { | ||
ApacheRandomStringGeneratorBuilderType() { | ||
this.hasQualifiedName("org.apache.commons.text", "RandomStringGenerator$Builder") | ||
} | ||
} | ||
|
||
class ApacheRandomStringGeneratorBuilderUsingRandomMethod extends Method { | ||
ApacheRandomStringGeneratorBuilderUsingRandomMethod() { | ||
this.getDeclaringType() instanceof ApacheRandomStringGeneratorBuilderType and | ||
this.hasName("usingRandom") | ||
} | ||
} | ||
|
||
class ApacheTextRandomProviderType extends RefType { | ||
ApacheTextRandomProviderType() { | ||
this.hasQualifiedName("org.apache.commons.text", "TextRandomProvider") | ||
} | ||
} | ||
|
||
class JavaStdlibInsecureRandomType extends RefType { | ||
JavaStdlibInsecureRandomType() { | ||
this.hasQualifiedName("java.util.concurrent", "ThreadLocalRandom") or | ||
this.hasQualifiedName("java.util", "Random") | ||
} | ||
} |
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.
Another file that should get moved.
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.
Add java.util.SplittableRandom
?
/** | ||
* A byte array tainted by insecure RNG. | ||
* | ||
* Examples: | ||
* | ||
* ``` | ||
* byte[] array = new byte[]; | ||
* new Random().nextBytes(array); | ||
* ``` | ||
*/ | ||
private class TaintedByteArrayWrite extends VarAccess { | ||
TaintedByteArrayWrite() { | ||
exists(PredictableJavaStdlibRandomMethodAcccess insecureMethodAccess, MethodAccess ma | | ||
ma.getMethod().hasName("nextBytes") and | ||
ma = insecureMethodAccess and | ||
ma.getArgument(0) = this | ||
) | ||
} | ||
} |
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.
Just discovered that this has a false positive on:
Random random = new SecureRandom();
byte[] array = new byte[];
random.nextBytes(array);
We have the |
There are a couple of similar "insecure randomness" queries for other languages. Prior to merging, we should consider whether to give this query the same ID ( |
@aschackmull The Got anything for this?
I'm running into this case still with Elasticsearch: |
Your example code snippet should work with |
In the ElasticSearch example, the fact that |
If the relevant FP in ElasticSearch is only in |
An alternative approach is to perhaps make method calls like |
ApacheRandomStringUtilsType() { | ||
this.hasQualifiedName("org.apache.commons.lang", "RandomStringUtils") or | ||
this.hasQualifiedName("org.apache.commons.lang3", "RandomStringUtils") | ||
} |
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.
Have you tried non-string variants (eg: org.apache.commons.lang3.RandomUtils
)? do they generate a high rate of FPs?
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.
Can you elaborate on what you mean?
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.
You are accounting for org.apache.commons.lang3.RandomStringUtils
, but not for org.apache.commons.lang3.RandomUtils
. Did you miss that class or did you discard it because it was generating too many false positives?
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.
Huh, nope, I just didn't know it existed, thanks!
This is still a WIP, I'll be coming back to it soon. |
…neration * master: (2719 commits) C++: Fix QLDoc on `FormattingFunction` library Update docs/language/ql-handbook/name-resolution.rst Mention `libraryPathDependencies` JS: Update another test JS: Autoformat JS: Update test QL reference: Update process for name resolution JS: Propagate locally returned functions out of calls JS: Add test Add link to Java regex Pattern documentation to language.rst Fix typo C++: Fix reference to `Block` JS: Fix a misleading javadoc comment CodeQL for Go: Edit AST reference C#: Rename a class C#: Recognize more calls to `IHtmlHelper.Raw` Give general syntax instead of examples for exprs Add more links to java AST class reference Move explicit hyperlink targets to the bottom Add references to the AST class reference for go ...
Hey @JLLeitschuh do you intend to come back to this PR? |
Very much so. It's been on the back-burner of my mind for the past few months. I probably won't have time/energy to finish it until after August. |
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.
FWIW, I'm waiting to review the text until this PR is nearly ready to be merged.
I will get back to this eventually |
@egregius313 rather old query, but you may find some things valuable, and worth including in the current variant of this query that ended up being merged. |
GitHub Security Lab BB Submission
The goal of this query is to detect the use of a PRNG like
java.util.Random
,org.apache.commons.lang.RandomStringUtils
,org.apache.commons.text.RandomStringGenerator
, orjava.util.concurrent.ThreadLocalRandom
in a security sensitive context.This vulnerability can have up to a CVSSv3 score of 9.8/10 depending upon the use of the insecure data generated.
Design
The design of the query is to find strings that are generated with some sort of insecure randomess component. Then use taint tracking to see if that string ever taints a value that has some sort of security sensitive indicator.
Currently the indicators I'm using are variables/methods that (when lowercase) match a name with the following predicate.
I've debated adding
%accnt%
,%account%
, and%trusted%
as well.Query Current Status
I'm currently not totally happy with the false-positive rate that this query produces and I'm looking for support/suggestions on how to increase its accuracy.
Additionally, because my 'sink' locations are inherently fuzzy you end up with multiple sink locations for the same source.
Determining whether or not a
Random
instance is actually aSecureRandom
type at runtime is also inherently difficult. Currently, I'm only considering aRandom
variable 'safe' if at any point it's assigned as a final variable.For example, these are 'safe' random values:
If there's a better way to guess the type of a
Random
expression, I'd love to use it.Known False Positives
Eclipse/Jetty
The way that Jetty sets it's random number generator is inherently 'safe', in probably 99% of cases.
https://github.com/eclipse/jetty.project/blob/69808d3851de31ffffb343115030d7bcf826ed01/jetty-server/src/main/java/org/eclipse/jetty/server/session/DefaultSessionIdManager.java#L367-L384
Query Results: https://lgtm.com/query/1859599740827953942/
Apache/shiro
https://github.com/apache/shiro/blob/cb3a1b3c0dcd8b89cc8d51ee9ede086882921faa/core/src/main/java/org/apache/shiro/session/mgt/eis/RandomSessionIdGenerator.java
Random random
type is ambiguous in my query because there's asetRandom
method that could be called by the user.Apache/cloudstack
https://github.com/apache/cloudstack/blob/ac202639a5108d126ecea585c7e793696679dbe4/utils/src/main/java/com/cloud/utils/PasswordGenerator.java#L53-L107
Can't track that the
generateLowercaseChar
will always be passed aSecureRandom
instance.Elasticsearch/Elasticsearch
https://github.com/elastic/elasticsearch/blob/a5c40b3d828fe6cc5d722ec134305864d31c6515/server/src/main/java/org/elasticsearch/common/RandomBasedUUIDGenerator.java
Can't track that the
Random
instance passed togetUUIDBytes
will always be an instance ofSecureRandom
.More... TODO
Test Repository
I've been testing this query against the code I've been uploading to this repository. Ideally, this repository would get contributed as a part of the tests for this PR when it's complete.
See: https://github.com/JLLeitschuh/bad-random-examples