{"record":{"id":"f5fdfe57b602e0c3","repo":"yiisoft/yii2","slug":"cannot-unserialize-yii-db-batchqueryresult","errorCode":null,"errorMessage":"Cannot unserialize yii\\db\\BatchQueryResult","messagePattern":"Cannot unserialize yii\\\\db\\\\BatchQueryResult","errorType":"exception","errorClass":"BadMethodCallException","httpStatus":null,"severity":"error","filePath":"framework/db/BatchQueryResult.php","lineNumber":263,"sourceCode":"        if (!empty($this->_batch)) {\n            $key = array_keys($this->_batch)[0];\n            if (isset($this->_batch[$key]->db->driverName)) {\n                return $this->_batch[$key]->db->driverName;\n            }\n        }\n\n        return null;\n    }\n\n    /**\n     * Unserialization is disabled to prevent remote code execution in case application\n     * calls unserialize() on user input containing specially crafted string.\n     * @see https://cve.mitre.org/cgi-bin/cvename.cgi?name=CVE-2020-15148\n     * @since 2.0.38\n     */\n    public function __wakeup()\n    {\n        throw new \\BadMethodCallException('Cannot unserialize ' . __CLASS__);\n    }\n}\n","sourceCodeStart":245,"sourceCodeEnd":266,"githubUrl":"https://github.com/yiisoft/yii2/blob/66f00d18a29b520f85e8e8f1e32d1e7e7b556cac/framework/db/BatchQueryResult.php#L245-L266","documentation":"BatchQueryResult — the iterator returned by $query->each() and $query->batch() — deliberately blocks unserialization: __wakeup() throws BadMethodCallException, added in Yii 2.0.38 to blunt CVE-2020-15148, where unserializing attacker-crafted strings containing framework objects enabled remote code execution. Any unserialize() over a payload that embeds a serialized BatchQueryResult hits this guard immediately.","triggerScenarios":"Storing a live each()/batch() cursor in cache/session/queue payload and later restoring it with unserialize(); calling unserialize() on request input crafted as O:26:\"yii\\\\db\\\\BatchQueryResult\":...; generic object-caching layers that capture the iterator inside a bigger object graph; serialized blobs created before 2.0.38 being revived after upgrade.","commonSituations":"Caching ->each() results instead of ->all(); job queues serializing job properties holding query iterators; pentest scanners probing for CVE-2020-15148; upgrading Yii past 2.0.38 so old serialized payloads now fail to wake.","solutions":["Materialize results before storing: call $query->all() or fully iterate each() and persist the rows (or JSON).","Never call unserialize() on user-controlled input — use JSON at trust boundaries.","For legacy stored payloads, regenerate them with the fixed version instead of unserializing.","Catch BadMethodCallException around unserialize() of legacy blobs and treat them as invalid/expired."],"exampleFix":"// before\n$cursor = (new \\yii\\db\\Query())->from('user')->each(100);\nYii::$app->cache->set('users', $cursor); // serializes the BatchQueryResult\n\n// after\n$users = (new \\yii\\db\\Query())->from('user')->all();\nYii::$app->cache->set('users', $users);","handlingStrategy":"validation","validationCode":"// Only unserialize payloads you produced and control — check the shape first\n$data = \\json_decode($raw, true);\nif (\\is_array($data) && !isset($data['__PHP_Incomplete_Class'])) {\n    $restored = $data; // JSON path — no object wakeup involved\n} else {\n    $restored = null; // legacy serialized blob: regenerate instead of unserialize()\n}","typeGuard":null,"tryCatchPattern":"try {\n    $obj = \\unserialize($blob, ['allowed_classes' => false]);\n} catch (\\BadMethodCallException $e) {\n    // blocked __wakeup (e.g. yii\\db\\BatchQueryResult guard) — treat as invalid\n    \\Yii::warning('Rejected serialized payload: ' . $e->getMessage(), 'security');\n    $obj = false;\n}","preventionTips":["Never call unserialize() on user-controlled input; use JSON at trust boundaries (CVE-2020-15148 class).","Cache materialized arrays ($query->all()), never cursors from each()/batch().","Pass ['allowed_classes' => false] (or an explicit allowlist) to every unserialize() call.","After upgrading past Yii 2.0.38, regenerate any legacy serialized payloads instead of reviving them."],"tags":["security","unserialize","cve-2020-15148","query-iterator","yii2"],"backgroundTag":"php-unserialize-blocked","analyzedSha":"66f00d18a29b520f85e8e8f1e32d1e7e7b556cac","analyzedAt":"2026-08-17T05:17:23.470Z","schemaVersion":2},"datasetVersion":"2026-08-17T09:17:11.063Z"}