Skip to content

Fleet SCEP proxy corrupts GET PKIOperation messages containing ''+'' (HTML-form-decodes literal ''+'' as space, breaking base64) #45291

@getvictor

Description

@getvictor

💥 Actual behavior

Fleet's SCEP proxy returns HTTP 400 "failed to base64 decode message: illegal base64 data at input byte N" for any GET PKIOperation whose base64 message query parameter contains a literal +. Because base64 produces + for roughly 1 in 16 input bytes, virtually every real PKCS#7 envelope triggers it.

Root cause: server/mdm/scep/server/transport.go's message() reads the SCEP message via r.URL.Query().Get("message"). url.Values.Get() performs HTML-form decoding, which converts a literal + into a space. That is correct for application/x-www-form-urlencoded bodies but wrong for RFC 3986 query strings, where + is a literal +. The space breaks base64 decoding at the first replaced byte.

Scope: any SCEP client doing GET PKIOperation without percent-escaping + — Fleet Android agent (jScep), some Microsoft NDES configurations that fall back to GET, embedded SCEP clients. POST PKIOperation puts the payload in the request body and is unaffected. Clients negotiate GET vs POST from the CA's GetCACaps response, so a CA that advertises POSTPKIOperation masks the bug; any CA misconfiguration or older client that uses GET re-exposes it.

🛠️ Expected behavior

We could simply document that we do not support GET PKIOperation and use POST instead (which almost everyone should already be using). But it is possible for someone to point Fleet at an old/misconfigured CA that is not configured to use POST.

The SCEP proxy should accept GET PKIOperation whether or not the client percent-escapes + and /. In transport.go's message(), parse the message value from r.URL.RawQuery directly so literal + is preserved, then url.PathUnescape and base64.StdEncoding.DecodeString as before. This is harmless for clients that do percent-escape — %2B round-trips through RawQuery plus PathUnescape exactly as before.

This is the opposite-side companion to micromdm/scep PR #250, which fixed the client encoder to send + (escaped to %2B) instead of -/_. This issue is about servers tolerating + whether or not the client escaped it.

🧑‍💻 Steps to reproduce

These steps:

  • Have been confirmed to consistently lead to reproduction in a Fleet dev instance against a real Android device (Fleet Android agent built with jScep 3.0.1).

Device-free repro with curl:

  1. Configure a custom SCEP CA on a Fleet team and create a certificate template; note its template ID and a host's host_uuid on that team.

  2. Grab any base64 PKCS#7 PKIOperation payload that contains at least one +.

  3. Hit the proxy with the literal-+ form (do not percent-escape):

    MSG="$(base64 < pkcs7.bin | tr -d '\n')"
    curl -vk "https://<fleet>/mdm/scep/proxy/<host_uuid>,g<template_id>,custom_scep_proxy,<challenge>?operation=PKIOperation&message=${MSG}"
  4. Observe HTTP 400 "failed to base64 decode message: illegal base64 data at input byte N: ..." where N is the offset of the first +.

  5. Repeat with + escaped to %2B and / to %2F: the request succeeds (modulo upstream CA). This confirms the failure is server-side decoding of literal +, not the payload.

🕯️ More info (optional)

Metadata

Metadata

Assignees

Labels

#g-security-complianceSecurity & Compliance product group:releaseReady to write code. Scheduled in a release. See "Making changes" in handbook.bugSomething isn't working as documented~released bugThis bug was found in a stable release.

Type

No type
No fields configured for issues without a type.

Projects

Status

🦤 ‎In review

Milestone

Relationships

None yet

Development

No branches or pull requests

Issue actions