Merge pull request #5635 from RasmusWL/port-weak-crypto-algorithm

Approved by yoff
This commit is contained in:
CodeQL CI
2021-05-20 01:22:32 -07:00
committed by GitHub
55 changed files with 1668 additions and 291 deletions

View File

@@ -439,7 +439,7 @@
],
"CryptoAlgorithms Python/JS": [
"javascript/ql/src/semmle/javascript/security/CryptoAlgorithms.qll",
"python/ql/src/semmle/crypto/Crypto.qll"
"python/ql/src/semmle/python/concepts/CryptoAlgorithms.qll"
],
"SensitiveDataHeuristics Python/JS": [
"javascript/ql/src/semmle/javascript/security/internal/SensitiveDataHeuristics.qll",

View File

@@ -0,0 +1,2 @@
lgtm,codescanning
* Updated the _Use of a broken or weak cryptographic algorithm_ (`py/weak-cryptographic-algorithm`) query, so it alerts on any use of a weak cryptographic non-hashing algorithm. Introduced a new query _Use of a broken or weak cryptographic hashing algorithm on sensitive data_ (`py/weak-sensitive-data-hashing`) to handle weak cryptographic hashing algorithms, which only alerts when used on sensitive data.

View File

@@ -15,22 +15,28 @@
secure than it appears to be.
</p>
<p>
This query alerts on any use of a weak cryptographic algorithm, that is
not a hashing algorithm. Use of broken or weak cryptographic hash
functions are handled by the
<code>py/weak-sensitive-data-hashing</code> query.
</p>
</overview>
<recommendation>
<p>
Ensure that you use a strong, modern cryptographic
algorithm. Use at least AES-128 or RSA-2048 for
encryption, and SHA-2 or SHA-3 for secure hashing.
algorithm, such as AES-128 or RSA-2048.
</p>
</recommendation>
<example>
<p>
The following code uses the <code>pycrypto</code>
The following code uses the <code>pycryptodome</code>
library to encrypt some secret data. When you create a cipher using
<code>pycrypto</code> you must specify the encryption
<code>pycryptodome</code> you must specify the encryption
algorithm to use. The first example uses DES, which is an
older algorithm that is now considered weak. The second
example uses AES, which is a stronger modern algorithm.
@@ -39,8 +45,12 @@
<sample src="examples/broken_crypto.py" />
<p>
WARNING: Although the second example above is more robust,
pycrypto is no longer actively maintained so we recommend using <code>cryptography</code> instead.
NOTICE: the original
<code><a href="https://pypi.org/project/pycrypto/">pycrypto</a></code>
PyPI package that provided the <code>Crypto</code> module is not longer
actively maintained, so you should use the
<code><a href="https://pypi.org/project/pycryptodome/">pycryptodome</a></code>
PyPI package instead (which has a compatible API).
</p>
</example>

View File

@@ -1,7 +1,7 @@
/**
* @name Use of a broken or weak cryptographic algorithm
* @description Using broken or weak cryptographic algorithms can compromise security.
* @kind path-problem
* @kind problem
* @problem.severity warning
* @precision high
* @id py/weak-cryptographic-algorithm
@@ -10,21 +10,15 @@
*/
import python
import semmle.python.security.Paths
import semmle.python.security.SensitiveData
import semmle.python.security.Crypto
import semmle.python.Concepts
class BrokenCryptoConfiguration extends TaintTracking::Configuration {
BrokenCryptoConfiguration() { this = "Broken crypto configuration" }
override predicate isSource(TaintTracking::Source source) {
source instanceof SensitiveDataSource
}
override predicate isSink(TaintTracking::Sink sink) { sink instanceof WeakCryptoSink }
}
from BrokenCryptoConfiguration config, TaintedPathSource src, TaintedPathSink sink
where config.hasFlowPath(src, sink)
select sink.getSink(), src, sink, "$@ is used in a broken or weak cryptographic algorithm.",
src.getSource(), "Sensitive data"
from Cryptography::CryptographicOperation operation, Cryptography::CryptographicAlgorithm algorithm
where
algorithm = operation.getAlgorithm() and
algorithm.isWeak() and
// `Cryptography::HashingAlgorithm` and `Cryptography::PasswordHashingAlgorithm` are
// handled by `py/weak-sensitive-data-hashing`
algorithm instanceof Cryptography::EncryptionAlgorithm
select operation,
"The cryptographic algorithm " + algorithm.getName() +
" is broken or weak, and should not be used."

View File

@@ -0,0 +1,104 @@
<!DOCTYPE qhelp PUBLIC
"-//Semmle//qhelp//EN"
"qhelp.dtd">
<qhelp>
<overview>
<p>
Using a broken or weak cryptographic hash function can leave data
vulnerable, and should not be used in security related code.
</p>
<p>
A strong cryptographic hash function should be resistant to:
</p>
<ul>
<li>
pre-image attacks: if you know a hash value <code>h(x)</code>,
you should not be able to easily find the input <code>x</code>.
</li>
<li>
collision attacks: if you know a hash value <code>h(x)</code>,
you should not be able to easily find a different input <code>y</code>
with the same hash value <code>h(x) = h(y)</code>.
</li>
</ul>
<p>
In cases with a limited input space, such as for passwords, the hash
function also needs to be computationally expensive to be resistant to
brute-force attacks. Passwords should also have an unique salt applied
before hashing, but that is not considered by this query.
</p>
<p>
As an example, both MD5 and SHA-1 are known to be vulnerable to collision attacks.
</p>
<p>
Since it's OK to use a weak cryptographic hash function in a non-security
context, this query only alerts when these are used to hash sensitive
data (such as passwords, certificates, usernames).
</p>
<p>
Use of broken or weak cryptographic algorithms that are not hashing algorithms, is
handled by the <code>py/weak-cryptographic-algorithm</code> query.
</p>
</overview>
<recommendation>
<p>
Ensure that you use a strong, modern cryptographic hash function:
</p>
<ul>
<li>
such as Argon2, scrypt, bcrypt, or PBKDF2 for passwords and other data with limited input space.
</li>
<li>
such as SHA-2, or SHA-3 in other cases.
</li>
</ul>
</recommendation>
<example>
<p>
The following example shows two functions for checking whether the hash
of a certificate matches a known value -- to prevent tampering.
The first function uses MD5 that is known to be vulnerable to collision attacks.
The second function uses SHA-256 that is a strong cryptographic hashing function.
</p>
<sample src="examples/weak_certificate_hashing.py" />
</example>
<example>
<p>
The following example shows two functions for hashing passwords.
The first function uses SHA-256 to hash passwords. Although SHA-256 is a
strong cryptographic hash function, it is not suitable for password
hashing since it is not computationally expensive.
</p>
<sample src="examples/weak_password_hashing_bad.py" />
<p>
The second function uses Argon2 (through the <code>argon2-cffi</code>
PyPI package), which is a strong password hashing algorithm (and
includes a per-password salt by default).
</p>
<sample src="examples/weak_password_hashing_good.py" />
</example>
<references>
<li>OWASP: <a href="https://cheatsheetseries.owasp.org/cheatsheets/Password_Storage_Cheat_Sheet.html">Password Storage Cheat Sheet</a></li>
</references>
</qhelp>

View File

@@ -0,0 +1,47 @@
/**
* @name Use of a broken or weak cryptographic hashing algorithm on sensitive data
* @description Using broken or weak cryptographic hashing algorithms can compromise security.
* @kind path-problem
* @problem.severity warning
* @precision high
* @id py/weak-sensitive-data-hashing
* @tags security
* external/cwe/cwe-327
* external/cwe/cwe-916
*/
import python
import semmle.python.security.dataflow.WeakSensitiveDataHashing
import semmle.python.dataflow.new.DataFlow
import semmle.python.dataflow.new.TaintTracking
import DataFlow::PathGraph
from
DataFlow::PathNode source, DataFlow::PathNode sink, string ending, string algorithmName,
string classification
where
exists(NormalHashFunction::Configuration config |
config.hasFlowPath(source, sink) and
algorithmName = sink.getNode().(NormalHashFunction::Sink).getAlgorithmName() and
classification = source.getNode().(NormalHashFunction::Source).getClassification() and
ending = "."
)
or
exists(ComputationallyExpensiveHashFunction::Configuration config |
config.hasFlowPath(source, sink) and
algorithmName = sink.getNode().(ComputationallyExpensiveHashFunction::Sink).getAlgorithmName() and
classification =
source.getNode().(ComputationallyExpensiveHashFunction::Source).getClassification() and
(
sink.getNode().(ComputationallyExpensiveHashFunction::Sink).isComputationallyExpensive() and
ending = "."
or
not sink.getNode().(ComputationallyExpensiveHashFunction::Sink).isComputationallyExpensive() and
ending =
" for " + classification +
" hashing, since it is not a computationally expensive hash function."
)
)
select sink.getNode(), source, sink,
"$@ is used in a hashing algorithm (" + algorithmName + ") that is insecure" + ending,
source.getNode(), "Sensitive data (" + classification + ")"

View File

@@ -0,0 +1,9 @@
import hashlib
def certificate_matches_known_hash_bad(certificate, known_hash):
hash = hashlib.md5(certificate).hexdigest() # BAD
return hash == known_hash
def certificate_matches_known_hash_good(certificate, known_hash):
hash = hashlib.sha256(certificate).hexdigest() # GOOD
return hash == known_hash

View File

@@ -0,0 +1,4 @@
import hashlib
def get_password_hash(password: str, salt: str):
return hashlib.sha256(password + salt).hexdigest() # BAD

View File

@@ -0,0 +1,9 @@
from argon2 import PasswordHasher
def get_initial_hash(password: str):
ph = PasswordHasher()
return ph.hash(password) # GOOD
def check_password(password: str, known_hash):
ph = PasswordHasher()
return ph.verify(known_hash, password) # GOOD

View File

@@ -0,0 +1,28 @@
/**
* @name OLD QUERY: Use of a broken or weak cryptographic algorithm
* @description Using broken or weak cryptographic algorithms can compromise security.
* @kind path-problem
* @problem.severity warning
* @id py/old/weak-cryptographic-algorithm
* @deprecated
*/
import python
import semmle.python.security.Paths
import semmle.python.security.SensitiveData
import semmle.python.security.Crypto
class BrokenCryptoConfiguration extends TaintTracking::Configuration {
BrokenCryptoConfiguration() { this = "Broken crypto configuration" }
override predicate isSource(TaintTracking::Source source) {
source instanceof SensitiveDataSource
}
override predicate isSink(TaintTracking::Sink sink) { sink instanceof WeakCryptoSink }
}
from BrokenCryptoConfiguration config, TaintedPathSource src, TaintedPathSink sink
where config.hasFlowPath(src, sink)
select sink.getSink(), src, sink, "$@ is used in a broken or weak cryptographic algorithm.",
src.getSource(), "Sensitive data"

View File

@@ -1,174 +1,3 @@
/**
* Provides classes modeling cryptographic algorithms, separated into strong and weak variants.
*
* The classification into strong and weak are based on Wikipedia, OWASP and google (2017).
*/
/** DEPRECATED: Use `semmle.python.concepts.CryptoAlgorithms` instead. */
/**
* Names of cryptographic algorithms, separated into strong and weak variants.
*
* The names are normalized: upper-case, no spaces, dashes or underscores.
*
* The names are inspired by the names used in real world crypto libraries.
*
* The classification into strong and weak are based on Wikipedia, OWASP and google (2017).
*/
private module AlgorithmNames {
predicate isStrongHashingAlgorithm(string name) {
name = "DSA" or
name = "ED25519" or
name = "ES256" or
name = "ECDSA256" or
name = "ES384" or
name = "ECDSA384" or
name = "ES512" or
name = "ECDSA512" or
name = "SHA2" or
name = "SHA224" or
name = "SHA256" or
name = "SHA384" or
name = "SHA512" or
name = "SHA3"
}
predicate isWeakHashingAlgorithm(string name) {
name = "HAVEL128" or
name = "MD2" or
name = "MD4" or
name = "MD5" or
name = "PANAMA" or
name = "RIPEMD" or
name = "RIPEMD128" or
name = "RIPEMD256" or
name = "RIPEMD160" or
name = "RIPEMD320" or
name = "SHA0" or
name = "SHA1"
}
predicate isStrongEncryptionAlgorithm(string name) {
name = "AES" or
name = "AES128" or
name = "AES192" or
name = "AES256" or
name = "AES512" or
name = "RSA" or
name = "RABBIT" or
name = "BLOWFISH"
}
predicate isWeakEncryptionAlgorithm(string name) {
name = "DES" or
name = "3DES" or
name = "TRIPLEDES" or
name = "TDEA" or
name = "TRIPLEDEA" or
name = "ARC2" or
name = "RC2" or
name = "ARC4" or
name = "RC4" or
name = "ARCFOUR" or
name = "ARC5" or
name = "RC5"
}
predicate isStrongPasswordHashingAlgorithm(string name) {
name = "ARGON2" or
name = "PBKDF2" or
name = "BCRYPT" or
name = "SCRYPT"
}
predicate isWeakPasswordHashingAlgorithm(string name) { none() }
}
private import AlgorithmNames
/**
* A cryptographic algorithm.
*/
private newtype TCryptographicAlgorithm =
MkHashingAlgorithm(string name, boolean isWeak) {
isStrongHashingAlgorithm(name) and isWeak = false
or
isWeakHashingAlgorithm(name) and isWeak = true
} or
MkEncryptionAlgorithm(string name, boolean isWeak) {
isStrongEncryptionAlgorithm(name) and isWeak = false
or
isWeakEncryptionAlgorithm(name) and isWeak = true
} or
MkPasswordHashingAlgorithm(string name, boolean isWeak) {
isStrongPasswordHashingAlgorithm(name) and isWeak = false
or
isWeakPasswordHashingAlgorithm(name) and isWeak = true
}
/**
* A cryptographic algorithm.
*/
abstract class CryptographicAlgorithm extends TCryptographicAlgorithm {
/** Gets a textual representation of this element. */
string toString() { result = getName() }
/**
* Gets the normalized name of this algorithm (upper-case, no spaces, dashes or underscores).
*/
abstract string getName();
/**
* Holds if the name of this algorithm matches `name` modulo case,
* white space, dashes, and underscores.
*/
bindingset[name]
predicate matchesName(string name) {
name.toUpperCase().regexpReplaceAll("[-_ ]", "") = getName()
}
/**
* Holds if this algorithm is weak.
*/
abstract predicate isWeak();
}
/**
* A hashing algorithm such as `MD5` or `SHA512`.
*/
class HashingAlgorithm extends MkHashingAlgorithm, CryptographicAlgorithm {
string name;
boolean isWeak;
HashingAlgorithm() { this = MkHashingAlgorithm(name, isWeak) }
override string getName() { result = name }
override predicate isWeak() { isWeak = true }
}
/**
* An encryption algorithm such as `DES` or `AES512`.
*/
class EncryptionAlgorithm extends MkEncryptionAlgorithm, CryptographicAlgorithm {
string name;
boolean isWeak;
EncryptionAlgorithm() { this = MkEncryptionAlgorithm(name, isWeak) }
override string getName() { result = name }
override predicate isWeak() { isWeak = true }
}
/**
* A password hashing algorithm such as `PBKDF2` or `SCRYPT`.
*/
class PasswordHashingAlgorithm extends MkPasswordHashingAlgorithm, CryptographicAlgorithm {
string name;
boolean isWeak;
PasswordHashingAlgorithm() { this = MkPasswordHashingAlgorithm(name, isWeak) }
override string getName() { result = name }
override predicate isWeak() { isWeak = true }
}
import semmle.python.concepts.CryptoAlgorithms

View File

@@ -527,7 +527,14 @@ module HTTP {
}
}
/** Provides models for cryptographic things. */
/**
* Provides models for cryptographic things.
*
* Note: The `CryptographicAlgorithm` class currently doesn't take weak keys into
* consideration for the `isWeak` member predicate. So RSA is always considered
* secure, although using a low number of bits will actually make it insecure. We plan
* to improve our libraries in the future to more precisely capture this aspect.
*/
module Cryptography {
/** Provides models for public-key cryptography, also called asymmetric cryptography. */
module PublicKey {
@@ -626,4 +633,43 @@ module Cryptography {
}
}
}
import semmle.python.concepts.CryptoAlgorithms
/**
* A data-flow node that is an application of a cryptographic algorithm. For example,
* encryption, decryption, signature-validation.
*
* Extend this class to refine existing API models. If you want to model new APIs,
* extend `CryptographicOperation::Range` instead.
*/
class CryptographicOperation extends DataFlow::Node {
CryptographicOperation::Range range;
CryptographicOperation() { this = range }
/** Gets the algorithm used, if it matches a known `CryptographicAlgorithm`. */
CryptographicAlgorithm getAlgorithm() { result = range.getAlgorithm() }
/** Gets an input the algorithm is used on, for example the plain text input to be encrypted. */
DataFlow::Node getAnInput() { result = range.getAnInput() }
}
/** Provides classes for modeling new applications of a cryptographic algorithms. */
module CryptographicOperation {
/**
* A data-flow node that is an application of a cryptographic algorithm. For example,
* encryption, decryption, signature-validation.
*
* Extend this class to model new APIs. If you want to refine existing API models,
* extend `CryptographicOperation` instead.
*/
abstract class Range extends DataFlow::Node {
/** Gets the algorithm used, if it matches a known `CryptographicAlgorithm`. */
abstract CryptographicAlgorithm getAlgorithm();
/** Gets an input the algorithm is used on, for example the plain text input to be encrypted. */
abstract DataFlow::Node getAnInput();
}
}
}

