mirror of
https://github.com/github/codeql.git
synced 2025-12-21 03:06:31 +01:00
Merge branch 'main' into post-release-prep/codeql-cli-2.7.5
This commit is contained in:
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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>
|
||||
21
java/ql/src/Security/CWE/CWE-117/LogInjection.ql
Normal file
21
java/ql/src/Security/CWE/CWE-117/LogInjection.ql
Normal 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"
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
@@ -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>
|
||||
@@ -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"
|
||||
@@ -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).
|
||||
@@ -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).
|
||||
@@ -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"
|
||||
@@ -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"
|
||||
@@ -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
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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(_) }
|
||||
}
|
||||
Reference in New Issue
Block a user