Skip to content

Python: Timing attack#9722

Merged
tausbn merged 179 commits into
github:mainfrom
ahmed-farid-dev:timing-attack-py
Mar 27, 2023
Merged

Python: Timing attack#9722
tausbn merged 179 commits into
github:mainfrom
ahmed-farid-dev:timing-attack-py

Conversation

@ahmed-farid-dev
Copy link
Copy Markdown
Contributor

A constant-time algorithm should be used for checking the value of info. In other words, the comparison time should not depend on the content of the input, Otherwise, an attacker may be able to implement a timing attacks that may reveal the value of sensitive info

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,
such as guessing the password of a user.
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Do you have any information that suggests that such attacks (guessing the password of a user) are

  1. done in practice
  2. done over the internet
    ?

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Hi, @intrigus-lgtm
I never heard.

@tausbn tausbn self-assigned this Jun 28, 2022
Comment thread python/ql/src/experimental/Security/CWE-208/TimingAttack.qll Outdated
Copy link
Copy Markdown
Contributor

@tausbn tausbn left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

There are some issues with compiling TimingAttackAgainstSignature.ql that need to be addressed before I can review this in earnest.

Comment on lines +29 to +31
override predicate isSource(DataFlow::Node source) { source instanceof UserInputMsgConfig }

override predicate isSink(DataFlow::Node sink) { sink instanceof UserInputInComparisonConfig }
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

UserInputMsgConfig and UserInputInComparisonConfig are instances of TaintTracking::Configuration, which is not a type that is compatible with DataFlow::Node.

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@ahmed-farid-dev Friendly reminder that we won't be able to review this PR until it is able to compile without errors. 🙂

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@tausbn sorry, i'm still working on it

@ahmed-farid-dev
Copy link
Copy Markdown
Contributor Author

Hi @tausbn, any update ?

@tausbn
Copy link
Copy Markdown
Contributor

tausbn commented Sep 12, 2022

Hi @tausbn, any update ?

Hi @ahmed-farid-dev. I'm currently waiting for the Security Lab to finish their analysis of the results for your query. As I understand it, they are currently looking at your other timing attack submission (for Java), and as there is considerable overlap in the approaches used in the two PRs, I will wait on submitting my review until that PR has been assessed.

@ahmed-farid-dev ahmed-farid-dev requested a review from a team as a code owner February 27, 2023 21:16
@github-actions
Copy link
Copy Markdown
Contributor

github-actions Bot commented Mar 24, 2023

QHelp previews:

python/ql/src/experimental/Security/CWE-208/TimingAttackAgainstHash/PossibleTimingAttackAgainstHash.qhelp

Timing attack against Hash

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.

Recommendation

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 hmac.compare_digest() 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 == is a fail fast check, If the first byte is not equal, it will return immediately.

Example

The following example uses == which is a fail fast check for validating a Hash.

#!/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

The next example use a safe constant-time algorithm for validating a Hash:

#!/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

References

python/ql/src/experimental/Security/CWE-208/TimingAttackAgainstHash/TimingAttackAgainstHash.qhelp

Timing attack against Hash

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.

Recommendation

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 hmac.compare_digest() 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 == is a fail fast check, If the first byte is not equal, it will return immediately.

Example

The following example uses == which is a fail fast check for validating a Hash.

#!/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

The next example use a safe constant-time algorithm for validating a Hash:

#!/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

References

python/ql/src/experimental/Security/CWE-208/TimingAttackAgainstHeaderValue/TimingAttackAgainstHeaderValue.qhelp

Timing attack against header value

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.

Recommendation

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 hmac.compare_digest() 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 == is a fail fast check, If the first byte is not equal, it will return immediately.

Example

The following example uses == which is a fail fast check for validating the value of sensitive headers.

#!/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() 

The next example use a safe constant-time algorithm for validating the value of sensitive headers:

#!/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() 

References

python/ql/src/experimental/Security/CWE-208/TimingAttackAgainstSensitiveInfo/PossibleTimingAttackAgainstSensitiveInfo.qhelp

Timing attack against secret

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.

Recommendation

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 hmac.compare_digest() 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 == is a fail fast check, If the first byte is not equal, it will return immediately.

Example

The following example uses == which is a fail fast check for validating a secret.

#!/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() 

The next example use a safe constant-time algorithm for validating a secret:

#!/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() 

References

python/ql/src/experimental/Security/CWE-208/TimingAttackAgainstSensitiveInfo/TimingAttackAgainstSensitiveInfo.qhelp

Timing attack against secret

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.

Recommendation

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 hmac.compare_digest() 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 == is a fail fast check, If the first byte is not equal, it will return immediately.

Example

