Skip to content

Add ApplicationLayerProbe for HTTP detection on TLS ports #141

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

Open
wants to merge 1 commit into
base: main
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,7 @@ public enum TlsAnalyzedProperty implements AnalyzedProperty {
CLOSED_AFTER_APP_DATA_DELTA(TlsAnalyzedPropertyCategory.CONNECTION),
KNOWN_PADDING_ORACLE_VULNERABILITY(TlsAnalyzedPropertyCategory.ATTACKS),
SUPPORTED_APPLICATIONS(TlsAnalyzedPropertyCategory.APPLICATION_LAYER),
SPEAKS_HTTP(TlsAnalyzedPropertyCategory.APPLICATION_LAYER),
BLEICHENBACHER_TEST_RESULT(TlsAnalyzedPropertyCategory.ATTACKS),
PADDING_ORACLE_TEST_RESULT(TlsAnalyzedPropertyCategory.ATTACKS),
DIRECT_RACCOON_TEST_RESULT(TlsAnalyzedPropertyCategory.ATTACKS),
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -63,6 +63,7 @@ public enum TlsProbeType implements ProbeType {
DTLS_MESSAGE_SEQUENCE_NUMBER("DTLS message sequence number"),
DTLS_RETRANSMISSIONS("DTLS retransmissions"),
DTLS_APPLICATION_FINGERPRINT("DTLS application fingerprint"),
APPLICATION_LAYER("Application layer detection"),
HTTP_FALSE_START("HTTP false start"),
HELLO_RETRY("Hello retry"),
CROSS_PROTOCOL_ALPACA("Alpaca attack"),
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -49,6 +49,7 @@
import de.rub.nds.tlsscanner.serverscanner.passive.SessionTicketExtractor;
import de.rub.nds.tlsscanner.serverscanner.probe.AlpacaProbe;
import de.rub.nds.tlsscanner.serverscanner.probe.AlpnProbe;
import de.rub.nds.tlsscanner.serverscanner.probe.ApplicationLayerProbe;
import de.rub.nds.tlsscanner.serverscanner.probe.BleichenbacherProbe;
import de.rub.nds.tlsscanner.serverscanner.probe.CcaRequiredProbe;
import de.rub.nds.tlsscanner.serverscanner.probe.CcaSupportProbe;
Expand Down Expand Up @@ -261,6 +262,7 @@ protected void fillProbeLists() {
registerProbeForExecution(new EsniProbe(configSelector, parallelExecutor));
registerProbeForExecution(new TokenbindingProbe(configSelector, parallelExecutor));
registerProbeForExecution(new HttpHeaderProbe(configSelector, parallelExecutor));
registerProbeForExecution(new ApplicationLayerProbe(configSelector, parallelExecutor));
registerProbeForExecution(new HttpFalseStartProbe(configSelector, parallelExecutor));
registerProbeForExecution(new DrownProbe(configSelector, parallelExecutor));
registerProbeForExecution(
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,163 @@
/*
* TLS-Scanner - A TLS configuration and analysis tool based on TLS-Attacker
*
* Copyright 2017-2023 Ruhr University Bochum, Paderborn University, Technology Innovation Institute, and Hackmanit GmbH
*
* Licensed under Apache License, Version 2.0
* http://www.apache.org/licenses/LICENSE-2.0.txt
*/
package de.rub.nds.tlsscanner.serverscanner.probe;

import de.rub.nds.scanner.core.probe.requirements.Requirement;
import de.rub.nds.scanner.core.probe.result.TestResult;
import de.rub.nds.scanner.core.probe.result.TestResults;
import de.rub.nds.tlsattacker.core.config.Config;
import de.rub.nds.tlsattacker.core.constants.RunningModeType;
import de.rub.nds.tlsattacker.core.protocol.ProtocolMessage;
import de.rub.nds.tlsattacker.core.protocol.message.ApplicationMessage;
import de.rub.nds.tlsattacker.core.record.Record;
import de.rub.nds.tlsattacker.core.state.State;
import de.rub.nds.tlsattacker.core.workflow.ParallelExecutor;
import de.rub.nds.tlsattacker.core.workflow.WorkflowTrace;
import de.rub.nds.tlsattacker.core.workflow.WorkflowTraceResultUtil;
import de.rub.nds.tlsattacker.core.workflow.action.ReceiveAction;
import de.rub.nds.tlsattacker.core.workflow.action.SendAction;
import de.rub.nds.tlsattacker.core.workflow.factory.WorkflowConfigurationFactory;
import de.rub.nds.tlsattacker.core.workflow.factory.WorkflowTraceType;
import de.rub.nds.tlsscanner.core.constants.TlsAnalyzedProperty;
import de.rub.nds.tlsscanner.core.constants.TlsProbeType;
import de.rub.nds.tlsscanner.serverscanner.constants.ApplicationProtocol;
import de.rub.nds.tlsscanner.serverscanner.probe.requirements.ServerOptionsRequirement;
import de.rub.nds.tlsscanner.serverscanner.report.ServerReport;
import de.rub.nds.tlsscanner.serverscanner.selector.ConfigSelector;
import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.nio.charset.StandardCharsets;
import java.util.ArrayList;
import java.util.List;
import org.apache.logging.log4j.LogManager;
import org.apache.logging.log4j.Logger;

public class ApplicationLayerProbe extends TlsServerProbe {

private static final Logger LOGGER = LogManager.getLogger();

private List<ApplicationProtocol> supportedApplications;
private TestResult speaksHttp = TestResults.COULD_NOT_TEST;

public ApplicationLayerProbe(ConfigSelector configSelector, ParallelExecutor parallelExecutor) {
super(parallelExecutor, TlsProbeType.APPLICATION_LAYER, configSelector);
register(TlsAnalyzedProperty.SUPPORTED_APPLICATIONS, TlsAnalyzedProperty.SPEAKS_HTTP);
}

@Override
protected void executeTest() {
supportedApplications = new ArrayList<>();
speaksHttp = TestResults.FALSE;

// Test for HTTP
if (isHttpSupported()) {
supportedApplications.add(ApplicationProtocol.HTTP);
speaksHttp = TestResults.TRUE;
}
}

private boolean isHttpSupported() {
// Send an HTTP GET request
String httpRequest =
"GET / HTTP/1.1\r\nHost: "
+ configSelector.getScannerConfig().getClientDelegate().getHost()
+ "\r\nConnection: close\r\n\r\n";
byte[] requestData = httpRequest.getBytes(StandardCharsets.UTF_8);

byte[] responseData = sendApplicationData(requestData);

if (responseData.length > 0) {
String response = new String(responseData, StandardCharsets.UTF_8);
// Check for HTTP response pattern
if (response.contains("HTTP/")
&& (response.contains("200")
|| response.contains("301")
|| response.contains("302")
|| response.contains("403")
|| response.contains("404")
|| response.contains("500")
|| response.contains("503"))) {
LOGGER.debug(
"HTTP response detected: "
+ response.substring(0, Math.min(response.length(), 100)));
return true;
}
}

return false;
}

private byte[] sendApplicationData(byte[] data) {
Config config = configSelector.getAnyWorkingBaseConfig();
WorkflowTrace trace =
new WorkflowConfigurationFactory(config)
.createWorkflowTrace(
WorkflowTraceType.DYNAMIC_HANDSHAKE, RunningModeType.CLIENT);
trace.addTlsAction(new SendAction(new ApplicationMessage(data)));
ReceiveAction receiveAction = new ReceiveAction(new ApplicationMessage());
trace.addTlsAction(receiveAction);

State state = new State(config, trace);
executeState(state);

if (receiveAction.getReceivedRecords() != null
&& !receiveAction.getReceivedRecords().isEmpty()) {
ByteArrayOutputStream receivedData = new ByteArrayOutputStream();
try {
for (Record record : receiveAction.getReceivedRecords()) {
receivedData.write(record.getCleanProtocolMessageBytes().getValue());
}
} catch (IOException ex) {
LOGGER.error("Could not write cleanProtocolMessageBytes to receivedData", ex);
}
return receivedData.toByteArray();
} else {
ProtocolMessage receivedMessage =
WorkflowTraceResultUtil.getLastReceivedMessage(state.getWorkflowTrace());
if (receivedMessage instanceof ApplicationMessage) {
ApplicationMessage appMessage = (ApplicationMessage) receivedMessage;
if (appMessage.getData() != null && appMessage.getData().getValue() != null) {
return appMessage.getData().getValue();
}
}
}

return new byte[0];
}

@Override
public void adjustConfig(ServerReport report) {}

@Override
protected void mergeData(ServerReport report) {
if (!supportedApplications.isEmpty()) {
@SuppressWarnings("unchecked")
List<ApplicationProtocol> existingApplications =
(List<ApplicationProtocol>)
report.getResult(TlsAnalyzedProperty.SUPPORTED_APPLICATIONS);
if (existingApplications != null) {
// Merge with existing applications (e.g., from DTLS probe)
for (ApplicationProtocol app : supportedApplications) {
if (!existingApplications.contains(app)) {
existingApplications.add(app);
}
}
put(TlsAnalyzedProperty.SUPPORTED_APPLICATIONS, existingApplications);
} else {
put(TlsAnalyzedProperty.SUPPORTED_APPLICATIONS, supportedApplications);
}
}
put(TlsAnalyzedProperty.SPEAKS_HTTP, speaksHttp);
}

@Override
public Requirement<ServerReport> getRequirements() {
return new ServerOptionsRequirement(configSelector.getScannerConfig(), getType());
}
}
3 changes: 3 additions & 0 deletions TLS-Server-Scanner/src/main/resources/rating/influencers.xml
Original file line number Diff line number Diff line change
Expand Up @@ -2167,6 +2167,9 @@
<ratingInfluencer>
<property>SUPPORTED_APPLICATIONS</property>
</ratingInfluencer>
<ratingInfluencer>
<property>SPEAKS_HTTP</property>
</ratingInfluencer>
<ratingInfluencer>
<property>SUPPORTED_CIPHERSUITES</property>
</ratingInfluencer>
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,88 @@
/*
* TLS-Scanner - A TLS configuration and analysis tool based on TLS-Attacker
*
* Copyright 2017-2023 Ruhr University Bochum, Paderborn University, Technology Innovation Institute, and Hackmanit GmbH
*
* Licensed under Apache License, Version 2.0
* http://www.apache.org/licenses/LICENSE-2.0.txt
*/
package de.rub.nds.tlsscanner.serverscanner.probe;

import static org.junit.jupiter.api.Assertions.assertNotNull;
import static org.junit.jupiter.api.Assertions.assertTrue;
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.when;

import de.rub.nds.tlsattacker.core.config.Config;
import de.rub.nds.tlsattacker.core.workflow.ParallelExecutor;
import de.rub.nds.tlsattacker.util.tests.TestCategories;
import de.rub.nds.tlsscanner.core.constants.TlsAnalyzedProperty;
import de.rub.nds.tlsscanner.serverscanner.config.ServerScannerConfig;
import de.rub.nds.tlsscanner.serverscanner.constants.ApplicationProtocol;
import de.rub.nds.tlsscanner.serverscanner.report.ServerReport;
import de.rub.nds.tlsscanner.serverscanner.selector.ConfigSelector;
import java.util.ArrayList;
import java.util.List;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Tag;
import org.junit.jupiter.api.Test;

@Tag(TestCategories.SLOW_TEST)
public class ApplicationLayerProbeTest {

private ApplicationLayerProbe probe;
private ConfigSelector configSelector;
private ParallelExecutor parallelExecutor;
private ServerScannerConfig scannerConfig;
private ServerReport report;

@BeforeEach
public void setUp() {
scannerConfig = mock(ServerScannerConfig.class);
// No need to mock hostname - handled by probe using clientDelegate

configSelector = mock(ConfigSelector.class);
when(configSelector.getScannerConfig()).thenReturn(scannerConfig);

Config config = Config.createConfig();
when(configSelector.getAnyWorkingBaseConfig()).thenReturn(config);

parallelExecutor = mock(ParallelExecutor.class);

report = new ServerReport("example.com", "example.com", 443);

probe = new ApplicationLayerProbe(configSelector, parallelExecutor);
}

@Test
public void testProbeRegistersCorrectProperties() {
// Since we can't easily mock the network interaction,
// just test that the probe structure is correct

// Execute without actually running network code
probe.merge(report);

// Check that properties exist (even if with default values)
assertNotNull(report.getResult(TlsAnalyzedProperty.SPEAKS_HTTP));
assertNotNull(report.getResult(TlsAnalyzedProperty.SUPPORTED_APPLICATIONS));
}

@Test
public void testMergeWithExistingApplications() {
// Test that the probe correctly merges with existing applications
List<ApplicationProtocol> existingApps = new ArrayList<>();
existingApps.add(ApplicationProtocol.ECHO);
report.putResult(TlsAnalyzedProperty.SUPPORTED_APPLICATIONS, existingApps);

// Run merge without actual network test
probe.merge(report);

@SuppressWarnings("unchecked")
List<ApplicationProtocol> mergedApps =
(List<ApplicationProtocol>)
report.getResult(TlsAnalyzedProperty.SUPPORTED_APPLICATIONS);
assertNotNull(mergedApps);
// Should still contain the existing app even without running the test
assertTrue(mergedApps.contains(ApplicationProtocol.ECHO));
}
}