-
Notifications
You must be signed in to change notification settings - Fork 1.8k
Java: Refactor Cleartext Storage queries #6493
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
Merged
aschackmull
merged 5 commits into
github:main
from
atorralba:atorralba/cleartext-storage-query-refactor
Sep 23, 2021
Merged
Changes from all commits
Commits
Show all changes
5 commits
Select commit
Hold shift + click to select a range
a30554e
Refactored cleartext storage libraries
atorralba 563e8a2
Remove unused library
atorralba 51d2b52
Remove cached property from SensitiveSource::flowsTo
atorralba d0b9920
Fix encryption sanitizer
atorralba b52a2cd
Apply code review comments
atorralba File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
There are no files selected for viewing
105 changes: 105 additions & 0 deletions
105
java/ql/lib/semmle/code/java/security/CleartextStorageClassQuery.qll
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,105 @@ | ||
/** Provides classes and predicates to reason about cleartext storage in serializable classes. */ | ||
|
||
import java | ||
import semmle.code.java.frameworks.JAXB | ||
import semmle.code.java.dataflow.DataFlow | ||
import semmle.code.java.dataflow.DataFlow2 | ||
import semmle.code.java.security.CleartextStorageQuery | ||
import semmle.code.java.security.CleartextStoragePropertiesQuery | ||
|
||
private class ClassCleartextStorageSink extends CleartextStorageSink { | ||
ClassCleartextStorageSink() { this.asExpr() = getInstanceInput(_, _) } | ||
} | ||
|
||
/** The instantiation of a storable class, which can be stored to disk. */ | ||
abstract class ClassStore extends Storable, ClassInstanceExpr { | ||
/** Gets an input, for example `input` in `instance.password = input`. */ | ||
override Expr getAnInput() { | ||
exists(ClassStoreFlowConfig conf, DataFlow::Node instance | | ||
conf.hasFlow(DataFlow::exprNode(this), instance) and | ||
result = getInstanceInput(instance, this.getConstructor().getDeclaringType()) | ||
) | ||
} | ||
} | ||
|
||
/** | ||
* The instantiation of a serializable class, which can be stored to disk. | ||
* | ||
* Only includes tainted instances where data from a `SensitiveSource` may flow | ||
* to an input of the `Serializable`. | ||
*/ | ||
private class Serializable extends ClassStore { | ||
Serializable() { | ||
this.getConstructor().getDeclaringType().getASupertype*() instanceof TypeSerializable and | ||
// `Properties` are `Serializable`, but handled elsewhere. | ||
not this instanceof Properties and | ||
// restrict attention to tainted instances | ||
exists(SensitiveSource data | | ||
data.flowsTo(getInstanceInput(_, this.getConstructor().getDeclaringType())) | ||
) | ||
} | ||
|
||
/** Gets a store, for example `outputStream.writeObject(instance)`. */ | ||
override Expr getAStore() { | ||
exists(ClassStoreFlowConfig conf, DataFlow::Node n | | ||
serializableStore(n, result) and | ||
conf.hasFlow(DataFlow::exprNode(this), n) | ||
) | ||
} | ||
} | ||
|
||
/** The instantiation of a marshallable class, which can be stored to disk as XML. */ | ||
private class Marshallable extends ClassStore { | ||
Marshallable() { this.getConstructor().getDeclaringType() instanceof JAXBElement } | ||
|
||
/** Gets a store, for example `marshaller.marshal(instance)`. */ | ||
override Expr getAStore() { | ||
exists(ClassStoreFlowConfig conf, DataFlow::Node n | | ||
marshallableStore(n, result) and | ||
conf.hasFlow(DataFlow::exprNode(this), n) | ||
) | ||
} | ||
} | ||
|
||
/** Gets an input, for example `input` in `instance.password = input`. */ | ||
private Expr getInstanceInput(DataFlow::Node instance, RefType t) { | ||
exists(AssignExpr a, FieldAccess fa | | ||
instance = DataFlow::getFieldQualifier(fa) and | ||
a.getDest() = fa and | ||
a.getSource() = result and | ||
fa.getField().getDeclaringType() = t | ||
| | ||
t.getASourceSupertype*() instanceof TypeSerializable or | ||
t instanceof JAXBElement | ||
) | ||
} | ||
|
||
private class ClassStoreFlowConfig extends DataFlow2::Configuration { | ||
ClassStoreFlowConfig() { this = "ClassStoreFlowConfig" } | ||
|
||
override predicate isSource(DataFlow::Node src) { src.asExpr() instanceof ClassStore } | ||
|
||
override predicate isSink(DataFlow::Node sink) { | ||
exists(getInstanceInput(sink, _)) or | ||
serializableStore(sink, _) or | ||
marshallableStore(sink, _) | ||
} | ||
|
||
override int fieldFlowBranchLimit() { result = 1 } | ||
} | ||
|
||
private predicate serializableStore(DataFlow::Node instance, Expr store) { | ||
exists(MethodAccess m | | ||
store = m and | ||
m.getMethod() instanceof WriteObjectMethod and | ||
instance.asExpr() = m.getArgument(0) | ||
) | ||
} | ||
|
||
private predicate marshallableStore(DataFlow::Node instance, Expr store) { | ||
exists(MethodAccess m | | ||
store = m and | ||
m.getMethod() instanceof JAXBMarshalMethod and | ||
instance.asExpr() = m.getArgument(0) | ||
) | ||
} |
48 changes: 48 additions & 0 deletions
48
java/ql/lib/semmle/code/java/security/CleartextStorageCookieQuery.qll
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,48 @@ | ||
/** Provides classes and predicates to reason about cleartext storage in cookies. */ | ||
|
||
import java | ||
import semmle.code.java.dataflow.DataFlow | ||
import semmle.code.java.dataflow.DataFlow3 | ||
import semmle.code.java.security.CleartextStorageQuery | ||
|
||
private class CookieCleartextStorageSink extends CleartextStorageSink { | ||
CookieCleartextStorageSink() { this.asExpr() = cookieInput(_) } | ||
} | ||
|
||
/** The instantiation of a cookie, which can act as storage. */ | ||
class Cookie extends Storable, ClassInstanceExpr { | ||
Cookie() { | ||
this.getConstructor().getDeclaringType().hasQualifiedName("javax.servlet.http", "Cookie") | ||
} | ||
|
||
/** Gets an input, for example `input` in `new Cookie("...", input);`. */ | ||
override Expr getAnInput() { result = cookieInput(this) } | ||
|
||
/** Gets a store, for example `response.addCookie(cookie);`. */ | ||
override Expr getAStore() { | ||
exists(CookieToStoreFlowConfig conf, DataFlow::Node n | | ||
cookieStore(n, result) and | ||
conf.hasFlow(DataFlow::exprNode(this), n) | ||
) | ||
} | ||
} | ||
|
||
private predicate cookieStore(DataFlow::Node cookie, Expr store) { | ||
exists(MethodAccess m, Method def | | ||
m.getMethod() = def and | ||
def.getName() = "addCookie" and | ||
def.getDeclaringType().hasQualifiedName("javax.servlet.http", "HttpServletResponse") and | ||
store = m and | ||
cookie.asExpr() = m.getAnArgument() | ||
) | ||
} | ||
|
||
private class CookieToStoreFlowConfig extends DataFlow3::Configuration { | ||
CookieToStoreFlowConfig() { this = "CookieToStoreFlowConfig" } | ||
|
||
override predicate isSource(DataFlow::Node src) { src.asExpr() instanceof Cookie } | ||
|
||
override predicate isSink(DataFlow::Node sink) { cookieStore(sink, _) } | ||
} | ||
|
||
private Expr cookieInput(Cookie c) { result = c.getArgument(1) } |
62 changes: 62 additions & 0 deletions
62
java/ql/lib/semmle/code/java/security/CleartextStoragePropertiesQuery.qll
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,62 @@ | ||
/** Provides classes and predicates to reason about cleartext storage in Properties files. */ | ||
|
||
import java | ||
import semmle.code.java.dataflow.DataFlow | ||
import semmle.code.java.frameworks.Properties | ||
import semmle.code.java.security.CleartextStorageQuery | ||
|
||
private class PropertiesCleartextStorageSink extends CleartextStorageSink { | ||
PropertiesCleartextStorageSink() { | ||
exists(MethodAccess m | | ||
m.getMethod() instanceof PropertiesSetPropertyMethod and this.asExpr() = m.getArgument(1) | ||
) | ||
} | ||
} | ||
|
||
/** The instantiation of a `Properties` object, which can be stored to disk. */ | ||
class Properties extends Storable, ClassInstanceExpr { | ||
Properties() { this.getConstructor().getDeclaringType() instanceof TypeProperty } | ||
|
||
/** Gets an input, for example `input` in `props.setProperty("password", input);`. */ | ||
override Expr getAnInput() { | ||
exists(PropertiesFlowConfig conf, DataFlow::Node n | | ||
propertiesInput(n, result) and | ||
conf.hasFlow(DataFlow::exprNode(this), n) | ||
) | ||
} | ||
|
||
/** Gets a store, for example `props.store(outputStream, "...")`. */ | ||
override Expr getAStore() { | ||
exists(PropertiesFlowConfig conf, DataFlow::Node n | | ||
propertiesStore(n, result) and | ||
conf.hasFlow(DataFlow::exprNode(this), n) | ||
) | ||
} | ||
} | ||
|
||
private predicate propertiesInput(DataFlow::Node prop, Expr input) { | ||
exists(MethodAccess m | | ||
m.getMethod() instanceof PropertiesSetPropertyMethod and | ||
input = m.getArgument(1) and | ||
prop.asExpr() = m.getQualifier() | ||
) | ||
} | ||
|
||
private predicate propertiesStore(DataFlow::Node prop, Expr store) { | ||
exists(MethodAccess m | | ||
m.getMethod() instanceof PropertiesStoreMethod and | ||
store = m and | ||
prop.asExpr() = m.getQualifier() | ||
) | ||
} | ||
|
||
private class PropertiesFlowConfig extends DataFlow::Configuration { | ||
PropertiesFlowConfig() { this = "PropertiesFlowConfig" } | ||
|
||
override predicate isSource(DataFlow::Node src) { src.asExpr() instanceof Properties } | ||
|
||
override predicate isSink(DataFlow::Node sink) { | ||
propertiesInput(sink, _) or | ||
propertiesStore(sink, _) | ||
} | ||
} |
101 changes: 101 additions & 0 deletions
101
java/ql/lib/semmle/code/java/security/CleartextStorageQuery.qll
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,101 @@ | ||
/** Provides classes and predicates to reason about cleartext storage vulnerabilities. */ | ||
|
||
import java | ||
private import semmle.code.java.dataflow.DataFlow4 | ||
private import semmle.code.java.dataflow.TaintTracking | ||
private import semmle.code.java.security.SensitiveActions | ||
|
||
/** A sink representing persistent storage that saves data in clear text. */ | ||
abstract class CleartextStorageSink extends DataFlow::Node { } | ||
|
||
/** A sanitizer for flows tracking sensitive data being stored in persistent storage. */ | ||
abstract class CleartextStorageSanitizer extends DataFlow::Node { } | ||
|
||
/** An additional taint step for sensitive data flowing into cleartext storage. */ | ||
class CleartextStorageAdditionalTaintStep extends Unit { | ||
abstract predicate step(DataFlow::Node n1, DataFlow::Node n2); | ||
} | ||
|
||
/** Class for expressions that may represent 'sensitive' information */ | ||
class SensitiveSource extends Expr instanceof SensitiveExpr { | ||
/** Holds if this source flows to the `sink`. */ | ||
predicate flowsTo(Expr sink) { | ||
exists(SensitiveSourceFlowConfig conf | | ||
conf.hasFlow(DataFlow::exprNode(this), DataFlow::exprNode(sink)) | ||
) | ||
} | ||
} | ||
|
||
/** | ||
* Class representing entities that may be stored/written, with methods | ||
* for finding values that are stored within them, and cases | ||
* of the entity being stored. | ||
*/ | ||
abstract class Storable extends Call { | ||
/** Gets an "input" that is stored in an instance of this class. */ | ||
abstract Expr getAnInput(); | ||
|
||
/** Gets an expression where an instance of this class is stored (e.g. to disk). */ | ||
abstract Expr getAStore(); | ||
} | ||
|
||
private class SensitiveSourceFlowConfig extends TaintTracking::Configuration { | ||
SensitiveSourceFlowConfig() { this = "SensitiveSourceFlowConfig" } | ||
|
||
override predicate isSource(DataFlow::Node src) { src.asExpr() instanceof SensitiveExpr } | ||
|
||
override predicate isSink(DataFlow::Node sink) { sink instanceof CleartextStorageSink } | ||
|
||
override predicate isSanitizer(DataFlow::Node sanitizer) { | ||
sanitizer instanceof CleartextStorageSanitizer | ||
} | ||
|
||
override predicate isAdditionalTaintStep(DataFlow::Node n1, DataFlow::Node n2) { | ||
any(CleartextStorageAdditionalTaintStep c).step(n1, n2) | ||
} | ||
} | ||
|
||
private class DefaultCleartextStorageSanitizer extends CleartextStorageSanitizer { | ||
DefaultCleartextStorageSanitizer() { | ||
this.getType() instanceof NumericType or | ||
this.getType() instanceof BooleanType or | ||
exists(EncryptedValueFlowConfig conf | conf.hasFlow(_, this)) | ||
} | ||
} | ||
|
||
/** | ||
* Method call for encrypting sensitive information. As there are various implementations of | ||
* encryption (reversible and non-reversible) from both JDK and third parties, this class simply | ||
* checks method name to take a best guess to reduce false positives. | ||
*/ | ||
private class EncryptedSensitiveMethodAccess extends MethodAccess { | ||
EncryptedSensitiveMethodAccess() { | ||
this.getMethod().getName().toLowerCase().matches(["%encrypt%", "%hash%", "%digest%"]) | ||
} | ||
} | ||
|
||
/** Flow configuration for encryption methods flowing to inputs of persistent storage. */ | ||
private class EncryptedValueFlowConfig extends DataFlow4::Configuration { | ||
EncryptedValueFlowConfig() { this = "EncryptedValueFlowConfig" } | ||
|
||
override predicate isSource(DataFlow::Node src) { | ||
src.asExpr() instanceof EncryptedSensitiveMethodAccess | ||
} | ||
|
||
override predicate isSink(DataFlow::Node sink) { sink.asExpr() instanceof SensitiveExpr } | ||
} | ||
|
||
/** A taint step for `EditText.toString` in Android. */ | ||
private class AndroidEditTextCleartextStorageStep extends CleartextStorageAdditionalTaintStep { | ||
override predicate step(DataFlow::Node n1, DataFlow::Node n2) { | ||
// EditText.getText() return type is parsed as `Object`, so we need to | ||
// add a taint step for `Object.toString` to model `editText.getText().toString()` | ||
exists(MethodAccess ma, Method m | | ||
ma.getMethod() = m and | ||
m.getDeclaringType() instanceof TypeObject and | ||
m.hasName("toString") | ||
| | ||
n1.asExpr() = ma.getQualifier() and n2.asExpr() = ma | ||
) | ||
} | ||
} |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Oops, something went wrong.
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
Uh oh!
There was an error while loading. Please reload this page.