digininja/DVWA · warning · Exception

Could not decode JSON object.

Error message

Could not decode JSON object.

What it means

After the token decrypts successfully, json_decode($decrypted) returned null, meaning the plaintext was not valid JSON. The expected plaintext is an object like {"user":"example","ex":1723620372,"level":"user","bio":"blah"}; garbage from a bad splice, empty plaintext, or the literal string 'null' all produce a null decode and land here.

Source

Thrown at vulnerabilities/cryptography/source/medium.php:29

$errors = "";
$success = "";
$messages = "";

if ($_SERVER['REQUEST_METHOD'] == "POST") {
	try {
		if (!array_key_exists ('token', $_POST)) {
			throw new Exception ("No token passed");
		} else {
			$token = $_POST['token'];
			if (strlen($token) % 32 != 0) {
				throw new Exception ("Token is in wrong format");
			} else {
				$decrypted = decrypt(hex2bin ($token), $key);

				$user = json_decode ($decrypted);
				if ($user === null) {
					throw new Exception ("Could not decode JSON object.");
				}

				if ($user->user == "sweep" && $user->ex > time() && $user->level == "admin") {
					$success = "Welcome administrator Sweep";
				} else {
					$messages = "Login successful but not as the right user.";
				}
			}
		}
	} catch(Exception $e) {
		$errors = $e->getMessage();
	}
}

$html = "
		<p>
		You have managed to get hold of three session tokens for an application you think is using poor cryptography to protect its secrets:
		</p>

View on GitHub (pinned to 5d5c76cced)

Solutions

  1. Temporarily var_dump($decrypted) to see the actual plaintext reaching json_decode.
  2. When splicing ECB blocks, keep whole 16-byte blocks aligned with the field layout of the valid 'Soo' token (its bio field is the attacker-friendly slot).
  3. Re-encrypt your intended JSON object with the same key/mode and hex-encode it instead of hand-editing.
  4. Use json_decode with JSON_THROW_ON_ERROR to get the precise syntax error position.

Example fix

// before
$user = json_decode ($decrypted);
if ($user === null) {
    throw new Exception ("Could not decode JSON object.");
}
// after
try {
    $user = json_decode($decrypted, false, 512, JSON_THROW_ON_ERROR);
} catch (JsonException $e) {
    throw new Exception("Could not decode JSON object: " . $e->getMessage());
}
Defensive patterns

Strategy: try-catch

Validate before calling

// Cheap shape check before json_decode: the documented token format is a JSON object
if (strlen($decrypted) === 0 || $decrypted[0] !== '{') {
    throw new Exception('Could not decode JSON object: plaintext is not a JSON object');
}

Type guard

function isDecryptedTokenObject(string $plaintext): bool
{
    $decoded = json_decode($plaintext);
    return $decoded instanceof stdClass
        && isset($decoded->user, $decoded->ex, $decoded->level);
}

Try / catch

use JsonException;
try {
    $user = json_decode($decrypted, false, 512, JSON_THROW_ON_ERROR);
} catch (JsonException $e) {
    // JSON_THROW_ON_ERROR distinguishes 'null' input, syntax errors, and depth errors
    throw new Exception('Could not decode JSON object: ' . $e->getMessage());
}

Prevention

When it happens

Trigger: Cut-and-pasting ECB blocks so the reassembled plaintext breaks JSON syntax (unbalanced quotes/braces); a decryption that yields binary garbage whose padding happened to validate; plaintext that is empty or the literal 'null' (json_decode('null') === null, a true false-positive of this check); plaintext produced by encrypting data with a different key that still decrypts to something non-JSON.

Common situations: ECB block-splicing attacks misaligning field boundaries; wrong-key ciphertext that slips past padding checks; token producers serializing with a different format (query string, CSV) than the consumer expects.

Related errors


AI-assisted analysis of digininja/DVWA@5d5c76cced (2026-08-21). Data as JSON: /api/errors/b4835ca759449762. Report an issue: GitHub.