Quando a validação é inútil
Vou falar sobre uma vulnerabilidade que já peguei em um projeto PHP com Laravel e apontar um problema de design do Laravel que contribuiu com essa vulnerabilidade.
Validação de inputs é algo que até mesmo os estagiários aprendem. É tão trivial e rotineiro que os desenvolvedores nem pensam muito nisso, só vão em fazem a validação. Talvez justamente por confiarem demais na trivialidade da tarefa, e pelo framework já entregar recursos prontos para fazer validação, que acabam negligenciando a tarefa.
Já peguei várias vulnerabilidades relacionadas a validação de inputs com Laravel, mas essa especificamente em parte é culpa do próprio Laravel também.
Em um endpoint PUT de uma REST API, no FormRequest do endpoint, era feito a validação do campo $this->id afim de verificar se o usuário autenticado tinha permissão para alterar aquele recurso. Se não tivesse, ele recebia um 403 e tudo certo. Algo parecido com isto:
use App\Models\Post;
use Illuminate\Foundation\Http\FormRequest;
class UpdatePostRequest extends FormRequest
{
public function authorize(): bool
{
$post = Post::find($this->id);
if (!$post) {
return false;
}
return $this->user()->id === $post->user_id;
}
public function rules(): array
{
return [
'title' => ['required', 'string'],
];
}
}
No controller do endpoint, o objeto atualizado no banco de dados (usando sua model) era obtido usando o segundo argumento do método, $postId, enquanto no UpdatePostRequest era validado o campo $this->id. O controller era mais ou menos assim:
use App\Http\Requests\UpdatePostRequest;
use App\Models\Post;
use Illuminate\Http\Response;
class PostController
{
public function put(UpdatePostRequest $request, int $postId): Response
{
$post = Post::findOrFail($postId);
$post->update($request->validated());
return response()->noContent();
}
}
Acontece que, no Laravel, os campos de um request são obtidos usando o método mágico __get() que consulta qualquer campo com o nome id no body request, query parameter e parâmetros de rotas. Então $this->id não é garantido de ser o parâmetro {id} na rota do endpoint, pode ser um ?id=x no query parameter ou {"id": x} no request body.
Ou seja: no controller é atualizado o recurso com o ID especificado no parâmetro da rota e no FormRequest é validado outro campo, também chamado id, só que vindo de outro lugar.
Com isso, era possível um atacante enviar uma requisição como PUT /posts/2 com um campo "id": 1 no body, onde: a validação para verificar se o usuário tinha permissão de acesso era feita com o ID 1 (o id no body), mas o recurso realmente atualizado no banco de dados tinha ID 2 (o id na rota).
Essa vulnerabilidade é conhecida como Broken Object Level Authentication (BOLA). No OWASP Top 10 de 2025 ela se enquadra na categoria Broken Access Control, sendo a categoria de vulnerabilidades mais comum em aplicações web.
Essa vulnerabilidade seria bem menos provável de acontecer se o Laravel não usasse métodos mágicos para obter os campos de input que, do ponto de vista de segurança, é um design bem ruim.
É uma vulnerabilidade bem simples e não tem nada de "uau" em ler sobre ela, mas ela serve para mostrar o que eu já falei em outro post: Usar framework não faz com que o seu projeto fique "seguro".
O $request->id é recurso documentado no framework, não foi invenção de moda do desenvolvedor. E por usar o que a documentação do Laravel diz que pode usar, uma vulnerabilidade ocorreu no código.
