Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions change-notes/1.19/analysis-python.md
Original file line number Diff line number Diff line change
Expand Up @@ -60,6 +60,7 @@ A new predicate `Stmt.getAnEntryNode()` has been added to make it easier to writ
| Information exposure through an exception (`py/stack-trace-exposure`) | security, external/cwe/cwe-209, external/cwe/cwe-497 | Finds instances where information about an exception may be leaked to an external user. Results are shown on LGTM by default. |
| Jinja2 templating with autoescape=False (`py/jinja2/autoescape-false`) | security, external/cwe/cwe-079 | Finds instantiations of `jinja2.Environment` with `autoescape=False` which may allow XSS attacks. Results are hidden on LGTM by default. |
| Request without certificate validation (`py/request-without-cert-validation`) | security, external/cwe/cwe-295 | Finds requests where certificate verification has been explicitly turned off, possibly allowing man-in-the-middle attacks. Results are hidden on LGTM by default. |
| Use of weak cryptographic key (`py/weak-crypto-key`) | security, external/cwe/cwe-326 | Finds creation of weak cryptographic keys. Results are shown on LGTM by default. |

## Changes to existing queries

Expand Down
48 changes: 48 additions & 0 deletions python/ql/src/Security/CWE-326/WeakCrypto.qhelp
Original file line number Diff line number Diff line change
@@ -0,0 +1,48 @@
<!DOCTYPE qhelp PUBLIC
"-//Semmle//qhelp//EN"
"qhelp.dtd">
<qhelp>

<overview>
<p>
Modern encryption relies on it being computationally infeasible to break the cipher and decode a message without the key.
As computational power increases, the ability to break ciphers grows and keys need to become larger.
</p>
<p>
The three main asymmetric key algorithms currently in use are Rivest–Shamir–Adleman (RSA) cryptography, Digital Signature Algorithm (DSA), and Elliptic-curve cryptography (ECC).
With current technology, key sizes of 2048 bits for RSA and DSA,
or 224 bits for ECC, are regarded as unbreakable.
</p>
</overview>

<recommendation>
<p>
Increase the key size to the recommended amount or larger. For RSA or DSA this is at least 2048 bits, for ECC this is at least 224 bits.
</p>
</recommendation>

<references>
<li>
Wikipedia:
<a href="https://en.wikipedia.org/wiki/Digital_Signature_Algorithm">Digital Signature Algorithm</a>.
</li>
<li>
Wikipedia:
<a href="https://en.wikipedia.org/wiki/RSA_(cryptosystem)">RSA cryptosystem</a>.
</li>
<li>
Wikipedia:
<a href="https://en.wikipedia.org/wiki/Elliptic-curve_cryptography">Elliptic-curve cryptography</a>.
</li>
<li>
Python cryptography module:
<a href="https://cryptography.io/en/latest/">cryptography.io</a>.
</li>
<li>
NIST:
<a href="https://nvlpubs.nist.gov/nistpubs/SpecialPublications/NIST.SP.800-131Ar1.pdf">
Recommendation for Transitioning the Use of Cryptographic Algorithms and Key Lengths</a>.
</li>
</references>
</qhelp>

81 changes: 81 additions & 0 deletions python/ql/src/Security/CWE-326/WeakCrypto.ql
Original file line number Diff line number Diff line change
@@ -0,0 +1,81 @@
/**
* @name Use of weak cryptographic key
* @description Use of a cryptographic key that is too small may allow the encryption to be broken.
* @kind problem
* @problem.severity error
* @precision high
* @id py/weak-crypto-key
* @tags security
* external/cwe/cwe-326
*/

import python

int minimumSecureKeySize(string algo) {
algo = "DSA" and result = 2048
or
algo = "RSA" and result = 2048
or
algo = "ECC" and result = 224
}

predicate dsaRsaKeySizeArg(FunctionObject obj, string algorithm, string arg) {
exists(ModuleObject mod |
mod.getAttribute(_) = obj |
algorithm = "DSA" and
(
mod.getName() = "cryptography.hazmat.primitives.asymmetric.dsa" and arg = "key_size"
or
mod.getName() = "Crypto.PublicKey.DSA" and arg = "bits"
or
mod.getName() = "Cryptodome.PublicKey.DSA" and arg = "bits"
)
or
algorithm = "RSA" and
(
mod.getName() = "cryptography.hazmat.primitives.asymmetric.rsa" and arg = "key_size"
or
mod.getName() = "Crypto.PublicKey.RSA" and arg = "bits"
or
mod.getName() = "Cryptodome.PublicKey.RSA" and arg = "bits"
)
)
}

predicate ecKeySizeArg(FunctionObject obj, string arg) {
exists(ModuleObject mod |
mod.getAttribute(_) = obj |
mod.getName() = "cryptography.hazmat.primitives.asymmetric.ec" and arg = "curve"
)
}

int keySizeFromCurve(ClassObject curveClass) {
result = curveClass.declaredAttribute("key_size").(NumericObject).intValue()
}

predicate algorithmAndKeysizeForCall(CallNode call, string algorithm, int keySize, ControlFlowNode keyOrigin) {
exists(FunctionObject func, string argname, ControlFlowNode arg |
arg = func.getNamedArgumentForCall(call, argname) |
exists(NumericObject key |
arg.refersTo(key, keyOrigin) and
dsaRsaKeySizeArg(func, algorithm, argname) and
keySize = key.intValue()
)
or
exists(ClassObject curve |
arg.refersTo(_, curve, keyOrigin) and
ecKeySizeArg(func, argname) and
algorithm = "ECC" and
keySize = keySizeFromCurve(curve)
)
)
}


