Skip Content

A08: Fallos de Integridad de Software o Datos (Blog)

¿Que es un fallo de integridad de software o datos?

Estos fallos ocurren cuando la aplicacion confia en datos o artefactos que pueden ser manipulados. Incluye webhooks, estado del cliente, payloads serializados, imports y artefactos de CI/CD.

Fallo tipico en Laravel

  • Confiar en webhooks sin verificar firmas.
  • Deserializacion insegura.
  • Confiar ciegamente en datos del cliente (ej. user_id en comentarios).
  • Importar archivos/datos sin controles de integridad.
  • Artefactos de CI/CD sin verificacion.

Impacto

  • Fraude, escalamiento de privilegios o acciones cross-user.
  • Deploys comprometidos por artefactos envenenados.
  • Corrupcion de datos por imports inseguros.

Remediacion en Laravel 12

  • Verifica firmas de webhooks (Stripe, Paddle, GitHub, etc.).
  • No uses unserialize con payloads no confiables.
  • Recalcula totales/precios sensibles del lado del servidor.
  • Trata todo estado del cliente como hostil.
  • Firma builds, protege pipelines de deploy y limita quien puede promover artefactos.
  • Valida archivos subidos y escanea cuando aplique.

Arreglo concreto

Nunca confies en ownership enviado por el cliente. En comentarios: siempre toma user_id desde la sesion autenticada.

// Mal: confiar en el owner enviado por el cliente
// Comment::create([
// 'post_id' => $request->input('post_id'),
// 'user_id' => $request->input('user_id'),
// 'body' => $request->input('body'),
// ]);
 
// Bien: derivar ownership del usuario autenticado
Comment::create([
'post_id' => $post->id,
'user_id' => $request->user()->id,
'body' => $request->string('body')->toString(),
]);

Enfoque de patron de diseno

Usa Template Method o un Pipeline para imports para que validacion, checks de integridad y autorizacion no sean pasos opcionales. Haz que el pipeline sea el unico punto de entrada publico para imports.

final class ImportPipeline
{
/** @param array<int, callable(array): array> $steps */
public function __construct(private array $steps) {}
 
public function run(array $context): array
{
foreach ($this->steps as $step) {
$context = $step($context);
}
 
return $context;
}
}
 
// Pasos: authorize -> validate -> verify integrity -> persist -> audit

Ejemplo de pasos (integridad primero, luego persistir):

use Illuminate\Support\Facades\Gate;
 
$pipeline = new ImportPipeline([
function (array $c): array {
Gate::authorize('import', $c['actor']);
return $c;
},
function (array $c): array {
validator($c['rows'], ['*.email' => 'required|email'])->validate();
return $c;
},
function (array $c): array {
if (!hash_equals($c['expected_sha256'], hash('sha256', $c['raw']))) {
throw new \RuntimeException('Fallo chequeo de integridad');
}
 
return $c;
},
function (array $c): array {
ImportJob::dispatch($c['rows']);
return $c;
},
]);
 
$pipeline->run([
'actor' => $request->user(),
'raw' => $rawFileContents,
'expected_sha256' => $request->string('sha256')->toString(),
'rows' => $rows,
]);

Referencias