View File

@@ -0,0 +1,174 @@
/**
* Provides classes modeling cryptographic algorithms, separated into strong and weak variants.
*
* The classification into strong and weak are based on Wikipedia, OWASP and google (2017).
*/
/**
* Names of cryptographic algorithms, separated into strong and weak variants.
*
* The names are normalized: upper-case, no spaces, dashes or underscores.
*
* The names are inspired by the names used in real world crypto libraries.
*
* The classification into strong and weak are based on Wikipedia, OWASP and google (2017).
*/
private module AlgorithmNames {
predicate isStrongHashingAlgorithm(string name) {
name = "DSA" or
name = "ED25519" or
name = "ES256" or
name = "ECDSA256" or
name = "ES384" or
name = "ECDSA384" or
name = "ES512" or
name = "ECDSA512" or
name = "SHA2" or
name = "SHA224" or
name = "SHA256" or
name = "SHA384" or
name = "SHA512" or
name = "SHA3"
}
predicate isWeakHashingAlgorithm(string name) {
name = "HAVEL128" or
name = "MD2" or
name = "MD4" or
name = "MD5" or
name = "PANAMA" or
name = "RIPEMD" or
name = "RIPEMD128" or
name = "RIPEMD256" or
name = "RIPEMD160" or
name = "RIPEMD320" or
name = "SHA0" or
name = "SHA1"
}
predicate isStrongEncryptionAlgorithm(string name) {
name = "AES" or
name = "AES128" or
name = "AES192" or
name = "AES256" or
name = "AES512" or
name = "RSA" or
name = "RABBIT" or
name = "BLOWFISH"
}
predicate isWeakEncryptionAlgorithm(string name) {
name = "DES" or
name = "3DES" or
name = "TRIPLEDES" or
name = "TDEA" or
name = "TRIPLEDEA" or
name = "ARC2" or
name = "RC2" or
name = "ARC4" or
name = "RC4" or
name = "ARCFOUR" or
name = "ARC5" or
name = "RC5"
}
predicate isStrongPasswordHashingAlgorithm(string name) {
name = "ARGON2" or
name = "PBKDF2" or
name = "BCRYPT" or
name = "SCRYPT"
}
predicate isWeakPasswordHashingAlgorithm(string name) { none() }
}
private import AlgorithmNames
/**
* A cryptographic algorithm.
*/
private newtype TCryptographicAlgorithm =
MkHashingAlgorithm(string name, boolean isWeak) {
isStrongHashingAlgorithm(name) and isWeak = false
or
isWeakHashingAlgorithm(name) and isWeak = true
} or
MkEncryptionAlgorithm(string name, boolean isWeak) {
isStrongEncryptionAlgorithm(name) and isWeak = false
or
isWeakEncryptionAlgorithm(name) and isWeak = true
} or
MkPasswordHashingAlgorithm(string name, boolean isWeak) {
isStrongPasswordHashingAlgorithm(name) and isWeak = false
or
isWeakPasswordHashingAlgorithm(name) and isWeak = true
}
/**
* A cryptographic algorithm.
*/
abstract class CryptographicAlgorithm extends TCryptographicAlgorithm {
/** Gets a textual representation of this element. */
string toString() { result = getName() }
/**
* Gets the normalized name of this algorithm (upper-case, no spaces, dashes or underscores).
*/
abstract string getName();
/**
* Holds if the name of this algorithm matches `name` modulo case,
* white space, dashes, and underscores.
*/
bindingset[name]
predicate matchesName(string name) {
name.toUpperCase().regexpReplaceAll("[-_ ]", "") = getName()
}
/**
* Holds if this algorithm is weak.
*/
abstract predicate isWeak();
}
/**
* A hashing algorithm such as `MD5` or `SHA512`.
*/
class HashingAlgorithm extends MkHashingAlgorithm, CryptographicAlgorithm {
string name;
boolean isWeak;
HashingAlgorithm() { this = MkHashingAlgorithm(name, isWeak) }
override string getName() { result = name }
override predicate isWeak() { isWeak = true }
}
/**
* An encryption algorithm such as `DES` or `AES512`.
*/
class EncryptionAlgorithm extends MkEncryptionAlgorithm, CryptographicAlgorithm {
string name;
boolean isWeak;
EncryptionAlgorithm() { this = MkEncryptionAlgorithm(name, isWeak) }
override string getName() { result = name }
override predicate isWeak() { isWeak = true }
}
/**
* A password hashing algorithm such as `PBKDF2` or `SCRYPT`.
*/
class PasswordHashingAlgorithm extends MkPasswordHashingAlgorithm, CryptographicAlgorithm {
string name;
boolean isWeak;
PasswordHashingAlgorithm() { this = MkPasswordHashingAlgorithm(name, isWeak) }
override string getName() { result = name }
override predicate isWeak() { isWeak = true }
}