from CallNode call, ControlFlowNode origin, string algo, int keySize
where
algorithmAndKeysizeForCall(call, algo, keySize, origin) and
keySize < minimumSecureKeySize(algo)
select call, "Creation of an " + algo + " key uses $@ bits, which is below " + minimumSecureKeySize(algo) + " and considered breakable.", origin, keySize.toString()


Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
| weak_crypto.py:67:1:67:30 | ControlFlowNode for dsa_gen_key() | Creation of an DSA key uses $@ bits, which is below 2048 and considered breakable. | weak_crypto.py:12:12:12:15 | ControlFlowNode for IntegerLiteral | 1024 |
| weak_crypto.py:68:1:68:28 | ControlFlowNode for ec_gen_key() | Creation of an ECC key uses $@ bits, which is below 224 and considered breakable. | weak_crypto.py:21:11:21:33 | ControlFlowNode for FakeWeakEllipticCurve() | 160 |
| weak_crypto.py:69:1:69:37 | ControlFlowNode for rsa_gen_key() | Creation of an RSA key uses $@ bits, which is below 2048 and considered breakable. | weak_crypto.py:12:12:12:15 | ControlFlowNode for IntegerLiteral | 1024 |
| weak_crypto.py:71:1:71:22 | ControlFlowNode for Attribute() | Creation of an DSA key uses $@ bits, which is below 2048 and considered breakable. | weak_crypto.py:12:12:12:15 | ControlFlowNode for IntegerLiteral | 1024 |
| weak_crypto.py:72:1:72:22 | ControlFlowNode for Attribute() | Creation of an RSA key uses $@ bits, which is below 2048 and considered breakable. | weak_crypto.py:12:12:12:15 | ControlFlowNode for IntegerLiteral | 1024 |
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
Security/CWE-326/WeakCrypto.ql
2 changes: 2 additions & 0 deletions python/ql/test/query-tests/Security/CWE-326/options
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
semmle-extractor-options: -p ../lib/ --max-import-depth=3
optimize: true
73 changes: 73 additions & 0 deletions python/ql/test/query-tests/Security/CWE-326/weak_crypto.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,73 @@
from cryptography.hazmat import backends
from cryptography.hazmat.primitives.asymmetric import ec, dsa, rsa

#Crypto and Cryptodome have same API
if random():
from Crypto.PublicKey import DSA
from Crypto.PublicKey import RSA
else:
from Cryptodome.PublicKey import DSA
from Cryptodome.PublicKey import RSA

RSA_WEAK = 1024
RSA_OK = 2048
RSA_STRONG = 3076
BIG = 10000

class FakeWeakEllipticCurve:
name = "fake"
key_size = 160

EC_WEAK = FakeWeakEllipticCurve()
EC_OK = ec.SECP224R1()
EC_STRONG = ec.SECP384R1()
EC_BIG = ec.SECT571R1()

dsa_gen_key = dsa.generate_private_key
ec_gen_key = ec.generate_private_key
rsa_gen_key = rsa.generate_private_key

default = backends.default_backend()

#Strong and OK keys.

dsa_gen_key(key_size=RSA_OK, backend=default)
dsa_gen_key(key_size=RSA_STRONG, backend=default)
dsa_gen_key(key_size=BIG, backend=default)
ec_gen_key(key_size=EC_OK, backend=default)
ec_gen_key(key_size=EC_STRONG, backend=default)
ec_gen_key(key_size=EC_BIG, backend=default)
rsa_gen_key(public_exponent=65537, key_size=RSA_OK, backend=default)
rsa_gen_key(public_exponent=65537, key_size=RSA_STRONG, backend=default)
rsa_gen_key(public_exponent=65537, key_size=BIG, backend=default)

DSA.generate(bits=RSA_OK)
DSA.generate(bits=RSA_STRONG)
RSA.generate(bits=RSA_OK)
RSA.generate(bits=RSA_STRONG)

dsa_gen_key(RSA_OK, default)
dsa_gen_key(RSA_STRONG, default)
dsa_gen_key(BIG, default)
ec_gen_key(EC_OK, default)
ec_gen_key(EC_STRONG, default)
ec_gen_key(EC_BIG, default)
rsa_gen_key(65537, RSA_OK, default)
rsa_gen_key(65537, RSA_STRONG, default)
rsa_gen_key(65537, BIG, default)

DSA.generate(RSA_OK)
DSA.generate(RSA_STRONG)
RSA.generate(RSA_OK)
RSA.generate(RSA_STRONG)


# Weak keys

dsa_gen_key(RSA_WEAK, default)
ec_gen_key(EC_WEAK, default)
rsa_gen_key(65537, RSA_WEAK, default)

DSA.generate(RSA_WEAK)
RSA.generate(RSA_WEAK)

Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
def generate(bits):
pass
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
def generate(bits):
pass
Empty file.
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@

def generate_private_key(key_size, backend):
pass

Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@

def generate_private_key(curve, backend):
pass

class SECT571R1(object):
name = "sect571r1"
key_size = 570


class SECP384R1(object):
name = "secp384r1"
key_size = 384


class SECP224R1(object):
name = "secp224r1"
key_size = 224


class SECT163K1(object):
name = "sect163k1"
key_size = 163
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@

def generate_private_key(public_exponent, key_size, backend):
pass