CVE-2026-10303
Description
In getssl ≤2.49, insufficient validation of ACME challenge tokens allows path traversal and remote command injection via malicious CA responses.
AI Insight
LLM-synthesized narrative grounded in this CVE's description and references.
In getssl ≤2.49, insufficient validation of ACME challenge tokens allows path traversal and remote command injection via malicious CA responses.
Vulnerability
In ServerCo getssl versions 2.49 and prior, the ACME challenge token received from a CA is not strictly validated against RFC 8555 before being used in challenge-file path construction. This lack of validation (CWE-73) allows a maliciously crafted token to influence local file paths during ACME domain validation. The affected code path is in the getssl script where tokens are written to disk for HTTP-01 challenges.
Exploitation
An attacker who can supply or tamper with ACME challenge responses—for example, a malicious or compromised Certificate Authority, or an on‑path network adversary—can provide a crafted token containing path traversal sequences (e.g., ../). Since the token is used directly in filename construction, the attacker can write files to arbitrary locations, typically with the privileges of the user running getssl.
Impact
Successful exploitation results in unauthorized file write outside the intended ACME challenge directory. By writing to critical system files (e.g., cron jobs, SSH authorized keys, or scripts executed by privileged processes), the attacker can achieve remote command execution with elevated privileges, leading to full host compromise, as described in the runZero advisory [4].
Mitigation
The vulnerability is fixed in getssl version 2.50, released on 2026-06-04 [3]. The fix is implemented in pull request #896 [2] and adds proper input validation of the ACME token against RFC 8555. No workaround is available for earlier versions; users must upgrade to 2.50 or later. This vulnerability is not currently listed in the CISA Known Exploited Vulnerabilities catalog.
AI Insight generated on Jun 16, 2026. Synthesized from this CVE's description and the cited reference URLs; citations are validated against the source bundle.
Affected products
2Patches
188380ee04a38Merge pull request #896 from xen0bit/security_rfc_8555
2 files changed · +178 −0
getssl+17 −0 modified@@ -979,6 +979,16 @@ clean_up() { # Perform pre-exit housekeeping fi } +validate_token() { # validate token per RFC 8555 §8.3: base64url alphabet, max 255 chars + local token="$1" + if [[ ${#token} -gt 255 ]]; then + error_exit "Invalid token: exceeds 255 character maximum (RFC 8555)" + fi + if [[ ! "$token" =~ ^[A-Za-z0-9_-]+$ ]]; then + error_exit "Invalid token: contains characters outside base64url alphabet (RFC 8555)" + fi +} + copy_file_to_location() { # copies a file, using scp, sftp or ftp if required. cert=$1 # descriptive name, just used for display from=$2 # current file location @@ -1426,12 +1436,14 @@ for d in "${alldomains[@]}"; do # get the dns component of the ACME response # get the token and uri from the dns component token=$(json_get "$response" "token" "dns-01") + validate_token "$token" uri=$(json_get "$response" "uri" "dns-01") debug uri "$uri" else # APIv2 debug "authlink response = ${response//[$'\t\r\n']}" # get the token and uri from the dns-01 component token=$(json_get "$response" "challenges" "type" "dns-01" '"token"') + validate_token "$token" uri=$(json_get "$response" "challenges" "type" "dns-01" '"url"') debug uri "$uri" fi @@ -1487,13 +1499,15 @@ for d in "${alldomains[@]}"; do if [[ $API -eq 1 ]]; then # get the token from the http component token=$(json_get "$response" "token" "http-01") + validate_token "$token" # get the uri from the http component uri=$(json_get "$response" "uri" "http-01") debug uri "$uri" else # APIv2 debug "authlink response = ${response//[$'\t\r\n']}" # get the token from the http-01 component token=$(json_get "$response" "challenges" "type" "http-01" '"token"') + validate_token "$token" # get the uri from the http component uri=$(json_get "$response" "challenges" "type" "http-01" '"url"' | head -n1) debug uri "$uri" @@ -1502,6 +1516,9 @@ for d in "${alldomains[@]}"; do #create signed authorization key from token. keyauthorization="$token.$thumbprint" + # defense-in-depth: strip path traversal components from token + token=$(basename -- "$token") + # save variable into temporary file echo -n "$keyauthorization" > "$TEMP_DIR/$token" chmod 644 "$TEMP_DIR/$token"
test/u12-test-validate_token.bats+161 −0 added@@ -0,0 +1,161 @@ +#! /usr/bin/env bats + +load '/bats-support/load.bash' +load '/bats-assert/load.bash' +load '/getssl/test/test_helper.bash' + + +# This is run for every test +setup() { + [ ! -f $BATS_RUN_TMPDIR/failed.skip ] || skip "skipping tests after first failure" + + . /getssl/getssl --source + export API=2 + _USE_DEBUG=1 +} + + +teardown() { + [ -n "$BATS_TEST_COMPLETED" ] || touch $BATS_RUN_TMPDIR/failed.skip +} + + +# Valid tokens + +@test "validate_token: accept valid base64url token (no padding)" { + run validate_token "kD1H4FVIEFvkWghLlKFoSPpR5u0FTGkRs4A_FnTfv3A" + assert_success + assert_output "" +} + + +@test "validate_token: accept token with dashes" { + run validate_token "abc-xyz-123" + assert_success + assert_output "" +} + + +@test "validate_token: accept token with underscores" { + run validate_token "abc_xyz_123" + assert_success + assert_output "" +} + + +@test "validate_token: accept single character token" { + run validate_token "a" + assert_success + assert_output "" +} + + +@test "validate_token: accept maximum length token (255 chars)" { + token=$(printf 'a%.0s' {1..255}) + run validate_token "$token" + assert_success +} + + +# Invalid tokens + +@test "validate_token: reject token with semicolon" { + run validate_token "abc;touch /tmp/pwned" + assert_failure + assert_output --partial "Invalid token" +} + + +@test "validate_token: reject token with path traversal" { + run validate_token "../../../etc/passwd" + assert_failure + assert_output --partial "Invalid token" +} + + +@test "validate_token: reject token with backticks" { + run validate_token 'abc`touch /tmp/pwned`' + assert_failure + assert_output --partial "Invalid token" +} + + +@test "validate_token: reject token with command substitution" { + run validate_token 'abc$(touch /tmp/pwned)' + assert_failure + assert_output --partial "Invalid token" +} + + +@test "validate_token: reject token with pipe" { + run validate_token "abc|sh" + assert_failure + assert_output --partial "Invalid token" +} + + +@test "validate_token: reject token with spaces" { + run validate_token "abc def" + assert_failure + assert_output --partial "Invalid token" +} + + +@test "validate_token: reject token with forward slash" { + run validate_token "abc/def" + assert_failure + assert_output --partial "Invalid token" +} + + +@test "validate_token: reject token with newline" { + run validate_token "$(echo -e 'abc\ndef')" + assert_failure + assert_output --partial "Invalid token" +} + + +@test "validate_token: reject token exceeding 255 characters" { + token=$(printf 'a%.0s' {1..256}) + run validate_token "$token" + assert_failure + assert_output --partial "Invalid token" +} + + +@test "validate_token: reject empty token" { + run validate_token "" + assert_failure + assert_output --partial "Invalid token" +} + + +@test "validate_token: reject token with equals sign (base64 padding)" { + # RFC 8555 requires trailing '=' to be stripped + run validate_token "abc123==" + assert_failure + assert_output --partial "Invalid token" +} + + +# Negative assertions: tokens that should NOT match the regex + +@test "validate_token: reject token with ampersand" { + run validate_token "abc&def" + assert_failure + assert_output --partial "Invalid token" +} + + +@test "validate_token: reject token with hash" { + run validate_token "abc#def" + assert_failure + assert_output --partial "Invalid token" +} + + +@test "validate_token: reject token with null byte" { + # bash truncates at null byte in function arguments, so validate_token + # receives only "abc" which would pass. Use printf to preserve the null. + skip "bash truncates null bytes in function arguments" +}
Vulnerability mechanics
Root cause
"Missing validation of the ACME challenge token against RFC 8555 allowed an attacker-controlled token to influence the file path used in challenge-file creation, enabling path traversal and command injection."
Attack vector
An attacker who can control the ACME challenge response—either by operating a malicious or compromised Certificate Authority endpoint, or by performing an on-path network manipulation—can supply a crafted `token` value that includes path-traversal sequences (e.g., `../../`) or shell metacharacters. Because `getssl` used the unsanitized token directly in a file path under `/tmp`, the attacker can write arbitrary content to an arbitrary location on the filesystem. When the written content is interpreted as a shell command (e.g., via a cron job or sourcing), this leads to remote command execution with the privileges of the `getssl` process, which often runs as root [ref_id=1].
Affected code
The vulnerability resides in the `getssl` shell script, specifically in the ACME challenge-token handling for both `dns-01` and `http-01` challenge types. The token extracted from the ACME server response via `json_get` was used directly in file-path construction (`$TEMP_DIR/$token`) without prior validation. The patch introduces a `validate_token` function and a `basename` call to sanitize the token before it is used as a filename [patch_id=6214602].
What the fix does
The patch adds a `validate_token` function that checks the token length (max 255 characters per RFC 8555 §8.3) and restricts the character set to the base64url alphabet (`[A-Za-z0-9_-]+`). This rejects tokens containing path separators (`/`), semicolons, backticks, `$()`, pipes, spaces, and other shell-special characters. Additionally, a defense-in-depth `basename` call strips any remaining path-traversal components from the token before it is used as a filename. Together these changes prevent an attacker from controlling the file path or injecting shell commands through the token field [patch_id=6214602].
Preconditions
- networkThe attacker must be able to supply or tamper with the ACME challenge response that getssl receives, e.g., by operating a malicious CA or performing an on-path network attack
- configgetssl must be run with sufficient privileges (often root) to write to the targeted path
Generated on Jun 16, 2026. Inputs: CWE entries + fix-commit diffs from this CVE's patches. Citations validated against bundle.
References
5News mentions
0No linked articles in our index yet.