Merge branch 'main' into post-release-prep/codeql-cli-2.7.5

This commit is contained in:
Andrew Eisenberg
2022-01-14 08:23:43 -08:00
744 changed files with 39341 additions and 19067 deletions

View File

@@ -102,7 +102,7 @@ class MismatchedContainerAccess extends MethodAccess {
|
this.getCallee()
.getDeclaringType()
.getASupertype*()
.getASourceSupertype*()
.getSourceDeclaration()
.hasQualifiedName(package, type) and
this.getCallee().getParameter(i).getType() instanceof TypeObject

View File

@@ -72,7 +72,7 @@ class MismatchedContainerModification extends MethodAccess {
|
this.getCallee()
.getDeclaringType()
.getASupertype*()
.getASourceSupertype*()
.getSourceDeclaration()
.hasQualifiedName(package, type) and
this.getCallee().getParameter(i).getType() instanceof TypeObject

View File

@@ -29,16 +29,15 @@ other forms of HTML injection.
</recommendation>
<example>
<p>In the example, a username, provided by the user, is logged using <code>logger.warn</code> (from <code>org.slf4j.Logger</code>).
<p>In the first example, a username, provided by the user, is logged using <code>logger.warn</code> (from <code>org.slf4j.Logger</code>).
In the first case (<code>/bad</code> endpoint), the username is logged without any sanitization.
If a malicious user provides <code>Guest'%0AUser:'Admin</code> as a username parameter,
the log entry will be split into two separate lines, where the first line will be <code>User:'Guest'</code> and the second one will be <code>User:'Admin'</code>.
</p>
<sample src="LogInjectionBad.java" />
<p> In the second case (<code>/good</code> endpoint), <code>matches()</code> is used to ensure the user input only has alphanumeric characters.
If a malicious user provides `Guest'%0AUser:'Admin` as a username parameter,
the log entry will not be split into two separate lines, resulting in a single line <code>User:'Guest'User:'Admin'</code>.</p>
<p> In the second example (<code>/good</code> endpoint), <code>matches()</code> is used to ensure the user input only has alphanumeric characters.
If a malicious user provides `Guest'%0AUser:'Admin` as a username parameter, the log entry will not be logged at all, preventing the injection.</p>
<sample src="LogInjectionGood.java" />
</example>

View File

@@ -0,0 +1,21 @@
/**
* @name Log Injection
* @description Building log entries from user-controlled data may allow
* insertion of forged log entries by malicious users.
* @kind path-problem
* @problem.severity error
* @security-severity 7.8
* @precision medium
* @id java/log-injection
* @tags security
* external/cwe/cwe-117
*/
import java
import semmle.code.java.security.LogInjectionQuery
import DataFlow::PathGraph
from LogInjectionConfiguration cfg, DataFlow::PathNode source, DataFlow::PathNode sink
where cfg.hasFlowPath(source, sink)
select sink.getNode(), source, sink, "This $@ flows to a log entry.", source.getNode(),
"user-provided value"

View File

@@ -16,9 +16,9 @@ public class LogInjection {
public String good(@RequestParam(value = "username", defaultValue = "name") String username) {
// The regex check here, allows only alphanumeric characters to pass.
// Hence, does not result in log injection
if (username.matches("\w*")) {
if (username.matches("\\w*")) {
log.warn("User:'{}'", username);
return username;
}
}

View File

@@ -1,7 +1,7 @@
public void testSetSharedPrefs(Context context, String name, String password)
{
{
// BAD - save sensitive information in cleartext
// BAD - sensitive information saved in cleartext.
SharedPreferences sharedPrefs = context.getSharedPreferences("user_prefs", Context.MODE_PRIVATE);
Editor editor = sharedPrefs.edit();
editor.putString("name", name);
@@ -10,7 +10,7 @@ public void testSetSharedPrefs(Context context, String name, String password)
}
{
// GOOD - save sensitive information in encrypted format
// GOOD - save sensitive information encrypted with a custom method.
SharedPreferences sharedPrefs = context.getSharedPreferences("user_prefs", Context.MODE_PRIVATE);
Editor editor = sharedPrefs.edit();
editor.putString("name", encrypt(name));
@@ -19,7 +19,7 @@ public void testSetSharedPrefs(Context context, String name, String password)
}
{
// GOOD - save sensitive information using the built-in `EncryptedSharedPreferences` class in androidx.
// GOOD - sensitive information saved using the built-in `EncryptedSharedPreferences` class in androidx.
MasterKey masterKey = new MasterKey.Builder(context, MasterKey.DEFAULT_MASTER_KEY_ALIAS)
.setKeyScheme(MasterKey.KeyScheme.AES256_GCM)
.build();
@@ -37,3 +37,12 @@ public void testSetSharedPrefs(Context context, String name, String password)
editor.commit();
}
}
private static String encrypt(String cleartext) throws Exception {
// Use an encryption or hashing algorithm in real world. The demo below just returns its
// hash.
MessageDigest digest = MessageDigest.getInstance("SHA-256");
byte[] hash = digest.digest(cleartext.getBytes(StandardCharsets.UTF_8));
String encoded = Base64.getEncoder().encodeToString(hash);
return encoded;
}

View File

@@ -2,7 +2,7 @@
<qhelp>
<overview>
<p>
<code>SharedPreferences</code> is an Android API that stores application preferences using simple sets of data values. Almost every Android application uses this API. It allows to easily save, alter, and retrieve the values stored in the user's profile. However, sensitive information should not be saved in cleartext. Otherwise it can be accessed by any process or user on rooted devices, or can be disclosed through chained vulnerabilities e.g. unexpected access to its private storage through exposed components.
<code>SharedPreferences</code> is an Android API that stores application preferences using simple sets of data values. It allows you to easily save, alter, and retrieve the values stored in a user's profile. However, sensitive information should not be saved in cleartext. Otherwise it can be accessed by any process or user in rooted devices, or can be disclosed through chained vulnerabilities, like unexpected access to the private storage through exposed components.
</p>
</overview>
@@ -24,10 +24,6 @@
</example>
<references>
<li>
CWE:
<a href="https://cwe.mitre.org/data/definitions/312.html">CWE-312: Cleartext Storage of Sensitive Information</a>
</li>
<li>
Android Developers:
<a href="https://developer.android.com/topic/security/data">Work with data more securely</a>

View File

@@ -0,0 +1,23 @@
/**
* @name Cleartext storage of sensitive information using `SharedPreferences` on Android
* @description Cleartext Storage of Sensitive Information using
* SharedPreferences on Android allows access for users with root
* privileges or unexpected exposure from chained vulnerabilities.
* @kind problem
* @problem.severity warning
* @precision medium
* @id java/android/cleartext-storage-shared-prefs
* @tags security
* external/cwe/cwe-312
*/
import java
import semmle.code.java.security.CleartextStorageSharedPrefsQuery
from SensitiveSource data, SharedPreferencesEditorMethodAccess s, Expr input, Expr store
where
input = s.getAnInput() and
store = s.getAStore() and
data.flowsTo(input)
select store, "'SharedPreferences' class $@ containing $@ is stored $@. Data was added $@.", s,
s.toString(), data, "sensitive data", store, "here", input, "here"

View File

@@ -0,0 +1,4 @@
---
category: newQuery
---
* The query "Cleartext storage of sensitive information using `SharedPreferences` on Android" (`java/android/cleartext-storage-shared-prefs`) has been promoted from experimental to the main query pack. Its results will now appear by default. This query was originally [submitted as an experimental query by @luchua-bc](https://github.com/github/codeql/pull/4675).

View File

@@ -0,0 +1,4 @@
---
category: newQuery
---
* The query "Log Injection" (`java/log-injection`) has been promoted from experimental to the main query pack. Its results will now appear by default. The query was originally [submitted as an experimental query by @porcupineyhairs and @dellalibera](https://github.com/github/codeql/pull/5099).

View File

@@ -1,38 +0,0 @@
/**
* @name Log Injection
* @description Building log entries from user-controlled data is vulnerable to
* insertion of forged log entries by a malicious user.
* @kind path-problem
* @problem.severity error
* @precision high
* @id java/log-injection
* @tags security
* external/cwe/cwe-117
*/
import java
import DataFlow::PathGraph
import experimental.semmle.code.java.Logging
import semmle.code.java.dataflow.FlowSources
/**
* A taint-tracking configuration for tracking untrusted user input used in log entries.
*/
private class LogInjectionConfiguration extends TaintTracking::Configuration {
LogInjectionConfiguration() { this = "Log Injection" }
override predicate isSource(DataFlow::Node source) { source instanceof RemoteFlowSource }
override predicate isSink(DataFlow::Node sink) {
sink.asExpr() = any(LoggingCall c).getALogArgument()
}
override predicate isSanitizer(DataFlow::Node node) {
node.getType() instanceof BoxedType or node.getType() instanceof PrimitiveType
}
}
from LogInjectionConfiguration cfg, DataFlow::PathNode source, DataFlow::PathNode sink
where cfg.hasFlowPath(source, sink)
select sink.getNode(), source, sink, "$@ flows to log entry.", source.getNode(),
"User-provided value"

View File

@@ -1,167 +0,0 @@
/**
* @name Cleartext storage of sensitive information using `SharedPreferences` on Android
* @description Cleartext Storage of Sensitive Information using
* SharedPreferences on Android allows access for users with root
* privileges or unexpected exposure from chained vulnerabilities.
* @kind problem
* @problem.severity warning
* @precision medium
* @id java/android/cleartext-storage-shared-prefs
* @tags security
* external/cwe/cwe-312
*/
import java
import semmle.code.java.dataflow.DataFlow4
import semmle.code.java.dataflow.DataFlow5
import semmle.code.java.dataflow.TaintTracking
import semmle.code.java.frameworks.android.Intent
import semmle.code.java.frameworks.android.SharedPreferences
import semmle.code.java.security.SensitiveActions
/** Holds if the method call is a setter method of `SharedPreferences`. */
private predicate sharedPreferencesInput(DataFlow::Node sharedPrefs, Expr input) {
exists(MethodAccess m |
m.getMethod() instanceof PutSharedPreferenceMethod and
input = m.getArgument(1) and
not exists(EncryptedValueFlowConfig conf | conf.hasFlow(_, DataFlow::exprNode(input))) and
sharedPrefs.asExpr() = m.getQualifier()
)
}
/** Holds if the method call is the store method of `SharedPreferences`. */
private predicate sharedPreferencesStore(DataFlow::Node sharedPrefs, Expr store) {
exists(MethodAccess m |
m.getMethod() instanceof StoreSharedPreferenceMethod and
store = m and
sharedPrefs.asExpr() = m.getQualifier()
)
}
/** Flow from `SharedPreferences` to either a setter or a store method. */
class SharedPreferencesFlowConfig extends DataFlow::Configuration {
SharedPreferencesFlowConfig() {
this = "CleartextStorageSharedPrefs::SharedPreferencesFlowConfig"
}
override predicate isSource(DataFlow::Node src) {
src.asExpr() instanceof SharedPreferencesEditorMethodAccess
}
override predicate isSink(DataFlow::Node sink) {
sharedPreferencesInput(sink, _) or
sharedPreferencesStore(sink, _)
}
}
/**
* Method call of 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.
*/
class EncryptedSensitiveMethodAccess extends MethodAccess {
EncryptedSensitiveMethodAccess() {
this.getMethod().getName().toLowerCase().matches(["%encrypt%", "%hash%"])
}
}
/** Flow configuration of encrypting sensitive information. */
class EncryptedValueFlowConfig extends DataFlow5::Configuration {
EncryptedValueFlowConfig() { this = "CleartextStorageSharedPrefs::EncryptedValueFlowConfig" }
override predicate isSource(DataFlow5::Node src) {
exists(EncryptedSensitiveMethodAccess ema | src.asExpr() = ema)
}
override predicate isSink(DataFlow5::Node sink) {
exists(MethodAccess ma |
ma.getMethod() instanceof PutSharedPreferenceMethod and
sink.asExpr() = ma.getArgument(1)
)
}
}
/** Flow from the create method of `androidx.security.crypto.EncryptedSharedPreferences` to its instance. */
private class EncryptedSharedPrefFlowConfig extends DataFlow4::Configuration {
EncryptedSharedPrefFlowConfig() {
this = "CleartextStorageSharedPrefs::EncryptedSharedPrefFlowConfig"
}
override predicate isSource(DataFlow4::Node src) {
src.asExpr().(MethodAccess).getMethod() instanceof CreateEncryptedSharedPreferencesMethod
}
override predicate isSink(DataFlow4::Node sink) {
sink.asExpr().getType() instanceof SharedPreferences
}
}
/** The call to get a `SharedPreferences.Editor` object, which can set shared preferences or be stored to device. */
class SharedPreferencesEditorMethodAccess extends MethodAccess {
SharedPreferencesEditorMethodAccess() {
this.getMethod() instanceof GetSharedPreferencesEditorMethod and
not exists(
EncryptedSharedPrefFlowConfig config // not exists `SharedPreferences sharedPreferences = EncryptedSharedPreferences.create(...)`
|
config.hasFlow(_, DataFlow::exprNode(this.getQualifier()))
)
}
/** Gets an input, for example `password` in `editor.putString("password", password);`. */
Expr getAnInput() {
exists(SharedPreferencesFlowConfig conf, DataFlow::Node n |
sharedPreferencesInput(n, result) and
conf.hasFlow(DataFlow::exprNode(this), n)
)
}
/** Gets a store, for example `editor.commit();`. */
Expr getAStore() {
exists(SharedPreferencesFlowConfig conf, DataFlow::Node n |
sharedPreferencesStore(n, result) and
conf.hasFlow(DataFlow::exprNode(this), n)
)
}
}
/**
* Flow from sensitive expressions to shared preferences.
* Note it can be merged into `SensitiveSourceFlowConfig` of `Security/CWE/CWE-312/SensitiveStorage.qll` when this query is promoted from the experimental directory.
*/
private class SensitiveSharedPrefsFlowConfig extends TaintTracking::Configuration {
SensitiveSharedPrefsFlowConfig() {
this = "CleartextStorageSharedPrefs::SensitiveSharedPrefsFlowConfig"
}
override predicate isSource(DataFlow::Node src) { src.asExpr() instanceof SensitiveExpr }
override predicate isSink(DataFlow::Node sink) {
exists(MethodAccess m |
m.getMethod() instanceof PutSharedPreferenceMethod and
sink.asExpr() = m.getArgument(1)
)
}
}
/** Class for shared preferences that may contain 'sensitive' information */
class SensitiveSharedPrefsSource extends Expr {
SensitiveSharedPrefsSource() {
// SensitiveExpr is abstract, this lets us inherit from it without
// being a technical subclass
this instanceof SensitiveExpr
}
/** Holds if this source flows to the `sink`. */
predicate flowsTo(Expr sink) {
exists(SensitiveSharedPrefsFlowConfig conf |
conf.hasFlow(DataFlow::exprNode(this), DataFlow::exprNode(sink))
)
}
}
from SensitiveSharedPrefsSource data, SharedPreferencesEditorMethodAccess s, Expr input, Expr store
where
input = s.getAnInput() and
store = s.getAStore() and
data.flowsTo(input)
select store, "'SharedPreferences' class $@ containing $@ is stored here. Data was added $@.", s,
s.toString(), data, "sensitive data", input, "here"

View File

@@ -2,7 +2,6 @@ import java
import DataFlow
import semmle.code.java.frameworks.Networking
import semmle.code.java.security.QueryInjection
import experimental.semmle.code.java.Logging
/**
* A data flow source of the client ip obtained according to the remote endpoint identifier specified

View File

@@ -11,9 +11,9 @@
*/
import java
import semmle.code.java.dataflow.ExternalFlow
import semmle.code.java.dataflow.TaintTracking
import semmle.code.java.security.SensitiveActions
import experimental.semmle.code.java.Logging
import DataFlow
import PathGraph
@@ -36,9 +36,7 @@ class LoggerConfiguration extends DataFlow::Configuration {
override predicate isSource(DataFlow::Node source) { source.asExpr() instanceof CredentialExpr }
override predicate isSink(DataFlow::Node sink) {
exists(LoggingCall c | sink.asExpr() = c.getALogArgument())
}
override predicate isSink(DataFlow::Node sink) { sinkNode(sink, "logging") }
override predicate isAdditionalFlowStep(DataFlow::Node node1, DataFlow::Node node2) {
TaintTracking::localTaintStep(node1, node2)

View File

@@ -1,40 +0,0 @@
/**
* Provides classes and predicates for working with loggers.
*/
import java
/** Models a call to a logging method. */
class LoggingCall extends MethodAccess {
LoggingCall() {
exists(RefType t, Method m |
t.hasQualifiedName("org.apache.log4j", "Category") or // Log4j 1
t.hasQualifiedName("org.apache.logging.log4j", ["Logger", "LogBuilder"]) or // Log4j 2
t.hasQualifiedName("org.apache.commons.logging", "Log") or
// JBoss Logging (`org.jboss.logging.Logger` in some implementations like JBoss Application Server 4.0.4 did not implement `BasicLogger`)
t.hasQualifiedName("org.jboss.logging", ["BasicLogger", "Logger"]) or
t.hasQualifiedName("org.slf4j.spi", "LoggingEventBuilder") or
t.hasQualifiedName("org.slf4j", "Logger") or
t.hasQualifiedName("org.scijava.log", "Logger") or
t.hasQualifiedName("com.google.common.flogger", "LoggingApi") or
t.hasQualifiedName("java.lang", "System$Logger") or
t.hasQualifiedName("java.util.logging", "Logger")
|
(
m.getDeclaringType().getASourceSupertype*() = t or
m.getDeclaringType().extendsOrImplements*(t)
) and
m.getReturnType() instanceof VoidType and
this = m.getAReference()
)
or
exists(RefType t, Method m | t.hasQualifiedName("android.util", "Log") |
m.hasName(["d", "e", "i", "v", "w", "wtf"]) and
m.getDeclaringType() = t and
this = m.getAReference()
)
}
/** Returns an argument which would be logged by this call. */
Argument getALogArgument() { result = this.getArgument(_) }
}