mirror of
https://github.com/github/codeql.git
synced 2025-12-22 11:46:32 +01:00
Merge pull request #9722 from ahmed-farid-dev/timing-attack-py
This commit is contained in:
@@ -0,0 +1,4 @@
|
||||
<!DOCTYPE qhelp PUBLIC "-//Semmle//qhelp//EN" "qhelp.dtd">
|
||||
<qhelp>
|
||||
<include src="TimingAttackAgainstHash.qhelp" />
|
||||
</qhelp>
|
||||
@@ -0,0 +1,37 @@
|
||||
/**
|
||||
* @name Timing attack against Hash
|
||||
* @description When checking a Hash over a message, a constant-time algorithm should be used.
|
||||
* Otherwise, an attacker may be able to forge a valid Hash for an arbitrary message
|
||||
* by running a timing attack if they can send to the validation procedure.
|
||||
* A successful attack can result in authentication bypass.
|
||||
* @kind path-problem
|
||||
* @problem.severity error
|
||||
* @precision low
|
||||
* @id py/possible-timing-attack-against-hash
|
||||
* @tags security
|
||||
* external/cwe/cwe-208
|
||||
* experimental
|
||||
*/
|
||||
|
||||
import python
|
||||
import semmle.python.dataflow.new.DataFlow
|
||||
import semmle.python.dataflow.new.TaintTracking
|
||||
import experimental.semmle.python.security.TimingAttack
|
||||
import DataFlow::PathGraph
|
||||
|
||||
/**
|
||||
* A configuration that tracks data flow from cryptographic operations
|
||||
* to equality test
|
||||
*/
|
||||
class PossibleTimingAttackAgainstHash extends TaintTracking::Configuration {
|
||||
PossibleTimingAttackAgainstHash() { this = "PossibleTimingAttackAgainstHash" }
|
||||
|
||||
override predicate isSource(DataFlow::Node source) { source instanceof ProduceCryptoCall }
|
||||
|
||||
override predicate isSink(DataFlow::Node sink) { sink instanceof NonConstantTimeComparisonSink }
|
||||
}
|
||||
|
||||
from PossibleTimingAttackAgainstHash config, DataFlow::PathNode source, DataFlow::PathNode sink
|
||||
where config.hasFlowPath(source, sink)
|
||||
select sink.getNode(), source, sink, "Possible Timing attack against $@ validation.",
|
||||
source.getNode().(ProduceCryptoCall).getResultType(), "message"
|
||||
@@ -0,0 +1,17 @@
|
||||
#!/usr/bin/env python
|
||||
# -*- coding: UTF-8 -*-
|
||||
|
||||
"""
|
||||
@Desc :preventing timing attack Against Hash
|
||||
"""
|
||||
import hmac
|
||||
import hashlib
|
||||
|
||||
key = "e179017a-62b0-4996-8a38-e91aa9f1"
|
||||
msg = "Test"
|
||||
|
||||
def sign(pre_key, imsg, alg):
|
||||
return hmac.new(pre_key, imsg, alg).digest()
|
||||
|
||||
def verify(msg, sig):
|
||||
return hmac.compare_digest(sig, sign(key, msg, hashlib.sha256)) #good
|
||||
@@ -0,0 +1,55 @@
|
||||
<!DOCTYPE qhelp PUBLIC "-//Semmle//qhelp//EN" "qhelp.dtd">
|
||||
<qhelp>
|
||||
|
||||
<overview>
|
||||
<p>
|
||||
Timing Attack is based on the leakage of information by studying how long it takes the system to respond to different inputs.
|
||||
it can be circumvented by using a constant-time algorithm for checking the value of Hash,
|
||||
more precisely, the comparison time should not depend on the content of the input. Otherwise the attacker gains
|
||||
information that is indirectly leaked by the application. This information may then be used for malicious purposes.
|
||||
</p>
|
||||
</overview>
|
||||
|
||||
|
||||
<recommendation>
|
||||
<p>
|
||||
Two types of countermeasures can be applied against timing attacks. The first one consists
|
||||
in eliminating timing variations whereas the second renders these variations useless for an attacker.
|
||||
The only absolute way to prevent timing attacks is to make the computation strictly constant time,
|
||||
independent of the input.
|
||||
|
||||
Use <code>hmac.compare_digest()</code> method to securely check the value of Hash.
|
||||
If this method is used, then the calculation time depends only on the length of input byte arrays,
|
||||
and does not depend on the contents of the arrays.
|
||||
Unlike <code>==</code> is a fail fast check, If the first byte is not equal, it will return immediately.
|
||||
</p>
|
||||
</recommendation>
|
||||
<example>
|
||||
<p>
|
||||
The following example uses <code>==</code> which is a fail fast check for validating a Hash.
|
||||
</p>
|
||||
<sample src="UnSafeComparisonOfHash.py" />
|
||||
|
||||
<p>
|
||||
The next example use a safe constant-time algorithm for validating a Hash:
|
||||
</p>
|
||||
<sample src="SafeComparisonOfHash.py" />
|
||||
</example>
|
||||
|
||||
<references>
|
||||
<li>
|
||||
Wikipedia:
|
||||
<a href="https://en.wikipedia.org/wiki/Timing_attack">Timing attack</a>.
|
||||
</li>
|
||||
|
||||
<li>
|
||||
<a href="https://docs.python.org/3/library/hmac.html#hmac.compare_digest">hmac.compare_digest() method</a>
|
||||
</li>
|
||||
|
||||
<li>
|
||||
HMAC:
|
||||
<a href="https://datatracker.ietf.org/doc/html/rfc2104.html">RFC 2104</a>
|
||||
</li>
|
||||
</references>
|
||||
|
||||
</qhelp>
|
||||
@@ -0,0 +1,38 @@
|
||||
/**
|
||||
* @name Timing attack against Hash
|
||||
* @description When checking a Hash over a message, a constant-time algorithm should be used.
|
||||
* Otherwise, an attacker may be able to forge a valid Hash for an arbitrary message
|
||||
* by running a timing attack if they can send to the validation procedure.
|
||||
* A successful attack can result in authentication bypass.
|
||||
* @kind path-problem
|
||||
* @problem.severity error
|
||||
* @precision low
|
||||
* @id py/timing-attack-against-hash
|
||||
* @tags security
|
||||
* external/cwe/cwe-208
|
||||
*/
|
||||
|
||||
import python
|
||||
import semmle.python.dataflow.new.DataFlow
|
||||
import semmle.python.dataflow.new.TaintTracking
|
||||
import experimental.semmle.python.security.TimingAttack
|
||||
import DataFlow::PathGraph
|
||||
|
||||
/**
|
||||
* A configuration that tracks data flow from cryptographic operations
|
||||
* to Equality test.
|
||||
*/
|
||||
class TimingAttackAgainsthash extends TaintTracking::Configuration {
|
||||
TimingAttackAgainsthash() { this = "TimingAttackAgainsthash" }
|
||||
|
||||
override predicate isSource(DataFlow::Node source) { source instanceof ProduceCryptoCall }
|
||||
|
||||
override predicate isSink(DataFlow::Node sink) { sink instanceof NonConstantTimeComparisonSink }
|
||||
}
|
||||
|
||||
from TimingAttackAgainsthash config, DataFlow::PathNode source, DataFlow::PathNode sink
|
||||
where
|
||||
config.hasFlowPath(source, sink) and
|
||||
sink.getNode().(NonConstantTimeComparisonSink).includesUserInput()
|
||||
select sink.getNode(), source, sink, "Timing attack against $@ validation.",
|
||||
source.getNode().(ProduceCryptoCall).getResultType(), "message"
|
||||
@@ -0,0 +1,17 @@
|
||||
#!/usr/bin/env python
|
||||
# -*- coding: UTF-8 -*-
|
||||
|
||||
"""
|
||||
@Desc :timing attack Against Hash
|
||||
"""
|
||||
import hmac
|
||||
import hashlib
|
||||
|
||||
key = "e179017a-62b0-4996-8a38-e91aa9f1"
|
||||
msg = "Test"
|
||||
|
||||
def sign(pre_key, imsg, alg):
|
||||
return hmac.new(pre_key, imsg, alg).digest()
|
||||
|
||||
def verify(msg, sig):
|
||||
return sig == sign(key, msg, hashlib.sha256) #bad
|
||||
@@ -0,0 +1,21 @@
|
||||
#!/usr/bin/env python
|
||||
# -*- coding: UTF-8 -*-
|
||||
|
||||
"""
|
||||
@Desc :preventing timing attack against header value
|
||||
"""
|
||||
|
||||
from flask import Flask
|
||||
from flask import request
|
||||
import hmac
|
||||
|
||||
@app.route('/good')
|
||||
def good():
|
||||
secret = request.headers.get('X-Auth-Token')
|
||||
if not hmac.compare_digest(secret, "token"):
|
||||
raise Exception('bad token')
|
||||
return 'good'
|
||||
|
||||
if __name__ == '__main__':
|
||||
app.debug = True
|
||||
app.run()
|
||||
@@ -0,0 +1,50 @@
|
||||
<!DOCTYPE qhelp PUBLIC "-//Semmle//qhelp//EN" "qhelp.dtd">
|
||||
<qhelp>
|
||||
|
||||
<overview>
|
||||
<p>
|
||||
A constant-time algorithm should be used for checking the value of sensitive headers.
|
||||
In other words, the comparison time should not depend on the content of the input.
|
||||
Otherwise timing information could be used to infer the header's expected, secret value.
|
||||
</p>
|
||||
</overview>
|
||||
|
||||
|
||||
<recommendation>
|
||||
<p>
|
||||
Two types of countermeasures can be applied against timing attacks. The first one consists
|
||||
in eliminating timing variations whereas the second renders these variations useless for an attacker.
|
||||
The only absolute way to prevent timing attacks is to make the computation strictly constant time,
|
||||
independent of the input.
|
||||
|
||||
Use <code>hmac.compare_digest()</code> method to securely check the secret value.
|
||||
If this method is used, then the calculation time depends only on the length of input byte arrays,
|
||||
and does not depend on the contents of the arrays.
|
||||
Unlike <code>==</code> is a fail fast check, If the first byte is not equal, it will return immediately.
|
||||
</p>
|
||||
</recommendation>
|
||||
<example>
|
||||
<p>
|
||||
The following example uses <code>==</code> which is a fail fast check for validating the value of sensitive headers.
|
||||
</p>
|
||||
<sample src="UnsafeComparisonOfHeaderValue.py" />
|
||||
|
||||
<p>
|
||||
The next example use a safe constant-time algorithm for validating the value of sensitive headers:
|
||||
</p>
|
||||
<sample src="SafeComparisonOfHeaderValue.py" />
|
||||
</example>
|
||||
|
||||
<references>
|
||||
<li>
|
||||
Wikipedia:
|
||||
<a href="https://en.wikipedia.org/wiki/Timing_attack">Timing attack</a>.
|
||||
</li>
|
||||
|
||||
<li>
|
||||
<a href="https://docs.python.org/3/library/hmac.html#hmac.compare_digest">hmac.compare_digest() method</a>
|
||||
</li>
|
||||
|
||||
</references>
|
||||
|
||||
</qhelp>
|
||||
@@ -0,0 +1,34 @@
|
||||
/**
|
||||
* @name Timing attack against header value
|
||||
* @description Use of a non-constant-time verification routine to check the value of an HTTP header,
|
||||
* possibly allowing a timing attack to infer the header's expected value.
|
||||
* @kind path-problem
|
||||
* @problem.severity error
|
||||
* @precision high
|
||||
* @id py/timing-attack-against-header-value
|
||||
* @tags security
|
||||
* external/cwe/cwe-208
|
||||
* experimental
|
||||
*/
|
||||
|
||||
import python
|
||||
import semmle.python.dataflow.new.DataFlow
|
||||
import semmle.python.dataflow.new.TaintTracking
|
||||
import experimental.semmle.python.security.TimingAttack
|
||||
import DataFlow::PathGraph
|
||||
|
||||
/**
|
||||
* A configuration tracing flow from a client Secret obtained by an HTTP header to a unsafe Comparison.
|
||||
*/
|
||||
class ClientSuppliedSecretConfig extends TaintTracking::Configuration {
|
||||
ClientSuppliedSecretConfig() { this = "ClientSuppliedSecretConfig" }
|
||||
|
||||
override predicate isSource(DataFlow::Node source) { source instanceof ClientSuppliedSecret }
|
||||
|
||||
override predicate isSink(DataFlow::Node sink) { sink instanceof CompareSink }
|
||||
}
|
||||
|
||||
from ClientSuppliedSecretConfig config, DataFlow::PathNode source, DataFlow::PathNode sink
|
||||
where config.hasFlowPath(source, sink) and not sink.getNode().(CompareSink).flowtolen()
|
||||
select sink.getNode(), source, sink, "Timing attack against $@ validation.", source.getNode(),
|
||||
"client-supplied token"
|
||||
@@ -0,0 +1,20 @@
|
||||
#!/usr/bin/env python
|
||||
# -*- coding: UTF-8 -*-
|
||||
|
||||
"""
|
||||
@Desc :preventing timing attack against header value
|
||||
"""
|
||||
|
||||
from flask import Flask
|
||||
from flask import request
|
||||
|
||||
@app.route('/bad')
|
||||
def bad():
|
||||
secret = request.headers.get('X-Auth-Token')
|
||||
if secret == "token":
|
||||
raise Exception('bad token')
|
||||
return 'bad'
|
||||
|
||||
if __name__ == '__main__':
|
||||
app.debug = True
|
||||
app.run()
|
||||
@@ -0,0 +1,4 @@
|
||||
<!DOCTYPE qhelp PUBLIC "-//Semmle//qhelp//EN" "qhelp.dtd">
|
||||
<qhelp>
|
||||
<include src="TimingAttackAgainstSensitiveInfo.qhelp" />
|
||||
</qhelp>
|
||||
@@ -0,0 +1,34 @@
|
||||
/**
|
||||
* @name Timing attack against secret
|
||||
* @description Use of a non-constant-time verification routine to check the value of an secret,
|
||||
* possibly allowing a timing attack to retrieve sensitive information.
|
||||
* @kind path-problem
|
||||
* @problem.severity error
|
||||
* @precision low
|
||||
* @id py/possible-timing-attack-sensitive-info
|
||||
* @tags security
|
||||
* external/cwe/cwe-208
|
||||
* experimental
|
||||
*/
|
||||
|
||||
import python
|
||||
import semmle.python.dataflow.new.DataFlow
|
||||
import semmle.python.dataflow.new.TaintTracking
|
||||
import experimental.semmle.python.security.TimingAttack
|
||||
import DataFlow::PathGraph
|
||||
|
||||
/**
|
||||
* A configuration tracing flow from obtaining a client Secret to a unsafe Comparison.
|
||||
*/
|
||||
class ClientSuppliedSecretConfig extends TaintTracking::Configuration {
|
||||
ClientSuppliedSecretConfig() { this = "ClientSuppliedSecretConfig" }
|
||||
|
||||
override predicate isSource(DataFlow::Node source) { source instanceof SecretSource }
|
||||
|
||||
override predicate isSink(DataFlow::Node sink) { sink instanceof NonConstantTimeComparisonSink }
|
||||
}
|
||||
|
||||
from ClientSuppliedSecretConfig config, DataFlow::PathNode source, DataFlow::PathNode sink
|
||||
where config.hasFlowPath(source, sink)
|
||||
select sink.getNode(), source, sink, "Timing attack against $@ validation.", source.getNode(),
|
||||
"client-supplied token"
|
||||
@@ -0,0 +1,21 @@
|
||||
#!/usr/bin/env python
|
||||
# -*- coding: UTF-8 -*-
|
||||
|
||||
"""
|
||||
@Desc :preventing timing attack sensitive info
|
||||
"""
|
||||
from flask import Flask
|
||||
from flask import request
|
||||
import hmac
|
||||
|
||||
app = Flask(__name__)
|
||||
|
||||
@app.route('/bad', methods = ['POST', 'GET'])
|
||||
def bad():
|
||||
if request.method == 'POST':
|
||||
password = request.form['pwd']
|
||||
return hmac.compare_digest(password, "1234")
|
||||
|
||||
if __name__ == '__main__':
|
||||
app.debug = True
|
||||
app.run()
|
||||
@@ -0,0 +1,52 @@
|
||||
<!DOCTYPE qhelp PUBLIC "-//Semmle//qhelp//EN" "qhelp.dtd">
|
||||
<qhelp>
|
||||
|
||||
<overview>
|
||||
<p>
|
||||
Timing Attack is based on the leakage of information of secret parameters by studying
|
||||
how long it takes the system to respond to different inputs.
|
||||
it can be circumvented by using a constant-time algorithm for checking the value of sensitive info,
|
||||
more precisely, the comparison time should not depend on the content of the input. Otherwise the attacker gains
|
||||
information that is indirectly leaked by the application. This information is then used for malicious purposes.
|
||||
</p>
|
||||
</overview>
|
||||
|
||||
|
||||
<recommendation>
|
||||
<p>
|
||||
Two types of countermeasures can be applied against timing attacks. The first one consists
|
||||
in eliminating timing variations whereas the second renders these variations useless for an attacker.
|
||||
The only absolute way to prevent timing attacks is to make the computation strictly constant time,
|
||||
independent of the input.
|
||||
|
||||
Use <code>hmac.compare_digest()</code> method to securely check the value of sensitive info.
|
||||
If this method is used, then the calculation time depends only on the length of input byte arrays,
|
||||
and does not depend on the contents of the arrays.
|
||||
Unlike <code>==</code> is a fail fast check, If the first byte is not equal, it will return immediately.
|
||||
</p>
|
||||
</recommendation>
|
||||
<example>
|
||||
<p>
|
||||
The following example uses <code>==</code> which is a fail fast check for validating a secret.
|
||||
</p>
|
||||
<sample src="UnSafeComparisonOfSensitiveInfo.py" />
|
||||
|
||||
<p>
|
||||
The next example use a safe constant-time algorithm for validating a secret:
|
||||
</p>
|
||||
<sample src="SafeComparisonOfSensitiveInfo.py" />
|
||||
</example>
|
||||
|
||||
<references>
|
||||
<li>
|
||||
Wikipedia:
|
||||
<a href="https://en.wikipedia.org/wiki/Timing_attack">Timing attack</a>.
|
||||
</li>
|
||||
|
||||
<li>
|
||||
<a href="https://docs.python.org/3/library/hmac.html#hmac.compare_digest">hmac.compare_digest() method</a>
|
||||
</li>
|
||||
|
||||
</references>
|
||||
|
||||
</qhelp>
|
||||
@@ -0,0 +1,39 @@
|
||||
/**
|
||||
* @name Timing attack against secret
|
||||
* @description Use of a non-constant-time verification routine to check the value of an secret,
|
||||
* possibly allowing a timing attack to retrieve sensitive information.
|
||||
* @kind path-problem
|
||||
* @problem.severity error
|
||||
* @precision low
|
||||
* @id py/timing-attack-sensitive-info
|
||||
* @tags security
|
||||
* external/cwe/cwe-208
|
||||
* experimental
|
||||
*/
|
||||
|
||||
import python
|
||||
import semmle.python.dataflow.new.DataFlow
|
||||
import semmle.python.dataflow.new.TaintTracking
|
||||
import experimental.semmle.python.security.TimingAttack
|
||||
import DataFlow::PathGraph
|
||||
|
||||
/**
|
||||
* A configuration tracing flow from obtaining a client Secret to a unsafe Comparison.
|
||||
*/
|
||||
class ClientSuppliedSecretConfig extends TaintTracking::Configuration {
|
||||
ClientSuppliedSecretConfig() { this = "ClientSuppliedSecretConfig" }
|
||||
|
||||
override predicate isSource(DataFlow::Node source) { source instanceof SecretSource }
|
||||
|
||||
override predicate isSink(DataFlow::Node sink) { sink instanceof NonConstantTimeComparisonSink }
|
||||
}
|
||||
|
||||
from ClientSuppliedSecretConfig config, DataFlow::PathNode source, DataFlow::PathNode sink
|
||||
where
|
||||
config.hasFlowPath(source, sink) and
|
||||
(
|
||||
source.getNode().(SecretSource).includesUserInput() or
|
||||
sink.getNode().(NonConstantTimeComparisonSink).includesUserInput()
|
||||
)
|
||||
select sink.getNode(), source, sink, "Timing attack against $@ validation.", source.getNode(),
|
||||
"client-supplied token"
|
||||
@@ -0,0 +1,18 @@
|
||||
#!/usr/bin/env python
|
||||
# -*- coding: UTF-8 -*-
|
||||
"""
|
||||
@Desc :timing attack against sensitive info
|
||||
"""
|
||||
|
||||
from flask import Flask
|
||||
from flask import request
|
||||
|
||||
@app.route('/bad', methods = ['POST', 'GET'])
|
||||
def bad():
|
||||
if request.method == 'POST':
|
||||
password = request.form['pwd']
|
||||
return password == "test"
|
||||
|
||||
if __name__ == '__main__':
|
||||
app.debug = True
|
||||
app.run()
|
||||
@@ -0,0 +1,350 @@
|
||||
private import python
|
||||
private import semmle.python.dataflow.new.TaintTracking2
|
||||
private import semmle.python.dataflow.new.TaintTracking
|
||||
private import semmle.python.dataflow.new.DataFlow
|
||||
private import semmle.python.dataflow.new.DataFlow2
|
||||
private import semmle.python.ApiGraphs
|
||||
private import semmle.python.dataflow.new.RemoteFlowSources
|
||||
private import semmle.python.frameworks.Flask
|
||||
private import semmle.python.frameworks.Django
|
||||
|
||||
/** A method call that produces cryptographic result. */
|
||||
abstract class ProduceCryptoCall extends API::CallNode {
|
||||
/** Gets a type of cryptographic operation such as MAC, signature, Hash or ciphertext. */
|
||||
abstract string getResultType();
|
||||
}
|
||||
|
||||
/** Gets a reference to the `cryptography.hazmat.primitives` module. */
|
||||
API::Node cryptographylib() {
|
||||
result = API::moduleImport("cryptography").getMember("hazmat").getMember("primitives")
|
||||
}
|
||||
|
||||
/** Gets a reference to the `Crypto` module. */
|
||||
API::Node cryptodome() { result = API::moduleImport(["Crypto", "Cryptodome"]) }
|
||||
|
||||
/** A method call that produces a MAC. */
|
||||
class ProduceMacCall extends ProduceCryptoCall {
|
||||
ProduceMacCall() {
|
||||
this = API::moduleImport("hmac").getMember("digest").getACall() or
|
||||
this =
|
||||
API::moduleImport("hmac")
|
||||
.getMember("new")
|
||||
.getReturn()
|
||||
.getMember(["digest", "hexdigest"])
|
||||
.getACall() or
|
||||
this =
|
||||
cryptodome()
|
||||
.getMember("Hash")
|
||||
.getMember("HMAC")
|
||||
.getMember(["new", "HMAC"])
|
||||
.getMember(["digest", "hexdigest"])
|
||||
.getACall() or
|
||||
this =
|
||||
cryptographylib()
|
||||
.getMember("hmac")
|
||||
.getMember("HMAC")
|
||||
.getReturn()
|
||||
.getMember("finalize")
|
||||
.getACall() or
|
||||
this =
|
||||
cryptographylib()
|
||||
.getMember("cmac")
|
||||
.getMember("CMAC")
|
||||
.getReturn()
|
||||
.getMember("finalize")
|
||||
.getACall() or
|
||||
this =
|
||||
cryptodome()
|
||||
.getMember("Hash")
|
||||
.getMember("CMAC")
|
||||
.getMember(["new", "CMAC"])
|
||||
.getMember(["digest", "hexdigest"])
|
||||
.getACall()
|
||||
}
|
||||
|
||||
override string getResultType() { result = "MAC" }
|
||||
}
|
||||
|
||||
/** A method call that produces a signature. */
|
||||
private class ProduceSignatureCall extends ProduceCryptoCall {
|
||||
ProduceSignatureCall() {
|
||||
this =
|
||||
cryptodome()
|
||||
.getMember("Signature")
|
||||
.getMember(["DSS", "pkcs1_15", "pss", "eddsa"])
|
||||
.getMember("new")
|
||||
.getReturn()
|
||||
.getMember("sign")
|
||||
.getACall()
|
||||
}
|
||||
|
||||
override string getResultType() { result = "signature" }
|
||||
}
|
||||
|
||||
private string hashalgo() {
|
||||
result = ["sha1", "sha224", "sha256", "sha384", "sha512", "blake2b", "blake2s", "md5"]
|
||||
}
|
||||
|
||||
/** A method call that produces a Hash. */
|
||||
private class ProduceHashCall extends ProduceCryptoCall {
|
||||
ProduceHashCall() {
|
||||
this =
|
||||
cryptographylib()
|
||||
.getMember("hashes")
|
||||
.getMember("Hash")
|
||||
.getReturn()
|
||||
.getMember("finalize")
|
||||
.getACall() or
|
||||
this =
|
||||
API::moduleImport("hashlib")
|
||||
.getMember(["new", hashalgo()])
|
||||
.getReturn()
|
||||
.getMember(["digest", "hexdigest"])
|
||||
.getACall() or
|
||||
this =
|
||||
cryptodome()
|
||||
.getMember(hashalgo())
|
||||
.getMember("new")
|
||||
.getReturn()
|
||||
.getMember(["digest", "hexdigest"])
|
||||
.getACall()
|
||||
}
|
||||
|
||||
override string getResultType() { result = "Hash" }
|
||||
}
|
||||
|
||||
/** A method call that produces a ciphertext. */
|
||||
private class ProduceCiphertextCall extends ProduceCryptoCall {
|
||||
ProduceCiphertextCall() {
|
||||
this =
|
||||
cryptodome()
|
||||
.getMember("Cipher")
|
||||
.getMember(["DES", "DES3", "ARC2", "ARC4", "Blowfish", "PKCS1_v1_5"])
|
||||
.getMember(["ARC4Cipher", "new", "PKCS115_Cipher"])
|
||||
.getMember("encrypt")
|
||||
.getACall() or
|
||||
this =
|
||||
cryptographylib()
|
||||
.getMember("ciphers")
|
||||
.getMember("Cipher")
|
||||
.getReturn()
|
||||
.getMember("finalize")
|
||||
.getACall()
|
||||
}
|
||||
|
||||
override string getResultType() { result = "ciphertext" }
|
||||
}
|
||||
|
||||
/** A data flow sink for comparison. */
|
||||
private predicate existsFailFastCheck(Expr firstInput, Expr secondInput) {
|
||||
exists(Compare compare |
|
||||
(
|
||||
compare.getOp(0) instanceof Eq or
|
||||
compare.getOp(0) instanceof NotEq or
|
||||
compare.getOp(0) instanceof In or
|
||||
compare.getOp(0) instanceof NotIn
|
||||
) and
|
||||
(
|
||||
compare.getLeft() = firstInput and
|
||||
compare.getComparator(0) = secondInput and
|
||||
not compare.getAComparator() instanceof None
|
||||
or
|
||||
compare.getLeft() = secondInput and
|
||||
compare.getComparator(0) = firstInput and
|
||||
not compare.getAComparator() instanceof None
|
||||
)
|
||||
)
|
||||
}
|
||||
|
||||
/** A sink that compares input using fail fast check. */
|
||||
class NonConstantTimeComparisonSink extends DataFlow::Node {
|
||||
Expr anotherParameter;
|
||||
|
||||
NonConstantTimeComparisonSink() { existsFailFastCheck(this.asExpr(), anotherParameter) }
|
||||
|
||||
/** Holds if remote user input was used in the comparison. */
|
||||
predicate includesUserInput() {
|
||||
exists(UserInputInComparisonConfig config |
|
||||
config.hasFlowTo(DataFlow2::exprNode(anotherParameter))
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
/** A data flow source of the secret obtained. */
|
||||
class SecretSource extends DataFlow::Node {
|
||||
CredentialExpr secret;
|
||||
|
||||
SecretSource() { secret = this.asExpr() }
|
||||
|
||||
/** Holds if the secret was deliverd by remote user. */
|
||||
predicate includesUserInput() {
|
||||
exists(UserInputSecretConfig config | config.hasFlowTo(DataFlow2::exprNode(secret)))
|
||||
}
|
||||
}
|
||||
|
||||
/** A string for `match` that identifies strings that look like they represent secret data. */
|
||||
private string suspicious() {
|
||||
result =
|
||||
[
|
||||
"%password%", "%passwd%", "%pwd%", "%refresh%token%", "%secret%token", "%secret%key",
|
||||
"%passcode%", "%passphrase%", "%token%", "%secret%", "%credential%", "%userpass%", "%digest%",
|
||||
"%signature%", "%mac%"
|
||||
]
|
||||
}
|
||||
|
||||
/** A variable that may hold sensitive information, judging by its name. * */
|
||||
class CredentialExpr extends Expr {
|
||||
CredentialExpr() {
|
||||
exists(Variable v | this = v.getAnAccess() | v.getId().toLowerCase().matches(suspicious()))
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* A data flow source of the client Secret obtained according to the remote endpoint identifier specified
|
||||
* (`X-auth-token`, `proxy-authorization`, `X-Csrf-Header`, etc.) in the header.
|
||||
*
|
||||
* For example: `request.headers.get("X-Auth-Token")`.
|
||||
*/
|
||||
abstract class ClientSuppliedSecret extends DataFlow::CallCfgNode { }
|
||||
|
||||
private class FlaskClientSuppliedSecret extends ClientSuppliedSecret {
|
||||
FlaskClientSuppliedSecret() {
|
||||
this = Flask::request().getMember("headers").getMember(["get", "get_all", "getlist"]).getACall() and
|
||||
[this.getArg(0), this.getArgByName(["key", "name"])].asExpr().(StrConst).getText().toLowerCase() =
|
||||
sensitiveheaders()
|
||||
}
|
||||
}
|
||||
|
||||
private class DjangoClientSuppliedSecret extends ClientSuppliedSecret {
|
||||
DjangoClientSuppliedSecret() {
|
||||
this =
|
||||
PrivateDjango::DjangoImpl::DjangoHttp::Request::HttpRequest::classRef()
|
||||
.getMember(["headers", "META"])
|
||||
.getMember("get")
|
||||
.getACall() and
|
||||
[this.getArg(0), this.getArgByName("key")].asExpr().(StrConst).getText().toLowerCase() =
|
||||
sensitiveheaders()
|
||||
}
|
||||
}
|
||||
|
||||
/** Gets a reference to the `tornado.web.RequestHandler` module. */
|
||||
API::Node requesthandler() {
|
||||
result = API::moduleImport("tornado").getMember("web").getMember("RequestHandler")
|
||||
}
|
||||
|
||||
private class TornadoClientSuppliedSecret extends ClientSuppliedSecret {
|
||||
TornadoClientSuppliedSecret() {
|
||||
this = requesthandler().getMember(["headers", "META"]).getMember("get").getACall() and
|
||||
[this.getArg(0), this.getArgByName("key")].asExpr().(StrConst).getText().toLowerCase() =
|
||||
sensitiveheaders()
|
||||
}
|
||||
}
|
||||
|
||||
/** Gets a reference to the `werkzeug.datastructures.Headers` module. */
|
||||
API::Node headers() {
|
||||
result = API::moduleImport("werkzeug").getMember("datastructures").getMember("Headers")
|
||||
}
|
||||
|
||||
private class WerkzeugClientSuppliedSecret extends ClientSuppliedSecret {
|
||||
WerkzeugClientSuppliedSecret() {
|
||||
this =
|
||||
headers().getMember(["headers", "META"]).getMember(["get", "get_all", "getlist"]).getACall() and
|
||||
[this.getArg(0), this.getArgByName(["key", "name"])].asExpr().(StrConst).getText().toLowerCase() =
|
||||
sensitiveheaders()
|
||||
}
|
||||
}
|
||||
|
||||
/** A string for `match` that identifies strings that look like they represent Sensitive Headers. */
|
||||
private string sensitiveheaders() {
|
||||
result =
|
||||
[
|
||||
"x-auth-token", "x-csrf-token", "http_x_csrf_token", "x-csrf-param", "x-csrf-header",
|
||||
"http_x_csrf_token", "x-api-key", "authorization", "proxy-authorization", "x-gitlab-token",
|
||||
"www-authenticate"
|
||||
]
|
||||
}
|
||||
|
||||
/**
|
||||
* A config that tracks data flow from remote user input to Variable that hold sensitive info
|
||||
*/
|
||||
class UserInputSecretConfig extends TaintTracking::Configuration {
|
||||
UserInputSecretConfig() { this = "UserInputSecretConfig" }
|
||||
|
||||
override predicate isSource(DataFlow::Node source) { source instanceof RemoteFlowSource }
|
||||
|
||||
override predicate isSink(DataFlow::Node sink) { sink.asExpr() instanceof CredentialExpr }
|
||||
}
|
||||
|
||||
/**
|
||||
* A config that tracks data flow from remote user input to Equality test
|
||||
*/
|
||||
class UserInputInComparisonConfig extends TaintTracking2::Configuration {
|
||||
UserInputInComparisonConfig() { this = "UserInputInComparisonConfig" }
|
||||
|
||||
override predicate isSource(DataFlow::Node source) { source instanceof RemoteFlowSource }
|
||||
|
||||
override predicate isSink(DataFlow::Node sink) {
|
||||
exists(Compare cmp, Expr left, Expr right, Cmpop cmpop |
|
||||
cmpop.getSymbol() = ["==", "in", "is not", "!="] and
|
||||
cmp.compares(left, cmpop, right) and
|
||||
sink.asExpr() = [left, right]
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* A configuration tracing flow from a client Secret obtained by an HTTP header to a len() function.
|
||||
*/
|
||||
private class ExcludeLenFunc extends TaintTracking2::Configuration {
|
||||
ExcludeLenFunc() { this = "ExcludeLenFunc" }
|
||||
|
||||
override predicate isSource(DataFlow::Node source) { source instanceof ClientSuppliedSecret }
|
||||
|
||||
override predicate isSink(DataFlow::Node sink) {
|
||||
exists(Call call |
|
||||
call.getFunc().(Name).getId() = "len" and
|
||||
sink.asExpr() = call.getArg(0)
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Holds if there is a fast-fail check.
|
||||
*/
|
||||
class CompareSink extends DataFlow::Node {
|
||||
CompareSink() {
|
||||
exists(Compare compare |
|
||||
(
|
||||
compare.getOp(0) instanceof Eq or
|
||||
compare.getOp(0) instanceof NotEq
|
||||
) and
|
||||
(
|
||||
compare.getLeft() = this.asExpr() and
|
||||
not compare.getComparator(0).(StrConst).getText() = "bearer"
|
||||
or
|
||||
compare.getComparator(0) = this.asExpr() and
|
||||
not compare.getLeft().(StrConst).getText() = "bearer"
|
||||
)
|
||||
)
|
||||
or
|
||||
exists(Compare compare |
|
||||
compare.getOp(0) instanceof IsNot and
|
||||
(
|
||||
compare.getLeft() = this.asExpr() and
|
||||
not compare.getComparator(0) instanceof None
|
||||
or
|
||||
compare.getComparator(0) = this.asExpr() and
|
||||
not compare.getLeft() instanceof None
|
||||
)
|
||||
)
|
||||
}
|
||||
|
||||
/**
|
||||
* Holds if there is a flow to len().
|
||||
*/
|
||||
predicate flowtolen() {
|
||||
exists(ExcludeLenFunc config, DataFlow2::PathNode source, DataFlow2::PathNode sink |
|
||||
config.hasFlowPath(source, sink)
|
||||
)
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,12 @@
|
||||
edges
|
||||
| TimingAttackAgainstHash.py:26:17:26:41 | ControlFlowNode for Attribute() | TimingAttackAgainstHash.py:27:24:27:32 | ControlFlowNode for signature |
|
||||
| TimingAttackAgainstHash.py:30:12:30:47 | ControlFlowNode for Attribute() | TimingAttackAgainstHash.py:37:19:37:48 | ControlFlowNode for sign() |
|
||||
nodes
|
||||
| TimingAttackAgainstHash.py:26:17:26:41 | ControlFlowNode for Attribute() | semmle.label | ControlFlowNode for Attribute() |
|
||||
| TimingAttackAgainstHash.py:27:24:27:32 | ControlFlowNode for signature | semmle.label | ControlFlowNode for signature |
|
||||
| TimingAttackAgainstHash.py:30:12:30:47 | ControlFlowNode for Attribute() | semmle.label | ControlFlowNode for Attribute() |
|
||||
| TimingAttackAgainstHash.py:37:19:37:48 | ControlFlowNode for sign() | semmle.label | ControlFlowNode for sign() |
|
||||
subpaths
|
||||
#select
|
||||
| TimingAttackAgainstHash.py:27:24:27:32 | ControlFlowNode for signature | TimingAttackAgainstHash.py:26:17:26:41 | ControlFlowNode for Attribute() | TimingAttackAgainstHash.py:27:24:27:32 | ControlFlowNode for signature | Possible Timing attack against $@ validation. | signature | message |
|
||||
| TimingAttackAgainstHash.py:37:19:37:48 | ControlFlowNode for sign() | TimingAttackAgainstHash.py:30:12:30:47 | ControlFlowNode for Attribute() | TimingAttackAgainstHash.py:37:19:37:48 | ControlFlowNode for sign() | Possible Timing attack against $@ validation. | MAC | message |
|
||||
@@ -0,0 +1 @@
|
||||
experimental/Security/CWE-208/TimingAttackAgainstHash/PossibleTimingAttackAgainstHash.ql
|
||||
@@ -0,0 +1,48 @@
|
||||
#!/usr/bin/env python
|
||||
# -*- coding: UTF-8 -*-
|
||||
"""
|
||||
@Desc :Timing Attack Against Hash
|
||||
"""
|
||||
import hashlib
|
||||
import hmac
|
||||
from cryptography.hazmat.primitives import cmac
|
||||
from cryptography.hazmat.primitives.ciphers import algorithms
|
||||
from Crypto.Signature import pkcs1_15
|
||||
from Crypto.Hash import SHA256
|
||||
from Crypto.PublicKey import RSA
|
||||
|
||||
|
||||
def UnsafeCmacCheck(actualCmac):
|
||||
expected = cmac.CMAC(algorithms.AES(key))
|
||||
expected.update(b"message to authenticate")
|
||||
expected.finalize()
|
||||
return actualCmac == expected
|
||||
|
||||
|
||||
def UnsafeCheckSignature(expected):
|
||||
message = b'To be signed'
|
||||
key = RSA.import_key(open('private_key.der').read())
|
||||
h = SHA256.new(message)
|
||||
signature = pkcs1_15.new(key).sign(h)
|
||||
return expected == signature
|
||||
|
||||
def sign(pre_key, msg, alg):
|
||||
return hmac.new(pre_key, msg, alg).digest()
|
||||
|
||||
def verifyGood(msg, sig):
|
||||
return constant_time_string_compare(sig, sign(key, msg, hashlib.sha256)) #good
|
||||
|
||||
def verifyBad(msg, sig):
|
||||
key = "e179017a-62b0-4996-8a38-e91aa9f1"
|
||||
return sig == sign(key, msg, hashlib.sha256) #bad
|
||||
|
||||
def constant_time_string_compare(a, b):
|
||||
if len(a) != len(b):
|
||||
return False
|
||||
|
||||
result = 0
|
||||
|
||||
for x, y in zip(a, b):
|
||||
result |= ord(x) ^ ord(y)
|
||||
|
||||
return result == 0
|
||||
@@ -0,0 +1,27 @@
|
||||
#!/usr/bin/env python
|
||||
# -*- coding: UTF-8 -*-
|
||||
"""
|
||||
@Desc :Timing Attack Against Header
|
||||
"""
|
||||
from flask import Flask
|
||||
from flask import request
|
||||
import hmac
|
||||
|
||||
app = Flask(__name__)
|
||||
|
||||
@app.route('/bad')
|
||||
def bad():
|
||||
if not request.headers.get('X-Auth-Token') == "token":
|
||||
raise Exception('bad token')
|
||||
return 'bad'
|
||||
|
||||
@app.route('/good')
|
||||
def good():
|
||||
tok = request.headers.get('X-Auth-Token')
|
||||
if not hmac.compare_digest(tok, "token"):
|
||||
raise Exception('bad token')
|
||||
return 'good'
|
||||
|
||||
if __name__ == '__main__':
|
||||
app.debug = True
|
||||
app.run()
|
||||
@@ -0,0 +1,6 @@
|
||||
edges
|
||||
nodes
|
||||
| TimingAttackAgainstHeader.py:14:12:14:46 | ControlFlowNode for Attribute() | semmle.label | ControlFlowNode for Attribute() |
|
||||
subpaths
|
||||
#select
|
||||
| TimingAttackAgainstHeader.py:14:12:14:46 | ControlFlowNode for Attribute() | TimingAttackAgainstHeader.py:14:12:14:46 | ControlFlowNode for Attribute() | TimingAttackAgainstHeader.py:14:12:14:46 | ControlFlowNode for Attribute() | Timing attack against $@ validation. | TimingAttackAgainstHeader.py:14:12:14:46 | ControlFlowNode for Attribute() | client-supplied token |
|
||||
@@ -0,0 +1 @@
|
||||
experimental/Security/CWE-208/TimingAttackAgainstHeaderValue/TimingAttackAgainstHeaderValue.ql
|
||||
@@ -0,0 +1,33 @@
|
||||
edges
|
||||
| TimingAttackAgainstSensitiveInfo.py:0:0:0:0 | ModuleVariableNode for TimingAttackAgainstSensitiveInfo.request | TimingAttackAgainstSensitiveInfo.py:14:8:14:14 | ControlFlowNode for request |
|
||||
| TimingAttackAgainstSensitiveInfo.py:0:0:0:0 | ModuleVariableNode for TimingAttackAgainstSensitiveInfo.request | TimingAttackAgainstSensitiveInfo.py:15:20:15:26 | ControlFlowNode for request |
|
||||
| TimingAttackAgainstSensitiveInfo.py:0:0:0:0 | ModuleVariableNode for TimingAttackAgainstSensitiveInfo.request | TimingAttackAgainstSensitiveInfo.py:20:8:20:14 | ControlFlowNode for request |
|
||||
| TimingAttackAgainstSensitiveInfo.py:0:0:0:0 | ModuleVariableNode for TimingAttackAgainstSensitiveInfo.request | TimingAttackAgainstSensitiveInfo.py:21:20:21:26 | ControlFlowNode for request |
|
||||
| TimingAttackAgainstSensitiveInfo.py:7:19:7:25 | ControlFlowNode for ImportMember | TimingAttackAgainstSensitiveInfo.py:7:19:7:25 | GSSA Variable request |
|
||||
| TimingAttackAgainstSensitiveInfo.py:7:19:7:25 | GSSA Variable request | TimingAttackAgainstSensitiveInfo.py:0:0:0:0 | ModuleVariableNode for TimingAttackAgainstSensitiveInfo.request |
|
||||
| TimingAttackAgainstSensitiveInfo.py:14:8:14:14 | ControlFlowNode for request | TimingAttackAgainstSensitiveInfo.py:15:20:15:31 | ControlFlowNode for Attribute |
|
||||
| TimingAttackAgainstSensitiveInfo.py:15:20:15:26 | ControlFlowNode for request | TimingAttackAgainstSensitiveInfo.py:15:20:15:31 | ControlFlowNode for Attribute |
|
||||
| TimingAttackAgainstSensitiveInfo.py:15:20:15:31 | ControlFlowNode for Attribute | TimingAttackAgainstSensitiveInfo.py:15:20:15:38 | ControlFlowNode for Subscript |
|
||||
| TimingAttackAgainstSensitiveInfo.py:15:20:15:38 | ControlFlowNode for Subscript | TimingAttackAgainstSensitiveInfo.py:16:16:16:23 | ControlFlowNode for password |
|
||||
| TimingAttackAgainstSensitiveInfo.py:20:8:20:14 | ControlFlowNode for request | TimingAttackAgainstSensitiveInfo.py:21:20:21:31 | ControlFlowNode for Attribute |
|
||||
| TimingAttackAgainstSensitiveInfo.py:21:20:21:26 | ControlFlowNode for request | TimingAttackAgainstSensitiveInfo.py:21:20:21:31 | ControlFlowNode for Attribute |
|
||||
| TimingAttackAgainstSensitiveInfo.py:21:20:21:31 | ControlFlowNode for Attribute | TimingAttackAgainstSensitiveInfo.py:21:20:21:38 | ControlFlowNode for Subscript |
|
||||
| TimingAttackAgainstSensitiveInfo.py:21:20:21:38 | ControlFlowNode for Subscript | TimingAttackAgainstSensitiveInfo.py:22:38:22:45 | ControlFlowNode for password |
|
||||
nodes
|
||||
| TimingAttackAgainstSensitiveInfo.py:0:0:0:0 | ModuleVariableNode for TimingAttackAgainstSensitiveInfo.request | semmle.label | ModuleVariableNode for TimingAttackAgainstSensitiveInfo.request |
|
||||
| TimingAttackAgainstSensitiveInfo.py:7:19:7:25 | ControlFlowNode for ImportMember | semmle.label | ControlFlowNode for ImportMember |
|
||||
| TimingAttackAgainstSensitiveInfo.py:7:19:7:25 | GSSA Variable request | semmle.label | GSSA Variable request |
|
||||
| TimingAttackAgainstSensitiveInfo.py:14:8:14:14 | ControlFlowNode for request | semmle.label | ControlFlowNode for request |
|
||||
| TimingAttackAgainstSensitiveInfo.py:15:20:15:26 | ControlFlowNode for request | semmle.label | ControlFlowNode for request |
|
||||
| TimingAttackAgainstSensitiveInfo.py:15:20:15:31 | ControlFlowNode for Attribute | semmle.label | ControlFlowNode for Attribute |
|
||||
| TimingAttackAgainstSensitiveInfo.py:15:20:15:38 | ControlFlowNode for Subscript | semmle.label | ControlFlowNode for Subscript |
|
||||
| TimingAttackAgainstSensitiveInfo.py:16:16:16:23 | ControlFlowNode for password | semmle.label | ControlFlowNode for password |
|
||||
| TimingAttackAgainstSensitiveInfo.py:16:16:16:23 | ControlFlowNode for password | semmle.label | ControlFlowNode for password |
|
||||
| TimingAttackAgainstSensitiveInfo.py:20:8:20:14 | ControlFlowNode for request | semmle.label | ControlFlowNode for request |
|
||||
| TimingAttackAgainstSensitiveInfo.py:21:20:21:26 | ControlFlowNode for request | semmle.label | ControlFlowNode for request |
|
||||
| TimingAttackAgainstSensitiveInfo.py:21:20:21:31 | ControlFlowNode for Attribute | semmle.label | ControlFlowNode for Attribute |
|
||||
| TimingAttackAgainstSensitiveInfo.py:21:20:21:38 | ControlFlowNode for Subscript | semmle.label | ControlFlowNode for Subscript |
|
||||
| TimingAttackAgainstSensitiveInfo.py:22:38:22:45 | ControlFlowNode for password | semmle.label | ControlFlowNode for password |
|
||||
subpaths
|
||||
#select
|
||||
| TimingAttackAgainstSensitiveInfo.py:16:16:16:23 | ControlFlowNode for password | TimingAttackAgainstSensitiveInfo.py:16:16:16:23 | ControlFlowNode for password | TimingAttackAgainstSensitiveInfo.py:16:16:16:23 | ControlFlowNode for password | Timing attack against $@ validation. | TimingAttackAgainstSensitiveInfo.py:16:16:16:23 | ControlFlowNode for password | client-supplied token |
|
||||
@@ -0,0 +1 @@
|
||||
experimental/Security/CWE-208/TimingAttackAgainstSensitiveInfo/PossibleTimingAttackAgainstSensitiveInfo.ql
|
||||
@@ -0,0 +1,26 @@
|
||||
#!/usr/bin/env python
|
||||
# -*- coding: UTF-8 -*-
|
||||
"""
|
||||
@Desc :timing attack against Secret
|
||||
"""
|
||||
from flask import Flask
|
||||
from flask import request
|
||||
from django.utils.crypto import constant_time_compare
|
||||
|
||||
app = Flask(__name__)
|
||||
|
||||
@app.route('/bad', methods = ['POST', 'GET'])
|
||||
def bad():
|
||||
if request.method == 'POST':
|
||||
password = request.form['pwd']
|
||||
return password == "1234"
|
||||
|
||||
@app.route('/good', methods = ['POST', 'GET'])
|
||||
def good():
|
||||
if request.method == 'POST':
|
||||
password = request.form['pwd']
|
||||
return constant_time_compare(password, "1234")
|
||||
|
||||
if __name__ == '__main__':
|
||||
app.debug = True
|
||||
app.run()
|
||||
Reference in New Issue
Block a user