| skipped 1 lines |
2 | 2 | | |
3 | 3 | | import org.apache.commons.collections4.queue.CircularFifoQueue; |
4 | 4 | | |
5 | | - | import java.io.ByteArrayOutputStream; |
6 | | - | import java.io.IOException; |
7 | | - | import java.io.PrintWriter; |
8 | 5 | | import java.util.*; |
9 | 6 | | import java.util.concurrent.ThreadPoolExecutor; |
10 | 7 | | |
| skipped 63 lines |
74 | 71 | | } |
75 | 72 | | this.attack = new ParamAttack(req, type, paramGrabber, stop, config); |
76 | 73 | | } |
| 74 | + | |
| 75 | + | // Check for mutations |
| 76 | + | if (this.type == Utilities.PARAM_HEADER && config.getBoolean("identify smuggle mutations")) { |
| 77 | + | HeaderMutationGuesser mutationGuesser = new HeaderMutationGuesser(req, this.config); |
| 78 | + | ArrayList<String> mutations = mutationGuesser.guessMutations(); |
| 79 | + | this.attack.setHeaderMutations(mutations); |
| 80 | + | |
| 81 | + | // Report if required |
| 82 | + | if (mutations != null) { |
| 83 | + | mutationGuesser.reportMutations(mutations); |
| 84 | + | } |
| 85 | + | } |
| 86 | + | |
77 | 87 | | ArrayList<Attack> paramGuesses = guessParams(attack); |
78 | 88 | | if (!paramGuesses.isEmpty()) { |
79 | 89 | | Utilities.callbacks.addScanIssue(Utilities.reportReflectionIssue(paramGuesses.toArray((new Attack[paramGuesses.size()])), req, "", "")); |
| skipped 43 lines |
123 | 133 | | final HashMap<String, String> requestParams = state.getRequestParams(); |
124 | 134 | | final WordProvider bonusParams = state.getBonusParams(); |
125 | 135 | | final byte type = state.type; |
| 136 | + | ArrayList<String> headerMutations = state.getHeaderMutations(); |
126 | 137 | | |
127 | 138 | | ArrayList<Attack> attacks = new ArrayList<>(); |
128 | 139 | | int completedAttacks = 0; |
| skipped 30 lines |
159 | 170 | | } |
160 | 171 | | newParams.add(next); |
161 | 172 | | } |
162 | | - | } |
163 | | - | else { |
| 173 | + | } else { |
164 | 174 | | if (!config.getBoolean("bruteforce")) { |
165 | | - | Utilities.out("Completed attack on "+ targetURL); |
| 175 | + | Utilities.out("Completed attack on " + targetURL); |
166 | 176 | | if (taskEngine != null) { |
167 | 177 | | Utilities.out("Completed " + (taskEngine.getCompletedTaskCount() + 1) + "/" + (taskEngine.getTaskCount())); |
168 | 178 | | } |
| skipped 8 lines |
177 | 187 | | ArrayList<String> candidates; |
178 | 188 | | try { |
179 | 189 | | candidates = paramBuckets.pop(); |
180 | | - | } |
181 | | - | catch (NoSuchElementException e) { |
| 190 | + | Iterator<String> iterator = candidates.iterator(); |
| 191 | + | } catch (NoSuchElementException e) { |
182 | 192 | | continue; |
183 | 193 | | } |
184 | 194 | | |
| skipped 10 lines |
195 | 205 | | } |
196 | 206 | | |
197 | 207 | | String submission = String.join("|", candidates); |
198 | | - | Attack paramGuess = injector.probeAttack(submission); |
199 | | - | |
200 | | - | if (!candidates.contains("~")) { |
201 | | - | if (findPersistent(baseRequestResponse, paramGuess, attackID, state.recentParams, candidates, state.alreadyReported)) { |
202 | | - | state.updateBaseline(); |
203 | | - | } |
204 | | - | state.recentParams.addAll(candidates); // fixme this results in params being found multiple times |
| 208 | + | if (headerMutations == null) { |
| 209 | + | headerMutations = new ArrayList<String>(); |
205 | 210 | | } |
206 | 211 | | |
207 | | - | Attack localBase; |
208 | | - | if (submission.contains("~")) { |
209 | | - | localBase = new Attack(); |
210 | | - | localBase.addAttack(base); |
| 212 | + | // Ensure that the identity mutation is scanned |
| 213 | + | if (headerMutations.size() == 0 || headerMutations.get(0) != null) { |
| 214 | + | headerMutations.add(0, null); |
211 | 215 | | } |
212 | | - | else { |
213 | | - | localBase = base; |
214 | | - | } |
| 216 | + | Iterator<String> iterator = headerMutations.iterator(); |
| 217 | + | while (iterator.hasNext()) { |
| 218 | + | String mutation = iterator.next(); |
| 219 | + | Attack paramGuess = injector.probeAttack(submission, mutation); |
215 | 220 | | |
216 | | - | if (!Utilities.globalSettings.getBoolean("carpet bomb") && !Utilities.similar(localBase, paramGuess)) { |
217 | | - | Attack confirmParamGuess = injector.probeAttack(submission); |
| 221 | + | if (!candidates.contains("~")) { |
| 222 | + | if (findPersistent(baseRequestResponse, paramGuess, attackID, state.recentParams, candidates, state.alreadyReported)) { |
| 223 | + | state.updateBaseline(); |
| 224 | + | } |
| 225 | + | state.recentParams.addAll(candidates); // fixme this results in params being found multiple times |
| 226 | + | } |
218 | 227 | | |
219 | | - | Attack failAttack = injector.probeAttack(Keysmith.permute(submission)); |
| 228 | + | Attack localBase; |
| 229 | + | if (submission.contains("~")) { |
| 230 | + | localBase = new Attack(); |
| 231 | + | localBase.addAttack(base); |
| 232 | + | } else { |
| 233 | + | localBase = base; |
| 234 | + | } |
220 | 235 | | |
221 | | - | // this to prevent error messages obscuring persistent inputs |
222 | | - | findPersistent(baseRequestResponse, failAttack, attackID, state.recentParams, null, state.alreadyReported); |
| 236 | + | if (!Utilities.globalSettings.getBoolean("carpet bomb") && !Utilities.similar(localBase, paramGuess)) { |
| 237 | + | Attack confirmParamGuess = injector.probeAttack(submission, mutation); |
223 | 238 | | |
224 | | - | localBase.addAttack(failAttack); |
225 | | - | if (!Utilities.similar(localBase, confirmParamGuess)) { |
| 239 | + | Attack failAttack = injector.probeAttack(Keysmith.permute(submission), mutation); |
226 | 240 | | |
227 | | - | if(candidates.size() > 1) { |
228 | | - | Utilities.log("Splitting "+ submission); |
229 | | - | ArrayList<String> left = new ArrayList<>(candidates.subList(0, candidates.size() / 2)); |
230 | | - | Utilities.log("Got "+String.join("|",left)); |
231 | | - | ArrayList<String> right = new ArrayList<>(candidates.subList(candidates.size() / 2, candidates.size())); |
232 | | - | Utilities.log("Got "+String.join("|",right)); |
233 | | - | paramBuckets.push(left); |
234 | | - | paramBuckets.push(right); |
235 | | - | } |
236 | | - | else { |
237 | | - | if (state.alreadyReported.contains(submission)) { |
238 | | - | continue; |
239 | | - | } |
| 241 | + | // this to prevent error messages obscuring persistent inputs |
| 242 | + | findPersistent(baseRequestResponse, failAttack, attackID, state.recentParams, null, state.alreadyReported); |
| 243 | + | localBase.addAttack(failAttack); |
240 | 244 | | |
241 | | - | Attack WAFCatcher = new Attack(Utilities.attemptRequest(service, Utilities.addOrReplaceHeader(baseRequestResponse.getRequest(), "junk-header", submission))); |
242 | | - | WAFCatcher.addAttack(new Attack(Utilities.attemptRequest(service, Utilities.addOrReplaceHeader(baseRequestResponse.getRequest(), "junk-head", submission)))); |
243 | | - | if (!Utilities.similar(WAFCatcher, confirmParamGuess)){ |
244 | | - | Probe validParam = new Probe("Found unlinked param: " + submission, 4, submission); |
245 | | - | validParam.setEscapeStrings(Keysmith.permute(submission), Keysmith.permute(submission, false)); |
246 | | - | validParam.setRandomAnchor(false); |
247 | | - | validParam.setPrefix(Probe.REPLACE); |
248 | | - | ArrayList<Attack> confirmed = injector.fuzz(localBase, validParam); |
249 | | - | if (!confirmed.isEmpty()) { |
250 | | - | state.alreadyReported.add(submission); |
251 | | - | Utilities.reportedParams.add(submission); |
252 | | - | Utilities.out("Identified parameter on "+targetURL + ": " + submission); |
| 245 | + | if (!Utilities.similar(localBase, confirmParamGuess)) { |
| 246 | + | if (candidates.size() > 1) { |
| 247 | + | Utilities.log("Splitting " + submission); |
| 248 | + | ArrayList<String> left = new ArrayList<>(candidates.subList(0, candidates.size() / 2)); |
| 249 | + | Utilities.log("Got " + String.join("|", left)); |
| 250 | + | ArrayList<String> right = new ArrayList<>(candidates.subList(candidates.size() / 2, candidates.size())); |
| 251 | + | Utilities.log("Got " + String.join("|", right)); |
| 252 | + | paramBuckets.push(left); |
| 253 | + | paramBuckets.push(right); |
| 254 | + | } else { |
| 255 | + | if (state.alreadyReported.contains(submission)) { |
| 256 | + | Utilities.out("Ignoring reporting of submission " + submission + " using mutation " + mutation + " as already reported."); |
| 257 | + | continue; |
| 258 | + | } |
253 | 259 | | |
254 | | - | boolean cacheSuccess = false; |
255 | | - | if (type == Utilities.PARAM_HEADER || type == IParameter.PARAM_COOKIE) { |
256 | | - | cacheSuccess = cachePoison(injector, submission, failAttack.getFirstRequest()); |
257 | | - | } |
| 260 | + | Attack WAFCatcher = new Attack(Utilities.attemptRequest(service, Utilities.addOrReplaceHeader(baseRequestResponse.getRequest(), "junk-header", submission))); |
| 261 | + | WAFCatcher.addAttack(new Attack(Utilities.attemptRequest(service, Utilities.addOrReplaceHeader(baseRequestResponse.getRequest(), "junk-head", submission)))); |
| 262 | + | if (!Utilities.similar(WAFCatcher, confirmParamGuess)) { |
| 263 | + | Probe validParam = new Probe("Found unlinked param: " + submission, 4, submission); |
| 264 | + | validParam.setEscapeStrings(Keysmith.permute(submission), Keysmith.permute(submission, false)); |
| 265 | + | validParam.setRandomAnchor(false); |
| 266 | + | validParam.setPrefix(Probe.REPLACE); |
| 267 | + | ArrayList<Attack> confirmed = injector.fuzz(localBase, validParam, mutation); |
| 268 | + | if (!confirmed.isEmpty()) { |
| 269 | + | state.alreadyReported.add(submission); |
| 270 | + | Utilities.reportedParams.add(submission); |
| 271 | + | Utilities.out("Identified parameter on " + targetURL + ": " + submission); |
258 | 272 | | |
259 | | - | if (!Utilities.globalSettings.getBoolean("poison only")) { |
260 | | - | String title = "Secret input: " + Utilities.getNameFromType(type); |
261 | | - | if (!cacheSuccess && canSeeCache(paramGuess.getFirstRequest().getResponse())) { |
262 | | - | title = "Secret uncached input: " + Utilities.getNameFromType(type); |
263 | | - | } |
264 | | - | if (Utilities.globalSettings.getBoolean("name in issue")) { |
265 | | - | title += ": " + submission.split("~")[0]; |
| 273 | + | boolean cacheSuccess = false; |
| 274 | + | if (type == Utilities.PARAM_HEADER || type == IParameter.PARAM_COOKIE) { |
| 275 | + | cacheSuccess = cachePoison(injector, submission, failAttack.getFirstRequest()); |
266 | 276 | | } |
267 | | - | Utilities.callbacks.addScanIssue(Utilities.reportReflectionIssue(confirmed.toArray(new Attack[2]), baseRequestResponse, title, "Unlinked parameter identified.")); |
| 277 | + | if (!Utilities.globalSettings.getBoolean("poison only")) { |
| 278 | + | String title = "Secret input: " + Utilities.getNameFromType(type); |
| 279 | + | if (!cacheSuccess && canSeeCache(paramGuess.getFirstRequest().getResponse())) { |
| 280 | + | title = "Secret uncached input: " + Utilities.getNameFromType(type); |
| 281 | + | } |
| 282 | + | if (Utilities.globalSettings.getBoolean("name in issue")) { |
| 283 | + | title += ": " + submission.split("~")[0]; |
| 284 | + | } |
| 285 | + | Utilities.callbacks.addScanIssue(Utilities.reportReflectionIssue(confirmed.toArray(new Attack[2]), baseRequestResponse, title, "Unlinked parameter identified.")); |
| 286 | + | if (type != Utilities.PARAM_HEADER || Utilities.containsBytes(paramGuess.getFirstRequest().getResponse(), staticCanary)) { |
| 287 | + | scanParam(insertionPoint, injector, submission.split("~", 2)[0]); |
| 288 | + | } |
268 | 289 | | |
269 | | - | if (type != Utilities.PARAM_HEADER || Utilities.containsBytes(paramGuess.getFirstRequest().getResponse(), staticCanary)) { |
270 | | - | scanParam(insertionPoint, injector, submission.split("~", 2)[0]); |
| 290 | + | base = state.updateBaseline(); |
271 | 291 | | } |
272 | 292 | | |
273 | | - | base = state.updateBaseline(); |
274 | | - | } |
| 293 | + | //Utilities.callbacks.doPassiveScan(service.getHost(), service.getPort(), service.getProtocol().equals("https"), paramGuess.getFirstRequest().getRequest(), paramGuess.getFirstRequest().getResponse()); |
275 | 294 | | |
276 | | - | //Utilities.callbacks.doPassiveScan(service.getHost(), service.getPort(), service.getProtocol().equals("https"), paramGuess.getFirstRequest().getRequest(), paramGuess.getFirstRequest().getResponse()); |
277 | | - | |
278 | | - | if (config.getBoolean("dynamic keyload")) { |
279 | | - | ArrayList<String> newWords = new ArrayList<>(Keysmith.getWords(Utilities.helpers.bytesToString(paramGuess.getFirstRequest().getResponse()))); |
280 | | - | addNewKeys(newWords, state, bucketSize, paramBuckets, candidates, paramGuess); |
| 295 | + | if (config.getBoolean("dynamic keyload")) { |
| 296 | + | ArrayList<String> newWords = new ArrayList<>(Keysmith.getWords(Utilities.helpers.bytesToString(paramGuess.getFirstRequest().getResponse()))); |
| 297 | + | addNewKeys(newWords, state, bucketSize, paramBuckets, candidates, paramGuess); |
| 298 | + | } |
| 299 | + | } else { |
| 300 | + | Utilities.out(targetURL + " questionable parameter: " + candidates); |
281 | 301 | | } |
282 | | - | } else { |
283 | | - | Utilities.out(targetURL + " questionable parameter: " + candidates); |
284 | 302 | | } |
285 | 303 | | } |
| 304 | + | } else{ |
| 305 | + | Utilities.log(targetURL + " couldn't replicate: " + candidates); |
| 306 | + | base.addAttack(paramGuess); |
286 | 307 | | } |
287 | | - | } else { |
288 | | - | Utilities.log(targetURL + " couldn't replicate: " + candidates); |
289 | | - | base.addAttack(paramGuess); |
290 | | - | } |
291 | 308 | | |
292 | | - | if(config.getBoolean("dynamic keyload")) { |
293 | | - | addNewKeys(Keysmith.getAllKeys(paramGuess.getFirstRequest().getResponse(), requestParams), state, bucketSize, paramBuckets, candidates, paramGuess); |
294 | | - | } |
| 309 | + | if (config.getBoolean("dynamic keyload")) { |
| 310 | + | addNewKeys(Keysmith.getAllKeys(paramGuess.getFirstRequest().getResponse(), requestParams), state, bucketSize, paramBuckets, candidates, paramGuess); |
| 311 | + | } |
295 | 312 | | |
296 | | - | } else if (tryMethodFlip) { |
297 | | - | Attack paramGrab = new Attack(Utilities.callbacks.makeHttpRequest(service, invertedBase)); |
298 | | - | findPersistent(baseRequestResponse, paramGrab, attackID, state.recentParams, null, state.alreadyReported); |
299 | | - | |
300 | | - | if (!Utilities.similar(altBase, paramGrab)) { |
301 | | - | Utilities.log("Potential GETbase param: " + candidates); |
302 | | - | injector.probeAttack(Keysmith.permute(submission)); |
303 | | - | altBase.addAttack(new Attack(Utilities.callbacks.makeHttpRequest(service, invertedBase))); |
304 | | - | injector.probeAttack(submission); |
| 313 | + | } else if (tryMethodFlip) { |
| 314 | + | Attack paramGrab = new Attack(Utilities.callbacks.makeHttpRequest(service, invertedBase)); |
| 315 | + | findPersistent(baseRequestResponse, paramGrab, attackID, state.recentParams, null, state.alreadyReported); |
305 | 316 | | |
306 | | - | paramGrab = new Attack(Utilities.callbacks.makeHttpRequest(service, invertedBase)); |
307 | 317 | | if (!Utilities.similar(altBase, paramGrab)) { |
| 318 | + | Utilities.log("Potential GETbase param: " + candidates); |
| 319 | + | injector.probeAttack(Keysmith.permute(submission), mutation); |
| 320 | + | altBase.addAttack(new Attack(Utilities.callbacks.makeHttpRequest(service, invertedBase))); |
| 321 | + | injector.probeAttack(submission, mutation); |
308 | 322 | | |
309 | | - | if(candidates.size() > 1) { |
310 | | - | Utilities.log("Splitting "+ submission); |
311 | | - | ArrayList<String> left = new ArrayList<>(candidates.subList(0, candidates.size() / 2)); |
312 | | - | ArrayList<String> right = new ArrayList<>(candidates.subList(candidates.size() / 2 + 1, candidates.size())); |
313 | | - | paramBuckets.push(left); |
314 | | - | paramBuckets.push(right); |
315 | | - | } |
316 | | - | else { |
317 | | - | Utilities.out("Confirmed GETbase param: " + candidates); |
318 | | - | IHttpRequestResponse[] evidence = new IHttpRequestResponse[3]; |
319 | | - | evidence[0] = altBase.getFirstRequest(); |
320 | | - | evidence[1] = paramGuess.getFirstRequest(); |
321 | | - | evidence[2] = paramGrab.getFirstRequest(); |
322 | | - | Utilities.callbacks.addScanIssue(new CustomScanIssue(service, Utilities.getURL(baseRequestResponse), evidence, "Secret parameter", "Parameter name: '" + candidates + "'. Review the three requests attached in chronological order.", "Medium", "Tentative", "Investigate")); |
| 323 | + | paramGrab = new Attack(Utilities.callbacks.makeHttpRequest(service, invertedBase)); |
| 324 | + | if (!Utilities.similar(altBase, paramGrab)) { |
323 | 325 | | |
324 | | - | altBase = new Attack(Utilities.callbacks.makeHttpRequest(service, invertedBase)); |
325 | | - | altBase.addAttack(new Attack(Utilities.callbacks.makeHttpRequest(service, invertedBase))); |
326 | | - | altBase.addAttack(new Attack(Utilities.callbacks.makeHttpRequest(service, invertedBase))); |
327 | | - | altBase.addAttack(new Attack(Utilities.callbacks.makeHttpRequest(service, invertedBase))); |
| 326 | + | if (candidates.size() > 1) { |
| 327 | + | Utilities.log("Splitting " + submission); |
| 328 | + | ArrayList<String> left = new ArrayList<>(candidates.subList(0, candidates.size() / 2)); |
| 329 | + | ArrayList<String> right = new ArrayList<>(candidates.subList(candidates.size() / 2 + 1, candidates.size())); |
| 330 | + | paramBuckets.push(left); |
| 331 | + | paramBuckets.push(right); |
| 332 | + | } else { |
| 333 | + | Utilities.out("Confirmed GETbase param: " + candidates); |
| 334 | + | IHttpRequestResponse[] evidence = new IHttpRequestResponse[3]; |
| 335 | + | evidence[0] = altBase.getFirstRequest(); |
| 336 | + | evidence[1] = paramGuess.getFirstRequest(); |
| 337 | + | evidence[2] = paramGrab.getFirstRequest(); |
| 338 | + | Utilities.callbacks.addScanIssue(new CustomScanIssue(service, Utilities.getURL(baseRequestResponse), evidence, "Secret parameter", "Parameter name: '" + candidates + "'. Review the three requests attached in chronological order.", "Medium", "Tentative", "Investigate")); |
| 339 | + | |
| 340 | + | altBase = new Attack(Utilities.callbacks.makeHttpRequest(service, invertedBase)); |
| 341 | + | altBase.addAttack(new Attack(Utilities.callbacks.makeHttpRequest(service, invertedBase))); |
| 342 | + | altBase.addAttack(new Attack(Utilities.callbacks.makeHttpRequest(service, invertedBase))); |
| 343 | + | altBase.addAttack(new Attack(Utilities.callbacks.makeHttpRequest(service, invertedBase))); |
| 344 | + | } |
328 | 345 | | } |
329 | 346 | | } |
330 | 347 | | } |
| skipped 427 lines |