View File

@@ -0,0 +1,66 @@
/**
* Provides an extension point for for modeling sensitive data, such as secrets, certificates, or passwords.
* Sensitive data can be interesting to use as data-flow sources in security queries.
*/
private import python
private import semmle.python.dataflow.new.DataFlow
// Need to import since frameworks can extend `RemoteFlowSource::Range`
private import semmle.python.Frameworks
private import semmle.python.Concepts
private import semmle.python.security.SensitiveData as OldSensitiveData
/**
* A data flow source of sensitive data, such as secrets, certificates, or passwords.
*
* Extend this class to refine existing API models. If you want to model new APIs,
* extend `SensitiveDataSource::Range` instead.
*/
class SensitiveDataSource extends DataFlow::Node {
SensitiveDataSource::Range range;
SensitiveDataSource() { this = range }
/**
* INTERNAL: Do not use.
*
* This will be rewritten to have better types soon, and therefore should only be used internally until then.
*
* Gets the classification of the sensitive data.
*/
string getClassification() { result = range.getClassification() }
}
/** Provides a class for modeling new sources of sensitive data, such as secrets, certificates, or passwords. */
module SensitiveDataSource {
/**
* A data flow source of sensitive data, such as secrets, certificates, or passwords.
*
* Extend this class to model new APIs. If you want to refine existing API models,
* extend `SensitiveDataSource` instead.
*/
abstract class Range extends DataFlow::Node {
/**
* INTERNAL: Do not use.
*
* This will be rewritten to have better types soon, and therefore should only be used internally until then.
*
* Gets the classification of the sensitive data.
*/
abstract string getClassification();
}
}
private class PortOfOldModeling extends SensitiveDataSource::Range {
OldSensitiveData::SensitiveData::Source oldSensitiveSource;
PortOfOldModeling() { this.asCfgNode() = oldSensitiveSource }
override string getClassification() {
exists(OldSensitiveData::SensitiveData classification |
oldSensitiveSource.isSourceOf(classification)
|
classification = "sensitive.data." + result
)
}
}

View File

@@ -17,7 +17,6 @@ private import semmle.python.ApiGraphs
* See https://pycryptodome.readthedocs.io/en/latest/
*/
private module CryptodomeModel {
// ---------------------------------------------------------------------------
/**
* A call to `Cryptodome.PublicKey.RSA.generate`/`Crypto.PublicKey.RSA.generate`
*
@@ -101,4 +100,120 @@ private module CryptodomeModel {
// Note: There is not really a key-size argument, since it's always specified by the curve.
override DataFlow::Node getKeySizeArg() { none() }
}
/**
* A cryptographic operation on an instance from the `Cipher` subpackage of `Cryptodome`/`Crypto`.
*/
class CryptodomeGenericCipherOperation extends Cryptography::CryptographicOperation::Range,
DataFlow::CallCfgNode {
string methodName;
string cipherName;
CryptodomeGenericCipherOperation() {
methodName in [
"encrypt", "decrypt", "verify", "update", "hexverify", "encrypt_and_digest",
"decrypt_and_verify"
] and
this =
API::moduleImport(["Crypto", "Cryptodome"])
.getMember(["Cipher"])
.getMember(cipherName)
.getMember("new")
.getReturn()
.getMember(methodName)
.getACall()
}
override Cryptography::CryptographicAlgorithm getAlgorithm() { result.matchesName(cipherName) }
override DataFlow::Node getAnInput() {
methodName = "encrypt" and
result in [this.getArg(0), this.getArgByName(["message", "plaintext"])]
or
methodName = "decrypt" and
result in [this.getArg(0), this.getArgByName("ciphertext")]
or
// for the following methods, method signatures can be found in
// https://pycryptodome.readthedocs.io/en/latest/src/cipher/modern.html
methodName in ["update"] and
result in [this.getArg(0), this.getArgByName("data")]
or
// although `mac_tag` is used as the parameter name in the spec above, some implementations use `received_mac_tag`, for an example, see
// https://github.com/Legrandin/pycryptodome/blob/5dace638b70ac35bb5d9b565f3e75f7869c9d851/lib/Crypto/Cipher/ChaCha20_Poly1305.py#L207
methodName in ["verify"] and
result in [this.getArg(0), this.getArgByName(["mac_tag", "received_mac_tag"])]
or
methodName in ["hexverify"] and
result in [this.getArg(0), this.getArgByName("mac_tag_hex")]
or
methodName in ["encrypt_and_digest"] and
result in [this.getArg(0), this.getArgByName("plaintext")]
or
methodName in ["decrypt_and_verify"] and
result in [
this.getArg(0), this.getArgByName("ciphertext"), this.getArg(1),
this.getArgByName("mac_tag")
]
}
}
/**
* A cryptographic operation on an instance from the `Signature` subpackage of `Cryptodome`/`Crypto`.
*/
class CryptodomeGenericSignatureOperation extends Cryptography::CryptographicOperation::Range,
DataFlow::CallCfgNode {
string methodName;
string signatureName;
CryptodomeGenericSignatureOperation() {
methodName in ["sign", "verify"] and
this =
API::moduleImport(["Crypto", "Cryptodome"])
.getMember(["Signature"])
.getMember(signatureName)
.getMember("new")
.getReturn()
.getMember(methodName)
.getACall()
}
override Cryptography::CryptographicAlgorithm getAlgorithm() {
result.matchesName(signatureName)
}
override DataFlow::Node getAnInput() {
methodName = "sign" and
result in [this.getArg(0), this.getArgByName("msg_hash")] // Cryptodome.Hash instance
or
methodName in ["verify"] and
(
result in [this.getArg(0), this.getArgByName(["msg_hash"])] // Cryptodome.Hash instance
or
result in [this.getArg(1), this.getArgByName(["signature"])]
)
}
}
/**
* A cryptographic operation on an instance from the `Hash` subpackage of `Cryptodome`/`Crypto`.
*/
class CryptodomeGenericHashOperation extends Cryptography::CryptographicOperation::Range,
DataFlow::CallCfgNode {
string hashName;
CryptodomeGenericHashOperation() {
exists(API::Node hashModule |
hashModule =
API::moduleImport(["Crypto", "Cryptodome"]).getMember(["Hash"]).getMember(hashName)
|
this = hashModule.getMember("new").getACall()
or
this = hashModule.getMember("new").getReturn().getMember("update").getACall()
)
}
override Cryptography::CryptographicAlgorithm getAlgorithm() { result.matchesName(hashName) }
override DataFlow::Node getAnInput() { result in [this.getArg(0), this.getArgByName("data")] }
}
}

View File

