Non-constant time HMAC comparison
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]
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 disconnectedLOGGER.log(Level.INFO, "Disconnecting {0} as we are reconnected from the current peer", clientName);
References
- ADVISORYhttps://nvd.nist.gov/vuln/detail/CVE-2020-2102
- WEBhttps://github.com/jenkinsci/jenkins/commit/6f35dbb939ebe947bdb1979010b208480f1d0e31
- WEBhttps://access.redhat.com/errata/RHBA-2020:0402
- WEBhttps://access.redhat.com/errata/RHBA-2020:0675
- WEBhttps://access.redhat.com/errata/RHSA-2020:0681
- WEBhttps://access.redhat.com/errata/RHSA-2020:0683
- PACKAGEhttps://github.com/jenkinsci/jenkins
- WEBhttps://jenkins.io/security/advisory/2020-01-29/#SECURITY-1660
- WEBhttp://www.openwall.com/lists/oss-security/2020/01/29/1