|
| 1 | +/** |
| 2 | + * Contributed by Eiliya Keshtkar (https://www.hackmelocal.com/) |
| 3 | + * @author Eiliya Keshtkar <eiliyakeshtkar0@gmail.com> |
| 4 | + */ |
| 5 | +var HttpSender = Java.type("org.parosproxy.paros.network.HttpSender"); |
| 6 | +var HttpMessage = Java.type("org.parosproxy.paros.network.HttpMessage"); |
| 7 | +var URI = Java.type("org.apache.commons.httpclient.URI"); |
| 8 | +var Model = Java.type("org.parosproxy.paros.model.Model"); |
| 9 | + |
| 10 | +// === Helper: Pretty Divider === |
| 11 | +function divider(label) { |
| 12 | + print("\n" + "-".repeat(60)); |
| 13 | + if (label) print("Target: " + label); |
| 14 | + print("-".repeat(60)); |
| 15 | +} |
| 16 | + |
| 17 | +// === Helper: Send GET Request with Original Headers === |
| 18 | +function sendGetWithOriginalHeaders(originalReqHeader, url) { |
| 19 | + try { |
| 20 | + var uri = new URI(url, true); |
| 21 | + var msg = new HttpMessage(); |
| 22 | + msg.getRequestHeader().setMethod("GET"); |
| 23 | + msg.getRequestHeader().setURI(uri); |
| 24 | + |
| 25 | + // Copy all request headers except Host & Content-Length |
| 26 | + var origHeaders = originalReqHeader.getHeaders(); |
| 27 | + for (var i = 0; i < origHeaders.size(); i++) { |
| 28 | + var h = origHeaders.get(i); |
| 29 | + var name = h.getName(); |
| 30 | + var value = h.getValue(); |
| 31 | + if (!name.equalsIgnoreCase("Host") && !name.equalsIgnoreCase("Content-Length")) { |
| 32 | + msg.getRequestHeader().setHeader(name, value); |
| 33 | + } |
| 34 | + } |
| 35 | + |
| 36 | + // Set Host header properly |
| 37 | + var host = uri.getHost(); |
| 38 | + var port = uri.getPort(); |
| 39 | + msg.getRequestHeader().setHeader("Host", port > 0 && port !== 80 && port !== 443 ? host + ":" + port : host); |
| 40 | + |
| 41 | + // Send it |
| 42 | + var sender = new HttpSender( |
| 43 | + Model.getSingleton().getOptionsParam().getConnectionParam(), |
| 44 | + true, |
| 45 | + HttpSender.CHECK_FOR_UPDATES_INITIATOR |
| 46 | + ); |
| 47 | + sender.sendAndReceive(msg, true); |
| 48 | + return msg; |
| 49 | + } catch (e) { |
| 50 | + print("[!] Error sending to: " + url + " => " + e); |
| 51 | + return null; |
| 52 | + } |
| 53 | +} |
| 54 | + |
| 55 | +// === Main === |
| 56 | +function invokeWith(msg) { |
| 57 | + var baseUrl = msg.getRequestHeader().getURI().toString(); |
| 58 | + if (!baseUrl || baseUrl.trim() === "") return; |
| 59 | + |
| 60 | + divider(baseUrl); |
| 61 | + |
| 62 | + var originalBody = msg.getResponseBody().toString(); |
| 63 | + var delimiters = [";", "%00", "%0A", "%09", ".", "/", "~"]; |
| 64 | + var exts = [ |
| 65 | + "css","js","jpg","png","gif","svg","webp","pdf","zip","docx","xlsx","mp3","mp4","ttf","woff","woff2","svgz" |
| 66 | + ]; |
| 67 | + var filename = "cachetest"; |
| 68 | + |
| 69 | + print("[+] Starting Web Cache Deception tests..."); |
| 70 | + print("[i] Base: " + baseUrl + "\n"); |
| 71 | + |
| 72 | + // === Standard Delimiter + Extension tests === |
| 73 | + for (var i = 0; i < delimiters.length; i++) { |
| 74 | + var d = delimiters[i]; |
| 75 | + var probeUrl = baseUrl + d + filename; |
| 76 | + var probeMsg = sendGetWithOriginalHeaders(msg.getRequestHeader(), probeUrl); |
| 77 | + if (!probeMsg) continue; |
| 78 | + |
| 79 | + var status = probeMsg.getResponseHeader().getStatusCode(); |
| 80 | + if (status !== 200) continue; |
| 81 | + |
| 82 | + print("\n[*] Delimiter accepted: '" + d + "'"); |
| 83 | + print(" Baseline probe: " + probeUrl + " | Status: " + status); |
| 84 | + |
| 85 | + for (var j = 0; j < exts.length; j++) { |
| 86 | + var ext = exts[j]; |
| 87 | + var testUrl = baseUrl + d + filename + "." + ext; |
| 88 | + var newMsg = sendGetWithOriginalHeaders(msg.getRequestHeader(), testUrl); |
| 89 | + if (!newMsg) continue; |
| 90 | + |
| 91 | + var code = newMsg.getResponseHeader().getStatusCode(); |
| 92 | + if (code !== 200) continue; |
| 93 | + |
| 94 | + var xCache = null; |
| 95 | + var hdrs = newMsg.getResponseHeader().getHeaders(); |
| 96 | + for (var h = 0; h < hdrs.size(); h++) { |
| 97 | + var hh = hdrs.get(h); |
| 98 | + if (hh.getName().equalsIgnoreCase("X-Cache")) { |
| 99 | + xCache = hh.getValue(); |
| 100 | + break; |
| 101 | + } |
| 102 | + } |
| 103 | + |
| 104 | + var testBody = newMsg.getResponseBody().toString(); |
| 105 | + var bodySame = (testBody === originalBody); |
| 106 | + print(" [+] Payload: " + testUrl); |
| 107 | + print(" Status: " + code + |
| 108 | + (xCache ? " | X-Cache: " + xCache : " | no X-Cache") + |
| 109 | + (bodySame ? " | SAME_BODY" : "")); |
| 110 | + |
| 111 | + if (xCache && xCache.toLowerCase().includes("hit") && bodySame) { |
| 112 | + print(" [!] Potential Cache Deception detected!"); |
| 113 | + } |
| 114 | + } |
| 115 | + } |
| 116 | + |
| 117 | + // === Traversal-style encoded payload tests === |
| 118 | + try { |
| 119 | + var urlObj = new java.net.URL(baseUrl); |
| 120 | + var origin = urlObj.getProtocol() + "://" + urlObj.getHost(); |
| 121 | + if (urlObj.getPort() > 0) origin += ":" + urlObj.getPort(); |
| 122 | + var path = urlObj.getPath(); |
| 123 | + |
| 124 | + var folders = ["static", "assets", "resources", "js", "css", "uploads", "files", "cdn"]; |
| 125 | + var encoders = [ |
| 126 | + "%23%2f..%2f", // #/../ |
| 127 | + "%23%2F..%2F", // uppercase |
| 128 | + "%2e%2e%2f", // ../ |
| 129 | + "..%2f" // simple |
| 130 | + ]; |
| 131 | + |
| 132 | + print("\n[+] Testing encoded traversal-style payloads...\n"); |
| 133 | + |
| 134 | + for (var f = 0; f < folders.length; f++) { |
| 135 | + for (var e = 0; e < encoders.length; e++) { |
| 136 | + var folder = folders[f]; |
| 137 | + var encoder = encoders[e]; |
| 138 | + var testUrl = origin + path + encoder + folder; |
| 139 | + var tMsg = sendGetWithOriginalHeaders(msg.getRequestHeader(), testUrl); |
| 140 | + if (!tMsg) continue; |
| 141 | + |
| 142 | + var code = tMsg.getResponseHeader().getStatusCode(); |
| 143 | + if (code !== 200) continue; |
| 144 | + |
| 145 | + var hdrs = tMsg.getResponseHeader().getHeaders(); |
| 146 | + var xCache = null; |
| 147 | + for (var h = 0; h < hdrs.size(); h++) { |
| 148 | + var hh = hdrs.get(h); |
| 149 | + if (hh.getName().equalsIgnoreCase("X-Cache")) { |
| 150 | + xCache = hh.getValue(); |
| 151 | + break; |
| 152 | + } |
| 153 | + } |
| 154 | + |
| 155 | + var testBody = tMsg.getResponseBody().toString(); |
| 156 | + var bodySame = (testBody === originalBody); |
| 157 | + |
| 158 | + print(" [+] Traversal Payload: " + testUrl + |
| 159 | + " | Status: " + code + |
| 160 | + (xCache ? " | X-Cache: " + xCache : " | no X-Cache") + |
| 161 | + (bodySame ? " | SAME_BODY" : "")); |
| 162 | + |
| 163 | + if (xCache && xCache.toLowerCase().includes("hit") && bodySame) { |
| 164 | + print(" [!] Potential Cache Deception via encoded traversal!"); |
| 165 | + } |
| 166 | + } |
| 167 | + } |
| 168 | + |
| 169 | +// === Semicolon traversal and encoded path confusion tests === |
| 170 | +print("\n[+] Testing semicolon and encoded path confusion payloads...\n"); |
| 171 | + |
| 172 | +try { |
| 173 | + var sensitiveFiles = [ |
| 174 | + "robots.txt", "sitemap.xml", "index.html", "index.php", |
| 175 | + "login", "admin", "config", "api", "dashboard" |
| 176 | + ]; |
| 177 | + |
| 178 | + for (var s = 0; s < sensitiveFiles.length; s++) { |
| 179 | + var sf = sensitiveFiles[s]; |
| 180 | + var testUrl = baseUrl + ";%2f%2e%2e%2f" + sf + "?test"; |
| 181 | + |
| 182 | + var tMsg = sendGetWithOriginalHeaders(msg.getRequestHeader(), testUrl); |
| 183 | + if (!tMsg) continue; |
| 184 | + |
| 185 | + var code = tMsg.getResponseHeader().getStatusCode(); |
| 186 | + if (code !== 200) continue; |
| 187 | + |
| 188 | + var hdrs = tMsg.getResponseHeader().getHeaders(); |
| 189 | + var xCache = null; |
| 190 | + for (var h = 0; h < hdrs.size(); h++) { |
| 191 | + var hh = hdrs.get(h); |
| 192 | + if (hh.getName().equalsIgnoreCase("X-Cache")) { |
| 193 | + xCache = hh.getValue(); |
| 194 | + break; |
| 195 | + } |
| 196 | + } |
| 197 | + |
| 198 | + var testBody = tMsg.getResponseBody().toString(); |
| 199 | + var bodySame = (testBody === originalBody); |
| 200 | + |
| 201 | + print(" [+] Semicolon Payload: " + testUrl + |
| 202 | + " | Status: " + code + |
| 203 | + (xCache ? " | X-Cache: " + xCache : " | no X-Cache") + |
| 204 | + (bodySame ? " | SAME_BODY" : "")); |
| 205 | + |
| 206 | + if (xCache && xCache.toLowerCase().includes("hit") && bodySame) { |
| 207 | + print(" [!] Potential Cache Deception via semicolon traversal!"); |
| 208 | + } |
| 209 | + } |
| 210 | +} catch (e) { |
| 211 | + print("[!] Semicolon traversal error: " + e); |
| 212 | +} |
| 213 | + |
| 214 | + |
| 215 | + // === Folder-prefixed traversal tests === |
| 216 | + print("\n[+] Testing folder-prefixed traversal payloads...\n"); |
| 217 | + for (var f = 0; f < folders.length; f++) { |
| 218 | + var folder = folders[f]; |
| 219 | + var testUrl = origin + "/" + folder + "/..%2f" + path + "?"; |
| 220 | + var tMsg = sendGetWithOriginalHeaders(msg.getRequestHeader(), testUrl); |
| 221 | + if (!tMsg) continue; |
| 222 | + |
| 223 | + var code = tMsg.getResponseHeader().getStatusCode(); |
| 224 | + if (code !== 200) continue; |
| 225 | + |
| 226 | + var hdrs = tMsg.getResponseHeader().getHeaders(); |
| 227 | + var xCache = null; |
| 228 | + for (var h = 0; h < hdrs.size(); h++) { |
| 229 | + var hh = hdrs.get(h); |
| 230 | + if (hh.getName().equalsIgnoreCase("X-Cache")) { |
| 231 | + xCache = hh.getValue(); |
| 232 | + break; |
| 233 | + } |
| 234 | + } |
| 235 | + |
| 236 | + var testBody = tMsg.getResponseBody().toString(); |
| 237 | + var bodySame = (testBody === originalBody); |
| 238 | + |
| 239 | + print(" " + |
| 240 | + (xCache ? "[X]" : "[!]") + |
| 241 | + " Prefix Payload: " + testUrl + |
| 242 | + " | Status: " + code + |
| 243 | + (xCache ? " | X-Cache: " + xCache : " | no X-Cache") + |
| 244 | + (bodySame ? " | SAME_BODY" : "")); |
| 245 | + |
| 246 | + if (xCache && xCache.toLowerCase().includes("hit") && bodySame) { |
| 247 | + print(" [!] Potential Cache Deception via folder prefix traversal!"); |
| 248 | + } |
| 249 | + } |
| 250 | + |
| 251 | + } catch (e) { |
| 252 | + print("[!] Traversal error: " + e); |
| 253 | + } |
| 254 | + |
| 255 | + divider(); |
| 256 | +} |
0 commit comments