VYPR
High severity7.4NVD Advisory· Published Jun 16, 2026· Updated Jun 16, 2026

CVE-2026-10303

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

2
  • Srvrco/Getsslreferences2 versions
    (expand)+ 1 more
    • (no CPE)
    • (no CPE)range: <=2.49

Patches

1
88380ee04a38

Merge pull request #896 from xen0bit/security_rfc_8555

https://github.com/srvrco/getsslTim KimberJun 3, 2026via nvd-ref
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

5

News mentions

0

No linked articles in our index yet.