mirror of
https://github.com/github/codeql.git
synced 2026-04-20 14:34:04 +02:00
Add Cleartext Loggin query for Swift.
With some caveats: see TODO comments and failing tests.
This commit is contained in:
@@ -89,6 +89,7 @@ private module Frameworks {
|
||||
private import codeql.swift.frameworks.StandardLibrary.UrlSession
|
||||
private import codeql.swift.frameworks.StandardLibrary.WebView
|
||||
private import codeql.swift.frameworks.Alamofire.Alamofire
|
||||
private import codeql.swift.security.CleartextLogging
|
||||
private import codeql.swift.security.PathInjection
|
||||
private import codeql.swift.security.PredicateInjection
|
||||
}
|
||||
|
||||
67
swift/ql/lib/codeql/swift/security/CleartextLogging.qll
Normal file
67
swift/ql/lib/codeql/swift/security/CleartextLogging.qll
Normal file
@@ -0,0 +1,67 @@
|
||||
/** Provides classes and predicates to reason about cleartext logging of sensitive data vulnerabilities. */
|
||||
|
||||
import swift
|
||||
private import codeql.swift.dataflow.DataFlow
|
||||
private import codeql.swift.dataflow.ExternalFlow
|
||||
private import codeql.swift.security.SensitiveExprs
|
||||
|
||||
/** A data flow sink for cleartext logging of sensitive data vulnerabilities. */
|
||||
abstract class CleartextLoggingSink extends DataFlow::Node { }
|
||||
|
||||
/** A sanitizer for cleartext logging of sensitive data vulnerabilities. */
|
||||
abstract class CleartextLoggingSanitizer extends DataFlow::Node { }
|
||||
|
||||
/**
|
||||
* A unit class for adding additional taint steps.
|
||||
*
|
||||
* Extend this class to add additional taint steps that should apply to paths related to
|
||||
* cleartext logging of sensitive data vulnerabilities.
|
||||
*/
|
||||
class CleartextLoggingAdditionalTaintStep extends Unit {
|
||||
/**
|
||||
* Holds if the step from `n1` to `n2` should be considered a taint
|
||||
* step for flows related to cleartext logging of sensitive data vulnerabilities.
|
||||
*/
|
||||
abstract predicate step(DataFlow::Node n1, DataFlow::Node n2);
|
||||
}
|
||||
|
||||
private class DefaultCleartextLoggingSink extends CleartextLoggingSink {
|
||||
DefaultCleartextLoggingSink() { sinkNode(this, "logging") }
|
||||
}
|
||||
|
||||
// TODO: Remove this. It shouldn't be necessary.
|
||||
private class EncryptionCleartextLoggingSanitizer extends CleartextLoggingSanitizer {
|
||||
EncryptionCleartextLoggingSanitizer() { this.asExpr() instanceof EncryptedExpr }
|
||||
}
|
||||
|
||||
/*
|
||||
* TODO: Add a sanitizer for the OsLogMessage interpolation with .private/.sensitive privacy options,
|
||||
* or restrict the sinks to require .public interpolation depending on what the default behavior is.
|
||||
*/
|
||||
|
||||
private class LoggingSinks extends SinkModelCsv {
|
||||
override predicate row(string row) {
|
||||
row =
|
||||
[
|
||||
";;false;print(_:separator:terminator:);;;Argument[0].ArrayElement;logging",
|
||||
";;false;print(_:separator:terminator:);;;Argument[1..2];logging",
|
||||
";;false;print(_:separator:terminator:toStream:);;;Argument[0].ArrayElement;logging",
|
||||
";;false;print(_:separator:terminator:toStream:);;;Argument[1..2];logging",
|
||||
";;false;NSLog(_:_:);;;Argument[0];logging",
|
||||
";;false;NSLog(_:_:);;;Argument[1].ArrayElement;logging",
|
||||
";;false;NSLogv(_:_:);;;Argument[0];logging",
|
||||
";;false;NSLogv(_:_:);;;Argument[1].ArrayElement;logging",
|
||||
";;false;vfprintf(_:_:_:);;;Agument[1..2];logging",
|
||||
";Logger;true;log(_:);;;Argument[0];logging",
|
||||
";Logger;true;log(level:_:);;;Argument[1];logging",
|
||||
";Logger;true;trace(_:);;;Argument[1];logging",
|
||||
";Logger;true;debug(_:);;;Argument[1];logging",
|
||||
";Logger;true;info(_:);;;Argument[1];logging",
|
||||
";Logger;true;notice(_:);;;Argument[1];logging",
|
||||
";Logger;true;warning(_:);;;Argument[1];logging",
|
||||
";Logger;true;error(_:);;;Argument[1];logging",
|
||||
";Logger;true;critical(_:);;;Argument[1];logging",
|
||||
";Logger;true;fault(_:);;;Argument[1];logging",
|
||||
]
|
||||
}
|
||||
}
|
||||
32
swift/ql/lib/codeql/swift/security/CleartextLoggingQuery.qll
Normal file
32
swift/ql/lib/codeql/swift/security/CleartextLoggingQuery.qll
Normal file
@@ -0,0 +1,32 @@
|
||||
/**
|
||||
* Provides a taint-tracking configuration for reasoning about cleartext logging of
|
||||
* sensitive data vulnerabilities.
|
||||
*/
|
||||
|
||||
import swift
|
||||
private import codeql.swift.dataflow.DataFlow
|
||||
private import codeql.swift.dataflow.TaintTracking
|
||||
private import codeql.swift.security.CleartextLogging
|
||||
private import codeql.swift.security.SensitiveExprs
|
||||
|
||||
/**
|
||||
* A taint-tracking configuration for cleartext logging of sensitive data vulnerabilities.
|
||||
*/
|
||||
class CleartextLoggingConfiguration extends TaintTracking::Configuration {
|
||||
CleartextLoggingConfiguration() { this = "CleartextLoggingConfiguration" }
|
||||
|
||||
override predicate isSource(DataFlow::Node source) { source.asExpr() instanceof SensitiveExpr }
|
||||
|
||||
override predicate isSink(DataFlow::Node sink) { sink instanceof CleartextLoggingSink }
|
||||
|
||||
override predicate isSanitizer(DataFlow::Node sanitizer) {
|
||||
sanitizer instanceof CleartextLoggingSanitizer
|
||||
}
|
||||
|
||||
// Disregard paths that contain other paths. This helps with performance.
|
||||
override predicate isSanitizerIn(DataFlow::Node node) { this.isSource(node) }
|
||||
|
||||
override predicate isAdditionalTaintStep(DataFlow::Node n1, DataFlow::Node n2) {
|
||||
any(CleartextLoggingAdditionalTaintStep s).step(n1, n2)
|
||||
}
|
||||
}
|
||||
50
swift/ql/src/queries/Security/CWE-312/CleartextLogging.qhelp
Normal file
50
swift/ql/src/queries/Security/CWE-312/CleartextLogging.qhelp
Normal file
@@ -0,0 +1,50 @@
|
||||
<!DOCTYPE qhelp PUBLIC
|
||||
"-//Semmle//qhelp//EN"
|
||||
"qhelp.dtd">
|
||||
<qhelp>
|
||||
|
||||
<overview>
|
||||
<p>
|
||||
Sensitive information that is logged unencrypted is accessible to an attacker
|
||||
who gains access to the logs.
|
||||
</p>
|
||||
</overview>
|
||||
|
||||
<recommendation>
|
||||
<p>
|
||||
Ensure that sensitive information is always encrypted or obfuscated before being
|
||||
logged.
|
||||
</p>
|
||||
|
||||
<p>
|
||||
In general, decrypt sensitive information only at the point where it is
|
||||
necessary for it to be used in cleartext.
|
||||
</p>
|
||||
|
||||
<p>
|
||||
Be aware that external processes often store the standard out and
|
||||
standard error streams of the application, causing logged sensitive
|
||||
information to be stored.
|
||||
</p>
|
||||
</recommendation>
|
||||
|
||||
<example>
|
||||
<p>
|
||||
The following example code logs user credentials (in this case, their password)
|
||||
in plain text:
|
||||
</p>
|
||||
<sample src="CleartextLoggingBad.swift"/>
|
||||
<p>
|
||||
Instead, the credentials should be encrypted, obfuscated, or omitted entirely:
|
||||
</p>
|
||||
<sample src="CleartextLoggingGood.swift"/>
|
||||
</example>
|
||||
|
||||
<references>
|
||||
|
||||
<li>M. Dowd, J. McDonald and J. Schuhm, <i>The Art of Software Security Assessment</i>, 1st Edition, Chapter 2 - 'Common Vulnerabilities of Encryption', p. 43. Addison Wesley, 2006.</li>
|
||||
<li>M. Howard and D. LeBlanc, <i>Writing Secure Code</i>, 2nd Edition, Chapter 9 - 'Protecting Secret Data', p. 299. Microsoft, 2002.</li>
|
||||
<li>OWASP: <a href="https://www.owasp.org/index.php/Password_Plaintext_Storage">Password Plaintext Storage</a>.</li>
|
||||
|
||||
</references>
|
||||
</qhelp>
|
||||
24
swift/ql/src/queries/Security/CWE-312/CleartextLogging.ql
Normal file
24
swift/ql/src/queries/Security/CWE-312/CleartextLogging.ql
Normal file
@@ -0,0 +1,24 @@
|
||||
/**
|
||||
* @name Clear-text logging of sensitive information
|
||||
* @description Logging sensitive information without encryption or hashing can
|
||||
* expose it to an attacker.
|
||||
* @kind path-problem
|
||||
* @problem.severity error
|
||||
* @security-severity 7.5
|
||||
* @precision high
|
||||
* @id swift/clear-text-logging
|
||||
* @tags security
|
||||
* external/cwe/cwe-312
|
||||
* external/cwe/cwe-359
|
||||
* external/cwe/cwe-532
|
||||
*/
|
||||
|
||||
import swift
|
||||
import codeql.swift.dataflow.DataFlow
|
||||
import codeql.swift.security.CleartextLoggingQuery
|
||||
import DataFlow::PathGraph
|
||||
|
||||
from DataFlow::PathNode src, DataFlow::PathNode sink
|
||||
where any(CleartextLoggingConfiguration c).hasFlowPath(src, sink)
|
||||
select sink.getNode(), src, sink, "This $@ is written to a log file.", src.getNode(),
|
||||
"potentially sensitive information"
|
||||
@@ -0,0 +1,24 @@
|
||||
import swift
|
||||
import codeql.swift.dataflow.DataFlow
|
||||
import codeql.swift.security.CleartextLoggingQuery
|
||||
import TestUtilities.InlineExpectationsTest
|
||||
|
||||
class CleartextLogging extends InlineExpectationsTest {
|
||||
CleartextLogging() { this = "CleartextLogging" }
|
||||
|
||||
override string getARelevantTag() { result = "hasCleartextLogging" }
|
||||
|
||||
override predicate hasActualResult(Location location, string element, string tag, string value) {
|
||||
exists(
|
||||
CleartextLoggingConfiguration config, DataFlow::Node source, DataFlow::Node sink,
|
||||
Expr sinkExpr
|
||||
|
|
||||
config.hasFlow(source, sink) and
|
||||
sinkExpr = sink.asExpr() and
|
||||
location = sinkExpr.getLocation() and
|
||||
element = sinkExpr.toString() and
|
||||
tag = "hasCleartextLogging" and
|
||||
value = source.asExpr().getLocation().getStartLine().toString()
|
||||
)
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,130 @@
|
||||
// --- stubs ---
|
||||
|
||||
func NSLog(_ format: String, _ args: CVarArg...) {}
|
||||
func NSLogv(_ format: String, _ args: CVaListPointer) {}
|
||||
|
||||
struct OSLogType : RawRepresentable {
|
||||
static let `default` = OSLogType(rawValue: 0)
|
||||
let rawValue: UInt8
|
||||
init(rawValue: UInt8) { self.rawValue = rawValue}
|
||||
}
|
||||
|
||||
struct OSLogStringAlignment {
|
||||
static var none = OSLogStringAlignment()
|
||||
}
|
||||
|
||||
struct OSLogPrivacy {
|
||||
enum Mask { case none }
|
||||
static var auto = OSLogPrivacy()
|
||||
static var `private` = OSLogPrivacy()
|
||||
static var sensitive = OSLogPrivacy()
|
||||
|
||||
static func auto(mask: OSLogPrivacy.Mask) -> OSLogPrivacy { return .auto }
|
||||
static func `private`(mask: OSLogPrivacy.Mask) -> OSLogPrivacy { return .private }
|
||||
static func `sensitive`(mask: OSLogPrivacy.Mask) -> OSLogPrivacy { return .sensitive }
|
||||
}
|
||||
|
||||
struct OSLogInterpolation : StringInterpolationProtocol {
|
||||
typealias StringLiteralType = String
|
||||
init(literalCapacity: Int, interpolationCount: Int) {}
|
||||
func appendLiteral(_: Self.StringLiteralType) {}
|
||||
mutating func appendInterpolation(_ argumentString: @autoclosure @escaping () -> String, align: OSLogStringAlignment = .none, privacy: OSLogPrivacy = .auto) {}
|
||||
mutating func appendInterpolation(_ argumentString: @autoclosure @escaping () -> String, align: OSLogStringAlignment = .none, privacy: OSLogPrivacy = .auto, attributes: String) {}
|
||||
}
|
||||
|
||||
struct OSLogMessage : ExpressibleByStringInterpolation {
|
||||
typealias StringInterpolation = OSLogInterpolation
|
||||
typealias StringLiteralType = String
|
||||
typealias ExtendedGraphemeClusterLiteralType = String
|
||||
typealias UnicodeScalarLiteralType = String
|
||||
init(stringInterpolation: OSLogInterpolation) {}
|
||||
init(stringLiteral: String) {}
|
||||
init(extendedGraphemeClusterLiteral: String) {}
|
||||
init(unicodeScalarLiteral: String) {}
|
||||
}
|
||||
|
||||
struct Logger {
|
||||
func log(_ message: OSLogMessage) {}
|
||||
func log(level: OSLogType, _ message: OSLogMessage) {}
|
||||
func notice(_: OSLogMessage) {}
|
||||
func debug(_: OSLogMessage) {}
|
||||
func trace(_: OSLogMessage) {}
|
||||
func info(_: OSLogMessage) {}
|
||||
func error(_: OSLogMessage) {}
|
||||
func warning(_: OSLogMessage) {}
|
||||
func fault(_: OSLogMessage) {}
|
||||
func critical(_: OSLogMessage) {}
|
||||
|
||||
}
|
||||
|
||||
// --- tests ---
|
||||
|
||||
func test1(password: String, passwordHash : String) {
|
||||
print(password) // $ MISSING: hasCleartextLogging=63
|
||||
print(password, separator: "") // $ MISSING: $ hasCleartextLogging=64
|
||||
print("", separator: password) // $ hasCleartextLogging=65
|
||||
print(password, separator: "", terminator: "") // $ MISSING: hasCleartextLogging=66
|
||||
print("", separator: password, terminator: "") // $ hasCleartextLogging=67
|
||||
print("", separator: "", terminator: password) // $ hasCleartextLogging=68
|
||||
|
||||
NSLog(password) // $ hasCleartextLogging=70
|
||||
NSLog("%@", password as! CVarArg) // $ MISSING: hasCleartextLogging=71
|
||||
NSLog("%@ %@", "" as! CVarArg, password as! CVarArg) // $ MISSING: hasCleartextLogging=72
|
||||
NSLog("\(password)") // $ hasCleartextLogging=73
|
||||
NSLogv("%@", getVaList([password as! CVarArg])) // $ MISSING: hasCleartextLogging=74
|
||||
NSLogv("%@ %@", getVaList(["" as! CVarArg, password as! CVarArg])) // $ MISSING: hasCleartextLogging=75
|
||||
|
||||
let log = Logger()
|
||||
log.log("\(password)") // $ hasCleartextLogging=78
|
||||
log.log("\(password, privacy: .private)") // Safe
|
||||
log.log(level: .default, "\(password)") // $ hasCleartextLogging=80
|
||||
log.trace("\(password)") // $ hasCleartextLogging=81
|
||||
log.debug("\(password)") // $ hasCleartextLogging=82
|
||||
log.info("\(password)") // $ hasCleartextLogging=83
|
||||
log.notice("\(password)") // $ hasCleartextLogging=84
|
||||
log.warning("\(password)") // $ hasCleartextLogging=85
|
||||
log.error("\(password)") // $ hasCleartextLogging=86
|
||||
log.critical("\(password)") // $ hasCleartextLogging=87
|
||||
log.fault("\(password)") // $ hasCleartextLogging=88
|
||||
}
|
||||
/*
|
||||
class MyClass {
|
||||
var harmless = "abc"
|
||||
var password = "123"
|
||||
}
|
||||
|
||||
func test3(x: String) {
|
||||
// alternative evidence of sensitivity...
|
||||
|
||||
UserDefaults.standard.set(x, forKey: "myKey") // $ MISSING: hasCleartextLogging
|
||||
doSomething(password: x);
|
||||
UserDefaults.standard.set(x, forKey: "myKey") // $ hasCleartextLogging
|
||||
|
||||
let y = getPassword();
|
||||
UserDefaults.standard.set(y, forKey: "myKey") // $ hasCleartextLogging
|
||||
|
||||
let z = MyClass()
|
||||
UserDefaults.standard.set(z.harmless, forKey: "myKey") // Safe
|
||||
UserDefaults.standard.set(z.password, forKey: "myKey") // $ hasCleartextLogging
|
||||
}
|
||||
|
||||
func test4(passwd: String) {
|
||||
// sanitizers...
|
||||
|
||||
var x = passwd;
|
||||
var y = passwd;
|
||||
var z = passwd;
|
||||
|
||||
UserDefaults.standard.set(x, forKey: "myKey") // $ hasCleartextLogging
|
||||
UserDefaults.standard.set(y, forKey: "myKey") // $ hasCleartextLogging
|
||||
UserDefaults.standard.set(z, forKey: "myKey") // $ hasCleartextLogging
|
||||
|
||||
x = encrypt(x);
|
||||
hash(data: &y);
|
||||
z = "";
|
||||
|
||||
UserDefaults.standard.set(x, forKey: "myKey") // Safe
|
||||
UserDefaults.standard.set(y, forKey: "myKey") // Safe
|
||||
UserDefaults.standard.set(z, forKey: "myKey") // Safe
|
||||
}
|
||||
*/
|
||||
Reference in New Issue
Block a user