Bug #105201
openPOST request with empty object generates a Bad multipart parameters parsing on F5 (RFC1341)
0%
Description
Our hosting is behind an F5 instance (WAF / Web Application Firewall) and with Typo3 v12 I get errors when the backend makes POST requests with an empty object as value. This happens for example for "Flush frontend caches", "Flush all caches", "Exit switch user mode", ...
The error report says that it's a "Bad multipart parameters parsing" because of "Closing multipart boundary is not found".
We also got a copy of the request payload:
POST /typo3/ajax/switch/user/exit?token=971033045db311905f14c9e91265dbb3ff2613ee HTTP/1.1 Host: test.domain.tdl Connection: keep-alive Content-Length: 44 Cache-Control: max-age=0 sec-ch-ua-platform: "macOS" User-Agent: Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/129.0.0.0 Safari/537.36 sec-ch-ua: "Google Chrome";v="129", "Not=A?Brand";v="8", "Chromium";v="129" Content-Type: multipart/form-data; boundary=----WebKitFormBoundaryFdQi1AC0iZClXe8T sec-ch-ua-mobile: ?0 Accept: */* Origin: https://test.domain.tdl Sec-Fetch-Site: same-origin Sec-Fetch-Mode: cors Sec-Fetch-Dest: empty Referer: https://test.domain.tdl/typo3/module/dashboard Accept-Encoding: gzip, deflate, br, zstd Accept-Language: en,en-US;q=0.9,fr;q=0.8,de;q=0.7 Cookie: dtCookie=[...] X-Forwarded-For: xxx.xxx.xxx.xxx X-Forwarded-Proto: https ------WebKitFormBoundaryFdQi1AC0iZClXe8T--
Reading the RFC 1341 it seems that we are missing the start boundary and we only have the end boundary.
Typo3 does not produce the final payload it-self and "just" passes everything to fetch(), but before passing those values it processes some of the data and specially if the main data is an empty javascript object, this error is produced.
I found a possible solution by changing AjaxRequest::post() (vendor/typo3/cms-core/Resources/Public/JavaScript/ajax/ajax-response.js) to return an empty string as body if we have an empty javascript object, instead of running InputTransformer.byHeader() on it and create an empty FormData that produces those boundaries.
async post(e, t = {}) {
const r = {
body: "string" == typeof e || e instanceof FormData ? e : ( Object.keys(e).length ? InputTransformer.byHeader(e, t?.headers) : ""),
cache: "no-cache",
method: "POST"
}, n = await this.send({...r, ...t});
return new AjaxResponse(n)
}
For now I've copied the file to my extension and I'm overriding it via JavaScriptModules.php.