Forwarded Header Sabotage (2022)

rdpintqogeogsaa1 pts0 comments

Forwarded Header Sabotage | adam-pForwarded Header Sabotage<br>March 30, 2022<br>We all know by now that the leftmost values in the X-Forwarded-For header can be spoofed and only the rightmost IPs – added by your own reverse proxies – can be trusted. The Forwarded header (RFC 7239, 2014) has that same problem, and a new one: If the header is parsed correctly, an attacker can sabotage the whole header.<br>Let&rsquo;s take a quick trip to understanding how that can happen and how complicated Forwarded parsing can get. (Think about how you&rsquo;d parse the header as we go.)<br>##<br>Syntax<br>A simple Forwarded header might look like this:<br>Forwarded: for=1.1.1.1, For=2.2.2.2<br>Here&rsquo;s what a header looks like with an IPv6 value:<br>Forwarded: for=1.1.1.1, FOR="[2001:db8:cafe::17]"<br>Colons and square brackets are not allowed in a &ldquo;token&rdquo;, so the IPv6 address needs to be quoted. But that means we could have:<br>Forwarded: host="with,comma=equals;semicolon";for=1.1.1.1<br>So now you can&rsquo;t just split by comma – you need to be aware of quoted strings as well.<br>But characters can also be escaped, so this is also legal:<br>Forwarded: ext="escaped\"quote";for=1.1.1.1<br>The blessed semicolon-separated parameter names in an entry are &ldquo;for&rdquo;, &ldquo;by&rdquo;, &ldquo;host&rdquo;, and &ldquo;proto&rdquo; (case-insensitive). There is allowance for &ldquo;extensions&rdquo; using other tokens.<br>Some more legal things:<br>Anything can be escaped, including backslashes: \\. So don&rsquo;t just delete all them all.<br>Any amount of whitespace around the commas and semicolons.<br>There can be multiple instances of the header, and they must be considered a single list, top to bottom.<br>And some illegal things:<br>Can&rsquo;t have whitespace around the equal sign.<br>Can&rsquo;t have disallowed characters in parameter names (and not quotable).<br>Can&rsquo;t have disallowed characters in a parameter value, if not quoted. (Which mean, for example, that an unquoted IPv6 address is illegal.)<br>IPv6 addresses must have square brackets.<br>Backslash escaping is only allowed in quoted strings.<br>There is only one single library I&rsquo;ve found that actually correctly parses the header: github.com/lpinca/forwarded-parse.1 Everything else just does what you were probably thinking after the first couple of steps above:<br>Split by comma.<br>Trim whitespace.<br>Split by semicolon.<br>Trim the quotes off the value.<br>Done.<br>Hilariously, this half-assed, RFC-violating parsing is resistant to sabotage and proper parsing is not.<br>##<br>Sabotage!<br>The Forwarded header is unique. It is the only header that:<br>Has untrusted values at the start and trusted values at the end.<br>Is official and specified.<br>This combination leads to its susceptibility to sabotage, where the whole header – including the trusted part – needs to be discarded because of chicanery in the untrusted part.<br>The RFC doesn&rsquo;t (that I can find) provide any special instructions about salvaging the rest of the header if a single entry (&ldquo;forwarded-element&rdquo;) has a syntax error. So, in theory, the whole header needs to be thrown if a spoofer adds, say, f*r= instead of for=.<br>The sabotage is even more fun with an unclosed double-quote:<br>Forwarded: for="1.1.1.1, for=2.2.2.2, for=3.3.3.3<br>It&rsquo;s illegal to have an unclosed quote, so the whole thing is immediately garbage. But even if you wanted to salvage the header&mldr; Where do you close the quote? What do you salvage and discard?<br>##<br>Why is X-Forwarded-For not sabotage-able?<br>Because there&rsquo;s no spec! People just split by comma, trim, and that&rsquo;s it. Your trusted reverse proxy will add ", 1.1.1.1" and you don&rsquo;t really need to care about what comes before that. (Unless you want the leftmost-ish value, but then you&rsquo;re in the danger zone regardless.)<br>##<br>Mitigations<br>&ldquo;Half-assed, RFC-violating parsing&rdquo; is the most obvious. If you&rsquo;re using a rightmost-ish value, you should know if your reverse proxies are going to be quoting things, escaping, etc. – and they probably aren&rsquo;t. So do a simple comma-splitting and throw away the stuff on the left.<br>Doing simple splitting means that you could end up with total garbage in your leftmost values – from spoofing or from valid-but-complicated values. You could probably make your parser more-complex-but-still-not-RFC-compliant by trying to handle quotes and escaping, without discarding everything in the case of bad data. Perhaps your deviation rule could be &ldquo;no commas allowed in quotes or escaped; they always signal a new entry&rdquo;. Or just don&rsquo;t use a leftmost value.<br>(Note that differences in parsing at different points – reverse proxies, server, etc. – could result in parser mismatch vulnerabilities.)<br>At the reverse proxy level, the obvious mitigation is to discard any existing Forwarded headers and start fresh, so there are only trusted, well-formed values. If you don&rsquo;t like the idea of discarding potentially valuable forensic information, maybe your reverse proxy could...

rsquo header forwarded sabotage ldquo rdquo

Related Articles