requestDoubleEncoding makes HTTP requests doing a double URL encode of the path. It tests three strategies: 1. Per-character: double-encode each character individually (e.g., /admi%256E) 2. Last char only: double-encode the last char of the final path segment (matches the Rhynorater/DEF CON 32 techn
(options RequestOptions)
| 2153 | // 3. Full segment: double-encode every char in the last path segment at once |
| 2154 | // (e.g., /%2561%2564%256D%2569%256E) |
| 2155 | func requestDoubleEncoding(options RequestOptions) { |
| 2156 | parsedURL, err := url.Parse(options.uri) |
| 2157 | if err != nil { |
| 2158 | log.Println(err) |
| 2159 | return |
| 2160 | } |
| 2161 | |
| 2162 | originalPath := parsedURL.Path |
| 2163 | if len(originalPath) == 0 || originalPath == "/" { |
| 2164 | return |
| 2165 | } |
| 2166 | |
| 2167 | baseURL := parsedURL.Scheme + "://" + parsedURL.Host |
| 2168 | query := "" |
| 2169 | if parsedURL.RawQuery != "" { |
| 2170 | query = "?" + parsedURL.RawQuery |
| 2171 | } |
| 2172 | |
| 2173 | var payloads []string |
| 2174 | |
| 2175 | // Strategy 1: per-character double encoding |
| 2176 | for i, c := range originalPath { |
| 2177 | if c == '/' { |
| 2178 | continue |
| 2179 | } |
| 2180 | modifiedPath := originalPath[:i] + doubleEncode(c) + originalPath[i+len(string(c)):] |
| 2181 | payloads = append(payloads, baseURL+modifiedPath+query) |
| 2182 | } |
| 2183 | |
| 2184 | // Strategy 2 & 3: last-char and full-segment double encoding |
| 2185 | segments := strings.Split(strings.Trim(originalPath, "/"), "/") |
| 2186 | if len(segments) > 0 { |
| 2187 | lastSeg := segments[len(segments)-1] |
| 2188 | basePath := "/" |
| 2189 | if len(segments) > 1 { |
| 2190 | basePath = "/" + strings.Join(segments[:len(segments)-1], "/") + "/" |
| 2191 | } |
| 2192 | |
| 2193 | // Strategy 2: double-encode only the last character of the last segment |
| 2194 | if len(lastSeg) > 0 { |
| 2195 | runes := []rune(lastSeg) |
| 2196 | lastChar := runes[len(runes)-1] |
| 2197 | modified := basePath + string(runes[:len(runes)-1]) + doubleEncode(lastChar) |
| 2198 | if strings.HasSuffix(originalPath, "/") { |
| 2199 | modified += "/" |
| 2200 | } |
| 2201 | payloads = append(payloads, baseURL+modified+query) |
| 2202 | } |
| 2203 | |
| 2204 | // Strategy 3: double-encode every character in the last segment |
| 2205 | if len(lastSeg) > 1 { |
| 2206 | var fullEncoded strings.Builder |
| 2207 | fullEncoded.WriteString(basePath) |
| 2208 | for _, c := range lastSeg { |
| 2209 | fullEncoded.WriteString(doubleEncode(c)) |
| 2210 | } |
| 2211 | if strings.HasSuffix(originalPath, "/") { |
| 2212 | fullEncoded.WriteString("/") |