@@ -169,4 +169,172 @@ private module CryptographyModel {
// Note: There is not really a key-size argument, since it's always specified by the curve.
override DataFlow::Node getKeySizeArg() { none() }
}
/** Provides models for the `cryptography.hazmat.primitives.ciphers` package */
private module Ciphers {
/** Gets a reference to a `cryptography.hazmat.primitives.ciphers.algorithms` Class */
API::Node algorithmClassRef(string algorithmName) {
result =
API::moduleImport("cryptography")
.getMember("hazmat")
.getMember("primitives")
.getMember("ciphers")
.getMember("algorithms")
.getMember(algorithmName)
}
/** Gets a reference to a Cipher instance using algorithm with `algorithmName`. */
DataFlow::LocalSourceNode cipherInstance(DataFlow::TypeTracker t, string algorithmName) {
t.start() and
exists(DataFlow::CallCfgNode call | result = call |
call =
API::moduleImport("cryptography")
.getMember("hazmat")
.getMember("primitives")
.getMember("ciphers")
.getMember("Cipher")
.getACall() and
algorithmClassRef(algorithmName).getReturn().getAUse() in [
call.getArg(0), call.getArgByName("algorithm")
]
)
or
exists(DataFlow::TypeTracker t2 | result = cipherInstance(t2, algorithmName).track(t2, t))
}
/** Gets a reference to a Cipher instance using algorithm with `algorithmName`. */
DataFlow::Node cipherInstance(string algorithmName) {
cipherInstance(DataFlow::TypeTracker::end(), algorithmName).flowsTo(result)
}
/** Gets a reference to the encryptor of a Cipher instance using algorithm with `algorithmName`. */
DataFlow::LocalSourceNode cipherEncryptor(DataFlow::TypeTracker t, string algorithmName) {
t.start() and
exists(DataFlow::AttrRead attr |
result.(DataFlow::CallCfgNode).getFunction() = attr and
attr.getAttributeName() = "encryptor" and
attr.getObject() = cipherInstance(algorithmName)
)
or
exists(DataFlow::TypeTracker t2 | result = cipherEncryptor(t2, algorithmName).track(t2, t))
}
/**
* Gets a reference to the encryptor of a Cipher instance using algorithm with `algorithmName`.
*
* You obtain an encryptor by using the `encryptor()` method on a Cipher instance.
*/
DataFlow::Node cipherEncryptor(string algorithmName) {
cipherEncryptor(DataFlow::TypeTracker::end(), algorithmName).flowsTo(result)
}
/** Gets a reference to the dncryptor of a Cipher instance using algorithm with `algorithmName`. */
DataFlow::LocalSourceNode cipherDecryptor(DataFlow::TypeTracker t, string algorithmName) {
t.start() and
exists(DataFlow::AttrRead attr |
result.(DataFlow::CallCfgNode).getFunction() = attr and
attr.getAttributeName() = "decryptor" and
attr.getObject() = cipherInstance(algorithmName)
)
or
exists(DataFlow::TypeTracker t2 | result = cipherDecryptor(t2, algorithmName).track(t2, t))
}
/**
* Gets a reference to the decryptor of a Cipher instance using algorithm with `algorithmName`.
*
* You obtain an decryptor by using the `decryptor()` method on a Cipher instance.
*/
DataFlow::Node cipherDecryptor(string algorithmName) {
cipherDecryptor(DataFlow::TypeTracker::end(), algorithmName).flowsTo(result)
}
/**
* An encrypt or decrypt operation from `cryptography.hazmat.primitives.ciphers`.
*/
class CryptographyGenericCipherOperation extends Cryptography::CryptographicOperation::Range,
DataFlow::CallCfgNode {
string algorithmName;
CryptographyGenericCipherOperation() {
exists(DataFlow::AttrRead attr |
this.getFunction() = attr and
attr.getAttributeName() = ["update", "update_into"] and
(
attr.getObject() = cipherEncryptor(algorithmName)
or
attr.getObject() = cipherDecryptor(algorithmName)
)
)
}
override Cryptography::CryptographicAlgorithm getAlgorithm() {
result.matchesName(algorithmName)
}
override DataFlow::Node getAnInput() { result in [this.getArg(0), this.getArgByName("data")] }
}
}
/** Provides models for the `cryptography.hazmat.primitives.hashes` package */
private module Hashes {
/**
* Gets a reference to a `cryptography.hazmat.primitives.hashes` class, representing
* a hashing algorithm.
*/
API::Node algorithmClassRef(string algorithmName) {
result =
API::moduleImport("cryptography")
.getMember("hazmat")
.getMember("primitives")
.getMember("hashes")
.getMember(algorithmName)
}
/** Gets a reference to a Hash instance using algorithm with `algorithmName`. */
private DataFlow::LocalSourceNode hashInstance(DataFlow::TypeTracker t, string algorithmName) {
t.start() and
exists(DataFlow::CallCfgNode call | result = call |
call =
API::moduleImport("cryptography")
.getMember("hazmat")
.getMember("primitives")
.getMember("hashes")
.getMember("Hash")
.getACall() and
algorithmClassRef(algorithmName).getReturn().getAUse() in [
call.getArg(0), call.getArgByName("algorithm")
]
)
or
exists(DataFlow::TypeTracker t2 | result = hashInstance(t2, algorithmName).track(t2, t))
}
/** Gets a reference to a Hash instance using algorithm with `algorithmName`. */
DataFlow::Node hashInstance(string algorithmName) {
hashInstance(DataFlow::TypeTracker::end(), algorithmName).flowsTo(result)
}
/**
* An hashing operation from `cryptography.hazmat.primitives.hashes`.
*/
class CryptographyGenericHashOperation extends Cryptography::CryptographicOperation::Range,
DataFlow::CallCfgNode {
string algorithmName;
CryptographyGenericHashOperation() {
exists(DataFlow::AttrRead attr |
this.getFunction() = attr and
attr.getAttributeName() = "update" and
attr.getObject() = hashInstance(algorithmName)
)
}
override Cryptography::CryptographicAlgorithm getAlgorithm() {
result.matchesName(algorithmName)
}
override DataFlow::Node getAnInput() { result in [this.getArg(0), this.getArgByName("data")] }
}
}
}

View File

@@ -1088,6 +1088,119 @@ private module Stdlib {
}
}
// ---------------------------------------------------------------------------
// hashlib
// ---------------------------------------------------------------------------
/** Gets a call to `hashlib.new` with `algorithmName` as the first argument. */
private DataFlow::CallCfgNode hashlibNewCall(string algorithmName) {
exists(DataFlow::Node nameArg |
result = API::moduleImport("hashlib").getMember("new").getACall() and
nameArg in [result.getArg(0), result.getArgByName("name")] and
exists(StrConst str |
DataFlow::exprNode(str).(DataFlow::LocalSourceNode).flowsTo(nameArg) and
algorithmName = str.getText()
)
)
}
/** Gets a reference to the result of calling `hashlib.new` with `algorithmName` as the first argument. */
private DataFlow::LocalSourceNode hashlibNewResult(DataFlow::TypeTracker t, string algorithmName) {
t.start() and
result = hashlibNewCall(algorithmName)
or
exists(DataFlow::TypeTracker t2 | result = hashlibNewResult(t2, algorithmName).track(t2, t))
}
/** Gets a reference to the result of calling `hashlib.new` with `algorithmName` as the first argument. */
DataFlow::Node hashlibNewResult(string algorithmName) {
hashlibNewResult(DataFlow::TypeTracker::end(), algorithmName).flowsTo(result)
}
/**
* A hashing operation by supplying initial data when calling the `hashlib.new` function.
*/
class HashlibNewCall extends Cryptography::CryptographicOperation::Range, DataFlow::CallCfgNode {
string hashName;
HashlibNewCall() {
this = hashlibNewCall(hashName) and
exists([this.getArg(1), this.getArgByName("data")])
}
override Cryptography::CryptographicAlgorithm getAlgorithm() { result.matchesName(hashName) }
override DataFlow::Node getAnInput() { result in [this.getArg(1), this.getArgByName("data")] }
}
/**
* A hashing operation by using the `update` method on the result of calling the `hashlib.new` function.
*/
class HashlibNewUpdateCall extends Cryptography::CryptographicOperation::Range,
DataFlow::CallCfgNode {
string hashName;
HashlibNewUpdateCall() {
exists(DataFlow::AttrRead attr |
attr.getObject() = hashlibNewResult(hashName) and
this.getFunction() = attr and
attr.getAttributeName() = "update"
)
}
override Cryptography::CryptographicAlgorithm getAlgorithm() { result.matchesName(hashName) }
override DataFlow::Node getAnInput() { result = this.getArg(0) }
}
/**
* A hashing operation from the `hashlib` package using one of the predefined classes
* (such as `hashlib.md5`). `hashlib.new` is not included, since it is handled by
* `HashlibNewCall` and `HashlibNewUpdateCall`.
*/
abstract class HashlibGenericHashOperation extends Cryptography::CryptographicOperation::Range,
DataFlow::CallCfgNode {
string hashName;
API::Node hashClass;
bindingset[this]
HashlibGenericHashOperation() {
not hashName = "new" and
hashClass = API::moduleImport("hashlib").getMember(hashName)
}
override Cryptography::CryptographicAlgorithm getAlgorithm() { result.matchesName(hashName) }
}
/**
* A hashing operation from the `hashlib` package using one of the predefined classes
* (such as `hashlib.md5`), by calling its' `update` mehtod.
*/
class HashlibHashClassUpdateCall extends HashlibGenericHashOperation {
HashlibHashClassUpdateCall() { this = hashClass.getReturn().getMember("update").getACall() }
override DataFlow::Node getAnInput() { result = this.getArg(0) }
}
/**
* A hashing operation from the `hashlib` package using one of the predefined classes
* (such as `hashlib.md5`), by passing data to when instantiating the class.
*/
class HashlibDataPassedToHashClass extends HashlibGenericHashOperation {
HashlibDataPassedToHashClass() {
// we only want to model calls to classes such as `hashlib.md5()` if initial data
// is passed as an argument
this = hashClass.getACall() and
exists([this.getArg(0), this.getArgByName("string")])
}
override DataFlow::Node getAnInput() {
result = this.getArg(0)
or
// in Python 3.9, you are allowed to use `hashlib.md5(string=<bytes-like>)`.
result = this.getArgByName("string")
}
}
// ---------------------------------------------------------------------------
// OTHER
// ---------------------------------------------------------------------------

View File

@@ -0,0 +1,74 @@
/**
* Provides a taint-tracking configuration for detecting use of a broken or weak
* cryptographic hashing algorithm on sensitive data.
*
* Note, for performance reasons: only import this file if
* `WeakSensitiveDataHashing::Configuration` is needed, otherwise
* `WeakSensitiveDataHashingCustomizations` should be imported instead.
*/
private import python
private import semmle.python.dataflow.new.DataFlow
private import semmle.python.dataflow.new.TaintTracking
private import semmle.python.Concepts
private import semmle.python.dataflow.new.RemoteFlowSources
private import semmle.python.dataflow.new.BarrierGuards
/**
* Provides a taint-tracking configuration for detecting use of a broken or weak
* cryptographic hash function on sensitive data, that does NOT require a
* computationally expensive hash function.
*/
module NormalHashFunction {
import WeakSensitiveDataHashingCustomizations::NormalHashFunction
/**
* A taint-tracking configuration for detecting use of a broken or weak
* cryptographic hashing algorithm on sensitive data.
*/
class Configuration extends TaintTracking::Configuration {
Configuration() { this = "NormalHashFunction" }
override predicate isSource(DataFlow::Node source) { source instanceof Source }
override predicate isSink(DataFlow::Node sink) { sink instanceof Sink }
override predicate isSanitizer(DataFlow::Node node) {
super.isSanitizer(node)
or
node instanceof Sanitizer
}
}
}
/**
* Provides a taint-tracking configuration for detecting use of a broken or weak
* cryptographic hashing algorithm on passwords.
*
* Passwords has stricter requirements on the hashing algorithm used (must be
* computationally expensive to prevent brute-force attacks).
*/
module ComputationallyExpensiveHashFunction {
import WeakSensitiveDataHashingCustomizations::ComputationallyExpensiveHashFunction
/**
* A taint-tracking configuration for detecting use of a broken or weak
* cryptographic hashing algorithm on passwords.
*
* Passwords has stricter requirements on the hashing algorithm used (must be
* computationally expensive to prevent brute-force attacks).
*/
class Configuration extends TaintTracking::Configuration {
Configuration() { this = "ComputationallyExpensiveHashFunction" }
override predicate isSource(DataFlow::Node source) { source instanceof Source }
override predicate isSink(DataFlow::Node sink) { sink instanceof Sink }
override predicate isSanitizer(DataFlow::Node node) {
super.isSanitizer(node)
or
node instanceof Sanitizer
}
}
}

View File

