Skip to content

Commit 872615b

Browse files
authored
Merge pull request #10536 from karimhamdanali/ecbmode
Swift: check for using ECB encryption mode
2 parents a589d8f + d44f6b0 commit 872615b

File tree

6 files changed

+223
-0
lines changed

6 files changed

+223
-0
lines changed
Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,22 @@
1+
<!DOCTYPE qhelp PUBLIC
2+
"-//Semmle//qhelp//EN"
3+
"qhelp.dtd">
4+
<qhelp>
5+
<overview>
6+
<p>ECB should not be used as a mode for encryption as it has dangerous weaknesses. Data is encrypted the same way every time, which means that the same plaintext input will always produce the same ciphertext. This behavior makes messages encrypted with ECB
7+
more vulnerable to replay attacks.</p>
8+
</overview>
9+
10+
<recommendation>
11+
<p>Use a different cipher mode such as CBC.</p>
12+
</recommendation>
13+
14+
<example>
15+
<p>The following example shows six cases of instantiating a cipher with various encryption keys and block modes. In the 'BAD' cases, the mode of encryption is ECB, making the encrypted data vulnerable to replay attacks. In the 'GOOD' cases, the encryption mode is CBC, which protects the encrypted data against replay attacks.</p>
16+
<sample src="ECBEncryption.swift" />
17+
</example>
18+
19+
<references>
20+
<li>Wikipedia, block cipher modes of operation, <a href="https://en.wikipedia.org/wiki/Block_cipher_mode_of_operation#Electronic_codebook_.28ECB.29">Electronic codebook (ECB)</a>.</li>
21+
</references>
22+
</qhelp>
Lines changed: 80 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,80 @@
1+
/**
2+
* @name Encryption using ECB
3+
* @description Using the ECB encryption mode makes code vulnerable to replay attacks.
4+
* @kind path-problem
5+
* @problem.severity warning
6+
* @security-severity 7.5
7+
* @precision high
8+
* @id swift/ecb-encryption
9+
* @tags security
10+
* external/cwe/cwe-327
11+
*/
12+
13+
import swift
14+
import codeql.swift.dataflow.DataFlow
15+
import codeql.swift.dataflow.TaintTracking
16+
import DataFlow::PathGraph
17+
18+
/**
19+
* An `Expr` that is used to initialize the block mode of a cipher.
20+
*/
21+
abstract class BlockMode extends Expr { }
22+
23+
/**
24+
* An `Expr` that is used to form an `AES` cipher.
25+
*/
26+
class AES extends BlockMode {
27+
AES() {
28+
// `blockMode` arg in `AES.init` is a sink
29+
exists(ClassDecl c, AbstractFunctionDecl f, CallExpr call |
30+
c.getName() = "AES" and
31+
c.getAMember() = f and
32+
f.getName() = ["init(key:blockMode:)", "init(key:blockMode:padding:)"] and
33+
call.getStaticTarget() = f and
34+
call.getArgument(1).getExpr() = this
35+
)
36+
}
37+
}
38+
39+
/**
40+
* An `Expr` that is used to form a `Blowfish` cipher.
41+
*/
42+
class Blowfish extends BlockMode {
43+
Blowfish() {
44+
// `blockMode` arg in `Blowfish.init` is a sink
45+
exists(ClassDecl c, AbstractFunctionDecl f, CallExpr call |
46+
c.getName() = "Blowfish" and
47+
c.getAMember() = f and
48+
f.getName() = "init(key:blockMode:padding:)" and
49+
call.getStaticTarget() = f and
50+
call.getArgument(1).getExpr() = this
51+
)
52+
}
53+
}
54+
55+
/**
56+
* A taint configuration from the constructor of ECB mode to expressions that use
57+
* it to initialize a cipher.
58+
*/
59+
class EcbEncryptionConfig extends DataFlow::Configuration {
60+
EcbEncryptionConfig() { this = "EcbEncryptionConfig" }
61+
62+
override predicate isSource(DataFlow::Node node) {
63+
exists(StructDecl s, AbstractFunctionDecl f, CallExpr call |
64+
s.getName() = "ECB" and
65+
s.getAMember() = f and
66+
f.getName() = "init()" and
67+
call.getStaticTarget() = f and
68+
node.asExpr() = call
69+
)
70+
}
71+
72+
override predicate isSink(DataFlow::Node node) { node.asExpr() instanceof BlockMode }
73+
}
74+
75+
// The query itself
76+
from EcbEncryptionConfig config, DataFlow::PathNode sourceNode, DataFlow::PathNode sinkNode
77+
where config.hasFlowPath(sourceNode, sinkNode)
78+
select sinkNode.getNode(), sourceNode, sinkNode,
79+
"The initialization of the cipher '" + sinkNode.getNode().toString() +
80+
"' uses the insecure ECB block mode from $@.", sourceNode, sourceNode.getNode().toString()
Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,18 @@
1+
2+
func encrypt(key : Key, padding : Padding) {
3+
// ...
4+
5+
// BAD: ECB is used for block mode
6+
let blockMode = ECB()
7+
_ = try AES(key: key, blockMode: blockMode, padding: padding)
8+
_ = try AES(key: key, blockMode: blockMode)
9+
_ = try Blowfish(key: key, blockMode: blockMode, padding: padding)
10+
11+
// GOOD: ECB is not used for block mode
12+
let blockMode = CBC()
13+
_ = try AES(key: key, blockMode: blockMode, padding: padding)
14+
_ = try AES(key: key, blockMode: blockMode)
15+
_ = try Blowfish(key: key, blockMode: blockMode, padding: padding)
16+
17+
// ...
18+
}
Lines changed: 30 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,30 @@
1+
edges
2+
| test.swift:34:9:34:13 | call to init() : | test.swift:54:37:54:53 | call to getECBBlockMode() |
3+
| test.swift:34:9:34:13 | call to init() : | test.swift:55:37:55:53 | call to getECBBlockMode() |
4+
| test.swift:34:9:34:13 | call to init() : | test.swift:67:42:67:58 | call to getECBBlockMode() |
5+
| test.swift:45:12:45:16 | call to init() : | test.swift:50:37:50:37 | ecb |
6+
| test.swift:45:12:45:16 | call to init() : | test.swift:51:37:51:37 | ecb |
7+
| test.swift:45:12:45:16 | call to init() : | test.swift:65:42:65:42 | ecb |
8+
nodes
9+
| test.swift:34:9:34:13 | call to init() : | semmle.label | call to init() : |
10+
| test.swift:45:12:45:16 | call to init() : | semmle.label | call to init() : |
11+
| test.swift:50:37:50:37 | ecb | semmle.label | ecb |
12+
| test.swift:51:37:51:37 | ecb | semmle.label | ecb |
13+
| test.swift:52:37:52:41 | call to init() | semmle.label | call to init() |
14+
| test.swift:53:37:53:41 | call to init() | semmle.label | call to init() |
15+
| test.swift:54:37:54:53 | call to getECBBlockMode() | semmle.label | call to getECBBlockMode() |
16+
| test.swift:55:37:55:53 | call to getECBBlockMode() | semmle.label | call to getECBBlockMode() |
17+
| test.swift:65:42:65:42 | ecb | semmle.label | ecb |
18+
| test.swift:66:42:66:46 | call to init() | semmle.label | call to init() |
19+
| test.swift:67:42:67:58 | call to getECBBlockMode() | semmle.label | call to getECBBlockMode() |
20+
subpaths
21+
#select
22+
| test.swift:50:37:50:37 | ecb | test.swift:45:12:45:16 | call to init() : | test.swift:50:37:50:37 | ecb | The initialization of the cipher 'ecb' uses the insecure ECB block mode from $@. | test.swift:45:12:45:16 | call to init() : | call to init() |
23+
| test.swift:51:37:51:37 | ecb | test.swift:45:12:45:16 | call to init() : | test.swift:51:37:51:37 | ecb | The initialization of the cipher 'ecb' uses the insecure ECB block mode from $@. | test.swift:45:12:45:16 | call to init() : | call to init() |
24+
| test.swift:52:37:52:41 | call to init() | test.swift:52:37:52:41 | call to init() | test.swift:52:37:52:41 | call to init() | The initialization of the cipher 'call to init()' uses the insecure ECB block mode from $@. | test.swift:52:37:52:41 | call to init() | call to init() |
25+
| test.swift:53:37:53:41 | call to init() | test.swift:53:37:53:41 | call to init() | test.swift:53:37:53:41 | call to init() | The initialization of the cipher 'call to init()' uses the insecure ECB block mode from $@. | test.swift:53:37:53:41 | call to init() | call to init() |
26+
| test.swift:54:37:54:53 | call to getECBBlockMode() | test.swift:34:9:34:13 | call to init() : | test.swift:54:37:54:53 | call to getECBBlockMode() | The initialization of the cipher 'call to getECBBlockMode()' uses the insecure ECB block mode from $@. | test.swift:34:9:34:13 | call to init() : | call to init() |
27+
| test.swift:55:37:55:53 | call to getECBBlockMode() | test.swift:34:9:34:13 | call to init() : | test.swift:55:37:55:53 | call to getECBBlockMode() | The initialization of the cipher 'call to getECBBlockMode()' uses the insecure ECB block mode from $@. | test.swift:34:9:34:13 | call to init() : | call to init() |
28+
| test.swift:65:42:65:42 | ecb | test.swift:45:12:45:16 | call to init() : | test.swift:65:42:65:42 | ecb | The initialization of the cipher 'ecb' uses the insecure ECB block mode from $@. | test.swift:45:12:45:16 | call to init() : | call to init() |
29+
| test.swift:66:42:66:46 | call to init() | test.swift:66:42:66:46 | call to init() | test.swift:66:42:66:46 | call to init() | The initialization of the cipher 'call to init()' uses the insecure ECB block mode from $@. | test.swift:66:42:66:46 | call to init() | call to init() |
30+
| test.swift:67:42:67:58 | call to getECBBlockMode() | test.swift:34:9:34:13 | call to init() : | test.swift:67:42:67:58 | call to getECBBlockMode() | The initialization of the cipher 'call to getECBBlockMode()' uses the insecure ECB block mode from $@. | test.swift:34:9:34:13 | call to init() : | call to init() |
Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
queries/Security/ECB-Encryption/ECBEncryption.ql
Lines changed: 72 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,72 @@
1+
2+
// --- stubs ---
3+
4+
// These stubs roughly follows the same structure as classes from CryptoSwift
5+
class AES
6+
{
7+
init(key: Array<UInt8>, blockMode: BlockMode, padding: Padding) { }
8+
init(key: Array<UInt8>, blockMode: BlockMode) { }
9+
}
10+
11+
class Blowfish
12+
{
13+
init(key: Array<UInt8>, blockMode: BlockMode, padding: Padding) { }
14+
}
15+
16+
protocol BlockMode { }
17+
18+
struct ECB: BlockMode {
19+
init() { }
20+
}
21+
22+
struct CBC: BlockMode {
23+
init() { }
24+
}
25+
26+
protocol PaddingProtocol { }
27+
28+
enum Padding: PaddingProtocol {
29+
case noPadding, zeroPadding, pkcs7, pkcs5, eme_pkcs1v15, emsa_pkcs1v15, iso78164, iso10126
30+
}
31+
32+
// Create some inter-procedural dependencies
33+
func getECBBlockMode() -> BlockMode {
34+
return ECB()
35+
}
36+
37+
func getCBCBlockMode() -> BlockMode {
38+
return CBC()
39+
}
40+
41+
// --- tests ---
42+
43+
func test1() {
44+
let key: Array<UInt8> = [0x2a, 0x3a, 0x80, 0x05, 0xaf, 0x46, 0x58, 0x2d, 0x66, 0x52, 0x10, 0xae, 0x86, 0xd3, 0x8e, 0x8f]
45+
let ecb = ECB()
46+
let cbc = CBC()
47+
let padding = Padding.noPadding
48+
49+
// AES test cases
50+
let ab1 = AES(key: key, blockMode: ecb, padding: padding) // BAD
51+
let ab2 = AES(key: key, blockMode: ecb) // BAD
52+
let ab3 = AES(key: key, blockMode: ECB(), padding: padding) // BAD
53+
let ab4 = AES(key: key, blockMode: ECB()) // BAD
54+
let ab5 = AES(key: key, blockMode: getECBBlockMode(), padding: padding) // BAD
55+
let ab6 = AES(key: key, blockMode: getECBBlockMode()) // BAD
56+
57+
let ag1 = AES(key: key, blockMode: cbc, padding: padding) // GOOD
58+
let ag2 = AES(key: key, blockMode: cbc) // GOOD
59+
let ag3 = AES(key: key, blockMode: CBC(), padding: padding) // GOOD
60+
let ag4 = AES(key: key, blockMode: CBC()) // GOOD
61+
let ag5 = AES(key: key, blockMode: getCBCBlockMode(), padding: padding) // GOOD
62+
let ag6 = AES(key: key, blockMode: getCBCBlockMode()) // GOOD
63+
64+
// Blowfish test cases
65+
let bb1 = Blowfish(key: key, blockMode: ecb, padding: padding) // BAD
66+
let bb2 = Blowfish(key: key, blockMode: ECB(), padding: padding) // BAD
67+
let bb3 = Blowfish(key: key, blockMode: getECBBlockMode(), padding: padding) // BAD
68+
69+
let bg1 = Blowfish(key: key, blockMode: cbc, padding: padding) // GOOD
70+
let bg2 = Blowfish(key: key, blockMode: CBC(), padding: padding) // GOOD
71+
let bg3 = Blowfish(key: key, blockMode: getCBCBlockMode(), padding: padding) // GOOD
72+
}

0 commit comments

Comments
 (0)