The following example uses == which is a fail fast check for validating a secret.

#!/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() 

The next example use a safe constant-time algorithm for validating a secret:

#!/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() 

References

* Holds if there is a flow to len().
*/
predicate flowtolen() {
exists(ExcludeLenFunc config, DataFlow2::PathNode source, DataFlow2::PathNode sink |

Check warning

Code scanning / CodeQL

Omittable 'exists' variable

This exists variable can be omitted by using a don't-care expression [in this argument](1).
Comment on lines +311 to +313
/**
* Holds if there is a fast-fail check.
*/

Check warning

Code scanning / CodeQL

Class QLDoc style.

The QLDoc for a class should start with 'A', 'An', or 'The'.
* Holds if there is a flow to len().
*/
predicate flowtolen() {
exists(ExcludeLenFunc config, DataFlow2::PathNode source, DataFlow2::PathNode sink |

Check warning

Code scanning / CodeQL

Omittable 'exists' variable

This exists variable can be omitted by using a don't-care expression [in this argument](1).
Copy link
Copy Markdown
Contributor

@tausbn tausbn left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

You have compilation errors:

Error: ERROR: Could not resolve module PrivateDjango::DjangoImpl::Http (/home/runner/work/semmle-code/semmle-code/ql/python/ql/src/experimental/semmle/python/security/TimingAttack.qll:221,7-38)
Failed [245/336] /home/runner/work/semmle-code/semmle-code/ql/python/ql/src/experimental/Security/CWE-208/TimingAttackAgainstHash/PossibleTimingAttackAgainstHash.ql.
/home/runner/work/semmle-code/semmle-code/ql/python/ql/src/experimental/Security/CWE-208/TimingAttackAgainstHash/PossibleTimingAttackAgainstHash.ql had compilation errors.

Error: ERROR: Could not resolve module PrivateDjango::DjangoImpl::Http (/home/runner/work/semmle-code/semmle-code/ql/python/ql/src/experimental/semmle/python/security/TimingAttack.qll:221,7-38)
Failed [248/336] /home/runner/work/semmle-code/semmle-code/ql/python/ql/src/experimental/Security/CWE-208/TimingAttackAgainstSensitiveInfo/TimingAttackAgainstSensitiveInfo.ql.
/home/runner/work/semmle-code/semmle-code/ql/python/ql/src/experimental/Security/CWE-208/TimingAttackAgainstSensitiveInfo/TimingAttackAgainstSensitiveInfo.ql had compilation errors.

Error: ERROR: Could not resolve module PrivateDjango::DjangoImpl::Http (/home/runner/work/semmle-code/semmle-code/ql/python/ql/src/experimental/semmle/python/security/TimingAttack.qll:221,7-38)
Failed [249/336] /home/runner/work/semmle-code/semmle-code/ql/python/ql/src/experimental/Security/CWE-208/TimingAttackAgainstHeaderValue/TimingAttackAgainstHeaderValue.ql.
/home/runner/work/semmle-code/semmle-code/ql/python/ql/src/experimental/Security/CWE-208/TimingAttackAgainstHeaderValue/TimingAttackAgainstHeaderValue.ql had compilation errors.

Also, the autoformat check is failing:

python/ql/src/experimental/semmle/python/security/TimingAttack.qll would change by autoformatting.

Would you please, please make sure that the files compile and are correctly formatted before pushing your commits? It would save a lot of time and effort.

* A successful attack can result in authentication bypass.
* @kind path-problem
* @problem.severity error
* @precision high
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
* @precision high
* @precision medium

In light of github/securitylab#691 (comment) I think the precision should be lowered.

* possibly allowing a timing attack to retrieve sensitive information.
* @kind path-problem
* @problem.severity error
* @precision high
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
* @precision high
* @precision medium

In light of github/securitylab#691 (comment) this precision should be lowered.

}

from ClientSuppliedSecretConfig config, DataFlow::PathNode source, DataFlow::PathNode sink
where config.hasFlowPath(source, sink) and not sink.getNode().(CompareSink).flowtolen()
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This flowtolen guard is a very strange way of addressing github/securitylab#691 (comment). I'm assuming that in the cases you were looking at, these len calls were always present? What would happen if there isn't any call to len? In that case you would still want in comparisons to not be considered.

I think it would be better to just remove in as a potential sink entirely.

tausbn
tausbn previously approved these changes Mar 27, 2023
@tausbn tausbn merged commit df19238 into github:main Mar 27, 2023
@ahmed-farid-dev ahmed-farid-dev deleted the timing-attack-py branch April 3, 2023 16:32
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Projects

None yet

Development

Successfully merging this pull request may close these issues.

5 participants