🤬
  • Support url target in tsunami core engine. Port Scanning will be skipped for these targets

    PiperOrigin-RevId: 447646861
    Change-Id: I47e04a2f5883a6a55ae39255ab3b15bf3e32a076
  • Loading...
  • Tsunami Team committed with Copybara-Service 2 years ago
    72cc74e0
    1 parent f0062405
  • ■ ■ ■ ■ ■ ■
    common/src/main/java/com/google/tsunami/common/data/NetworkServiceUtils.java
    skipped 19 lines
    20 20   
    21 21  import com.google.common.base.Ascii;
    22 22  import com.google.common.collect.ImmutableMap;
     23 +import com.google.tsunami.proto.AddressFamily;
     24 +import com.google.tsunami.proto.Hostname;
     25 +import com.google.tsunami.proto.IpAddress;
     26 +import com.google.tsunami.proto.NetworkEndpoint;
    23 27  import com.google.tsunami.proto.NetworkService;
     28 +import com.google.tsunami.proto.Port;
     29 +import com.google.tsunami.proto.ServiceContext;
     30 +import com.google.tsunami.proto.TransportProtocol;
     31 +import com.google.tsunami.proto.WebServiceContext;
     32 +import java.net.Inet4Address;
     33 +import java.net.Inet6Address;
     34 +import java.net.InetAddress;
     35 +import java.net.URI;
     36 +import java.net.URISyntaxException;
     37 +import java.net.UnknownHostException;
    24 38  import java.util.Optional;
    25 39   
    26 40  /** Static utility methods pertaining to {@link NetworkService} proto buffer. */
    skipped 35 lines
    62 76   return Ascii.toLowerCase(networkService.getSoftware().getName());
    63 77   }
    64 78   return Ascii.toLowerCase(networkService.getServiceName());
     79 + }
     80 + 
     81 + public static NetworkService buildUriNetworkService(String uriString) {
     82 + try {
     83 + URI uri = new URI(uriString);
     84 + NetworkEndpoint uriEndPoint = buildUriNetworkEndPoint(uri);
     85 + 
     86 + return NetworkService.newBuilder()
     87 + .setNetworkEndpoint(uriEndPoint)
     88 + .setTransportProtocol(TransportProtocol.TCP)
     89 + .setServiceName(uri.getScheme())
     90 + .setServiceContext(
     91 + ServiceContext.newBuilder()
     92 + .setWebServiceContext(
     93 + WebServiceContext.newBuilder().setApplicationRoot(uri.getPath())))
     94 + .build();
     95 + } catch (URISyntaxException exception) {
     96 + throw new AssertionError(
     97 + String.format(
     98 + "Invalid uri syntax passed as target '%s'. Error: %s", uriString, exception));
     99 + }
     100 + }
     101 + 
     102 + private static NetworkEndpoint buildUriNetworkEndPoint(URI uri) {
     103 + try {
     104 + String hostname = uri.getHost();
     105 + String scheme = uri.getScheme();
     106 + checkArgument(
     107 + scheme.equals("http") || scheme.equals("https"),
     108 + "Uri scheme should be one of the following: 'http', 'https'");
     109 + 
     110 + int port = uri.getPort();
     111 + if (port < 0) {
     112 + port = scheme.equals("http") ? 80 : 443;
     113 + }
     114 + 
     115 + String ipAddress = InetAddress.getByName(hostname).getHostAddress();
     116 + InetAddress inetAddress = InetAddress.getByName(uri.getHost());
     117 + checkArgument(
     118 + (inetAddress instanceof Inet4Address) || (inetAddress instanceof Inet6Address),
     119 + "Invalid address family");
     120 + AddressFamily addressFamily =
     121 + inetAddress instanceof Inet4Address ? AddressFamily.IPV4 : AddressFamily.IPV6;
     122 + 
     123 + return NetworkEndpoint.newBuilder()
     124 + .setType(NetworkEndpoint.Type.IP_HOSTNAME_PORT)
     125 + .setPort(Port.newBuilder().setPortNumber(port))
     126 + .setHostname(Hostname.newBuilder().setName(uri.getHost()))
     127 + .setIpAddress(
     128 + IpAddress.newBuilder().setAddressFamily(addressFamily).setAddress(ipAddress))
     129 + .build();
     130 + } catch (UnknownHostException exception) {
     131 + throw new AssertionError(
     132 + String.format("Unable to get valid host from uri. Error: %s", exception));
     133 + }
    65 134   }
    66 135   
    67 136   /**
    skipped 43 lines
  • ■ ■ ■ ■ ■ ■
    common/src/test/java/com/google/tsunami/common/data/NetworkServiceUtilsTest.java
    skipped 15 lines
    16 16  package com.google.tsunami.common.data;
    17 17   
    18 18  import static com.google.common.truth.Truth.assertThat;
     19 +import static com.google.common.truth.extensions.proto.ProtoTruth.assertThat;
    19 20  import static com.google.tsunami.common.data.NetworkEndpointUtils.forIpAndPort;
    20 21   
     22 +import com.google.tsunami.proto.AddressFamily;
     23 +import com.google.tsunami.proto.Hostname;
     24 +import com.google.tsunami.proto.IpAddress;
     25 +import com.google.tsunami.proto.NetworkEndpoint;
    21 26  import com.google.tsunami.proto.NetworkService;
     27 +import com.google.tsunami.proto.Port;
    22 28  import com.google.tsunami.proto.ServiceContext;
    23 29  import com.google.tsunami.proto.Software;
     30 +import com.google.tsunami.proto.TransportProtocol;
    24 31  import com.google.tsunami.proto.WebServiceContext;
     32 +import java.io.IOException;
     33 +import java.net.Inet4Address;
     34 +import java.net.InetAddress;
     35 +import java.net.URL;
    25 36  import org.junit.Test;
    26 37  import org.junit.runner.RunWith;
    27 38  import org.junit.runners.JUnit4;
    skipped 232 lines
    260 271   WebServiceContext.newBuilder().setApplicationRoot("test_root")))
    261 272   .build()))
    262 273   .isEqualTo("https://127.0.0.1/test_root/");
     274 + }
     275 + 
     276 + @Test
     277 + public void buildUriNetworkService_returnsNetworkService() throws IOException {
     278 + 
     279 + URL url = new URL("https://localhost/function1");
     280 + String hostname = url.getHost();
     281 + String ipaddress = InetAddress.getByName(hostname).getHostAddress();
     282 + InetAddress inetAddress = InetAddress.getByName(url.getHost());
     283 + AddressFamily addressFamily =
     284 + inetAddress instanceof Inet4Address ? AddressFamily.IPV4 : AddressFamily.IPV6;
     285 + 
     286 + NetworkEndpoint networkEndpoint =
     287 + NetworkEndpoint.newBuilder()
     288 + .setType(NetworkEndpoint.Type.IP_HOSTNAME_PORT)
     289 + .setIpAddress(
     290 + IpAddress.newBuilder().setAddressFamily(addressFamily).setAddress(ipaddress))
     291 + .setPort(Port.newBuilder().setPortNumber(443))
     292 + .setHostname(Hostname.newBuilder().setName("localhost"))
     293 + .build();
     294 + 
     295 + NetworkService networkService =
     296 + NetworkService.newBuilder()
     297 + .setNetworkEndpoint(networkEndpoint)
     298 + .setTransportProtocol(TransportProtocol.TCP)
     299 + .setServiceName("https")
     300 + .setServiceContext(
     301 + ServiceContext.newBuilder()
     302 + .setWebServiceContext(
     303 + WebServiceContext.newBuilder().setApplicationRoot("/function1")))
     304 + .build();
     305 + 
     306 + assertThat(NetworkServiceUtils.buildUriNetworkService("https://localhost/function1"))
     307 + .isEqualTo(networkService);
    263 308   }
    264 309  }
    265 310   
  • ■ ■ ■ ■ ■ ■
    main/src/main/java/com/google/tsunami/main/cli/TsunamiCli.java
    skipped 18 lines
    19 19  import static com.google.tsunami.common.data.NetworkEndpointUtils.forHostname;
    20 20  import static com.google.tsunami.common.data.NetworkEndpointUtils.forIp;
    21 21  import static com.google.tsunami.common.data.NetworkEndpointUtils.forIpAndHostname;
     22 +import static com.google.tsunami.common.data.NetworkServiceUtils.buildUriNetworkService;
    22 23   
    23 24  import com.google.common.base.Splitter;
    24 25  import com.google.common.base.Stopwatch;
    skipped 85 lines
    110 111   scanTargetBuilder.setNetworkEndpoint(forIpAndHostname(ip, mainCliOptions.hostnameTarget));
    111 112   } else if (ip != null) {
    112 113   scanTargetBuilder.setNetworkEndpoint(forIp(ip));
     114 + } else if (mainCliOptions.uriTarget != null) {
     115 + scanTargetBuilder.setNetworkService(buildUriNetworkService(mainCliOptions.uriTarget));
    113 116   } else {
    114 117   scanTargetBuilder.setNetworkEndpoint(forHostname(mainCliOptions.hostnameTarget));
    115 118   }
    skipped 100 lines
  • ■ ■ ■ ■ ■
    main/src/main/java/com/google/tsunami/main/cli/option/MainCliOptions.java
    skipped 45 lines
    46 46   @Parameter(names = "--log-id", description = "A log ID to print in front of the logs.")
    47 47   public String logId;
    48 48   
     49 + @Parameter(
     50 + names = "--uri-target",
     51 + description =
     52 + "The URI of the scanning target that supports both http & https schemes. When this"
     53 + + " parameter is set, port scan is automatically skipped.")
     54 + public String uriTarget;
     55 + 
    49 56   @Override
    50 57   public void validate() {
    51  - List<String> nonEmptyTargets = new ArrayList<>();
     58 + List<String> portScanEnabledTargets = new ArrayList<>();
     59 + List<String> portScanDisabledTargets = new ArrayList<>();
    52 60   if (ipV4Target != null) {
    53  - nonEmptyTargets.add("--ip-v4-target");
     61 + portScanEnabledTargets.add("--ip-v4-target");
    54 62   }
    55 63   if (ipV6Target != null) {
    56  - nonEmptyTargets.add("--ip-v6-target");
     64 + portScanEnabledTargets.add("--ip-v6-target");
    57 65   }
    58 66   if (hostnameTarget != null) {
    59  - nonEmptyTargets.add("--hostname-target");
     67 + portScanEnabledTargets.add("--hostname-target");
     68 + }
     69 + if (uriTarget != null) {
     70 + portScanDisabledTargets.add("--uri-target");
    60 71   }
    61 72   
    62  - if (nonEmptyTargets.isEmpty()) {
     73 + if (portScanEnabledTargets.isEmpty() && portScanDisabledTargets.isEmpty()) {
    63 74   throw new ParameterException(
    64 75   "One of the following parameters is expected: --ip-v4-target, --ip-v6-target,"
    65  - + " --hostname-target");
     76 + + " --hostname-target, --uri-target");
     77 + }
     78 + if (!portScanEnabledTargets.isEmpty() && !portScanDisabledTargets.isEmpty()) {
     79 + throw new ParameterException(
     80 + "Parameters that require port scan (--ip-v4-target, --ip-v6-target, --hostname-target)"
     81 + + " should not be passed along with parameters that skip port scan (--uri-target)");
    66 82   }
    67 83   }
    68 84  }
    skipped 1 lines
  • ■ ■ ■ ■ ■ ■
    main/src/test/java/com/google/tsunami/main/cli/TsunamiCliTest.java
    skipped 37 lines
    38 38  import com.google.tsunami.plugin.testing.FakeVulnDetector2;
    39 39  import com.google.tsunami.plugin.testing.FakeVulnDetectorBootstrapModule;
    40 40  import com.google.tsunami.plugin.testing.FakeVulnDetectorBootstrapModule2;
     41 +import com.google.tsunami.proto.AddressFamily;
    41 42  import com.google.tsunami.proto.DetectionReport;
     43 +import com.google.tsunami.proto.Hostname;
     44 +import com.google.tsunami.proto.IpAddress;
     45 +import com.google.tsunami.proto.NetworkEndpoint;
    42 46  import com.google.tsunami.proto.NetworkService;
     47 +import com.google.tsunami.proto.Port;
    43 48  import com.google.tsunami.proto.ReconnaissanceReport;
    44 49  import com.google.tsunami.proto.ScanFinding;
    45 50  import com.google.tsunami.proto.ScanResults;
    46 51  import com.google.tsunami.proto.ScanStatus;
     52 +import com.google.tsunami.proto.ServiceContext;
    47 53  import com.google.tsunami.proto.TargetInfo;
     54 +import com.google.tsunami.proto.TransportProtocol;
     55 +import com.google.tsunami.proto.WebServiceContext;
    48 56  import com.google.tsunami.workflow.ScanningWorkflowException;
    49 57  import io.github.classgraph.ClassGraph;
    50 58  import io.github.classgraph.ScanResult;
    51 59  import java.io.IOException;
     60 +import java.net.Inet4Address;
     61 +import java.net.InetAddress;
     62 +import java.net.URL;
    52 63  import java.util.concurrent.ExecutionException;
    53 64  import java.util.stream.Stream;
    54 65  import javax.inject.Inject;
    skipped 12 lines
    67 78  public final class TsunamiCliTest {
    68 79   private static final String IP_TARGET = "127.0.0.1";
    69 80   private static final String HOSTNAME_TARGET = "localhost";
     81 + private static final String URI_TARGET = "https://localhost/function1";
    70 82   
    71 83   @Rule public MockitoRule mockitoRule = MockitoJUnit.rule();
    72 84   
    skipped 98 lines
    171 183   FakeServiceFingerprinter.addWebServiceContext(
    172 184   FakePortScanner.getFakeNetworkService(
    173 185   NetworkEndpointUtils.forHostname(HOSTNAME_TARGET))))
     186 + .build());
     187 + }
     188 + 
     189 + @Test
     190 + public void run_whenUriTarget_generatesCorrectResult()
     191 + throws InterruptedException, ExecutionException, IOException {
     192 + 
     193 + boolean scanSucceeded = runCli(ImmutableMap.of(), "--uri-target=" + URI_TARGET);
     194 + assertThat(scanSucceeded).isTrue();
     195 + 
     196 + URL url = new URL(URI_TARGET);
     197 + String hostname = url.getHost();
     198 + String ipaddress = InetAddress.getByName(hostname).getHostAddress();
     199 + InetAddress inetAddress = InetAddress.getByName(url.getHost());
     200 + AddressFamily addressFamily =
     201 + inetAddress instanceof Inet4Address ? AddressFamily.IPV4 : AddressFamily.IPV6;
     202 + 
     203 + NetworkEndpoint networkEndpoint =
     204 + NetworkEndpoint.newBuilder()
     205 + .setType(NetworkEndpoint.Type.IP_HOSTNAME_PORT)
     206 + .setHostname(Hostname.newBuilder().setName("localhost"))
     207 + .setPort(Port.newBuilder().setPortNumber(443))
     208 + .setIpAddress(
     209 + IpAddress.newBuilder().setAddressFamily(addressFamily).setAddress(ipaddress))
     210 + .build();
     211 + 
     212 + verify(scanResultsArchiver, times(1)).archive(scanResultsCaptor.capture());
     213 + ScanResults storedScanResult = scanResultsCaptor.getValue();
     214 + assertThat(storedScanResult.getScanStatus()).isEqualTo(ScanStatus.SUCCEEDED);
     215 + assertThat(storedScanResult.getReconnaissanceReport())
     216 + .isEqualTo(
     217 + ReconnaissanceReport.newBuilder()
     218 + .setTargetInfo(TargetInfo.newBuilder().addNetworkEndpoints(networkEndpoint))
     219 + .addNetworkServices(
     220 + NetworkService.newBuilder()
     221 + .setNetworkEndpoint(networkEndpoint)
     222 + .setTransportProtocol(TransportProtocol.TCP)
     223 + .setServiceName("https")
     224 + .setServiceContext(
     225 + ServiceContext.newBuilder()
     226 + .setWebServiceContext(
     227 + WebServiceContext.newBuilder()
     228 + .setApplicationRoot(url.getPath()))))
    174 229   .build());
    175 230   }
    176 231   
    skipped 76 lines
  • ■ ■ ■ ■ ■ ■
    main/src/test/java/com/google/tsunami/main/cli/option/MainCliOptionsTest.java
    skipped 31 lines
    32 32   
    33 33   assertThrows(ParameterException.class, cliOptions::validate);
    34 34   }
     35 + 
     36 + @Test
     37 + public void validate_whenUriTargetPassedWithHostnameTarget_throwsParameterException() {
     38 + MainCliOptions cliOptions = new MainCliOptions();
     39 + 
     40 + cliOptions.hostnameTarget = "localhost";
     41 + cliOptions.uriTarget = "https://localhost/function1";
     42 + 
     43 + assertThrows(ParameterException.class, cliOptions::validate);
     44 + }
    35 45  }
    36 46   
  • ■ ■ ■ ■ ■
    proto/scan_target.proto
    skipped 19 lines
    20 20  package tsunami.proto;
    21 21   
    22 22  import "network.proto";
     23 +import "network_service.proto";
    23 24   
    24 25  option java_multiple_files = true;
    25 26  option java_outer_classname = "ScanTargetProtos";
    skipped 2 lines
    28 29   
    29 30  // The information about a scan target.
    30 31  message ScanTarget {
    31  - // The network endpoint to be scanned.
    32  - NetworkEndpoint network_endpoint = 1;
     32 + oneof target {
     33 + // The network endpoint to be scanned.
     34 + NetworkEndpoint network_endpoint = 1;
     35 + // The network service to be scanned.
     36 + NetworkService network_service = 2;
     37 + }
    33 38  }
    34 39   
  • ■ ■ ■ ■ ■
    workflow/src/main/java/com/google/tsunami/workflow/DefaultScanningWorkflow.java
    skipped 121 lines
    122 122   /**
    123 123   * Performs the scanning workflow asynchronously.
    124 124   *
    125  - * @param scanTarget the IP or hostname target to be scanned
     125 + * @param scanTarget the IP or hostname or uri target to be scanned
    126 126   * @return A {@link ListenableFuture} over the result of the scanning workflow.
    127 127   */
    128 128   public ListenableFuture<ScanResults> runAsync(ScanTarget scanTarget) {
    skipped 1 lines
    130 130   scanStartTimestamp = Instant.now(clock);
    131 131   executionTracer = ExecutionTracer.startWorkflow();
    132 132   logger.atInfo().log("Staring Tsunami scanning workflow.");
    133  - return FluentFuture.from(scanPorts(scanTarget))
    134  - .transformAsync(this::fingerprintNetworkServices, directExecutor())
     133 + FluentFuture<ReconnaissanceReport> reconnaissanceReport;
     134 + 
     135 + if (scanTarget.hasNetworkService()) {
     136 + PortScanningReport portScanningReport = buildUriPortScanningReport(scanTarget);
     137 + reconnaissanceReport = FluentFuture.from(fingerprintNetworkServices(portScanningReport));
     138 + } else {
     139 + reconnaissanceReport =
     140 + FluentFuture.from(scanPorts(scanTarget))
     141 + .transformAsync(this::fingerprintNetworkServices, directExecutor());
     142 + }
     143 + return reconnaissanceReport
    135 144   .transformAsync(this::detectVulnerabilities, directExecutor())
    136 145   // Unfortunately FluentFuture doesn't support future peeking.
    137 146   .transform(
    skipped 5 lines
    143 152   // Execution errors are handled and reported back in the ScanResults.
    144 153   .catching(PluginExecutionException.class, this::onExecutionError, directExecutor())
    145 154   .catching(ScanningWorkflowException.class, this::onExecutionError, directExecutor());
     155 + }
     156 + 
     157 + private PortScanningReport buildUriPortScanningReport(ScanTarget scanTarget) {
     158 + 
     159 + Optional<PluginMatchingResult<PortScanner>> matchedPortScanner = pluginManager.getPortScanner();
     160 + executionTracer.startPortScanning(ImmutableList.of(matchedPortScanner.get()));
     161 + 
     162 + NetworkService networkService = scanTarget.getNetworkService();
     163 + 
     164 + return PortScanningReport.newBuilder()
     165 + .setTargetInfo(
     166 + TargetInfo.newBuilder().addNetworkEndpoints(networkService.getNetworkEndpoint()))
     167 + .addNetworkServices(networkService)
     168 + .build();
    146 169   }
    147 170   
    148 171   private ScanResults onExecutionError(TsunamiException exception) {
    skipped 207 lines
  • ■ ■ ■ ■ ■ ■
    workflow/src/test/java/com/google/tsunami/workflow/DefaultScanningWorkflowTest.java
    skipped 17 lines
    18 18  import static com.google.common.truth.Truth.assertThat;
    19 19  import static com.google.common.truth.Truth8.assertThat;
    20 20  import static com.google.tsunami.common.data.NetworkEndpointUtils.forIp;
     21 +import static com.google.tsunami.common.data.NetworkServiceUtils.buildUriNetworkService;
    21 22  import static org.junit.Assert.assertThrows;
    22 23   
    23 24  import com.google.common.collect.ImmutableList;
    skipped 60 lines
    84 85   .map(selectedVulnDetector -> selectedVulnDetector.tsunamiPlugin().getClass()))
    85 86   .containsExactlyElementsIn(
    86 87   ImmutableList.of(FakeVulnDetector.class, FakeVulnDetector2.class));
     88 + }
     89 + 
     90 + @Test
     91 + public void run_whenUriScanTarget_executesScanningWorkflow()
     92 + throws InterruptedException, ExecutionException {
     93 + ScanResults scanResults = scanningWorkflow.run(buildUriScanTarget());
     94 + ExecutionTracer executionTracer = scanningWorkflow.getExecutionTracer();
     95 + 
     96 + assertThat(scanResults.getScanStatus()).isEqualTo(ScanStatus.SUCCEEDED);
     97 + assertThat(executionTracer.isDone()).isTrue();
     98 + assertThat(
     99 + executionTracer.getSelectedVulnDetectors().stream()
     100 + .map(selectedVulnDetector -> selectedVulnDetector.tsunamiPlugin().getClass()))
     101 + .containsExactlyElementsIn(
     102 + ImmutableList.of(FakeVulnDetector.class, FakeVulnDetector2.class));
     103 + assertThat(scanResults.getScanFindings(0).getNetworkService().getServiceName())
     104 + .isEqualTo("https");
     105 + assertThat(
     106 + scanResults
     107 + .getScanFindings(0)
     108 + .getNetworkService()
     109 + .getServiceContext()
     110 + .getWebServiceContext()
     111 + .getApplicationRoot())
     112 + .isEqualTo("/function1");
     113 + assertThat(
     114 + scanResults
     115 + .getScanFindings(0)
     116 + .getNetworkService()
     117 + .getNetworkEndpoint()
     118 + .getPort()
     119 + .getPortNumber())
     120 + .isEqualTo(443);
     121 + assertThat(
     122 + scanResults
     123 + .getScanFindings(0)
     124 + .getNetworkService()
     125 + .getNetworkEndpoint()
     126 + .getHostname()
     127 + .getName())
     128 + .isEqualTo("localhost");
    87 129   }
    88 130   
    89 131   // TODO(b/145315535): add default output for the fake plugins and test the output of the workflow.
    skipped 163 lines
    253 295   
    254 296   private static ScanTarget buildScanTarget() {
    255 297   return ScanTarget.newBuilder().setNetworkEndpoint(forIp("1.2.3.4")).build();
     298 + }
     299 + 
     300 + private static ScanTarget buildUriScanTarget() {
     301 + return ScanTarget.newBuilder()
     302 + .setNetworkService(buildUriNetworkService("https://localhost/function1"))
     303 + .build();
    256 304   }
    257 305  }
    258 306   
Please wait...
Page is in error, reload to recover