■ ■ ■ ■ ■ ■
common/src/main/java/com/google/tsunami/common/net/FuzzingUtils.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.common.net; |
| 17 | + | |
| 18 | + | import static com.google.common.base.Strings.isNullOrEmpty; |
| 19 | + | import static com.google.common.collect.ImmutableList.toImmutableList; |
| 20 | + | import static java.util.stream.Collectors.joining; |
| 21 | + | |
| 22 | + | import com.google.auto.value.AutoValue; |
| 23 | + | import com.google.common.base.Splitter; |
| 24 | + | import com.google.common.collect.ImmutableList; |
| 25 | + | import com.google.common.collect.ImmutableSet; |
| 26 | + | import com.google.tsunami.common.net.http.HttpRequest; |
| 27 | + | import java.net.URI; |
| 28 | + | import java.util.ArrayList; |
| 29 | + | import java.util.List; |
| 30 | + | import java.util.Optional; |
| 31 | + | |
| 32 | + | /** Fuzzing utilities for HTTP request properties. */ |
| 33 | + | public final class FuzzingUtils { |
| 34 | + | /* TODO(b/251480660): Refactor to generic fuzzing library. */ |
| 35 | + | |
| 36 | + | /** |
| 37 | + | * Fuzz GET parameters by replacing values with the provided payload. If no GET parameter is |
| 38 | + | * found, add a new parameter called {@code defaultParameter}. |
| 39 | + | */ |
| 40 | + | public static ImmutableList<HttpRequest> fuzzGetParametersWithDefaultParameter( |
| 41 | + | HttpRequest request, String payload, String defaultParameter) { |
| 42 | + | return fuzzGetParameters(request, payload, Optional.of(defaultParameter)); |
| 43 | + | } |
| 44 | + | |
| 45 | + | /** |
| 46 | + | * Fuzz GET parameters by replacing values with the provided payload. If no GET parameter is |
| 47 | + | * found, return an empty list. |
| 48 | + | */ |
| 49 | + | public static ImmutableList<HttpRequest> fuzzGetParameters(HttpRequest request, String payload) { |
| 50 | + | return fuzzGetParameters(request, payload, Optional.empty()); |
| 51 | + | } |
| 52 | + | |
| 53 | + | private static ImmutableList<HttpRequest> fuzzGetParameters( |
| 54 | + | HttpRequest request, String payload, Optional<String> defaultParameter) { |
| 55 | + | URI parsedUrl = URI.create(request.url()); |
| 56 | + | ImmutableList<HttpQueryParams> queryParams = parseQuery(parsedUrl.getQuery()); |
| 57 | + | if (queryParams.isEmpty() && defaultParameter.isPresent()) { |
| 58 | + | return ImmutableList.of( |
| 59 | + | request.toBuilder() |
| 60 | + | .setUrl( |
| 61 | + | assembleUrlWithQueries( |
| 62 | + | parsedUrl, |
| 63 | + | ImmutableList.of(HttpQueryParams.create(defaultParameter.get(), payload)))) |
| 64 | + | .build()); |
| 65 | + | } |
| 66 | + | return fuzzParams(queryParams, payload).stream() |
| 67 | + | .map(fuzzedParams -> assembleUrlWithQueries(parsedUrl, fuzzedParams)) |
| 68 | + | .map(fuzzedUrl -> request.toBuilder().setUrl(fuzzedUrl).build()) |
| 69 | + | .collect(toImmutableList()); |
| 70 | + | } |
| 71 | + | |
| 72 | + | private static ImmutableSet<ImmutableList<HttpQueryParams>> fuzzParams( |
| 73 | + | ImmutableList<HttpQueryParams> params, String payload) { |
| 74 | + | ImmutableSet.Builder<ImmutableList<HttpQueryParams>> fuzzedParamsbuilder = |
| 75 | + | ImmutableSet.builder(); |
| 76 | + | |
| 77 | + | for (int i = 0; i < params.size(); i++) { |
| 78 | + | List<HttpQueryParams> paramsWithPayload = new ArrayList<>(params); |
| 79 | + | paramsWithPayload.set(i, HttpQueryParams.create(params.get(i).name(), payload)); |
| 80 | + | fuzzedParamsbuilder.add(ImmutableList.copyOf(paramsWithPayload)); |
| 81 | + | } |
| 82 | + | |
| 83 | + | return fuzzedParamsbuilder.build(); |
| 84 | + | } |
| 85 | + | |
| 86 | + | private static ImmutableList<HttpQueryParams> parseQuery(String query) { |
| 87 | + | if (isNullOrEmpty(query)) { |
| 88 | + | return ImmutableList.of(); |
| 89 | + | } |
| 90 | + | ImmutableList.Builder<HttpQueryParams> queryParamsBuilder = ImmutableList.builder(); |
| 91 | + | for (String param : Splitter.on('&').split(query)) { |
| 92 | + | int equalPosition = param.indexOf("="); |
| 93 | + | if (equalPosition > -1) { |
| 94 | + | String name = param.substring(0, equalPosition); |
| 95 | + | String value = param.substring(equalPosition + 1); |
| 96 | + | queryParamsBuilder.add(HttpQueryParams.create(name, value)); |
| 97 | + | } else { |
| 98 | + | queryParamsBuilder.add(HttpQueryParams.create(param, "")); |
| 99 | + | } |
| 100 | + | } |
| 101 | + | return queryParamsBuilder.build(); |
| 102 | + | } |
| 103 | + | |
| 104 | + | private static String assembleUrlWithQueries( |
| 105 | + | URI parsedUrl, ImmutableList<HttpQueryParams> params) { |
| 106 | + | String query = assembleQueryParams(params); |
| 107 | + | StringBuilder urlBuilder = new StringBuilder(); |
| 108 | + | urlBuilder.append(parsedUrl.getScheme()).append("://").append(parsedUrl.getRawAuthority()); |
| 109 | + | if (!isNullOrEmpty(parsedUrl.getRawPath())) { |
| 110 | + | urlBuilder.append(parsedUrl.getRawPath()); |
| 111 | + | } |
| 112 | + | if (!isNullOrEmpty(query)) { |
| 113 | + | urlBuilder.append('?').append(query); |
| 114 | + | } |
| 115 | + | if (!isNullOrEmpty(parsedUrl.getRawFragment())) { |
| 116 | + | urlBuilder.append('#').append(parsedUrl.getRawFragment()); |
| 117 | + | } |
| 118 | + | return urlBuilder.toString(); |
| 119 | + | } |
| 120 | + | |
| 121 | + | private static String assembleQueryParams(ImmutableList<HttpQueryParams> params) { |
| 122 | + | return params.stream() |
| 123 | + | .map(param -> String.format("%s=%s", param.name(), param.value())) |
| 124 | + | .collect(joining("&")); |
| 125 | + | } |
| 126 | + | |
| 127 | + | @AutoValue |
| 128 | + | abstract static class HttpQueryParams { |
| 129 | + | abstract String name(); |
| 130 | + | |
| 131 | + | abstract String value(); |
| 132 | + | |
| 133 | + | public static HttpQueryParams create(String name, String value) { |
| 134 | + | return new AutoValue_FuzzingUtils_HttpQueryParams(name, value); |
| 135 | + | } |
| 136 | + | } |
| 137 | + | |
| 138 | + | private FuzzingUtils() {} |
| 139 | + | } |
| 140 | + | |