🤬
  • Add config to disable fallback behavior in payload selection.

    Previous behavior was to try to return a payload even if a callback server
    payload was requested but the callback server was not configured. This change
    adds a config option to instead throw an exception if this happens.
    
    PiperOrigin-RevId: 435750474
    Change-Id: I503974097420db38f5ff3793718aa929ff2c4fb7
  • Loading...
  • Albert Cui committed with Copybara-Service 2 years ago
    b1c8bc8d
    1 parent 7135325e
Revision indexing in progress... (symbol navigation in revisions will be accurate after indexed)
  • ■ ■ ■ ■ ■ ■
    plugin/src/main/java/com/google/tsunami/plugin/payload/NoCallbackServerException.java
     1 +/*
     2 + * Copyright 2022 Google LLC
     3 + *
     4 + * Licensed under the Apache License, Version 2.0 (the "License");
     5 + * you may not use this file except in compliance with the License.
     6 + * You may obtain a copy of the License at
     7 + *
     8 + * http://www.apache.org/licenses/LICENSE-2.0
     9 + *
     10 + * Unless required by applicable law or agreed to in writing, software
     11 + * distributed under the License is distributed on an "AS IS" BASIS,
     12 + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
     13 + * See the License for the specific language governing permissions and
     14 + * limitations under the License.
     15 + */
     16 + 
     17 +package com.google.tsunami.plugin.payload;
     18 + 
     19 +/**
     20 + * Thrown if {@link PayloadFrameworkConfigs#throwErrorIfCallbackServerUnconfigured} is true and
     21 + * {@link PayloadGenerator} is asked to return a payload that uses the callback server but either
     22 + * the Tsunami instance does not have the callback server configured OR no callback-enabled payload
     23 + * exists for the requested payload configuration.
     24 + *
     25 + * <p>To reduce the burden on callers, this is an unchecked exception.
     26 + */
     27 +public final class NoCallbackServerException extends RuntimeException {
     28 + 
     29 + public NoCallbackServerException() {
     30 + super(
     31 + "Received a request for a payload that uses the callback server, but no callback server is"
     32 + + " configured. To have the payload generator attempt to find a fallback payload that"
     33 + + " doesn't use the callback server, set"
     34 + + " PayloadFrameworkConfigs.throwErrorIfCallbackServerUnconfigured to false.");
     35 + }
     36 +}
     37 + 
  • ■ ■ ■ ■ ■ ■
    plugin/src/main/java/com/google/tsunami/plugin/payload/PayloadFrameworkConfigs.java
     1 +/*
     2 + * Copyright 2022 Google LLC
     3 + *
     4 + * Licensed under the Apache License, Version 2.0 (the "License");
     5 + * you may not use this file except in compliance with the License.
     6 + * You may obtain a copy of the License at
     7 + *
     8 + * http://www.apache.org/licenses/LICENSE-2.0
     9 + *
     10 + * Unless required by applicable law or agreed to in writing, software
     11 + * distributed under the License is distributed on an "AS IS" BASIS,
     12 + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
     13 + * See the License for the specific language governing permissions and
     14 + * limitations under the License.
     15 + */
     16 +package com.google.tsunami.plugin.payload;
     17 + 
     18 +import com.google.tsunami.common.config.annotations.ConfigProperties;
     19 + 
     20 +/** Configuration options for {@link PayloadGenerator}. */
     21 +@ConfigProperties("plugin.payload")
     22 +public final class PayloadFrameworkConfigs {
     23 + // If true, the payload generator will throw an error if a callback-server-enabled
     24 + // payload is requested but could not be fulfilled because Tsunami is not configured
     25 + // to use the callback server. If false, the payload generator will still try to find
     26 + // a payload that doesn't use the callback server but meets the remaining requested parameters.
     27 + public boolean throwErrorIfCallbackServerUnconfigured;
     28 +}
     29 + 
  • ■ ■ ■ ■ ■ ■
    plugin/src/main/java/com/google/tsunami/plugin/payload/PayloadGenerator.java
    skipped 18 lines
    19 19  import static java.lang.annotation.RetentionPolicy.RUNTIME;
    20 20   
    21 21  import com.google.common.collect.ImmutableList;
     22 +import com.google.common.flogger.GoogleLogger;
    22 23  import com.google.protobuf.ByteString;
    23 24  import com.google.tsunami.plugin.TcsClient;
    24 25  import com.google.tsunami.proto.PayloadAttributes;
    skipped 6 lines
    31 32   
    32 33  /** Holds the generate function to get a detection payload given config parameters */
    33 34  public final class PayloadGenerator {
     35 + private static final GoogleLogger logger = GoogleLogger.forEnclosingClass();
    34 36   
    35 37   private static final int SECRET_LENGTH = 8;
    36  - 
    37 38   private static final String TOKEN_CALLBACK_SERVER_URL = "$TSUNAMI_PAYLOAD_TOKEN_URL";
    38 39   private static final String TOKEN_RANDOM_STRING = "$TSUNAMI_PAYLOAD_TOKEN_RANDOM";
    39 40   
    40 41   private final TcsClient tcsClient;
    41  - 
    42 42   private final PayloadSecretGenerator secretGenerator;
    43  - 
    44 43   private final ImmutableList<PayloadDefinition> payloads;
     44 + private final PayloadFrameworkConfigs frameworkConfig;
    45 45   
    46 46   @Inject
    47 47   PayloadGenerator(
    48 48   TcsClient tcsClient,
    49 49   PayloadSecretGenerator secretGenerator,
    50  - @Payloads ImmutableList<PayloadDefinition> payloads) {
     50 + @Payloads ImmutableList<PayloadDefinition> payloads,
     51 + PayloadFrameworkConfigs config) {
    51 52   this.tcsClient = checkNotNull(tcsClient);
    52 53   this.secretGenerator = checkNotNull(secretGenerator);
    53 54   this.payloads = checkNotNull(payloads);
     55 + this.frameworkConfig = checkNotNull(config);
    54 56   }
    55 57   
    56 58   public boolean isCallbackServerEnabled() {
    skipped 1 lines
    58 60   }
    59 61   
    60 62   public Payload generate(PayloadGeneratorConfig config) {
    61  - PayloadDefinition p = null;
     63 + PayloadDefinition selectedPayload = null;
    62 64   
    63 65   // If a payload that uses callback server is requested, prioritize finding
    64 66   // one. If there's none, fallback to any payload that matches.
    65  - if (tcsClient.isCallbackServerEnabled() && config.getUseCallbackServer()) {
    66  - for (PayloadDefinition candidate : payloads) {
    67  - if (isMatchingPayload(candidate, config)
    68  - && candidate.getUsesCallbackServer().getValue()) {
    69  - p = candidate;
    70  - break;
     67 + if (config.getUseCallbackServer()) {
     68 + if (tcsClient.isCallbackServerEnabled()) {
     69 + for (PayloadDefinition candidate : payloads) {
     70 + if (isMatchingPayload(candidate, config)
     71 + && candidate.getUsesCallbackServer().getValue()) {
     72 + selectedPayload = candidate;
     73 + break;
     74 + }
     75 + }
     76 + }
     77 + 
     78 + if (selectedPayload == null) { // or implictly the callback server is not enabled
     79 + if (frameworkConfig.throwErrorIfCallbackServerUnconfigured) {
     80 + throw new NoCallbackServerException();
     81 + } else {
     82 + logger.atWarning().log(
     83 + "Received request for payload that uses the callback server but no callback server is"
     84 + + " configured. Attemping to fallback and find a suitable payload that does not"
     85 + + " use the callback server. To disable this behavior and error instead, set"
     86 + + " PayloadFrameworkConfigs.throwErrorIfCallbackServerUnconfigured to true.");
    71 87   }
    72 88   }
    73 89   }
    74 90   
    75  - if (p == null) {
     91 + if (selectedPayload == null) {
    76 92   for (PayloadDefinition candidate : payloads) {
    77  - if (isMatchingPayload(candidate, config)
    78  - && !candidate.getUsesCallbackServer().getValue()) {
    79  - p = candidate;
     93 + if (isMatchingPayload(candidate, config) && !candidate.getUsesCallbackServer().getValue()) {
     94 + selectedPayload = candidate;
    80 95   break;
    81 96   }
    82 97   }
    83 98   }
    84 99   
    85  - if (p == null) {
     100 + if (selectedPayload == null) {
    86 101   throw new NotImplementedException(
    87 102   "No payload implemented for %s vulnerability type, %s interpretation environment, %s"
    88 103   + " execution environment",
    skipped 2 lines
    91 106   config.getExecutionEnvironment());
    92 107   }
    93 108   
    94  - return convertParsedPayload(p, config);
     109 + return convertParsedPayload(selectedPayload, config);
    95 110   }
    96 111   
    97 112   private boolean isMatchingPayload(PayloadDefinition p, PayloadGeneratorConfig c) {
    skipped 44 lines
  • ■ ■ ■ ■ ■ ■
    plugin/src/main/java/com/google/tsunami/plugin/payload/testing/FakePayloadGeneratorModule.java
    skipped 18 lines
    19 19  import com.google.auto.value.AutoBuilder;
    20 20  import com.google.inject.AbstractModule;
    21 21  import com.google.tsunami.plugin.TcsConfigProperties;
     22 +import com.google.tsunami.plugin.payload.PayloadFrameworkConfigs;
    22 23  import com.google.tsunami.plugin.payload.PayloadGenerator;
    23 24  import com.google.tsunami.plugin.payload.PayloadGeneratorModule;
    24 25  import java.security.SecureRandom;
    skipped 6 lines
    31 32   * FakePayloadGeneratorModuleBuilder} instead of this directly.
    32 33   */
    33 34  public final class FakePayloadGeneratorModule extends AbstractModule {
    34  - private final TcsConfigProperties config = new TcsConfigProperties();
     35 + private final TcsConfigProperties tcsConfig = new TcsConfigProperties();
     36 + private final PayloadFrameworkConfigs frameworkConfigs;
    35 37   private final SecureRandom secureRng;
    36 38   
    37 39   /**
    skipped 3 lines
    41 43   * in tests, leave this empty.
    42 44   */
    43 45   FakePayloadGeneratorModule(
    44  - Optional<MockWebServer> callbackServer, Optional<SecureRandom> secureRng) {
     46 + Optional<MockWebServer> callbackServer,
     47 + Optional<PayloadFrameworkConfigs> frameworkConfigs,
     48 + Optional<SecureRandom> secureRng) {
    45 49   
    46  - this.config.callbackAddress = callbackServer.map(c -> c.getHostName()).orElse(null);
    47  - this.config.callbackPort = callbackServer.map(c -> c.getPort()).orElse(null);
    48  - this.config.pollingUri = callbackServer.map(c -> c.url("/").toString()).orElse(null);
     50 + this.tcsConfig.callbackAddress = callbackServer.map(c -> c.getHostName()).orElse(null);
     51 + this.tcsConfig.callbackPort = callbackServer.map(c -> c.getPort()).orElse(null);
     52 + this.tcsConfig.pollingUri = callbackServer.map(c -> c.url("/").toString()).orElse(null);
    49 53   this.secureRng = secureRng.orElse(new SecureRandom());
     54 + 
     55 + PayloadFrameworkConfigs defaultFrameworkConfigs = new PayloadFrameworkConfigs();
     56 + defaultFrameworkConfigs.throwErrorIfCallbackServerUnconfigured = false;
     57 + this.frameworkConfigs = frameworkConfigs.orElse(defaultFrameworkConfigs);
    50 58   }
    51 59   
    52 60   @Override
    53 61   protected void configure() {
    54  - install(new PayloadGeneratorModule(this.secureRng));
    55  - bind(TcsConfigProperties.class).toInstance(this.config);
     62 + install(new PayloadGeneratorModule(secureRng));
     63 + bind(TcsConfigProperties.class).toInstance(tcsConfig);
     64 + bind(PayloadFrameworkConfigs.class).toInstance(frameworkConfigs);
    56 65   }
    57 66   
    58 67   /** Returns a builder for configuring the module */
    skipped 2 lines
    61 70   }
    62 71   
    63 72   static FakePayloadGeneratorModule build(
    64  - @Nullable MockWebServer callbackServer, @Nullable SecureRandom secureRng) {
     73 + @Nullable MockWebServer callbackServer,
     74 + @Nullable PayloadFrameworkConfigs frameworkConfigs,
     75 + @Nullable SecureRandom secureRng) {
    65 76   return new FakePayloadGeneratorModule(
    66  - Optional.ofNullable(callbackServer), Optional.ofNullable(secureRng));
     77 + Optional.ofNullable(callbackServer),
     78 + Optional.ofNullable(frameworkConfigs),
     79 + Optional.ofNullable(secureRng));
    67 80   }
    68 81   
    69 82   /** Configures {@link FakePayloadGeneratorModule}. */
    skipped 4 lines
    74 87   }
    75 88   
    76 89   public abstract Builder setCallbackServer(MockWebServer callbackServer);
     90 + 
     91 + public abstract Builder setFrameworkConfigs(PayloadFrameworkConfigs config);
    77 92   
    78 93   public abstract Builder setSecureRng(SecureRandom secureRng);
    79 94   
    skipped 4 lines
  • ■ ■ ■ ■ ■ ■
    plugin/src/test/java/com/google/tsunami/plugin/payload/PayloadGeneratorTest.java
    skipped 69 lines
    70 70   .setVulnerabilityType(PayloadGeneratorConfig.VulnerabilityType.SSRF)
    71 71   .setInterpretationEnvironment(
    72 72   PayloadGeneratorConfig.InterpretationEnvironment.INTERPRETATION_ANY)
    73  - .setExecutionEnvironment(PayloadGeneratorConfig.ExecutionEnvironment.EXEC_ANY).build();
     73 + .setExecutionEnvironment(PayloadGeneratorConfig.ExecutionEnvironment.EXEC_ANY)
     74 + .build();
    74 75   private static final String CORRECT_PRINTF =
    75 76   "printf %s%s%s TSUNAMI_PAYLOAD_START ffffffffffffffff TSUNAMI_PAYLOAD_END";
    76 77   
    skipped 148 lines
    225 226   
    226 227   @Test
    227 228   public void getPayload_withSsrfConfiguration_andCallbackServer_returnsCallbackUrl() {
    228  - Payload payload = payloadGenerator.generate(ANY_SSRF_CONFIG.toBuilder().setUseCallbackServer(true).build());
     229 + Payload payload =
     230 + payloadGenerator.generate(ANY_SSRF_CONFIG.toBuilder().setUseCallbackServer(true).build());
    229 231   
    230 232   assertTrue(payload.getPayloadAttributes().getUsesCallbackServer());
    231 233   assertThat(payload.getPayload()).contains(mockCallbackServer.getHostName());
    skipped 82 lines
    314 316   assertThrows(
    315 317   NotImplementedException.class,
    316 318   () -> payloadGenerator.generate(PayloadGeneratorConfig.getDefaultInstance()));
     319 + }
     320 + 
     321 + /**
     322 + * Tests that NoCallbackServerException is correctly thrown.
     323 + *
     324 + * <p>We expect the exception to be thrown when all the following circumstances are met:
     325 + * <ol>
     326 + * <li>throwErrorIfCallbackServerUnconfigured flag is set
     327 + * <li>the callback server is configured
     328 + * <li>callback-server-utilizing payload is requested
     329 + * <li>there is no payload defined that meets the other requested parameters
     330 + * </ol>
     331 + */
     332 + @Test
     333 + public void
     334 + generate_with_andThrowErrorIfCallbackServerUnconfigured_throwsNoCallbackServerException() {
     335 + PayloadFrameworkConfigs config = new PayloadFrameworkConfigs();
     336 + config.throwErrorIfCallbackServerUnconfigured = true;
     337 + Guice.createInjector(
     338 + new HttpClientModule.Builder().build(),
     339 + FakePayloadGeneratorModule.builder()
     340 + .setFrameworkConfigs(config)
     341 + .setCallbackServer(mockCallbackServer)
     342 + .build())
     343 + .injectMembers(this);
     344 + 
     345 + assertThrows(
     346 + NoCallbackServerException.class,
     347 + () ->
     348 + payloadGenerator.generate(
     349 + // We keep the other parameters e.g. VulnerabilityType unset so that we will never
     350 + // find a payload since the definition doesn't exist.
     351 + PayloadGeneratorConfig.newBuilder().setUseCallbackServer(true).build()));
    317 352   }
    318 353  }
    319 354   
Please wait...
Page is in error, reload to recover