@@ -0,0 +1,157 @@
/**
* Provides default sources, sinks and sanitizers for detecting
* "use of a broken or weak cryptographic hashing algorithm on sensitive data"
* vulnerabilities, as well as extension points for adding your own.
*/
private import python
private import semmle.python.dataflow.new.DataFlow
private import semmle.python.dataflow.new.TaintTracking
private import semmle.python.Concepts
private import semmle.python.dataflow.new.SensitiveDataSources
private import semmle.python.dataflow.new.BarrierGuards
/**
* Provides default sources, sinks and sanitizers for detecting
* "use of a broken or weak cryptographic hashing algorithm on sensitive data"
* vulnerabilities on sensitive data that does NOT require computationally expensive
* hashing, as well as extension points for adding your own.
*
* Also see the `ComputationallyExpensiveHashFunction` module.
*/
module NormalHashFunction {
/**
* A data flow source for "use of a broken or weak cryptographic hashing algorithm on
* sensitive data" vulnerabilities.
*/
abstract class Source extends DataFlow::Node {
Source() { not this instanceof ComputationallyExpensiveHashFunction::Source }
/** Gets the classification of the sensitive data. */
abstract string getClassification();
}
/**
* A data flow sink for "use of a broken or weak cryptographic hashing algorithm on
* sensitive data" vulnerabilities.
*/
abstract class Sink extends DataFlow::Node {
/**
* Gets the name of the weak hashing algorithm.
*/
abstract string getAlgorithmName();
}
/**
* A sanitizer for "use of a broken or weak cryptographic hashing algorithm on
* sensitive data" vulnerabilities.
*/
abstract class Sanitizer extends DataFlow::Node { }
/**
* A source of sensitive data, considered as a flow source.
*/
class SensitiveDataSourceAsSource extends Source, SensitiveDataSource {
override string getClassification() { result = SensitiveDataSource.super.getClassification() }
}
/** The input to a hashing operation using a weak algorithm, considered as a flow sink. */
class WeakHashingOperationInputSink extends Sink {
Cryptography::HashingAlgorithm algorithm;
WeakHashingOperationInputSink() {
exists(Cryptography::CryptographicOperation operation |
algorithm = operation.getAlgorithm() and
algorithm.isWeak() and
this = operation.getAnInput()
)
}
override string getAlgorithmName() { result = algorithm.getName() }
}
}
/**
* Provides default sources, sinks and sanitizers for detecting
* "use of a broken or weak cryptographic hashing algorithm on sensitive data"
* vulnerabilities on sensitive data that DOES require computationally expensive
* hashing, as well as extension points for adding your own.
*
* Also see the `NormalHashFunction` module.
*/
module ComputationallyExpensiveHashFunction {
/**
* A data flow source of sensitive data that requires computationally expensive
* hashing for "use of a broken or weak cryptographic hashing algorithm on sensitive
* data" vulnerabilities.
*/
abstract class Source extends DataFlow::Node {
/** Gets the classification of the sensitive data. */
abstract string getClassification();
}
/**
* A data flow sink for sensitive data that requires computationally expensive
* hashing for "use of a broken or weak cryptographic hashing algorithm on sensitive
* data" vulnerabilities.
*/
abstract class Sink extends DataFlow::Node {
/**
* Gets the name of the weak hashing algorithm.
*/
abstract string getAlgorithmName();
/**
* Holds if this sink is for a computationally expensive hash function (meaning that
* hash function is just weak in some other regard.
*/
abstract predicate isComputationallyExpensive();
}
/**
* A sanitizer of sensitive data that requires computationally expensive
* hashing for "use of a broken or weak cryptographic hashing
* algorithm on sensitive data" vulnerabilities.
*/
abstract class Sanitizer extends DataFlow::Node { }
/**
* A source of passwords, considered as a flow source.
*/
class PasswordSourceAsSource extends Source, SensitiveDataSource {
PasswordSourceAsSource() {
// TODO: once https://github.com/github/codeql/pull/5739 has been merged,
// don't use hardcoded value anymore
SensitiveDataSource.super.getClassification() = "password"
}
override string getClassification() { result = SensitiveDataSource.super.getClassification() }
}
/**
* The input to a password hashing operation using a weak algorithm, considered as a
* flow sink.
*/
class WeakPasswordHashingOperationInputSink extends Sink {
Cryptography::CryptographicAlgorithm algorithm;
WeakPasswordHashingOperationInputSink() {
(
algorithm instanceof Cryptography::PasswordHashingAlgorithm and
algorithm.isWeak()
or
algorithm instanceof Cryptography::HashingAlgorithm // Note that HashingAlgorithm and PasswordHashingAlgorithm are disjoint
) and
exists(Cryptography::CryptographicOperation operation |
algorithm = operation.getAlgorithm() and
this = operation.getAnInput()
)
}
override string getAlgorithmName() { result = algorithm.getName() }
override predicate isComputationallyExpensive() {
algorithm instanceof Cryptography::PasswordHashingAlgorithm
}
}
}

View File

@@ -0,0 +1,20 @@
import python
import semmle.python.dataflow.new.DataFlow
import TestUtilities.InlineExpectationsTest
import semmle.python.dataflow.new.SensitiveDataSources
class SensitiveDataSourcesTest extends InlineExpectationsTest {
SensitiveDataSourcesTest() { this = "SensitiveDataSourcesTest" }
override string getARelevantTag() { result = "SensitiveDataSource" }
override predicate hasActualResult(Location location, string element, string tag, string value) {
exists(location.getFile().getRelativePath()) and
exists(SensitiveDataSource source |
location = source.getLocation() and
element = source.toString() and
value = source.getClassification() and
tag = "SensitiveDataSource"
)
}
}

View File

@@ -0,0 +1,33 @@
from not_found import get_passwd, account_id
def get_password():
pass
def get_secret():
pass
def fetch_certificate():
pass
def encrypt_password(pwd):
pass
get_password() # $ SensitiveDataSource=password
get_passwd() # $ SensitiveDataSource=password
get_secret() # $ SensitiveDataSource=secret
fetch_certificate() # $ SensitiveDataSource=certificate
account_id() # $ SensitiveDataSource=id
safe_to_store = encrypt_password(pwd)
# attributes
foo = ObjectFromDatabase()
foo.secret # $ SensitiveDataSource=secret
foo.username # $ SensitiveDataSource=id
# Special handling of lookups of sensitive properties
request.args["password"], # $ MISSING: SensitiveDataSource=password
request.args.get("password") # $ SensitiveDataSource=password
# I don't think handling `getlist` is super important, just included it to show what we don't handle
request.args.getlist("password")[0] # $ MISSING: SensitiveDataSource=password

View File

@@ -341,3 +341,33 @@ class PublicKeyGenerationTest extends InlineExpectationsTest {
)
}
}
class CryptographicOperationTest extends InlineExpectationsTest {
CryptographicOperationTest() { this = "CryptographicOperationTest" }
override string getARelevantTag() {
result in [
"CryptographicOperation", "CryptographicOperationInput", "CryptographicOperationAlgorithm"
]
}
override predicate hasActualResult(Location location, string element, string tag, string value) {
exists(location.getFile().getRelativePath()) and
exists(Cryptography::CryptographicOperation cryptoOperation |
location = cryptoOperation.getLocation() and
(
element = cryptoOperation.toString() and
value = "" and
tag = "CryptographicOperation"
or
element = cryptoOperation.toString() and
value = value_from_expr(cryptoOperation.getAnInput().asExpr()) and
tag = "CryptographicOperationInput"
or
element = cryptoOperation.toString() and
value = cryptoOperation.getAlgorithm().getName() and
tag = "CryptographicOperationAlgorithm"
)
)
}
}

View File

