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 static com.google.common.truth.Truth.assertThat; |
19 | | - | import static org.junit.Assert.assertFalse; |
20 | | - | import static org.junit.Assert.assertThrows; |
21 | | - | import static org.junit.Assert.assertTrue; |
22 | | - | |
23 | | - | import com.google.inject.Guice; |
24 | | - | import com.google.protobuf.ByteString; |
25 | | - | import com.google.tsunami.common.net.http.HttpClientModule; |
26 | | - | import com.google.tsunami.plugin.payload.testing.FakePayloadGeneratorModule; |
27 | | - | import com.google.tsunami.plugin.payload.testing.PayloadTestHelper; |
28 | | - | import com.google.tsunami.proto.PayloadGeneratorConfig; |
29 | | - | import java.io.IOException; |
30 | | - | import java.security.SecureRandom; |
31 | | - | import java.util.Arrays; |
32 | | - | import javax.inject.Inject; |
33 | | - | import okhttp3.mockwebserver.MockWebServer; |
34 | | - | import org.junit.Before; |
35 | | - | import org.junit.Test; |
36 | | - | import org.junit.runner.RunWith; |
37 | | - | import org.junit.runners.JUnit4; |
38 | | - | |
39 | | - | /** Tests for {@link PayloadGenerator}. */ |
40 | | - | @RunWith(JUnit4.class) |
41 | | - | public final class PayloadGeneratorTest { |
42 | | - | |
43 | | - | @Inject private PayloadGenerator payloadGenerator; |
44 | | - | |
45 | | - | private MockWebServer mockCallbackServer; |
46 | | - | private final SecureRandom testSecureRandom = |
47 | | - | new SecureRandom() { |
48 | | - | @Override |
49 | | - | public void nextBytes(byte[] bytes) { |
50 | | - | Arrays.fill(bytes, (byte) 0xFF); |
51 | | - | } |
52 | | - | }; |
53 | | - | private static final PayloadGeneratorConfig LINUX_REFLECTIVE_RCE_CONFIG = |
54 | | - | PayloadGeneratorConfig.newBuilder() |
55 | | - | .setVulnerabilityType(PayloadGeneratorConfig.VulnerabilityType.REFLECTIVE_RCE) |
56 | | - | .setInterpretationEnvironment( |
57 | | - | PayloadGeneratorConfig.InterpretationEnvironment.LINUX_SHELL) |
58 | | - | .setExecutionEnvironment( |
59 | | - | PayloadGeneratorConfig.ExecutionEnvironment.EXEC_INTERPRETATION_ENVIRONMENT) |
60 | | - | .build(); |
61 | | - | private static final PayloadGeneratorConfig JAVA_REFLECTIVE_RCE_CONFIG = |
62 | | - | PayloadGeneratorConfig.newBuilder() |
63 | | - | .setVulnerabilityType(PayloadGeneratorConfig.VulnerabilityType.REFLECTIVE_RCE) |
64 | | - | .setInterpretationEnvironment(PayloadGeneratorConfig.InterpretationEnvironment.JAVA) |
65 | | - | .setExecutionEnvironment( |
66 | | - | PayloadGeneratorConfig.ExecutionEnvironment.EXEC_INTERPRETATION_ENVIRONMENT) |
67 | | - | .build(); |
68 | | - | private static final PayloadGeneratorConfig ANY_SSRF_CONFIG = |
69 | | - | PayloadGeneratorConfig.newBuilder() |
70 | | - | .setVulnerabilityType(PayloadGeneratorConfig.VulnerabilityType.SSRF) |
71 | | - | .setInterpretationEnvironment( |
72 | | - | PayloadGeneratorConfig.InterpretationEnvironment.INTERPRETATION_ANY) |
73 | | - | .setExecutionEnvironment(PayloadGeneratorConfig.ExecutionEnvironment.EXEC_ANY) |
74 | | - | .build(); |
75 | | - | private static final String CORRECT_PRINTF = |
76 | | - | "printf %s%s%s TSUNAMI_PAYLOAD_START ffffffffffffffff TSUNAMI_PAYLOAD_END"; |
77 | | - | |
78 | | - | @Before |
79 | | - | public void setUp() throws IOException { |
80 | | - | mockCallbackServer = new MockWebServer(); |
81 | | - | mockCallbackServer.start(); |
82 | | - | Guice.createInjector( |
83 | | - | new HttpClientModule.Builder().build(), |
84 | | - | FakePayloadGeneratorModule.builder() |
85 | | - | .setCallbackServer(mockCallbackServer) |
86 | | - | .setSecureRng(testSecureRandom) |
87 | | - | .build()) |
88 | | - | .injectMembers(this); |
89 | | - | } |
90 | | - | |
91 | | - | @Test |
92 | | - | public void isCallbackServerEnabled_withConfiguredCallbackServer_returnsTrue() { |
93 | | - | assertTrue(payloadGenerator.isCallbackServerEnabled()); |
94 | | - | } |
95 | | - | |
96 | | - | @Test |
97 | | - | public void isCallbackServerEnabled_withUnconfiguredCallbackServer_returnsFalse() { |
98 | | - | // Replace PayloadGenerator with a version without a configured callback server |
99 | | - | Guice.createInjector( |
100 | | - | new HttpClientModule.Builder().build(), |
101 | | - | FakePayloadGeneratorModule.builder().setSecureRng(testSecureRandom).build()) |
102 | | - | .injectMembers(this); |
103 | | - | |
104 | | - | assertFalse(payloadGenerator.isCallbackServerEnabled()); |
105 | | - | } |
106 | | - | |
107 | | - | @Test |
108 | | - | public void generate_withLinuxConfiguration_andCallbackServer_returnsCurlPayload() { |
109 | | - | Payload payload = |
110 | | - | payloadGenerator.generate( |
111 | | - | LINUX_REFLECTIVE_RCE_CONFIG.toBuilder().setUseCallbackServer(true).build()); |
112 | | - | |
113 | | - | assertThat(payload.getPayload()).contains("curl"); |
114 | | - | assertThat(payload.getPayload()).contains(mockCallbackServer.getHostName()); |
115 | | - | assertThat(payload.getPayload()).contains(Integer.toString(mockCallbackServer.getPort(), 10)); |
116 | | - | assertTrue(payload.getPayloadAttributes().getUsesCallbackServer()); |
117 | | - | } |
118 | | - | |
119 | | - | @Test |
120 | | - | public void |
121 | | - | checkIfExecuted_withLinuxConfiguration_andCallbackServer_andExecutedCallbackUrl_returnsTrue() |
122 | | - | throws IOException { |
123 | | - | |
124 | | - | mockCallbackServer.enqueue(PayloadTestHelper.generateMockSuccessfulCallbackResponse()); |
125 | | - | Payload payload = |
126 | | - | payloadGenerator.generate( |
127 | | - | LINUX_REFLECTIVE_RCE_CONFIG.toBuilder().setUseCallbackServer(true).build()); |
128 | | - | |
129 | | - | assertTrue(payload.checkIfExecuted()); |
130 | | - | } |
131 | | - | |
132 | | - | @Test |
133 | | - | public void |
134 | | - | checkIfExecuted_withLinuxConfiguration_andCallbackServer_andNotExecutedCallbackUrl_returnsFalse() { |
135 | | - | |
136 | | - | mockCallbackServer.enqueue(PayloadTestHelper.generateMockUnsuccessfulCallbackResponse()); |
137 | | - | Payload payload = |
138 | | - | payloadGenerator.generate( |
139 | | - | LINUX_REFLECTIVE_RCE_CONFIG.toBuilder().setUseCallbackServer(true).build()); |
140 | | - | |
141 | | - | assertFalse(payload.checkIfExecuted()); |
142 | | - | } |
143 | | - | |
144 | | - | @Test |
145 | | - | public void getPayload_withLinuxConfiguration_andNoCallbackServer_returnsPrintfPayload() { |
146 | | - | Payload payload = |
147 | | - | payloadGenerator.generate( |
148 | | - | LINUX_REFLECTIVE_RCE_CONFIG.toBuilder().setUseCallbackServer(false).build()); |
149 | | - | |
150 | | - | assertThat(payload.getPayload()).contains(CORRECT_PRINTF); |
151 | | - | assertFalse(payload.getPayloadAttributes().getUsesCallbackServer()); |
152 | | - | } |
153 | | - | |
154 | | - | @Test |
155 | | - | public void |
156 | | - | getPayload_withLinuxConfiguration_andUnconfiguredCallbackServer_returnsPrintfPayload() { |
157 | | - | |
158 | | - | // Replace PayloadGenerator with a version without a configured callback server |
159 | | - | Guice.createInjector( |
160 | | - | new HttpClientModule.Builder().build(), |
161 | | - | FakePayloadGeneratorModule.builder().setSecureRng(testSecureRandom).build()) |
162 | | - | .injectMembers(this); |
163 | | - | |
164 | | - | Payload payload = |
165 | | - | payloadGenerator.generate( |
166 | | - | LINUX_REFLECTIVE_RCE_CONFIG.toBuilder().setUseCallbackServer(true).build()); |
167 | | - | |
168 | | - | assertThat(payload.getPayload()).contains(CORRECT_PRINTF); |
169 | | - | assertFalse(payload.getPayloadAttributes().getUsesCallbackServer()); |
170 | | - | } |
171 | | - | |
172 | | - | @Test |
173 | | - | public void |
174 | | - | checkIfExecuted_withLinuxConfiguration_andNoCallbackServer_andCorrectInput_returnsTrue() { |
175 | | - | Payload payload = |
176 | | - | payloadGenerator.generate( |
177 | | - | LINUX_REFLECTIVE_RCE_CONFIG.toBuilder().setUseCallbackServer(false).build()); |
178 | | - | |
179 | | - | assertTrue( |
180 | | - | payload.checkIfExecuted( |
181 | | - | ByteString.copyFromUtf8( |
182 | | - | "RANDOMOUTPUTTSUNAMI_PAYLOAD_STARTffffffffffffffffTSUNAMI_PAYLOAD_END"))); |
183 | | - | } |
184 | | - | |
185 | | - | @Test |
186 | | - | public void |
187 | | - | checkIfExecuted_withLinuxConfiguration_andNoCallbackServer_andIncorectInput_returnsFalse() { |
188 | | - | Payload payload = |
189 | | - | payloadGenerator.generate( |
190 | | - | LINUX_REFLECTIVE_RCE_CONFIG.toBuilder().setUseCallbackServer(false).build()); |
191 | | - | |
192 | | - | assertFalse(payload.checkIfExecuted(ByteString.copyFromUtf8(CORRECT_PRINTF))); |
193 | | - | } |
194 | | - | |
195 | | - | @Test |
196 | | - | public void getPayload_withJavaConfiguration_andNoCallbackServer_returnsPrintfPayload() { |
197 | | - | Payload payload = payloadGenerator.generate(JAVA_REFLECTIVE_RCE_CONFIG); |
198 | | - | |
199 | | - | assertThat(payload.getPayload()) |
200 | | - | .contains( |
201 | | - | "String.format(\"%s%s%s\", \"TSUNAMI_PAYLOAD_START\", \"ffffffffffffffff\"," |
202 | | - | + " \"TSUNAMI_PAYLOAD_END\")"); |
203 | | - | assertFalse(payload.getPayloadAttributes().getUsesCallbackServer()); |
204 | | - | } |
205 | | - | |
206 | | - | @Test |
207 | | - | public void |
208 | | - | checkIfExecuted_withJavaConfiguration_andNoCallbackServer_andCorrectInput_returnsTrue() { |
209 | | - | Payload payload = payloadGenerator.generate(JAVA_REFLECTIVE_RCE_CONFIG); |
210 | | - | |
211 | | - | assertTrue( |
212 | | - | payload.checkIfExecuted( |
213 | | - | ByteString.copyFromUtf8( |
214 | | - | "RANDOMOUTPUTTSUNAMI_PAYLOAD_STARTffffffffffffffffTSUNAMI_PAYLOAD_END"))); |
215 | | - | } |
216 | | - | |
217 | | - | @Test |
218 | | - | public void |
219 | | - | checkIfExecuted_withJavaConfiguration_andNoCallbackServer_andIncorrectInput_returnsFalse() { |
220 | | - | Payload payload = payloadGenerator.generate(JAVA_REFLECTIVE_RCE_CONFIG); |
221 | | - | |
222 | | - | assertFalse( |
223 | | - | payload.checkIfExecuted( |
224 | | - | ByteString.copyFromUtf8("TSUNAMI_PAYLOAD_START ffffffffffffffff TSUNAMI_PAYLOAD_END"))); |
225 | | - | } |
226 | | - | |
227 | | - | @Test |
228 | | - | public void getPayload_withSsrfConfiguration_andCallbackServer_returnsCallbackUrl() { |
229 | | - | Payload payload = |
230 | | - | payloadGenerator.generate(ANY_SSRF_CONFIG.toBuilder().setUseCallbackServer(true).build()); |
231 | | - | |
232 | | - | assertTrue(payload.getPayloadAttributes().getUsesCallbackServer()); |
233 | | - | assertThat(payload.getPayload()).contains(mockCallbackServer.getHostName()); |
234 | | - | assertThat(payload.getPayload()).contains(Integer.toString(mockCallbackServer.getPort(), 10)); |
235 | | - | } |
236 | | - | |
237 | | - | @Test |
238 | | - | public void checkIfExecuted_withSsrfConfiguration_andCallbackServer_andExecutedUrl_returnsTrue() |
239 | | - | throws IOException { |
240 | | - | mockCallbackServer.enqueue(PayloadTestHelper.generateMockSuccessfulCallbackResponse()); |
241 | | - | Payload payload = |
242 | | - | payloadGenerator.generate(ANY_SSRF_CONFIG.toBuilder().setUseCallbackServer(true).build()); |
243 | | - | |
244 | | - | assertTrue(payload.checkIfExecuted()); |
245 | | - | } |
246 | | - | |
247 | | - | @Test |
248 | | - | public void getPayload_withSsrfConfiguration_andCallbackServer_andNotExecutedUrl_returnsFalse() { |
249 | | - | mockCallbackServer.enqueue(PayloadTestHelper.generateMockUnsuccessfulCallbackResponse()); |
250 | | - | Payload payload = |
251 | | - | payloadGenerator.generate(ANY_SSRF_CONFIG.toBuilder().setUseCallbackServer(true).build()); |
252 | | - | |
253 | | - | assertFalse(payload.checkIfExecuted()); |
254 | | - | } |
255 | | - | |
256 | | - | @Test |
257 | | - | public void |
258 | | - | checkIfExecuted_withSsrfConfiguration_andNoCallbackServer_andCorrectInput_returnsTrue() { |
259 | | - | Payload payload = |
260 | | - | payloadGenerator.generate(ANY_SSRF_CONFIG.toBuilder().setUseCallbackServer(false).build()); |
261 | | - | |
262 | | - | assertTrue(payload.checkIfExecuted("<title>Error 404 (Not Found)!!1</title>")); |
263 | | - | } |
264 | | - | |
265 | | - | @Test |
266 | | - | public void |
267 | | - | checkIfExecuted_withSsrfConfiguration_andNoCallbackServer_andIncorrectInput_returnsFalse() { |
268 | | - | Payload payload = |
269 | | - | payloadGenerator.generate(ANY_SSRF_CONFIG.toBuilder().setUseCallbackServer(false).build()); |
270 | | - | |
271 | | - | assertFalse(payload.checkIfExecuted("404 not found")); |
272 | | - | } |
273 | | - | |
274 | | - | @Test |
275 | | - | public void generate_withoutVulnerabilityType_throwsNotImplementedException() { |
276 | | - | assertThrows( |
277 | | - | NotImplementedException.class, |
278 | | - | () -> |
279 | | - | payloadGenerator.generate( |
280 | | - | PayloadGeneratorConfig.newBuilder() |
281 | | - | .setInterpretationEnvironment( |
282 | | - | PayloadGeneratorConfig.InterpretationEnvironment.LINUX_SHELL) |
283 | | - | .setExecutionEnvironment( |
284 | | - | PayloadGeneratorConfig.ExecutionEnvironment.EXEC_INTERPRETATION_ENVIRONMENT) |
285 | | - | .build())); |
286 | | - | } |
287 | | - | |
288 | | - | @Test |
289 | | - | public void generate_withoutInterpretationEnvironment_throwsNotImplementedException() { |
290 | | - | assertThrows( |
291 | | - | NotImplementedException.class, |
292 | | - | () -> |
293 | | - | payloadGenerator.generate( |
294 | | - | PayloadGeneratorConfig.newBuilder() |
295 | | - | .setVulnerabilityType(PayloadGeneratorConfig.VulnerabilityType.REFLECTIVE_RCE) |
296 | | - | .setExecutionEnvironment( |
297 | | - | PayloadGeneratorConfig.ExecutionEnvironment.EXEC_INTERPRETATION_ENVIRONMENT) |
298 | | - | .build())); |
299 | | - | } |
300 | | - | |
301 | | - | @Test |
302 | | - | public void generate_withoutExecutionEnvironment_throwsNotImplementedException() { |
303 | | - | assertThrows( |
304 | | - | NotImplementedException.class, |
305 | | - | () -> |
306 | | - | payloadGenerator.generate( |
307 | | - | PayloadGeneratorConfig.newBuilder() |
308 | | - | .setVulnerabilityType(PayloadGeneratorConfig.VulnerabilityType.REFLECTIVE_RCE) |
309 | | - | .setInterpretationEnvironment( |
310 | | - | PayloadGeneratorConfig.InterpretationEnvironment.LINUX_SHELL) |
311 | | - | .build())); |
312 | | - | } |
313 | | - | |
314 | | - | @Test |
315 | | - | public void generate_withoutConfig_throwsNotImplementedException() { |
316 | | - | assertThrows( |
317 | | - | NotImplementedException.class, |
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())); |
352 | | - | } |
353 | | - | } |
354 | | - | |