Skip to content

Commit 519f53f

Browse files
authored
Merge pull request #63 from RUB-NDS/sessionTicketZeroKeyProbeNonDev
Session ticket zero key probe
2 parents b3947c0 + 3b68d4d commit 519f53f

File tree

8 files changed

+270
-2
lines changed

8 files changed

+270
-2
lines changed

src/main/java/de/rub/nds/tlsscanner/ThreadedScanJobExecutor.java

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -124,7 +124,7 @@ private void executeProbesTillNoneCanBeExecuted(SiteReport report) {
124124
}
125125
futureResults.removeAll(finishedFutures);
126126
update(report, this);
127-
} while (executor.getActiveCount() != 0 || !executor.getQueue().isEmpty());
127+
} while (!futureResults.isEmpty());
128128
}
129129

130130
private void reportAboutNotExecutedProbes() {

src/main/java/de/rub/nds/tlsscanner/TlsScanner.java

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -35,6 +35,7 @@
3535
import de.rub.nds.tlsscanner.probe.ProtocolVersionProbe;
3636
import de.rub.nds.tlsscanner.probe.RenegotiationProbe;
3737
import de.rub.nds.tlsscanner.probe.ResumptionProbe;
38+
import de.rub.nds.tlsscanner.probe.SessionTicketZeroKeyProbe;
3839
import de.rub.nds.tlsscanner.probe.SniProbe;
3940
import de.rub.nds.tlsscanner.probe.Tls13Probe;
4041
import de.rub.nds.tlsscanner.probe.TlsPoodleProbe;
@@ -133,13 +134,15 @@ private void fillDefaultProbeLists() {
133134
probeList.add(new DrownProbe(config, parallelExecutor));
134135
probeList.add(new EarlyCcsProbe(config, parallelExecutor));
135136
probeList.add(new MacProbe(config, parallelExecutor));
137+
probeList.add(new SessionTicketZeroKeyProbe(config, parallelExecutor));
136138
afterList.add(new Sweet32AfterProbe());
137139
afterList.add(new FreakAfterProbe());
138140
afterList.add(new LogjamAfterprobe());
139141
afterList.add(new EvaluateRandomnessAfterProbe());
140142
afterList.add(new EcPublicKeyAfterProbe());
141143
afterList.add(new DhValueAfterProbe());
142144
afterList.add(new PaddingOracleIdentificationAfterProbe());
145+
143146
}
144147

145148
public SiteReport scan() {

src/main/java/de/rub/nds/tlsscanner/constants/ProbeType.java

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -35,6 +35,7 @@ public enum ProbeType {
3535
RESUMPTION,
3636
RENEGOTIATION,
3737
SNI,
38+
SESSION_TICKET_ZERO_KEY,
3839
HANDSHAKE_SIMULATION,
3940
TLS13,
4041
MAC,
Lines changed: 214 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,214 @@
1+
/**
2+
* TLS-Scanner - A TLS configuration and analysis tool based on TLS-Attacker.
3+
*
4+
* Copyright 2017-2019 Ruhr University Bochum / Hackmanit GmbH
5+
*
6+
* Licensed under Apache License 2.0
7+
* http://www.apache.org/licenses/LICENSE-2.0
8+
*/
9+
package de.rub.nds.tlsscanner.probe;
10+
11+
import java.util.ArrayList;
12+
import java.util.Arrays;
13+
import java.util.Collections;
14+
import java.util.LinkedList;
15+
import java.util.List;
16+
17+
import javax.crypto.Cipher;
18+
import javax.crypto.SecretKey;
19+
import javax.crypto.spec.IvParameterSpec;
20+
import javax.crypto.spec.SecretKeySpec;
21+
22+
import org.apache.commons.lang3.ArrayUtils;
23+
24+
import de.rub.nds.modifiablevariable.util.ArrayConverter;
25+
import de.rub.nds.tlsattacker.core.config.Config;
26+
import de.rub.nds.tlsattacker.core.constants.CipherSuite;
27+
import de.rub.nds.tlsattacker.core.constants.HandshakeMessageType;
28+
import de.rub.nds.tlsattacker.core.constants.NamedGroup;
29+
import de.rub.nds.tlsattacker.core.constants.ProtocolVersion;
30+
import de.rub.nds.tlsattacker.core.protocol.message.NewSessionTicketMessage;
31+
import de.rub.nds.tlsattacker.core.protocol.message.ProtocolMessage;
32+
import de.rub.nds.tlsattacker.core.state.State;
33+
import de.rub.nds.tlsattacker.core.state.TlsContext;
34+
import de.rub.nds.tlsattacker.core.workflow.ParallelExecutor;
35+
import de.rub.nds.tlsattacker.core.workflow.WorkflowTraceUtil;
36+
import de.rub.nds.tlsattacker.core.workflow.factory.WorkflowTraceType;
37+
import de.rub.nds.tlsscanner.config.ScannerConfig;
38+
import de.rub.nds.tlsscanner.constants.ProbeType;
39+
import de.rub.nds.tlsscanner.rating.TestResult;
40+
import de.rub.nds.tlsscanner.report.SiteReport;
41+
import de.rub.nds.tlsscanner.report.result.ProbeResult;
42+
import de.rub.nds.tlsscanner.report.result.SessionTicketZeroKeyResult;
43+
44+
/**
45+
*
46+
* The Probe checks for CVE-2020-13777.
47+
*
48+
* Quote: "GnuTLS 3.6.x before 3.6.14 uses incorrect cryptography for encrypting
49+
* a session ticket (a loss of confidentiality in TLS 1.2, and an authentication
50+
* bypass in TLS 1.3). The earliest affected version is 3.6.4 (2018-09-24)
51+
* because of an error in a 2018-09-18 commit. Until the first key rotation, the
52+
* TLS server always uses wrong data in place of an encryption key derived from
53+
* an application."[1]
54+
*
55+
* Reference [1]: http://cve.mitre.org/cgi-bin/cvename.cgi?name=CVE-2020-13777
56+
* Reference [2]: https://www.gnutls.org/security-new.html
57+
*
58+
*/
59+
public class SessionTicketZeroKeyProbe extends TlsProbe {
60+
61+
/**
62+
* Magic Bytes the plaintext state in GnuTls starts with
63+
*/
64+
public static final byte[] GNU_TLS_MAGIC_BYTES = ArrayConverter.hexStringToByteArray("FAE1C0EA");
65+
66+
/**
67+
* Offset of the IV according to the ticket struct in rfc5077
68+
*/
69+
public static final int IV_OFFSET = 16;
70+
71+
/**
72+
* Length of the IV according to the ticket struct in rfc5077
73+
*/
74+
public static final int IV_LEN = 16;
75+
76+
/**
77+
* Offset of the length field for the in the encrypted state according to
78+
* the ticket struct in rfc5077
79+
*/
80+
public static final int SESSION_STATE_LENFIELD_OFFSET = 32;
81+
82+
/**
83+
* Length of the length field for the in the encrypted state according to
84+
* the ticket struct in rfc5077
85+
*/
86+
public static final int SESSION_STATE_LENFIELD_LEN = 2;
87+
88+
/**
89+
* Offset of the encrypted state according to the ticket struct in rfc5077
90+
*/
91+
public static final int SESSION_STATE_OFFSET = 34;
92+
93+
private List<CipherSuite> supportedSuites;
94+
95+
public SessionTicketZeroKeyProbe(ScannerConfig scannerConfig, ParallelExecutor parallelExecutor) {
96+
super(parallelExecutor, ProbeType.SESSION_TICKET_ZERO_KEY, scannerConfig, 0);
97+
}
98+
99+
public SessionTicketZeroKeyProbe(ParallelExecutor parallelExecutor, ProbeType type, ScannerConfig scannerConfig,
100+
int danger) {
101+
super(parallelExecutor, type, scannerConfig, danger);
102+
}
103+
104+
@Override
105+
public ProbeResult executeTest() {
106+
State state;
107+
try {
108+
Config tlsConfig = getScannerConfig().createConfig();
109+
tlsConfig.setQuickReceive(true);
110+
List<CipherSuite> ciphersuites = new LinkedList<>();
111+
ciphersuites.addAll(supportedSuites);
112+
tlsConfig.setDefaultClientNamedGroups(NamedGroup.getImplemented());
113+
tlsConfig.setWorkflowTraceType(WorkflowTraceType.HANDSHAKE);
114+
tlsConfig.setHighestProtocolVersion(ProtocolVersion.TLS12);
115+
tlsConfig.setDefaultClientSupportedCiphersuites(ciphersuites.get(0));
116+
tlsConfig.setDefaultSelectedCipherSuite(tlsConfig.getDefaultClientSupportedCiphersuites().get(0));
117+
tlsConfig.setAddECPointFormatExtension(true);
118+
tlsConfig.setAddEllipticCurveExtension(true);
119+
tlsConfig.setAddSessionTicketTLSExtension(true);
120+
tlsConfig.setAddServerNameIndicationExtension(true);
121+
tlsConfig.setAddRenegotiationInfoExtension(false);
122+
state = new State(tlsConfig);
123+
executeState(state);
124+
} catch (Exception E) {
125+
LOGGER.error("Could not scan for " + getProbeName(), E);
126+
return new SessionTicketZeroKeyResult(TestResult.ERROR_DURING_TEST, TestResult.ERROR_DURING_TEST);
127+
}
128+
129+
if (!WorkflowTraceUtil.didReceiveMessage(HandshakeMessageType.NEW_SESSION_TICKET, state.getWorkflowTrace())) {
130+
return new SessionTicketZeroKeyResult(TestResult.UNSUPPORTED, TestResult.UNSUPPORTED);
131+
}
132+
133+
byte[] ticket = null;
134+
for (ProtocolMessage msg : WorkflowTraceUtil.getAllReceivedMessages(state.getWorkflowTrace())) {
135+
if (msg instanceof NewSessionTicketMessage) {
136+
NewSessionTicketMessage newSessionTicketMessage = (NewSessionTicketMessage) msg;
137+
ticket = newSessionTicketMessage.getTicket().getIdentity().getValue();
138+
}
139+
}
140+
141+
byte[] key = new byte[32];
142+
byte[] iv, encryptedSessionState;
143+
byte[] decryptedSessionState = null;
144+
145+
try {
146+
iv = Arrays.copyOfRange(ticket, IV_OFFSET, IV_OFFSET + IV_LEN);
147+
byte[] sessionStateLen = Arrays.copyOfRange(ticket, SESSION_STATE_LENFIELD_OFFSET,
148+
SESSION_STATE_LENFIELD_OFFSET + SESSION_STATE_LENFIELD_LEN);
149+
int sessionStateLenInt = ArrayConverter.bytesToInt(sessionStateLen);
150+
encryptedSessionState = Arrays.copyOfRange(ticket, SESSION_STATE_OFFSET, SESSION_STATE_OFFSET
151+
+ sessionStateLenInt);
152+
Cipher cipher = Cipher.getInstance("AES/CBC/NOPADDING");
153+
SecretKey aesKey = new SecretKeySpec(key, "AES");
154+
cipher.init(Cipher.DECRYPT_MODE, aesKey, new IvParameterSpec(iv));
155+
decryptedSessionState = cipher.doFinal(encryptedSessionState);
156+
LOGGER.debug("decryptedSsessionState" + ArrayConverter.bytesToHexString(decryptedSessionState));
157+
} catch (Exception e) {
158+
return new SessionTicketZeroKeyResult(TestResult.FALSE, TestResult.FALSE);
159+
}
160+
TestResult hasDecryptableMasterSecret;
161+
TestResult hasGnuTlsMagicBytes;
162+
163+
if (checkForMasterSecret(decryptedSessionState, state.getTlsContext())) {
164+
hasDecryptableMasterSecret = TestResult.TRUE;
165+
} else {
166+
hasDecryptableMasterSecret = TestResult.FALSE;
167+
}
168+
169+
if (checkForGnuTlsMagicBytes(decryptedSessionState)) {
170+
hasGnuTlsMagicBytes = TestResult.TRUE;
171+
172+
} else {
173+
hasGnuTlsMagicBytes = TestResult.FALSE;
174+
}
175+
176+
return new SessionTicketZeroKeyResult(hasDecryptableMasterSecret, hasGnuTlsMagicBytes);
177+
}
178+
179+
@Override
180+
public boolean canBeExecuted(SiteReport report) {
181+
return report.getCipherSuites() != null && (report.getCipherSuites().size() > 0);
182+
}
183+
184+
private boolean checkForMasterSecret(byte[] decState, TlsContext context) {
185+
List<Byte> target = Arrays.asList(ArrayUtils.toObject(context.getMasterSecret()));
186+
List<Byte> source = Arrays.asList(ArrayUtils.toObject(decState));
187+
if (Collections.indexOfSubList(source, target) == -1) {
188+
return false;
189+
}
190+
return true;
191+
}
192+
193+
private boolean checkForGnuTlsMagicBytes(byte[] decState) {
194+
try {
195+
for (int i = 0; i < GNU_TLS_MAGIC_BYTES.length; i++)
196+
if (decState[i] != GNU_TLS_MAGIC_BYTES[i])
197+
return false;
198+
} catch (Exception e) {
199+
return false;
200+
}
201+
return true;
202+
}
203+
204+
@Override
205+
public ProbeResult getCouldNotExecuteResult() {
206+
return new SessionTicketZeroKeyResult(TestResult.COULD_NOT_TEST, TestResult.COULD_NOT_TEST);
207+
}
208+
209+
@Override
210+
public void adjustConfig(SiteReport report) {
211+
supportedSuites = new ArrayList<>(report.getCipherSuites());
212+
}
213+
214+
}

src/main/java/de/rub/nds/tlsscanner/report/AnalyzedProperty.java

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -166,6 +166,7 @@ public enum AnalyzedProperty {
166166
VULNERABLE_TO_INVALID_CURVE_EPHEMERAL_EXPLOITABLE(AnalyzedPropertyCategory.ATTACKS),
167167
VULNERABLE_TO_POODLE(AnalyzedPropertyCategory.ATTACKS),
168168
VULNERABLE_TO_TLS_POODLE(AnalyzedPropertyCategory.ATTACKS),
169+
VULNERABLE_TO_SESSION_TICKET_ZERO_KEY(AnalyzedPropertyCategory.ATTACKS),
169170
VULNERABLE_TO_SWEET_32(AnalyzedPropertyCategory.ATTACKS),
170171
VULNERABLE_TO_DROWN(AnalyzedPropertyCategory.ATTACKS),
171172
VULNERABLE_TO_HEARTBLEED(AnalyzedPropertyCategory.ATTACKS),
@@ -185,7 +186,8 @@ public enum AnalyzedProperty {
185186
REUSES_EC_PUBLICKEY(AnalyzedPropertyCategory.FRESHNESS),
186187
REUSES_DH_PUBLICKEY(AnalyzedPropertyCategory.FRESHNESS),
187188
REUSES_GCM_NONCES(AnalyzedPropertyCategory.FRESHNESS),
188-
REQUIRES_SNI(AnalyzedPropertyCategory.SNI);
189+
REQUIRES_SNI(AnalyzedPropertyCategory.SNI),
190+
HAS_GNU_TLS_MAGIC_BYTES(AnalyzedPropertyCategory.SESSION_TICKET);
189191

190192
private AnalyzedPropertyCategory category;
191193

src/main/java/de/rub/nds/tlsscanner/report/AnalyzedPropertyCategory.java

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,7 @@ public enum AnalyzedPropertyCategory {
1313
CIPHER_SUITES,
1414
EXTENSIONS,
1515
SESSION_RESUMPTION,
16+
SESSION_TICKET,
1617
RENEGOTIATION,
1718
HTTPS_HEADERS,
1819
QUIRKS,

src/main/java/de/rub/nds/tlsscanner/report/SiteReportPrinter.java

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -117,6 +117,7 @@ public String getFullReport() {
117117
appendAttackVulnerabilities(builder);
118118
appendBleichenbacherResults(builder);
119119
appendPaddingOracleResults(builder);
120+
sessionTicketZeroKeyDetails(builder);
120121
//appendGcm(builder);
121122
appendRfc(builder);
122123
appendCertificate(builder);
@@ -130,6 +131,7 @@ public String getFullReport() {
130131
appendRecommendations(builder);
131132
appendPerformanceData(builder);
132133

134+
133135
return builder.toString();
134136
}
135137

@@ -575,6 +577,7 @@ private StringBuilder appendAttackVulnerabilities(StringBuilder builder) {
575577
prettyAppend(builder, "DROWN", AnalyzedProperty.VULNERABLE_TO_DROWN);
576578
prettyAppend(builder, "Heartbleed", AnalyzedProperty.VULNERABLE_TO_HEARTBLEED);
577579
prettyAppend(builder, "EarlyCcs", AnalyzedProperty.VULNERABLE_TO_EARLY_CCS);
580+
prettyAppend(builder, "CVE-2020-13777 (Zero key)", AnalyzedProperty.VULNERABLE_TO_SESSION_TICKET_ZERO_KEY);
578581
return builder;
579582
}
580583

@@ -1415,4 +1418,14 @@ private void appendPerformanceData(StringBuilder builder) {
14151418
LOGGER.debug("Not printing performance data.");
14161419
}
14171420
}
1421+
1422+
private StringBuilder sessionTicketZeroKeyDetails(StringBuilder builder) {
1423+
if (report.getResult(AnalyzedProperty.VULNERABLE_TO_SESSION_TICKET_ZERO_KEY) == TestResult.TRUE) {
1424+
prettyAppendHeading(builder, "Session Ticket Zero Key Attack Details");
1425+
prettyAppend(builder, "Has GnuTls magic bytes:", AnalyzedProperty.HAS_GNU_TLS_MAGIC_BYTES);
1426+
}
1427+
return builder;
1428+
}
1429+
14181430
}
1431+
Lines changed: 34 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,34 @@
1+
/**
2+
* TLS-Scanner - A TLS configuration and analysis tool based on TLS-Attacker.
3+
*
4+
* Copyright 2017-2019 Ruhr University Bochum / Hackmanit GmbH
5+
*
6+
* Licensed under Apache License 2.0
7+
* http://www.apache.org/licenses/LICENSE-2.0
8+
*/
9+
package de.rub.nds.tlsscanner.report.result;
10+
11+
import de.rub.nds.tlsscanner.rating.TestResult;
12+
import de.rub.nds.tlsscanner.constants.ProbeType;
13+
import de.rub.nds.tlsscanner.report.AnalyzedProperty;
14+
import de.rub.nds.tlsscanner.report.SiteReport;
15+
16+
public class SessionTicketZeroKeyResult extends ProbeResult {
17+
18+
private TestResult hasDecryptableMasterSecret;
19+
private TestResult hasGnuTlsMagicBytes;
20+
21+
public SessionTicketZeroKeyResult(TestResult hasDecryptableMasterSecret, TestResult hasGnuTlsMagicBytes) {
22+
super(ProbeType.SESSION_TICKET_ZERO_KEY);
23+
this.hasDecryptableMasterSecret = hasDecryptableMasterSecret;
24+
this.hasGnuTlsMagicBytes = hasGnuTlsMagicBytes;
25+
26+
}
27+
28+
@Override
29+
protected void mergeData(SiteReport report) {
30+
report.putResult(AnalyzedProperty.VULNERABLE_TO_SESSION_TICKET_ZERO_KEY, this.hasDecryptableMasterSecret);
31+
report.putResult(AnalyzedProperty.HAS_GNU_TLS_MAGIC_BYTES, this.hasGnuTlsMagicBytes);
32+
}
33+
34+
}

0 commit comments

Comments
 (0)