Security context
Medium· 5.3GHSA-fj6f-6933-839j CVE-2020-2102CWE-203CWE-208Published May 24, 2022

Non-constant time HMAC comparison

Research this vulnerability

Research is free — Hunters explains how the bug works, the root-cause code pattern, how the fix addresses it, and how to test whether a target is affected, in chat. Investigate & write exploit is a paid run — the engine reads the advisory and fix commits, then builds and validates a working proof-of-concept exploit with reproduction steps.

Affected versions

0 → fixed in 2.204.22.205 → fixed in 2.219

Details

Jenkins 2.218 and earlier, LTS 2.204.1 and earlier does not use a constant-time comparison when checking whether two HMACs are equal. This could potentially allow attackers to use statistical methods to obtain a valid HMAC for an attacker-controlled input value. Jenkins 2.219, LTS 2.204.2 now uses a constant-time comparison when validating HMACs.

The fix

[SECURITY-1660]

Jeff Thompson· Jan 14, 2020, 11:15 AM+736f35dbb939
core/src/main/java/jenkins/security/HMACConfidentialKey.java+4 2
@@ -8,7 +8,9 @@
import javax.crypto.spec.SecretKeySpec;
import java.io.IOException;
import java.io.UnsupportedEncodingException;
+import java.nio.charset.StandardCharsets;
import java.security.GeneralSecurityException;
+import java.security.MessageDigest;
import java.security.NoSuchAlgorithmException;
import java.util.Arrays;
@@ -76,7 +78,7 @@ public synchronized byte[] mac(byte[] message) {
* Convenience method for verifying the MAC code.
*/
public boolean checkMac(byte[] message, byte[] mac) {
- return Arrays.equals(mac(message),mac);
+ return MessageDigest.isEqual(mac(message),mac);
}
/**
@@ -95,7 +97,7 @@ public String mac(String message) {
* Verifies MAC constructed from {@link #mac(String)}
*/
public boolean checkMac(String message, String mac) {
- return mac(message).equals(mac);
+ return MessageDigest.isEqual(mac(message).getBytes(StandardCharsets.UTF_8), mac.getBytes(StandardCharsets.UTF_8));
}
private byte[] chop(byte[] mac) {
core/src/main/java/jenkins/slaves/DefaultJnlpSlaveReceiver.java+3 1
@@ -24,6 +24,8 @@
import org.jenkinsci.remoting.engine.JnlpConnectionState;
import java.io.IOException;
+import java.nio.charset.StandardCharsets;
+import java.security.MessageDigest;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.TimeoutException;
@@ -122,7 +124,7 @@ public void afterProperties(@NonNull JnlpConnectionState event) {
Channel ch = computer.getChannel();
if (ch != null) {
String cookie = event.getProperty(JnlpConnectionState.COOKIE_KEY);
- if (cookie != null && cookie.equals(ch.getProperty(COOKIE_NAME))) {
+ if (cookie != null && MessageDigest.isEqual(cookie.getBytes(StandardCharsets.UTF_8), ch.getProperty(COOKIE_NAME).toString().getBytes(StandardCharsets.UTF_8))) {
// we think we are currently connected, but this request proves that it's from the party
// we are supposed to be communicating to. so let the current one get disconnected
LOGGER.log(Level.INFO, "Disconnecting {0} as we are reconnected from the current peer", clientName);

References