Skip Content

Aplicacion Web de Ejemplo (Laravel 12)

Este repositorio es la aplicacion de ejemplo. Los directorios vendor/ y node_modules/ no se documentan aqui a proposito.

Estructura del Proyecto (Alto Nivel)

app/
Example/ # Codigo de ejemplo A01-A10 (patrones seguros)
Http/Middleware/
bootstrap/
config/
public/
resources/
docs/ # Documentacion Markdown renderizada como views
views/
routes/
storage/
tests/

OWASP 2025 A01-A10 (Mapa de Codigo)

A01: Control de Acceso Roto

Policies + autorizacion en Form Requests (lectura publica, escritura restringida):

Archivos:

app/Example/Policies/PostPolicy.php app/Example/Http/Requests/UpdatePostRequest.php app/Example/Policies/CommentPolicy.php app/Example/Http/Requests/StoreCommentRequest.php

// app/Example/Policies/PostPolicy.php
// Solo autores pueden crear posts.
public function create(User $user): Response
{
return (bool) $user->getAttribute('is_author')
? Response::allow()
: Response::denyAsNotFound();
}
// app/Example/Http/Requests/UpdatePostRequest.php
public function authorize(): bool
{
return $this->user()?->can('update', $this->route('post')) ?? false;
}
// app/Example/Policies/CommentPolicy.php
// Cualquier usuario autenticado puede crear un comentario.
public function create(User $user): Response
{
return $user->id > 0
? Response::allow()
: Response::denyAsNotFound();
}
 
// Solo el owner (o un admin) puede editar/eliminar.
public function update(User $user, Comment $comment): Response
{
$isAdmin = (bool) $user->getAttribute('is_admin');
 
return ($isAdmin || $user->id === $comment->user_id)
? Response::allow()
: Response::denyAsNotFound();
}
// app/Example/Http/Requests/StoreCommentRequest.php
public function authorize(): bool
{
// Requisito: solo usuarios autenticados pueden comentar.
return $this->user() !== null;
}
 
public function rules(): array
{
return [
'body' => ['required', 'string', 'max:2000'],
];
}

A02: Configuracion de Seguridad Incorrecta

Endurece sesion + CORS y mantenlo controlado por variables de entorno.

Archivos:

config/session.php config/cors.php

// config/session.php
'secure' => env('SESSION_SECURE_COOKIE', env('APP_ENV') === 'production'),
'http_only' => env('SESSION_HTTP_ONLY', true),
'same_site' => env('SESSION_SAME_SITE', 'lax'),
// config/cors.php
'allowed_origins' => array_values(array_filter(array_map(
trim(...),
explode(',', (string) env('CORS_ALLOWED_ORIGINS', '')),
))),
'supports_credentials' => (bool) env('CORS_SUPPORTS_CREDENTIALS', false),

Baseline recomendado para .env (produccion):

APP_ENV=production
APP_DEBUG=false
 
SESSION_SECURE_COOKIE=true
SESSION_HTTP_ONLY=true
SESSION_SAME_SITE=lax
 
# Ejemplo: limitar CORS a tus origenes reales
CORS_ALLOWED_ORIGINS=https://app.ejemplo.com

A03: Fallas en la Cadena de Suministro de Software

Fija dependencias y auditarlas en CI.

Archivos:

composer.lock composer.json (scripts)

Comandos:

composer audit
composer outdated

A04: Fallas Criptograficas

Usa Hash para passwords y Crypt solo para valores que deban desencriptarse; rota llaves de forma segura.

Archivos:

app/Example/Crypto/LaravelCipher.php

// app/Example/Crypto/LaravelCipher.php
return Crypt::encryptString($plain);

Patron recomendado de rotacion en .env:

APP_KEY=base64:NUEVA_LLAVE_AQUI
APP_PREVIOUS_KEYS=base64:LLAVE_ANTIGUA_1,base64:LLAVE_ANTIGUA_2

A05: Inyeccion

Nunca dejes que input del usuario se convierta en identificadores SQL (columnas/order), SQL raw o comandos.

Archivos:

app/Example/Query/PostIndexQuery.php

$allowedSorts = ['created_at', 'title'];
$sort = $request->string('sort')->toString();
$sort = in_array($sort, $allowedSorts, true) ? $sort : 'created_at';
 
$direction = strtolower($request->string('direction')->toString());
$direction = in_array($direction, ['asc', 'desc'], true) ? $direction : 'desc';
 
$query->orderBy($sort, $direction);

A06: Diseno Inseguro

Los workflows criticos deben validar invariantes primero: autorizacion, transicion de estado, auditoria.

Archivos:

app/Example/Actions/PublishPostAction.php

use Illuminate\Support\Facades\Gate;
 
Gate::authorize('update', $post);
abort_unless((string) $post->getAttribute('status') === 'draft', 422);
 
$post->forceFill(['status' => 'published'])->save();

A07: Fallos de Autenticacion

Usa los patrones de Laravel (regenerar sesion en login, invalidar en logout, abilities en tokens con Sanctum).

Este sitio de documentacion no es un producto de autenticacion, pero aplican los patrones estandar.

Referencia:

resources/docs/es/Vulnerabilidades/FallosAutenticacion.md

A08: Fallos de Integridad de Software o Datos

Nunca confies en ownership enviado por el cliente (ej. user_id en comentarios).

Archivos:

app/Example/Actions/CreateCommentAction.php

Gate::authorize('create', Comment::class);
 
return Comment::query()->create([
'post_id' => $post->id,
'user_id' => $user->id,
'body' => $body,
]);

A09: Fallos de Logging y Alertas de Seguridad

Centraliza contexto por request y emite eventos de seguridad normalizados.

Archivos:

app/Http/Middleware/SecurityLogContext.php config/logging.php

// app/Http/Middleware/SecurityLogContext.php
Log::withContext([
'correlation_id' => $correlationId,
'user_id' => $request->user()?->id,
'ip' => $request->ip(),
'user_agent' => $request->userAgent(),
]);
// config/logging.php
'security' => [
'driver' => 'stack',
'channels' => explode(',', (string) env('SECURITY_LOG_CHANNELS', 'daily')),
],

A10: Mal Manejo de Condiciones Excepcionales

Para clientes JSON en produccion, responde con errores genericos seguros y reporta internamente.

Archivos:

bootstrap/app.php app/Example/Http/Controllers/CommentModerationController.php

// bootstrap/app.php
$exceptions->render(function (Throwable $throwable, Request $request) {
if (! $request->expectsJson() || ! app()->isProduction()) return null;
if ($throwable instanceof ValidationException) return null;
if ($throwable instanceof HttpExceptionInterface) return null;
 
report($throwable);
return response()->json(['message' => 'Server error.'], 500);
});
// app/Example/Http/Controllers/CommentModerationController.php
try {
$isSpam = $spamDetectionService->isSpam($request->string('text')->toString());
} catch (SpamServiceTimeout $e) {
report($e);
return response()->json(['message' => 'Falla temporal.'], 503);
}