diff --git a/ql/src/experimental/CWE-327/BrokenCryptoAlgorithm.ql b/ql/src/experimental/CWE-327/BrokenCryptoAlgorithm.ql new file mode 100644 index 00000000000..535c3d85c6a --- /dev/null +++ b/ql/src/experimental/CWE-327/BrokenCryptoAlgorithm.ql @@ -0,0 +1,16 @@ +/** + * @name Use of a broken or risky cryptographic algorithm + * @description Using broken or weak cryptographic algorithms can allow an attacker to compromise security. + * @kind problem + * @problem.severity error + * @precision high + * @id go/weak-cryptographic-algorithm + * @tags security + */ + +import go +import BrokenCryptoAlgorithmCustomizations::BrokenCryptoAlgorithm + +from Configuration cfg, DataFlow::PathNode source, DataFlow::PathNode sink +where cfg.hasFlowPath(source, sink) +select sink.getNode(), "Sensitive data is used in a broken or weak cryptographic algorithm." diff --git a/ql/src/experimental/CWE-327/BrokenCryptoAlgorithmCustomizations.qll b/ql/src/experimental/CWE-327/BrokenCryptoAlgorithmCustomizations.qll new file mode 100644 index 00000000000..5866b12d076 --- /dev/null +++ b/ql/src/experimental/CWE-327/BrokenCryptoAlgorithmCustomizations.qll @@ -0,0 +1,60 @@ +/** + * Provides default sources, sinks and sanitizers for reasoning about + * sensitive information in broken or weak cryptographic algorithms, + * as well as extension points for adding your own. + */ + +import go +private import semmle.go.security.SensitiveActions +private import CryptoLibraries + +module BrokenCryptoAlgorithm { + /** + * A data flow source for sensitive information in broken or weak cryptographic algorithms. + */ + abstract class Source extends DataFlow::Node { } + + /** + * A data flow sink for sensitive information in broken or weak cryptographic algorithms. + */ + abstract class Sink extends DataFlow::Node { } + + /** + * A sanitizer for sensitive information in broken or weak cryptographic algorithms. + */ + abstract class Sanitizer extends DataFlow::Node { } + + /** + * A sensitive source. + */ + class SensitiveSource extends Source { + SensitiveSource() { this.asExpr() instanceof SensitiveExpr } + } + + /** + * An expression used by a broken or weak cryptographic algorithm. + */ + class WeakCryptographicOperationSink extends Sink { + WeakCryptographicOperationSink() { + exists(CryptographicOperation application | + application.getAlgorithm().isWeak() and + this.asExpr() = application.getInput() + ) + } + } + + class Configuration extends TaintTracking::Configuration { + Configuration() { this = "BrokenCryptoAlgorithm" } + + override predicate isSource(DataFlow::Node source) { source instanceof SensitiveSource } + + override predicate isSink(DataFlow::Node sink) { + sink instanceof WeakCryptographicOperationSink + } + + override predicate isSanitizer(DataFlow::Node node) { + super.isSanitizer(node) or + node instanceof Sanitizer + } + } +} diff --git a/ql/src/experimental/CWE-327/CryptoLibraries.qll b/ql/src/experimental/CWE-327/CryptoLibraries.qll new file mode 100644 index 00000000000..deca29c96ec --- /dev/null +++ b/ql/src/experimental/CWE-327/CryptoLibraries.qll @@ -0,0 +1,226 @@ +/** + * Provides classes for modelling cryptographic libraries. + */ + +import go + +/** + * 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 name of this algorithm. + */ + 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("[-_ ]", "").regexpMatch(".*" + 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 } +} + +/** + * An application of a cryptographic algorithm. + */ +abstract class CryptographicOperation extends Expr { + /** + * Gets the input the algorithm is used on, e.g. the plain text input to be encrypted. + */ + abstract Expr getInput(); + + /** + * Gets the applied algorithm. + */ + abstract CryptographicAlgorithm getAlgorithm(); +} + +class Md5 extends CryptographicOperation { + Expr input; + CryptographicAlgorithm algorithm; + SelectorExpr sel; + CallExpr call; + + Md5() { + this = call and + algorithm.matchesName(sel.getBase().toString()) and + algorithm.matchesName("MD5") and + sel.getSelector().toString() = call.getCalleeName().toString() and + call.getArgument(0) = input + } + + override Expr getInput() { result = input } + + override CryptographicAlgorithm getAlgorithm() { result = algorithm } +} + +class Des extends CryptographicOperation { + Expr input; + CryptographicAlgorithm algorithm; + CallExpr call; + SelectorExpr sel; + + Des() { + this = call and + algorithm.matchesName(call.getCalleeName()) and + algorithm.matchesName("DES") and + call.getArgument(0) = input + } + + override Expr getInput() { result = input } + + override CryptographicAlgorithm getAlgorithm() { result = algorithm } +} diff --git a/ql/test/experimental/CWE-327/BadCrypto.go b/ql/test/experimental/CWE-327/BadCrypto.go new file mode 100644 index 00000000000..6d7023d9ca8 --- /dev/null +++ b/ql/test/experimental/CWE-327/BadCrypto.go @@ -0,0 +1,26 @@ +package main + +import ( + "crypto/des" + "crypto/md5" + "fmt" +) + +func main() { + + password := []byte("password") + + var tripleDESKey []byte + tripleDESKey = append(tripleDESKey, password[:16]...) + tripleDESKey = append(tripleDESKey, password[:8]...) + + // BAD, des is a weak crypto algorithm + _, err := des.NewTripleDESCipher(tripleDESKey) + if err != nil { + panic(err) + } + + // BAD, md5 is a weak crypto algorithm + fmt.Printf("%x", md5.Sum(password)) + +} \ No newline at end of file diff --git a/ql/test/experimental/CWE-327/BrokenCryptoAlgorithm.expected b/ql/test/experimental/CWE-327/BrokenCryptoAlgorithm.expected new file mode 100644 index 00000000000..1ecef951863 --- /dev/null +++ b/ql/test/experimental/CWE-327/BrokenCryptoAlgorithm.expected @@ -0,0 +1,2 @@ +| BadCrypto.go:18:35:18:46 | tripleDESKey | Sensitive data is used in a broken or weak cryptographic algorithm. | +| BadCrypto.go:24:27:24:34 | password | Sensitive data is used in a broken or weak cryptographic algorithm. | diff --git a/ql/test/experimental/CWE-327/BrokenCryptoAlgorithm.qlref b/ql/test/experimental/CWE-327/BrokenCryptoAlgorithm.qlref new file mode 100644 index 00000000000..dc7d541336c --- /dev/null +++ b/ql/test/experimental/CWE-327/BrokenCryptoAlgorithm.qlref @@ -0,0 +1 @@ +experimental/CWE-327/BrokenCryptoAlgorithm.ql