Skip to content

With ip-from-header:true, malformed UTF-8 in X-Forwarded-For causes 502 crash #312

@ulidtko

Description

@ulidtko

Rescuing this from pentest scans fallout.

Steps to reproduce

  • Uncomment ip-from-header: true in keter config,
    # Get the user's IP address from x-forwarded-for. Useful when sitting behind a
    # load balancer like Amazon ELB.
    # ip-from-header: true
  • Run keter, with any sort of webapp bundle under incoming/ — so that a VHost is active (e.g. localhost)
  • Make a request to that vhost, with bytes that don't decode to UTF-8 text in X-Forwarded-For header:
    >>> requests.get('http://localhost:8080/whatever', headers={'X-Forwarded-For': b'\xbf\xf0\x9f\x92\xa1'})
    <Response [502]>
    
    or the same, using curl:
    curl http://localhost:8080/whatever -H"X-Forwarded-For:$(printf '\xbf\xf0\x9f\x92\xa1')"
    <!DOCTYPE html>
    <html><head><title>Welcome to Keter</title></head><body><h1>Welcome to Keter</h1><p>There was a proxy error, check the keter logs for details.</p></body></html>
    

Actual result

A proxy exception, 502 crash. Request never gets to the webapp.

Keter logs:

keter|2025-03-28 10:08:51.91|:0|Error> Got a proxy exception on request Request {requestMethod = "GET", httpVersion = HTTP/1.1, rawPathInfo = "/whatever", rawQueryString = "", requestHeaders = [("X-Forwarded-Proto","http"),("Host","localhost:8080"),("User-Agent","curl/8.5.0"),("Accept","/"),("X-Forwarded-For","\191\240\159\146\161")], isSecure = False, remoteHost = 127.0.0.1:54760, pathInfo = ["whatever"], queryString = [], requestBody = , vault = , requestBodyLength = KnownLength 0, requestHeaderHost = Just "localhost:8080", requestHeaderRange = Nothing} with exception Cannot decode byte '\xbf': Data.Text.Internal.Encoding.decodeUtf8: Invalid UTF-8 stream

or, perhaps more clearly readable, with this patch applied 5a2022f :

2025-03-28 09:54:10.73: Caught a proxy exception --[ Cannot decode byte '\xbf': Data.Text.Internal.Encoding.decodeUtf8: Invalid UTF-8 stream ]-- on Request {requestMethod = "GET", httpVersion = HTTP/1.1, rawPathInfo = "/whatever", rawQueryString = "", requestHeaders = [("X-Forwarded-Proto","http"),("X-Forwarded-For","\191\240\159\146\161, 185.189.115.50"),("X-Forwarded-Proto","https"),("X-Forwarded-Port","443"),("Host","zdocs-testing.zoominsoftware.io"),("X-Amzn-Trace-Id","Root=1-67e671c2-59009b0f31dc1f82066dd306"),("User-Agent","python-requests/2.32.0"),("Accept-Encoding","gzip, deflate, br"),("Accept","/")], isSecure = False, remoteHost = 172.30.5.13:63224, pathInfo = ["whatever"], queryString = [], requestBody = , vault = , requestBodyLength = KnownLength 0, requestHeaderHost = Just "zdocs-testing.zoominsoftware.io", requestHeaderRange = Nothing}

Expected result

Up for discussion. My take is:

  • Bogus contents in X-Forwarded-For should not bother Keter as the reverse-proxy — exactly as it doesn't, when ip-from-header is disabled (which is the default).
  • Either the malformed header should get stripped altogether, or passed to the webapp as-is.
    • Again, consider both ip-from-header:false & ip-from-header:true
  • In any case, such requests should be reaching the web-app despite malformed X-Forwarded-For. It may not care.
  • No 502 crash.

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions