Skip to content

Commit c4f604b

Browse files
authored
Merge pull request github#5896 from geoffw0/weak_crypto
C++: Improve cpp/weak-cryptographic-algorithm
2 parents c80495f + aaae717 commit c4f604b

File tree

7 files changed

+552
-41
lines changed

7 files changed

+552
-41
lines changed
Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,2 @@
1+
lgtm,codescanning
2+
* The "Use of a broken or risky cryptographic algorithm" (`cpp/weak-cryptographic-algorithm`) query has been enhanced to reduce false positive results, and (rarely) find more true positive results.

cpp/ql/src/Security/CWE/CWE-327/BrokenCryptoAlgorithm.ql

Lines changed: 93 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -13,39 +13,111 @@
1313
import cpp
1414
import semmle.code.cpp.security.Encryption
1515

16-
abstract class InsecureCryptoSpec extends Locatable {
17-
abstract string description();
16+
/**
17+
* A function which may relate to an insecure encryption algorithm.
18+
*/
19+
Function getAnInsecureEncryptionFunction() {
20+
(
21+
isInsecureEncryption(result.getName()) or
22+
isInsecureEncryption(result.getAParameter().getName()) or
23+
isInsecureEncryption(result.getDeclaringType().getName())
24+
) and
25+
exists(result.getACallToThisFunction())
1826
}
1927

20-
Function getAnInsecureFunction() {
21-
result.getName().regexpMatch(getInsecureAlgorithmRegex()) and
28+
/**
29+
* A function with additional evidence it is related to encryption.
30+
*/
31+
Function getAdditionalEvidenceFunction() {
32+
(
33+
isEncryptionAdditionalEvidence(result.getName()) or
34+
isEncryptionAdditionalEvidence(result.getAParameter().getName())
35+
) and
2236
exists(result.getACallToThisFunction())
2337
}
2438

25-
class InsecureFunctionCall extends InsecureCryptoSpec, FunctionCall {
26-
InsecureFunctionCall() { this.getTarget() = getAnInsecureFunction() }
27-
28-
override string description() { result = "function call" }
29-
30-
override string toString() { result = FunctionCall.super.toString() }
31-
32-
override Location getLocation() { result = FunctionCall.super.getLocation() }
39+
/**
40+
* A macro which may relate to an insecure encryption algorithm.
41+
*/
42+
Macro getAnInsecureEncryptionMacro() {
43+
isInsecureEncryption(result.getName()) and
44+
exists(result.getAnInvocation())
3345
}
3446

35-
Macro getAnInsecureMacro() {
36-
result.getName().regexpMatch(getInsecureAlgorithmRegex()) and
47+
/**
48+
* A macro with additional evidence it is related to encryption.
49+
*/
50+
Macro getAdditionalEvidenceMacro() {
51+
isEncryptionAdditionalEvidence(result.getName()) and
3752
exists(result.getAnInvocation())
3853
}
3954

40-
class InsecureMacroSpec extends InsecureCryptoSpec, MacroInvocation {
41-
InsecureMacroSpec() { this.getMacro() = getAnInsecureMacro() }
55+
/**
56+
* An enum constant which may relate to an insecure encryption algorithm.
57+
*/
58+
EnumConstant getAnInsecureEncryptionEnumConst() { isInsecureEncryption(result.getName()) }
59+
60+
/**
61+
* An enum constant with additional evidence it is related to encryption.
62+
*/
63+
EnumConstant getAdditionalEvidenceEnumConst() { isEncryptionAdditionalEvidence(result.getName()) }
64+
65+
/**
66+
* A function call we have a high confidence is related to use of an insecure
67+
* encryption algorithm.
68+
*/
69+
class InsecureFunctionCall extends FunctionCall {
70+
Element blame;
71+
string explain;
4272

43-
override string description() { result = "macro invocation" }
73+
InsecureFunctionCall() {
74+
// find use of an insecure algorithm name
75+
(
76+
getTarget() = getAnInsecureEncryptionFunction() and
77+
blame = this and
78+
explain = "function call"
79+
or
80+
exists(MacroInvocation mi |
81+
(
82+
mi.getAnExpandedElement() = this or
83+
mi.getAnExpandedElement() = this.getAnArgument()
84+
) and
85+
mi.getMacro() = getAnInsecureEncryptionMacro() and
86+
blame = mi and
87+
explain = "macro invocation"
88+
)
89+
or
90+
exists(EnumConstantAccess ec |
91+
ec = this.getAnArgument() and
92+
ec.getTarget() = getAnInsecureEncryptionEnumConst() and
93+
blame = ec and
94+
explain = "enum constant access"
95+
)
96+
) and
97+
// find additional evidence that this function is related to encryption.
98+
(
99+
getTarget() = getAdditionalEvidenceFunction()
100+
or
101+
exists(MacroInvocation mi |
102+
(
103+
mi.getAnExpandedElement() = this or
104+
mi.getAnExpandedElement() = this.getAnArgument()
105+
) and
106+
mi.getMacro() = getAdditionalEvidenceMacro()
107+
)
108+
or
109+
exists(EnumConstantAccess ec |
110+
ec = this.getAnArgument() and
111+
ec.getTarget() = getAdditionalEvidenceEnumConst()
112+
)
113+
)
114+
}
44115

45-
override string toString() { result = MacroInvocation.super.toString() }
116+
Element getBlame() { result = blame }
46117

47-
override Location getLocation() { result = MacroInvocation.super.getLocation() }
118+
string getDescription() { result = explain }
48119
}
49120

50-
from InsecureCryptoSpec c
51-
select c, "This " + c.description() + " specifies a broken or weak cryptographic algorithm."
121+
from InsecureFunctionCall c
122+
select c.getBlame(),
123+
"This " + c.getDescription() + " specifies a broken or weak cryptographic algorithm."

cpp/ql/src/semmle/code/cpp/security/Encryption.qll

Lines changed: 44 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -10,10 +10,18 @@ import cpp
1010
string getAnInsecureAlgorithmName() {
1111
result =
1212
[
13-
"DES", "RC2", "RC4", "RC5", "ARCFOUR" // ARCFOUR is a variant of RC4
13+
"DES", "RC2", "RC4", "RC5", "ARCFOUR", // ARCFOUR is a variant of RC4
14+
"3DES", "DES3" // also appears separated, e.g. "TRIPLE-DES", which will be matched as "DES".
1415
]
1516
}
1617

18+
/**
19+
* Gets the name of an algorithm that is known to be secure.
20+
*/
21+
string getASecureAlgorithmName() {
22+
result = ["RSA", "SHA256", "CCM", "GCM", "AES", "Blowfish", "ECIES"]
23+
}
24+
1725
/**
1826
* Gets the name of a hash algorithm that is insecure if it is being used for
1927
* encryption (but it is hard to know when that is happening).
@@ -23,25 +31,40 @@ string getAnInsecureHashAlgorithmName() { result = ["SHA1", "MD5"] }
2331
/**
2432
* Gets the regular expression used for matching strings that look like they
2533
* contain an algorithm that is known to be insecure.
34+
*
35+
* Consider using `isInsecureEncryption` rather than accessing this regular
36+
* expression directly.
2637
*/
2738
string getInsecureAlgorithmRegex() {
2839
result =
2940
// algorithms usually appear in names surrounded by characters that are not
30-
// alphabetical characters in the same case. This handles the upper and lower
31-
// case cases
32-
"(^|.*[^A-Z])(" + strictconcat(getAnInsecureAlgorithmName(), "|") + ")([^A-Z].*|$)" + "|" +
33-
// for lowercase, we want to be careful to avoid being confused by camelCase
34-
// hence we require two preceding uppercase letters to be sure of a case switch,
35-
// or a preceding non-alphabetic character
36-
"(^|.*[A-Z]{2}|.*[^a-zA-Z])(" + strictconcat(getAnInsecureAlgorithmName().toLowerCase(), "|") +
37-
")([^a-z].*|$)"
41+
// alphabetical characters in the same case or numerical digits. This
42+
// handles the upper case:
43+
"(^|.*[^A-Z0-9])(" + strictconcat(getAnInsecureAlgorithmName(), "|") + ")([^A-Z0-9].*|$)" + "|" +
44+
// for lowercase, we want to be careful to avoid being confused by
45+
//camelCase, hence we require two preceding uppercase letters to be
46+
// sure of a case switch (or a preceding non-alphabetic, non-numeric
47+
// character).
48+
"(^|.*[A-Z]{2}|.*[^a-zA-Z0-9])(" +
49+
strictconcat(getAnInsecureAlgorithmName().toLowerCase(), "|") + ")([^a-z0-9].*|$)"
3850
}
3951

4052
/**
41-
* Gets the name of an algorithm that is known to be secure.
53+
* Holds if `name` looks like it might be related to operations with an
54+
* insecure encyption algorithm.
4255
*/
43-
string getASecureAlgorithmName() {
44-
result = ["RSA", "SHA256", "CCM", "GCM", "AES", "Blowfish", "ECIES"]
56+
bindingset[name]
57+
predicate isInsecureEncryption(string name) { name.regexpMatch(getInsecureAlgorithmRegex()) }
58+
59+
/**
60+
* Holds if there is additional evidence that `name` looks like it might be
61+
* related to operations with an encyption algorithm, besides the name of a
62+
* specific algorithm. This can be used in conjuction with
63+
* `isInsecureEncryption` to produce a stronger heuristic.
64+
*/
65+
bindingset[name]
66+
predicate isEncryptionAdditionalEvidence(string name) {
67+
name.toUpperCase().matches("%" + ["CRYPT", "CODE", "CODING", "CBC", "KEY", "CIPHER", "MAC"] + "%")
4568
}
4669

4770
/**
@@ -51,14 +74,15 @@ string getASecureAlgorithmName() {
5174
string getSecureAlgorithmRegex() {
5275
result =
5376
// algorithms usually appear in names surrounded by characters that are not
54-
// alphabetical characters in the same case. This handles the upper and lower
55-
// case cases
56-
"(^|.*[^A-Z])(" + strictconcat(getASecureAlgorithmName(), "|") + ")([^A-Z].*|$)" + "|" +
57-
// for lowercase, we want to be careful to avoid being confused by camelCase
58-
// hence we require two preceding uppercase letters to be sure of a case
59-
// switch, or a preceding non-alphabetic character
60-
"(^|.*[A-Z]{2}|.*[^a-zA-Z])(" + strictconcat(getASecureAlgorithmName().toLowerCase(), "|") +
61-
")([^a-z].*|$)"
77+
// alphabetical characters in the same case or numerical digits. This
78+
// handles the upper case:
79+
"(^|.*[^A-Z0-9])(" + strictconcat(getASecureAlgorithmName(), "|") + ")([^A-Z0-9].*|$)" + "|" +
80+
// for lowercase, we want to be careful to avoid being confused by
81+
//camelCase, hence we require two preceding uppercase letters to be
82+
// sure of a case switch (or a preceding non-alphabetic, non-numeric
83+
// character).
84+
"(^|.*[A-Z]{2}|.*[^a-zA-Z0-9])(" + strictconcat(getASecureAlgorithmName().toLowerCase(), "|") +
85+
")([^a-z0-9].*|$)"
6286
}
6387

6488
/**
Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,25 @@
1+
| test2.cpp:49:4:49:24 | call to my_des_implementation | This function call specifies a broken or weak cryptographic algorithm. |
2+
| test2.cpp:62:33:62:40 | ALGO_DES | This macro invocation specifies a broken or weak cryptographic algorithm. |
3+
| test2.cpp:124:4:124:24 | call to my_des_implementation | This function call specifies a broken or weak cryptographic algorithm. |
4+
| test2.cpp:144:27:144:29 | DES | This enum constant access specifies a broken or weak cryptographic algorithm. |
5+
| test2.cpp:172:28:172:35 | ALGO_DES | This macro invocation specifies a broken or weak cryptographic algorithm. |
6+
| test2.cpp:175:28:175:34 | USE_DES | This enum constant access specifies a broken or weak cryptographic algorithm. |
7+
| test2.cpp:182:38:182:45 | ALGO_DES | This macro invocation specifies a broken or weak cryptographic algorithm. |
8+
| test2.cpp:185:38:185:44 | USE_DES | This enum constant access specifies a broken or weak cryptographic algorithm. |
9+
| test2.cpp:238:2:238:20 | call to encrypt | This function call specifies a broken or weak cryptographic algorithm. |
10+
| test2.cpp:245:5:245:11 | call to encrypt | This function call specifies a broken or weak cryptographic algorithm. |
11+
| test.cpp:38:2:38:31 | ENCRYPT_WITH_DES(data,amount) | This macro invocation specifies a broken or weak cryptographic algorithm. |
12+
| test.cpp:39:2:39:31 | ENCRYPT_WITH_RC2(data,amount) | This macro invocation specifies a broken or weak cryptographic algorithm. |
13+
| test.cpp:41:2:41:32 | ENCRYPT_WITH_3DES(data,amount) | This macro invocation specifies a broken or weak cryptographic algorithm. |
14+
| test.cpp:42:2:42:38 | ENCRYPT_WITH_TRIPLE_DES(data,amount) | This macro invocation specifies a broken or weak cryptographic algorithm. |
15+
| test.cpp:51:2:51:32 | DES_DO_ENCRYPTION(data,amount) | This macro invocation specifies a broken or weak cryptographic algorithm. |
16+
| test.cpp:52:2:52:31 | RUN_DES_ENCODING(data,amount) | This macro invocation specifies a broken or weak cryptographic algorithm. |
17+
| test.cpp:53:2:53:25 | DES_ENCODE(data,amount) | This macro invocation specifies a broken or weak cryptographic algorithm. |
18+
| test.cpp:54:2:54:26 | DES_SET_KEY(data,amount) | This macro invocation specifies a broken or weak cryptographic algorithm. |
19+
| test.cpp:88:2:88:11 | call to encryptDES | This function call specifies a broken or weak cryptographic algorithm. |
20+
| test.cpp:89:2:89:11 | call to encryptRC2 | This function call specifies a broken or weak cryptographic algorithm. |
21+
| test.cpp:91:2:91:12 | call to encrypt3DES | This function call specifies a broken or weak cryptographic algorithm. |
22+
| test.cpp:92:2:92:17 | call to encryptTripleDES | This function call specifies a broken or weak cryptographic algorithm. |
23+
| test.cpp:101:2:101:15 | call to do_des_encrypt | This function call specifies a broken or weak cryptographic algorithm. |
24+
| test.cpp:102:2:102:12 | call to DES_Set_Key | This function call specifies a broken or weak cryptographic algorithm. |
25+
| test.cpp:121:2:121:24 | INIT_ENCRYPT_WITH_DES() | This macro invocation specifies a broken or weak cryptographic algorithm. |
Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
Security/CWE/CWE-327/BrokenCryptoAlgorithm.ql
Lines changed: 125 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,125 @@
1+
2+
typedef unsigned long size_t;
3+
4+
// --- simple encryption macro invocations ---
5+
6+
void my_implementation1(void *data, size_t amount);
7+
void my_implementation2(void *data, size_t amount);
8+
void my_implementation3(void *data, size_t amount);
9+
void my_implementation4(void *data, size_t amount);
10+
void my_implementation5(void *data, size_t amount);
11+
void my_implementation6(const char *str);
12+
13+
#define ENCRYPT_WITH_DES(data, amount) my_implementation1(data, amount)
14+
#define ENCRYPT_WITH_RC2(data, amount) my_implementation2(data, amount)
15+
#define ENCRYPT_WITH_AES(data, amount) my_implementation3(data, amount)
16+
#define ENCRYPT_WITH_3DES(data, amount) my_implementation4(data, amount)
17+
#define ENCRYPT_WITH_TRIPLE_DES(data, amount) my_implementation4(data, amount)
18+
#define ENCRYPT_WITH_RC20(data, amount) my_implementation5(data, amount)
19+
#define ENCRYPT_WITH_DES_REMOVED(data, amount)
20+
21+
#define DESENCRYPT(data, amount) my_implementation1(data, amount)
22+
#define RC2ENCRYPT(data, amount) my_implementation2(data, amount)
23+
#define AESENCRYPT(data, amount) my_implementation3(data, amount)
24+
#define DES3ENCRYPT(data, amount) my_implementation4(data, amount)
25+
26+
#define DES_DO_ENCRYPTION(data, amount) my_implementation1(data, amount)
27+
#define RUN_DES_ENCODING(data, amount) my_implementation1(data, amount)
28+
#define DES_ENCODE(data, amount) my_implementation1(data, amount)
29+
#define DES_SET_KEY(data, amount) my_implementation1(data, amount)
30+
31+
#define DES(str) my_implementation6(str)
32+
#define DESMOND(str) my_implementation6(str)
33+
#define ANODES(str) my_implementation6(str)
34+
#define SORT_ORDER_DES (1)
35+
36+
void test_macros(void *data, size_t amount, const char *str)
37+
{
38+
ENCRYPT_WITH_DES(data, amount); // BAD
39+
ENCRYPT_WITH_RC2(data, amount); // BAD
40+
ENCRYPT_WITH_AES(data, amount); // GOOD (good algorithm)
41+
ENCRYPT_WITH_3DES(data, amount); // BAD
42+
ENCRYPT_WITH_TRIPLE_DES(data, amount); // BAD
43+
ENCRYPT_WITH_RC20(data, amount); // GOOD (if there ever is an RC20 algorithm, we have no reason to believe it's weak)
44+
ENCRYPT_WITH_DES_REMOVED(data, amount); // GOOD (implementation has been deleted)
45+
46+
DESENCRYPT(data, amount); // BAD [NOT DETECTED]
47+
RC2ENCRYPT(data, amount); // BAD [NOT DETECTED]
48+
AESENCRYPT(data, amount); // GOOD (good algorithm)
49+
DES3ENCRYPT(data, amount); // BAD [NOT DETECTED]
50+
51+
DES_DO_ENCRYPTION(data, amount); // BAD
52+
RUN_DES_ENCODING(data, amount); // BAD
53+
DES_ENCODE(data, amount); // BAD
54+
DES_SET_KEY(data, amount); // BAD
55+
56+
DES(str); // GOOD (probably nothing to do with encryption)
57+
DESMOND(str); // GOOD (probably nothing to do with encryption)
58+
ANODES(str); // GOOD (probably nothing to do with encryption)
59+
int ord = SORT_ORDER_DES; // GOOD (probably nothing to do with encryption)
60+
}
61+
62+
// --- simple encryption function calls ---
63+
64+
void encryptDES(void *data, size_t amount);
65+
void encryptRC2(void *data, size_t amount);
66+
void encryptAES(void *data, size_t amount);
67+
void encrypt3DES(void *data, size_t amount);
68+
void encryptTripleDES(void *data, size_t amount);
69+
70+
void DESEncrypt(void *data, size_t amount);
71+
void RC2Encrypt(void *data, size_t amount);
72+
void AESEncrypt(void *data, size_t amount);
73+
void DES3Encrypt(void *data, size_t amount);
74+
75+
void DoDESEncryption(void *data, size_t amount);
76+
void encryptDes(void *data, size_t amount);
77+
void do_des_encrypt(void *data, size_t amount);
78+
void DES_Set_Key(const char *key);
79+
void DESSetKey(const char *key);
80+
81+
int Des();
82+
void Desmond(const char *str);
83+
void Anodes(int i);
84+
void ConDes();
85+
86+
void test_functions(void *data, size_t amount, const char *str)
87+
{
88+
encryptDES(data, amount); // BAD
89+
encryptRC2(data, amount); // BAD
90+
encryptAES(data, amount); // GOOD (good algorithm)
91+
encrypt3DES(data, amount); // BAD
92+
encryptTripleDES(data, amount); // BAD
93+
94+
DESEncrypt(data, amount); // BAD [NOT DETECTED]
95+
RC2Encrypt(data, amount); // BAD [NOT DETECTED]
96+
AESEncrypt(data, amount); // GOOD (good algorithm)
97+
DES3Encrypt(data, amount); // BAD [NOT DETECTED]
98+
99+
DoDESEncryption(data, amount); // BAD [NOT DETECTED]
100+
encryptDes(data, amount); // BAD [NOT DETECTED]
101+
do_des_encrypt(data, amount); // BAD
102+
DES_Set_Key(str); // BAD
103+
DESSetKey(str); // BAD [NOT DETECTED]
104+
105+
Des(); // GOOD (probably nothing to do with encryption)
106+
Desmond(str); // GOOD (probably nothing to do with encryption)
107+
Anodes(1); // GOOD (probably nothing to do with encryption)
108+
ConDes(); // GOOD (probably nothing to do with encryption)
109+
}
110+
111+
// --- macros for functions with no arguments ---
112+
113+
void my_implementation7();
114+
void my_implementation8();
115+
116+
#define INIT_ENCRYPT_WITH_DES() my_implementation7()
117+
#define INIT_ENCRYPT_WITH_AES() my_implementation8()
118+
119+
void test_macros2()
120+
{
121+
INIT_ENCRYPT_WITH_DES(); // BAD
122+
INIT_ENCRYPT_WITH_AES(); // GOOD (good algorithm)
123+
124+
// ...
125+
}

0 commit comments

Comments
 (0)