@@ -0,0 +1,13 @@
These tests are a copy of the tests in [../cryptodome](../cryptodome) with `Cryptodome` replaced by `Crypto`.
You can run the following command to update the tests:
```sh
rm *.py && cp ../cryptodome/*.py . && sed -i -e 's/Cryptodome/Crypto/' *.py
```
The original [`pycrypto` PyPI package](https://pypi.org/project/pycrypto/) that provided the `Crypto` Python package has not been updated since 2013, so it is reasonable to assume that people will use the replacement [`pycryptodome` PyPI package](https://pypi.org/project/pycryptodome/) that also provides a `Crypto` Python package and has a (mostly) compatible API.
The pycryptodome functionality is also available in the [`pycryptodomex` PyPI package](https://pypi.org/project/pycryptodomex/) which provides the `Cryptodome` Python package.
To ensure our modeling actually covers _both_ ways of importing the same functionality, we have this convoluted test setup.

View File

@@ -0,0 +1,36 @@
# https://pycryptodome.readthedocs.io/en/latest/src/cipher/aes.html
from Crypto.Cipher import AES
import os
key = os.urandom(256//8)
iv = os.urandom(16)
# ------------------------------------------------------------------------------
# encrypt/decrypt
# ------------------------------------------------------------------------------
print("encrypt/decrypt")
secret_message = b"secret message"
padding_len = 16 - (len(secret_message) % 16)
padding = b"\0"*padding_len
cipher = AES.new(key, AES.MODE_CBC, iv=iv)
# using separate .encrypt calls on individual lines does not work
whole_plantext = secret_message + padding
encrypted = cipher.encrypt(whole_plantext) # $ CryptographicOperation CryptographicOperationAlgorithm=AES CryptographicOperationInput=whole_plantext
print("encrypted={}".format(encrypted))
print()
cipher = AES.new(key, AES.MODE_CBC, iv=iv)
decrypted = cipher.decrypt(encrypted) # $ CryptographicOperation CryptographicOperationAlgorithm=AES CryptographicOperationInput=encrypted
decrypted = decrypted[:-padding_len]
print("decrypted={}".format(decrypted))
assert decrypted == secret_message

View File

@@ -20,8 +20,8 @@ message = b"message"
signer = DSS.new(private_key, mode='fips-186-3')
hasher = SHA256.new(message)
signature = signer.sign(hasher)
hasher = SHA256.new(message) # $ CryptographicOperation CryptographicOperationAlgorithm=SHA256 CryptographicOperationInput=message
signature = signer.sign(hasher) # $ CryptographicOperation CryptographicOperationInput=hasher # MISSING: CryptographicOperationAlgorithm=DSA
print("signature={}".format(signature))
@@ -29,13 +29,13 @@ print()
verifier = DSS.new(public_key, mode='fips-186-3')
hasher = SHA256.new(message)
verifier.verify(hasher, signature)
hasher = SHA256.new(message) # $ CryptographicOperation CryptographicOperationAlgorithm=SHA256 CryptographicOperationInput=message
verifier.verify(hasher, signature) # $ CryptographicOperation CryptographicOperationInput=hasher CryptographicOperationInput=signature # MISSING: CryptographicOperationAlgorithm=DSA
print("Signature verified (as expected)")
try:
hasher = SHA256.new(b"other message")
verifier.verify(hasher, signature)
hasher = SHA256.new(b"other message") # $ CryptographicOperation CryptographicOperationAlgorithm=SHA256 CryptographicOperationInput=b"other message"
verifier.verify(hasher, signature) # $ CryptographicOperation CryptographicOperationInput=hasher CryptographicOperationInput=signature # MISSING: CryptographicOperationAlgorithm=DSA
raise Exception("Signature verified (unexpected)")
except ValueError:
print("Signature mismatch (as expected)")

View File

@@ -17,8 +17,8 @@ message = b"message"
signer = DSS.new(private_key, mode='fips-186-3')
hasher = SHA256.new(message)
signature = signer.sign(hasher)
hasher = SHA256.new(message) # $ CryptographicOperation CryptographicOperationAlgorithm=SHA256 CryptographicOperationInput=message
signature = signer.sign(hasher) # $ CryptographicOperation CryptographicOperationInput=hasher # MISSING: CryptographicOperationAlgorithm=ECDSA
print("signature={}".format(signature))
@@ -26,13 +26,13 @@ print()
verifier = DSS.new(public_key, mode='fips-186-3')
hasher = SHA256.new(message)
verifier.verify(hasher, signature)
hasher = SHA256.new(message) # $ CryptographicOperation CryptographicOperationAlgorithm=SHA256 CryptographicOperationInput=message
verifier.verify(hasher, signature) # $ CryptographicOperation CryptographicOperationInput=hasher CryptographicOperationInput=signature
print("Signature verified (as expected)")
try:
hasher = SHA256.new(b"other message")
verifier.verify(hasher, signature)
hasher = SHA256.new(b"other message") # $ CryptographicOperation CryptographicOperationAlgorithm=SHA256 CryptographicOperationInput=b"other message"
verifier.verify(hasher, signature) # $ CryptographicOperation CryptographicOperationInput=hasher CryptographicOperationInput=signature # MISSING: CryptographicOperationAlgorithm=ECDSA
raise Exception("Signature verified (unexpected)")
except ValueError:
print("Signature mismatch (as expected)")

View File

@@ -0,0 +1,10 @@
from Crypto.Hash import MD5
hasher = MD5.new(b"secret message") # $ CryptographicOperation CryptographicOperationInput=b"secret message" CryptographicOperationAlgorithm=MD5
print(hasher.hexdigest())
hasher = MD5.new() # $ CryptographicOperation CryptographicOperationAlgorithm=MD5
hasher.update(b"secret") # $ CryptographicOperation CryptographicOperationInput=b"secret" CryptographicOperationAlgorithm=MD5
hasher.update(b" message") # $ CryptographicOperation CryptographicOperationInput=b" message" CryptographicOperationAlgorithm=MD5
print(hasher.hexdigest())

View File

@@ -0,0 +1,30 @@
# https://pycryptodome.readthedocs.io/en/latest/src/cipher/arc4.html
from Crypto.Cipher import ARC4
import os
key = os.urandom(256//8)
# ------------------------------------------------------------------------------
# encrypt/decrypt
# ------------------------------------------------------------------------------
print("encrypt/decrypt")
secret_message = b"secret message"
cipher = ARC4.new(key)
encrypted = cipher.encrypt(secret_message) # $ CryptographicOperation CryptographicOperationAlgorithm=ARC4 CryptographicOperationInput=secret_message
print("encrypted={}".format(encrypted))
print()
cipher = ARC4.new(key)
decrypted = cipher.decrypt(encrypted) # $ CryptographicOperation CryptographicOperationAlgorithm=ARC4 CryptographicOperationInput=encrypted
print("decrypted={}".format(decrypted))
assert decrypted == secret_message

View File

@@ -23,7 +23,7 @@ secret_message = b"secret message"
encrypt_cipher = PKCS1_OAEP.new(public_key)
encrypted = encrypt_cipher.encrypt(secret_message)
encrypted = encrypt_cipher.encrypt(secret_message) # $ CryptographicOperation CryptographicOperationInput=secret_message # MISSING: CryptographicOperationAlgorithm=RSA-OAEP?
print("encrypted={}".format(encrypted))
@@ -31,9 +31,7 @@ print()
decrypt_cipher = PKCS1_OAEP.new(private_key)
decrypted = decrypt_cipher.decrypt(
encrypted,
)
decrypted = decrypt_cipher.decrypt(encrypted) # $ CryptographicOperation CryptographicOperationInput=encrypted # MISSING: CryptographicOperationAlgorithm=RSA-OAEP?
print("decrypted={}".format(decrypted))
assert decrypted == secret_message
@@ -51,23 +49,23 @@ message = b"message"
signer = pss.new(private_key)
hasher = SHA256.new(message)
signature = signer.sign(hasher)
hasher = SHA256.new(message) # $ CryptographicOperation CryptographicOperationAlgorithm=SHA256 CryptographicOperationInput=message
signature = signer.sign(hasher) # $ CryptographicOperation CryptographicOperationInput=hasher # MISSING: CryptographicOperationAlgorithm=RSA-PSS?
print("signature={}".format(signature))
print()
verifier = pss.new(public_key)
hasher = SHA256.new(message)
verifier.verify(hasher, signature)
hasher = SHA256.new(message) # $ CryptographicOperation CryptographicOperationAlgorithm=SHA256 CryptographicOperationInput=message
verifier.verify(hasher, signature) # $ CryptographicOperation CryptographicOperationInput=hasher CryptographicOperationInput=signature # MISSING: CryptographicOperationAlgorithm=RSA-PSS?
print("Signature verified (as expected)")
try:
verifier = pss.new(public_key)
hasher = SHA256.new(b"other message")
verifier.verify(hasher, signature)
hasher = SHA256.new(b"other message") # $ CryptographicOperation CryptographicOperationAlgorithm=SHA256 CryptographicOperationInput=b"other message"
verifier.verify(hasher, signature) # $ CryptographicOperation CryptographicOperationInput=hasher CryptographicOperationInput=signature # MISSING: CryptographicOperationAlgorithm=RSA-PSS?
raise Exception("Signature verified (unexpected)")
except ValueError:
print("Signature mismatch (as expected)")

View File

@@ -0,0 +1,36 @@
# https://pycryptodome.readthedocs.io/en/latest/src/cipher/aes.html
from Cryptodome.Cipher import AES
import os
key = os.urandom(256//8)
iv = os.urandom(16)
# ------------------------------------------------------------------------------
# encrypt/decrypt
# ------------------------------------------------------------------------------
print("encrypt/decrypt")
secret_message = b"secret message"
padding_len = 16 - (len(secret_message) % 16)
padding = b"\0"*padding_len
cipher = AES.new(key, AES.MODE_CBC, iv=iv)
# using separate .encrypt calls on individual lines does not work
whole_plantext = secret_message + padding
encrypted = cipher.encrypt(whole_plantext) # $ CryptographicOperation CryptographicOperationAlgorithm=AES CryptographicOperationInput=whole_plantext
print("encrypted={}".format(encrypted))
print()
cipher = AES.new(key, AES.MODE_CBC, iv=iv)
decrypted = cipher.decrypt(encrypted) # $ CryptographicOperation CryptographicOperationAlgorithm=AES CryptographicOperationInput=encrypted
decrypted = decrypted[:-padding_len]
print("decrypted={}".format(decrypted))
assert decrypted == secret_message

View File

@@ -20,8 +20,8 @@ message = b"message"
signer = DSS.new(private_key, mode='fips-186-3')
hasher = SHA256.new(message)
signature = signer.sign(hasher)
hasher = SHA256.new(message) # $ CryptographicOperation CryptographicOperationAlgorithm=SHA256 CryptographicOperationInput=message
signature = signer.sign(hasher) # $ CryptographicOperation CryptographicOperationInput=hasher # MISSING: CryptographicOperationAlgorithm=DSA
print("signature={}".format(signature))
@@ -29,13 +29,13 @@ print()
verifier = DSS.new(public_key, mode='fips-186-3')
hasher = SHA256.new(message)
verifier.verify(hasher, signature)
hasher = SHA256.new(message) # $ CryptographicOperation CryptographicOperationAlgorithm=SHA256 CryptographicOperationInput=message
verifier.verify(hasher, signature) # $ CryptographicOperation CryptographicOperationInput=hasher CryptographicOperationInput=signature # MISSING: CryptographicOperationAlgorithm=DSA
print("Signature verified (as expected)")
try:
hasher = SHA256.new(b"other message")
verifier.verify(hasher, signature)
hasher = SHA256.new(b"other message") # $ CryptographicOperation CryptographicOperationAlgorithm=SHA256 CryptographicOperationInput=b"other message"
verifier.verify(hasher, signature) # $ CryptographicOperation CryptographicOperationInput=hasher CryptographicOperationInput=signature # MISSING: CryptographicOperationAlgorithm=DSA
raise Exception("Signature verified (unexpected)")
except ValueError:
print("Signature mismatch (as expected)")

View File

@@ -17,8 +17,8 @@ message = b"message"
signer = DSS.new(private_key, mode='fips-186-3')
hasher = SHA256.new(message)
signature = signer.sign(hasher)
hasher = SHA256.new(message) # $ CryptographicOperation CryptographicOperationAlgorithm=SHA256 CryptographicOperationInput=message
signature = signer.sign(hasher) # $ CryptographicOperation CryptographicOperationInput=hasher # MISSING: CryptographicOperationAlgorithm=ECDSA
print("signature={}".format(signature))
@@ -26,13 +26,13 @@ print()
verifier = DSS.new(public_key, mode='fips-186-3')
hasher = SHA256.new(message)
verifier.verify(hasher, signature)
hasher = SHA256.new(message) # $ CryptographicOperation CryptographicOperationAlgorithm=SHA256 CryptographicOperationInput=message
verifier.verify(hasher, signature) # $ CryptographicOperation CryptographicOperationInput=hasher CryptographicOperationInput=signature
print("Signature verified (as expected)")
try:
hasher = SHA256.new(b"other message")
verifier.verify(hasher, signature)
hasher = SHA256.new(b"other message") # $ CryptographicOperation CryptographicOperationAlgorithm=SHA256 CryptographicOperationInput=b"other message"
verifier.verify(hasher, signature) # $ CryptographicOperation CryptographicOperationInput=hasher CryptographicOperationInput=signature # MISSING: CryptographicOperationAlgorithm=ECDSA
raise Exception("Signature verified (unexpected)")
except ValueError:
print("Signature mismatch (as expected)")

View File

@@ -0,0 +1,10 @@
from Cryptodome.Hash import MD5
hasher = MD5.new(b"secret message") # $ CryptographicOperation CryptographicOperationInput=b"secret message" CryptographicOperationAlgorithm=MD5
print(hasher.hexdigest())
hasher = MD5.new() # $ CryptographicOperation CryptographicOperationAlgorithm=MD5
hasher.update(b"secret") # $ CryptographicOperation CryptographicOperationInput=b"secret" CryptographicOperationAlgorithm=MD5
hasher.update(b" message") # $ CryptographicOperation CryptographicOperationInput=b" message" CryptographicOperationAlgorithm=MD5
print(hasher.hexdigest())

View File

@@ -0,0 +1,30 @@
# https://pycryptodome.readthedocs.io/en/latest/src/cipher/arc4.html
from Cryptodome.Cipher import ARC4
import os
key = os.urandom(256//8)
# ------------------------------------------------------------------------------
# encrypt/decrypt
# ------------------------------------------------------------------------------
print("encrypt/decrypt")
secret_message = b"secret message"
cipher = ARC4.new(key)
encrypted = cipher.encrypt(secret_message) # $ CryptographicOperation CryptographicOperationAlgorithm=ARC4 CryptographicOperationInput=secret_message
print("encrypted={}".format(encrypted))
print()
cipher = ARC4.new(key)
decrypted = cipher.decrypt(encrypted) # $ CryptographicOperation CryptographicOperationAlgorithm=ARC4 CryptographicOperationInput=encrypted
print("decrypted={}".format(decrypted))
assert decrypted == secret_message

View File

@@ -23,7 +23,7 @@ secret_message = b"secret message"
encrypt_cipher = PKCS1_OAEP.new(public_key)
encrypted = encrypt_cipher.encrypt(secret_message)
encrypted = encrypt_cipher.encrypt(secret_message) # $ CryptographicOperation CryptographicOperationInput=secret_message # MISSING: CryptographicOperationAlgorithm=RSA-OAEP?
print("encrypted={}".format(encrypted))
@@ -31,9 +31,7 @@ print()
decrypt_cipher = PKCS1_OAEP.new(private_key)
decrypted = decrypt_cipher.decrypt(
encrypted,
)
decrypted = decrypt_cipher.decrypt(encrypted) # $ CryptographicOperation CryptographicOperationInput=encrypted # MISSING: CryptographicOperationAlgorithm=RSA-OAEP?
print("decrypted={}".format(decrypted))
assert decrypted == secret_message
@@ -51,8 +49,8 @@ message = b"message"
signer = pss.new(private_key)
hasher = SHA256.new(message)
signature = signer.sign(hasher)
hasher = SHA256.new(message) # $ CryptographicOperation CryptographicOperationAlgorithm=SHA256 CryptographicOperationInput=message
signature = signer.sign(hasher) # $ CryptographicOperation CryptographicOperationInput=hasher # MISSING: CryptographicOperationAlgorithm=RSA-PSS?
print("signature={}".format(signature))
@@ -60,14 +58,14 @@ print()
verifier = pss.new(public_key)
hasher = SHA256.new(message)
verifier.verify(hasher, signature)
hasher = SHA256.new(message) # $ CryptographicOperation CryptographicOperationAlgorithm=SHA256 CryptographicOperationInput=message
verifier.verify(hasher, signature) # $ CryptographicOperation CryptographicOperationInput=hasher CryptographicOperationInput=signature # MISSING: CryptographicOperationAlgorithm=RSA-PSS?
print("Signature verified (as expected)")
try:
verifier = pss.new(public_key)
hasher = SHA256.new(b"other message")
verifier.verify(hasher, signature)
hasher = SHA256.new(b"other message") # $ CryptographicOperation CryptographicOperationAlgorithm=SHA256 CryptographicOperationInput=b"other message"
verifier.verify(hasher, signature) # $ CryptographicOperation CryptographicOperationInput=hasher CryptographicOperationInput=signature # MISSING: CryptographicOperationAlgorithm=RSA-PSS?
raise Exception("Signature verified (unexpected)")
except ValueError:
print("Signature mismatch (as expected)")

View File

@@ -0,0 +1,40 @@
from cryptography.hazmat.primitives.ciphers import algorithms, Cipher, modes
import os
key = os.urandom(256//8)
iv = os.urandom(16)
algorithm = algorithms.AES(key)
cipher = Cipher(algorithm, mode=modes.CBC(iv))
# ------------------------------------------------------------------------------
# encrypt/decrypt
# ------------------------------------------------------------------------------
# following https://cryptography.io/en/latest/hazmat/primitives/symmetric-encryption/#cryptography.hazmat.primitives.ciphers.Cipher
print("encrypt/decrypt")
secret_message = b"secret message"
padding_len = 16 - (len(secret_message) % 16)
padding = b"\0"*padding_len
encryptor = cipher.encryptor()
print(padding_len)
encrypted = encryptor.update(secret_message) # $ CryptographicOperation CryptographicOperationAlgorithm=AES CryptographicOperationInput=secret_message
encrypted += encryptor.update(padding) # $ CryptographicOperation CryptographicOperationAlgorithm=AES CryptographicOperationInput=padding
encrypted += encryptor.finalize()
print("encrypted={}".format(encrypted))
print()
decryptor = cipher.decryptor()
decrypted = decryptor.update(encrypted) # $ CryptographicOperation CryptographicOperationAlgorithm=AES CryptographicOperationInput=encrypted
decrypted += decryptor.finalize()
decrypted = decrypted[:-padding_len]
print("decrypted={}".format(decrypted))
assert decrypted == secret_message

View File

@@ -0,0 +1,10 @@
from cryptography.hazmat.primitives import hashes
from binascii import hexlify
hasher = hashes.Hash(hashes.MD5())
hasher.update(b"secret message") # $ CryptographicOperation CryptographicOperationInput=b"secret message" CryptographicOperationAlgorithm=MD5
digest = hasher.finalize()
print(hexlify(digest).decode('utf-8'))

View File

@@ -0,0 +1,32 @@
from cryptography.hazmat.primitives.ciphers import algorithms, Cipher
import os
key = os.urandom(256//8)
algorithm = algorithms.ARC4(key)
cipher = Cipher(algorithm, mode=None)
# ------------------------------------------------------------------------------
# encrypt/decrypt
# ------------------------------------------------------------------------------
# following https://cryptography.io/en/latest/hazmat/primitives/symmetric-encryption.html#cryptography.hazmat.primitives.ciphers.algorithms.ARC4
print("encrypt/decrypt")
secret_message = b"secret message"
encryptor = cipher.encryptor()
encrypted = encryptor.update(secret_message) # $ CryptographicOperation CryptographicOperationAlgorithm=ARC4 CryptographicOperationInput=secret_message
encrypted += encryptor.finalize()
print("encrypted={}".format(encrypted))
print()
decryptor = cipher.decryptor()
decrypted = decryptor.update(encrypted) # $ CryptographicOperation CryptographicOperationAlgorithm=ARC4 CryptographicOperationInput=encrypted
decrypted += decryptor.finalize()
print("decrypted={}".format(decrypted))
assert decrypted == secret_message

View File

@@ -0,0 +1,29 @@
import hashlib
hasher = hashlib.md5(b"secret message") # $ CryptographicOperation CryptographicOperationInput=b"secret message" CryptographicOperationAlgorithm=MD5
print(hasher.hexdigest())
hasher = hashlib.md5(string=b"secret message") # $ CryptographicOperation CryptographicOperationInput=b"secret message" CryptographicOperationAlgorithm=MD5
print(hasher.hexdigest())
hasher = hashlib.md5()
hasher.update(b"secret") # $ CryptographicOperation CryptographicOperationInput=b"secret" CryptographicOperationAlgorithm=MD5
hasher.update(b" message") # $ CryptographicOperation CryptographicOperationInput=b" message" CryptographicOperationAlgorithm=MD5
print(hasher.hexdigest())
hasher = hashlib.new('md5', b"secret message") # $ CryptographicOperation CryptographicOperationInput=b"secret message" CryptographicOperationAlgorithm=MD5
print(hasher.hexdigest())
hasher = hashlib.new('md5', data=b"secret message") # $ CryptographicOperation CryptographicOperationInput=b"secret message" CryptographicOperationAlgorithm=MD5
print(hasher.hexdigest())
hasher = hashlib.new('md5')
hasher.update(b"secret") # $ CryptographicOperation CryptographicOperationInput=b"secret" CryptographicOperationAlgorithm=MD5
hasher.update(b" message") # $ CryptographicOperation CryptographicOperationInput=b" message" CryptographicOperationAlgorithm=MD5
print(hasher.hexdigest())

View File

@@ -0,0 +1,2 @@
| test_cryptodome.py:11:13:11:42 | ControlFlowNode for Attribute() | The cryptographic algorithm ARC4 is broken or weak, and should not be used. |
| test_cryptography.py:13:13:13:44 | ControlFlowNode for Attribute() | The cryptographic algorithm ARC4 is broken or weak, and should not be used. |

View File

@@ -0,0 +1,3 @@
Note that the tests in this directory are very shallow, and simply show that the query is able to produce alerts.
More in-depth tests can be found for the individual frameworks that we have modeled `Cryptography::CryptographicOperation` for.

View File

@@ -0,0 +1,13 @@
# snippet from python/ql/test/experimental/library-tests/frameworks/cryptodome/test_rc4.py
from Cryptodome.Cipher import ARC4
import os
key = os.urandom(256//8)
secret_message = b"secret message"
cipher = ARC4.new(key)
encrypted = cipher.encrypt(secret_message) # NOT OK
print(secret_message, encrypted)

View File

@@ -0,0 +1,16 @@
# snippet from python/ql/test/experimental/library-tests/frameworks/cryptography/test_rc4.py
from cryptography.hazmat.primitives.ciphers import algorithms, Cipher
import os
key = os.urandom(256//8)
algorithm = algorithms.ARC4(key)
cipher = Cipher(algorithm, mode=None)
secret_message = b"secret message"
encryptor = cipher.encryptor()
encrypted = encryptor.update(secret_message) # NOT OK
encrypted += encryptor.finalize()
print(secret_message, encrypted)

View File

@@ -0,0 +1,3 @@
Note that the tests in this directory are very shallow, and simply show that the query is able to produce alerts.
More in-depth tests can be found for the individual frameworks that we have modeled `Cryptography::CryptographicOperation` for.

View File

@@ -0,0 +1,27 @@
edges
| test_cryptodome.py:6:17:6:33 | ControlFlowNode for get_certificate() | test_cryptodome.py:8:19:8:27 | ControlFlowNode for dangerous |
| test_cryptodome.py:13:17:13:30 | ControlFlowNode for get_password() | test_cryptodome.py:15:19:15:27 | ControlFlowNode for dangerous |
| test_cryptodome.py:20:17:20:30 | ControlFlowNode for get_password() | test_cryptodome.py:24:19:24:27 | ControlFlowNode for dangerous |
| test_cryptography.py:7:17:7:33 | ControlFlowNode for get_certificate() | test_cryptography.py:9:19:9:27 | ControlFlowNode for dangerous |
| test_cryptography.py:15:17:15:30 | ControlFlowNode for get_password() | test_cryptography.py:17:19:17:27 | ControlFlowNode for dangerous |
| test_cryptography.py:23:17:23:30 | ControlFlowNode for get_password() | test_cryptography.py:27:19:27:27 | ControlFlowNode for dangerous |
nodes
| test_cryptodome.py:6:17:6:33 | ControlFlowNode for get_certificate() | semmle.label | ControlFlowNode for get_certificate() |
| test_cryptodome.py:8:19:8:27 | ControlFlowNode for dangerous | semmle.label | ControlFlowNode for dangerous |
| test_cryptodome.py:13:17:13:30 | ControlFlowNode for get_password() | semmle.label | ControlFlowNode for get_password() |
| test_cryptodome.py:15:19:15:27 | ControlFlowNode for dangerous | semmle.label | ControlFlowNode for dangerous |
| test_cryptodome.py:20:17:20:30 | ControlFlowNode for get_password() | semmle.label | ControlFlowNode for get_password() |
| test_cryptodome.py:24:19:24:27 | ControlFlowNode for dangerous | semmle.label | ControlFlowNode for dangerous |
| test_cryptography.py:7:17:7:33 | ControlFlowNode for get_certificate() | semmle.label | ControlFlowNode for get_certificate() |
| test_cryptography.py:9:19:9:27 | ControlFlowNode for dangerous | semmle.label | ControlFlowNode for dangerous |
| test_cryptography.py:15:17:15:30 | ControlFlowNode for get_password() | semmle.label | ControlFlowNode for get_password() |
| test_cryptography.py:17:19:17:27 | ControlFlowNode for dangerous | semmle.label | ControlFlowNode for dangerous |
| test_cryptography.py:23:17:23:30 | ControlFlowNode for get_password() | semmle.label | ControlFlowNode for get_password() |
| test_cryptography.py:27:19:27:27 | ControlFlowNode for dangerous | semmle.label | ControlFlowNode for dangerous |
#select
| test_cryptodome.py:8:19:8:27 | ControlFlowNode for dangerous | test_cryptodome.py:6:17:6:33 | ControlFlowNode for get_certificate() | test_cryptodome.py:8:19:8:27 | ControlFlowNode for dangerous | $@ is used in a hashing algorithm (MD5) that is insecure. | test_cryptodome.py:6:17:6:33 | ControlFlowNode for get_certificate() | Sensitive data (certificate) |
| test_cryptodome.py:15:19:15:27 | ControlFlowNode for dangerous | test_cryptodome.py:13:17:13:30 | ControlFlowNode for get_password() | test_cryptodome.py:15:19:15:27 | ControlFlowNode for dangerous | $@ is used in a hashing algorithm (MD5) that is insecure for password hashing, since it is not a computationally expensive hash function. | test_cryptodome.py:13:17:13:30 | ControlFlowNode for get_password() | Sensitive data (password) |
| test_cryptodome.py:24:19:24:27 | ControlFlowNode for dangerous | test_cryptodome.py:20:17:20:30 | ControlFlowNode for get_password() | test_cryptodome.py:24:19:24:27 | ControlFlowNode for dangerous | $@ is used in a hashing algorithm (SHA256) that is insecure for password hashing, since it is not a computationally expensive hash function. | test_cryptodome.py:20:17:20:30 | ControlFlowNode for get_password() | Sensitive data (password) |
| test_cryptography.py:9:19:9:27 | ControlFlowNode for dangerous | test_cryptography.py:7:17:7:33 | ControlFlowNode for get_certificate() | test_cryptography.py:9:19:9:27 | ControlFlowNode for dangerous | $@ is used in a hashing algorithm (MD5) that is insecure. | test_cryptography.py:7:17:7:33 | ControlFlowNode for get_certificate() | Sensitive data (certificate) |
| test_cryptography.py:17:19:17:27 | ControlFlowNode for dangerous | test_cryptography.py:15:17:15:30 | ControlFlowNode for get_password() | test_cryptography.py:17:19:17:27 | ControlFlowNode for dangerous | $@ is used in a hashing algorithm (MD5) that is insecure for password hashing, since it is not a computationally expensive hash function. | test_cryptography.py:15:17:15:30 | ControlFlowNode for get_password() | Sensitive data (password) |
| test_cryptography.py:27:19:27:27 | ControlFlowNode for dangerous | test_cryptography.py:23:17:23:30 | ControlFlowNode for get_password() | test_cryptography.py:27:19:27:27 | ControlFlowNode for dangerous | $@ is used in a hashing algorithm (SHA256) that is insecure for password hashing, since it is not a computationally expensive hash function. | test_cryptography.py:23:17:23:30 | ControlFlowNode for get_password() | Sensitive data (password) |

View File

@@ -0,0 +1 @@
Security/CWE-327/WeakSensitiveDataHashing.ql

View File

@@ -0,0 +1,25 @@
from Cryptodome.Hash import MD5, SHA256
from my_module import get_password, get_certificate
def get_badly_hashed_certificate():
dangerous = get_certificate()
hasher = MD5.new()
hasher.update(dangerous) # NOT OK
return hasher.hexdigest()
def get_badly_hashed_password():
dangerous = get_password()
hasher = MD5.new()
hasher.update(dangerous) # NOT OK
return hasher.hexdigest()
def get_badly_hashed_password2():
dangerous = get_password()
# Although SHA-256 is a strong cryptographic hash functions,
# it is not suitable for password hashing.
hasher = SHA256.new()
hasher.update(dangerous) # NOT OK
return hasher.hexdigest()

View File

@@ -0,0 +1,29 @@
from cryptography.hazmat.primitives import hashes
from binascii import hexlify
from my_module import get_password, get_certificate
def get_badly_hashed_certificate():
dangerous = get_certificate()
hasher = hashes.Hash(hashes.MD5())
hasher.update(dangerous) # NOT OK
digest = hasher.finalize()
return hexlify(digest).decode("utf-8")
def get_badly_hashed_password():
dangerous = get_password()
hasher = hashes.Hash(hashes.MD5())
hasher.update(dangerous) # NOT OK
digest = hasher.finalize()
return hexlify(digest).decode("utf-8")
def get_badly_hashed_password2():
dangerous = get_password()
# Although SHA-256 is a strong cryptographic hash functions,
# it is not suitable for password hashing.
hasher = hashes.Hash(hashes.SHA256())
hasher.update(dangerous) # NOT OK
digest = hasher.finalize()
return hexlify(digest).decode("utf-8")

View File

@@ -1,8 +0,0 @@
edges
| test_cryptography.py:5:17:5:30 | a password | test_cryptography.py:8:29:8:37 | a password |
| test_cryptography.py:5:17:5:30 | a password | test_cryptography.py:8:29:8:37 | a password |
| test_pycrypto.py:5:17:5:30 | a password | test_pycrypto.py:7:27:7:35 | a password |
| test_pycrypto.py:5:17:5:30 | a password | test_pycrypto.py:7:27:7:35 | a password |
#select
| test_cryptography.py:8:29:8:37 | dangerous | test_cryptography.py:5:17:5:30 | a password | test_cryptography.py:8:29:8:37 | a password | $@ is used in a broken or weak cryptographic algorithm. | test_cryptography.py:5:17:5:30 | get_password() | Sensitive data |
| test_pycrypto.py:7:27:7:35 | dangerous | test_pycrypto.py:5:17:5:30 | a password | test_pycrypto.py:7:27:7:35 | a password | $@ is used in a broken or weak cryptographic algorithm. | test_pycrypto.py:5:17:5:30 | get_password() | Sensitive data |

View File

@@ -1,12 +0,0 @@
| Taint Crypto.Cipher.ARC4 | test_pycrypto.py:6:14:6:27 | test_pycrypto.py:6 | test_pycrypto.py:6:14:6:27 | Attribute() | |
| Taint Crypto.Cipher.ARC4 | test_pycrypto.py:7:12:7:17 | test_pycrypto.py:7 | test_pycrypto.py:7:12:7:17 | cipher | |
| Taint cryptography.Cipher.RC4 | test_cryptography.py:6:14:6:47 | test_cryptography.py:6 | test_cryptography.py:6:14:6:47 | Cipher() | |
| Taint cryptography.Cipher.RC4 | test_cryptography.py:7:17:7:22 | test_cryptography.py:7 | test_cryptography.py:7:17:7:22 | cipher | |
| Taint cryptography.encryptor.RC4 | test_cryptography.py:7:17:7:34 | test_cryptography.py:7 | test_cryptography.py:7:17:7:34 | Attribute() | |
| Taint cryptography.encryptor.RC4 | test_cryptography.py:8:12:8:20 | test_cryptography.py:8 | test_cryptography.py:8:12:8:20 | encryptor | |
| Taint cryptography.encryptor.RC4 | test_cryptography.py:8:42:8:50 | test_cryptography.py:8 | test_cryptography.py:8:42:8:50 | encryptor | |
| Taint sensitive.data.password | test_cryptography.py:5:17:5:30 | test_cryptography.py:5 | test_cryptography.py:5:17:5:30 | get_password() | |
| Taint sensitive.data.password | test_cryptography.py:8:29:8:37 | test_cryptography.py:8 | test_cryptography.py:8:29:8:37 | dangerous | |
| Taint sensitive.data.password | test_cryptography.py:8:42:8:50 | test_cryptography.py:8 | test_cryptography.py:8:42:8:50 | encryptor | |
| Taint sensitive.data.password | test_pycrypto.py:5:17:5:30 | test_pycrypto.py:5 | test_pycrypto.py:5:17:5:30 | get_password() | |
| Taint sensitive.data.password | test_pycrypto.py:7:27:7:35 | test_pycrypto.py:7 | test_pycrypto.py:7:27:7:35 | dangerous | |

View File

@@ -1,9 +0,0 @@
import python
import semmle.python.dataflow.TaintTracking
import python
import semmle.python.security.SensitiveData
import semmle.python.security.Crypto
from TaintedNode n, AstNode src
where src = n.getAstNode() and src.getLocation().getFile().getAbsolutePath().matches("%test%")
select "Taint " + n.getTaintKind(), n.getLocation(), src, n.getContext()

View File

@@ -1,9 +0,0 @@
from cryptography.hazmat.primitives.ciphers import Cipher, algorithms
from secrets_store import get_password
def get_badly_encrypted_password():
dangerous = get_password()
cipher = Cipher(algorithms.ARC4(key), _, _)
encryptor = cipher.encryptor()
return encryptor.update(dangerous) + encryptor.finalize()

View File

@@ -1,8 +0,0 @@
from Crypto.Cipher import ARC4
from secrets_store import get_password
def get_badly_encrypted_password():
dangerous = get_password()
cipher = ARC4.new(_, _)
return cipher.encrypt(dangerous)