Skip to content

Commit

Permalink
Add a detector for CVE-2024-6387: a remote code execution in OpenSSH.
Browse files Browse the repository at this point in the history
PiperOrigin-RevId: 662908793
Change-Id: I4cd825f556e38cebce362d629825c30a9553cc1a
  • Loading branch information
tooryx authored and copybara-github committed Aug 14, 2024
1 parent 44cd3e0 commit 45428c5
Show file tree
Hide file tree
Showing 3 changed files with 367 additions and 0 deletions.
Original file line number Diff line number Diff line change
@@ -0,0 +1,166 @@
/*
* Copyright 2024 Google LLC
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.google.tsunami.plugins.detectors.rce.cve20246387;

import static com.google.common.base.Preconditions.checkNotNull;
import static com.google.common.collect.ImmutableList.toImmutableList;

import com.google.common.annotations.VisibleForTesting;
import com.google.common.collect.ImmutableList;
import com.google.common.flogger.GoogleLogger;
import com.google.protobuf.util.Timestamps;
import com.google.tsunami.common.time.UtcClock;
import com.google.tsunami.plugin.PluginType;
import com.google.tsunami.plugin.VulnDetector;
import com.google.tsunami.plugin.annotations.PluginInfo;
import com.google.tsunami.proto.AdditionalDetail;
import com.google.tsunami.proto.DetectionReport;
import com.google.tsunami.proto.DetectionReportList;
import com.google.tsunami.proto.DetectionStatus;
import com.google.tsunami.proto.NetworkService;
import com.google.tsunami.proto.Severity;
import com.google.tsunami.proto.TargetInfo;
import com.google.tsunami.proto.TextData;
import com.google.tsunami.proto.Vulnerability;
import com.google.tsunami.proto.VulnerabilityId;
import java.time.Clock;
import javax.inject.Inject;

/** A {@link VulnDetector} that detects CVE-2024-6387. */
@PluginInfo(
type = PluginType.VULN_DETECTION,
name = "Cve20246387Detector",
version = "0.1",
description = "Detects CVE-2024-6387.",
author = "Tsunami Team ([email protected])",
bootstrapModule = Cve20246387DetectorBootstrapModule.class)
public final class Cve20246387Detector implements VulnDetector {
private static final GoogleLogger logger = GoogleLogger.forEnclosingClass();
private static final ImmutableList<String> VULNERABLE_BANNER_VERSIONS_SUFFIX =
ImmutableList.of(
// Ubuntu
"8.8p1 Ubuntu-1",
"8.9p1 Ubuntu-3",
"8.9p1 Ubuntu-3ubuntu0.1",
"8.9p1 Ubuntu-3ubuntu0.3",
"8.9p1 Ubuntu-3ubuntu0.4",
"8.9p1 Ubuntu-3ubuntu0.5",
"8.9p1 Ubuntu-3ubuntu0.6",
"8.9p1 Ubuntu-3ubuntu0.7",
"8.9p1 Ubuntu-3ubuntu0.7+Fips1",
"9.0p1 Ubuntu-1ubuntu7",
"9.0p1 Ubuntu-1ubuntu7.1",
"9.0p1 Ubuntu-1ubuntu8.4",
"9.0p1 Ubuntu-1ubuntu8.7",
"9.3p1 Ubuntu-1ubuntu3.2",
"9.3p1 Ubuntu-1ubuntu3.3",
"9.6p1 Ubuntu-3ubuntu13",
// Debian
"8.7p1 Debian-4",
"9.0p1 Debian-1+b1",
"9.2p1 Debian-2",
"9.2p1 Debian-2+deb12u1",
"9.2p1 Debian-2+deb12u2",
"9.3p1 Debian-1",
"9.4p1 Debian-1",
"9.6p1 Debian-2",
"9.6p1 Debian-3",
"9.6p1 Debian-4",
"9.7p1 Debian-4",
"9.7p1 Debian-5",
"9.7p1 Debian-6");

@VisibleForTesting
static final String TITLE =
"CVE-2024-6387 Unauthenticated Remote Code Execution in OpenSSH Server";

@VisibleForTesting
static final String DESCRIPTION =
"A signal handler race condition was found in OpenSSH's server (sshd), where a client does"
+ " not authenticate within LoginGraceTime seconds (120 by default, 600 in old OpenSSH"
+ " versions), then sshd's SIGALRM handler is called asynchronously. However, this signal"
+ " handler calls various functions that are not async-signal-safe, for example,"
+ " syslog().";

@VisibleForTesting
static final String RECOMMENDATION =
"Upgrade OpenSSH to the latest version or restrict the access to the SSH server to trusted"
+ " peers. When upgrade is not available, you could set the `LoginGraceTime` parameter to"
+ " 0 in OpenSSH config file at `/etc/ssh/sshd_config` and restart the OpenSSH server.";

private final Clock utcClock;

@Inject
Cve20246387Detector(@UtcClock Clock utcClock) {
this.utcClock = checkNotNull(utcClock);
}

@Override
public DetectionReportList detect(
TargetInfo targetInfo, ImmutableList<NetworkService> matchedServices) {
logger.atInfo().log("Scanning CVE-2024-6387 via banner comparison.");
return DetectionReportList.newBuilder()
.addAllDetectionReports(
matchedServices.stream()
.filter(this::hasOpenSshBanner)
.filter(this::isServiceVulnerable)
.map(networkService -> buildDetectionReport(targetInfo, networkService))
.collect(toImmutableList()))
.build();
}

private boolean hasOpenSshBanner(NetworkService networkService) {
return networkService.getBannerCount() > 0
&& networkService.getBannerList().stream()
.anyMatch(banner -> banner.contains("SSH-2.0-OpenSSH"));
}

private boolean isServiceVulnerable(NetworkService networkService) {
return networkService.getBannerList().stream()
.map(String::trim)
.anyMatch(banner -> VULNERABLE_BANNER_VERSIONS_SUFFIX.stream().anyMatch(banner::endsWith));
}

private DetectionReport buildDetectionReport(
TargetInfo targetInfo, NetworkService networkService) {
ImmutableList<AdditionalDetail> additionalDetails =
networkService.getBannerList().stream()
.map(
banner ->
AdditionalDetail.newBuilder()
.setTextData(TextData.newBuilder().setText(banner))
.build())
.collect(toImmutableList());

return DetectionReport.newBuilder()
.setTargetInfo(targetInfo)
.setNetworkService(networkService)
.setDetectionTimestamp(Timestamps.fromMillis(utcClock.millis()))
.setDetectionStatus(DetectionStatus.VULNERABILITY_PRESENT)
.setVulnerability(
Vulnerability.newBuilder()
.setMainId(
VulnerabilityId.newBuilder().setPublisher("GOOGLE").setValue("CVE-2024-6387"))
.addRelatedId(
VulnerabilityId.newBuilder().setPublisher("CVE").setValue("CVE-2024-6387"))
.setSeverity(Severity.CRITICAL)
.setTitle(TITLE)
.setDescription(DESCRIPTION)
.setRecommendation(RECOMMENDATION)
.addAllAdditionalDetails(additionalDetails))
.build();
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
/*
* Copyright 2024 Google LLC
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.google.tsunami.plugins.detectors.rce.cve20246387;

import com.google.tsunami.plugin.PluginBootstrapModule;

/** A {@link PluginBootstrapModule} for {@link Cve20246387Detector}. */
public final class Cve20246387DetectorBootstrapModule extends PluginBootstrapModule {

@Override
protected void configurePlugin() {
registerPlugin(Cve20246387Detector.class);
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,174 @@
/*
* Copyright 2024 Google LLC
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.google.tsunami.plugins.detectors.rce.cve20246387;

import static com.google.common.truth.extensions.proto.ProtoTruth.assertThat;
import static com.google.tsunami.common.data.NetworkEndpointUtils.forHostnameAndPort;
import static com.google.tsunami.plugins.detectors.rce.cve20246387.Cve20246387Detector.DESCRIPTION;
import static com.google.tsunami.plugins.detectors.rce.cve20246387.Cve20246387Detector.RECOMMENDATION;
import static com.google.tsunami.plugins.detectors.rce.cve20246387.Cve20246387Detector.TITLE;

import com.google.common.collect.ImmutableList;
import com.google.inject.Guice;
import com.google.protobuf.util.Timestamps;
import com.google.tsunami.common.time.testing.FakeUtcClock;
import com.google.tsunami.common.time.testing.FakeUtcClockModule;
import com.google.tsunami.proto.AdditionalDetail;
import com.google.tsunami.proto.DetectionReport;
import com.google.tsunami.proto.DetectionReportList;
import com.google.tsunami.proto.DetectionStatus;
import com.google.tsunami.proto.NetworkService;
import com.google.tsunami.proto.Severity;
import com.google.tsunami.proto.TargetInfo;
import com.google.tsunami.proto.TextData;
import com.google.tsunami.proto.Vulnerability;
import com.google.tsunami.proto.VulnerabilityId;
import java.time.Instant;
import javax.inject.Inject;
import org.junit.Before;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.junit.runners.JUnit4;

/** Unit tests for {@link Cve20246387Detector} */
@RunWith(JUnit4.class)
public final class Cve20246387DetectorTest {
private final FakeUtcClock fakeUtcClock =
FakeUtcClock.create().setNow(Instant.parse("2020-01-01T00:00:00.00Z"));

@Inject Cve20246387Detector detector;

@Before
public void setUp() {
Guice.createInjector(
new FakeUtcClockModule(fakeUtcClock), new Cve20246387DetectorBootstrapModule())
.injectMembers(this);
}

@Test
public void detect_noBanner_returnsEmpty() {
var targetNetworkService =
NetworkService.newBuilder().setNetworkEndpoint(forHostnameAndPort("localhost", 22)).build();
var targetInfo =
TargetInfo.newBuilder()
.addNetworkEndpoints(targetNetworkService.getNetworkEndpoint())
.build();

DetectionReportList detectionReports =
detector.detect(targetInfo, ImmutableList.of(targetNetworkService));

assertThat(detectionReports.getDetectionReportsList()).isEmpty();
}

@Test
public void detect_nonSshBanner_returnsEmpty() {
var targetNetworkService =
NetworkService.newBuilder()
.setNetworkEndpoint(forHostnameAndPort("localhost", 22))
.addBanner("irrelevant")
.build();
var targetInfo =
TargetInfo.newBuilder()
.addNetworkEndpoints(targetNetworkService.getNetworkEndpoint())
.build();

DetectionReportList detectionReports =
detector.detect(targetInfo, ImmutableList.of(targetNetworkService));

assertThat(detectionReports.getDetectionReportsList()).isEmpty();
}

@Test
public void detect_nonVulnerableSshBanner_returnsNoFinding() {
var targetNetworkService =
NetworkService.newBuilder()
.setNetworkEndpoint(forHostnameAndPort("localhost", 22))
.addBanner("\n\nSSH-2.0-OpenSSH_8.4p1 Debian-5+deb11u3")
.build();
var targetInfo =
TargetInfo.newBuilder()
.addNetworkEndpoints(targetNetworkService.getNetworkEndpoint())
.build();

DetectionReportList detectionReports =
detector.detect(targetInfo, ImmutableList.of(targetNetworkService));

assertThat(detectionReports.getDetectionReportsList()).isEmpty();
}

@Test
public void detect_nonVulnerableGenericSshBanner_returnsNoFinding() {
var targetNetworkService =
NetworkService.newBuilder()
.setNetworkEndpoint(forHostnameAndPort("localhost", 22))
.addBanner("\n\nSSH-2.0-OpenSSH_8.0")
.build();
var targetInfo =
TargetInfo.newBuilder()
.addNetworkEndpoints(targetNetworkService.getNetworkEndpoint())
.build();

DetectionReportList detectionReports =
detector.detect(targetInfo, ImmutableList.of(targetNetworkService));

assertThat(detectionReports.getDetectionReportsList()).isEmpty();
}

@Test
public void detect_vulnerableSshBanner_returnsVulnerability() {
var targetNetworkService =
NetworkService.newBuilder()
.setNetworkEndpoint(forHostnameAndPort("localhost", 22))
.addBanner("SSH-2.0-OpenSSH_9.6p1 Ubuntu-3ubuntu13")
.build();
var targetInfo =
TargetInfo.newBuilder()
.addNetworkEndpoints(targetNetworkService.getNetworkEndpoint())
.build();

DetectionReportList detectionReports =
detector.detect(targetInfo, ImmutableList.of(targetNetworkService));

assertThat(detectionReports.getDetectionReportsList())
.containsExactly(
DetectionReport.newBuilder()
.setTargetInfo(targetInfo)
.setNetworkService(targetNetworkService)
.setDetectionTimestamp(Timestamps.fromMillis(fakeUtcClock.millis()))
.setDetectionStatus(DetectionStatus.VULNERABILITY_PRESENT)
.setVulnerability(
Vulnerability.newBuilder()
.setMainId(
VulnerabilityId.newBuilder()
.setPublisher("GOOGLE")
.setValue("CVE-2024-6387"))
.addRelatedId(
VulnerabilityId.newBuilder()
.setPublisher("CVE")
.setValue("CVE-2024-6387"))
.setSeverity(Severity.CRITICAL)
.setTitle(TITLE)
.setDescription(DESCRIPTION)
.setRecommendation(RECOMMENDATION)
.addAdditionalDetails(
AdditionalDetail.newBuilder()
.setTextData(
TextData.newBuilder()
.setText("SSH-2.0-OpenSSH_9.6p1 Ubuntu-3ubuntu13"))
.build()))
.build());
}
}

0 comments on commit 45428c5

Please sign in to comment.