Auditando lacunas de guarda: o par desprotegido é o bug
A maioria dos bugs de controle de acesso e validação ausente não é exótica - são um
manipulador em uma família que esqueceu a verificação que seus irmãos realizam. O
admin_delete que verifica um papel e o bulk_delete que não verifica. O
read_bounded que valida o tamanho e o read_fast que confia cegamente. Ler
arquivos de cima para baixo esconde esses bugs; comparar pares os revela. Este é um
dos movimentos de white-box de maior rendimento.
A assimetria que você está caçando
guarda → sumidouro (o caminho pretendido, seguro)
??? → sumidouro (um caminho par com a guarda faltando - o bug)
Quando usar
- Um sumidouro é acessível de vários locais de chamada e você suspeita que um deles pula uma verificação.
- Você está auditando um modelo de autorização, uma família de parsers ou um grupo de manipuladores em busca de uma verificação esquecida.
- Um achado precisa de seu "por que isso está errado" enquadrado como um diff concreto contra um par correto.
Verificação de escopo
Apenas fonte autorizada. Se você não pode nomear a autorização, pare.
O loop
-
Escolha uma âncora guardada. Encontre uma função que realiza validação antes de um sumidouro sensível - uma verificação de autenticação/propriedade, uma verificação de limites, uma lista de permissões, um sanitizador. Esta é sua referência para "o que é correto aqui".
-
Prenda a guarda ao sumidouro que ela protege. Nomeie a verificação exata e o sumidouro exato. "Correto" significa que a guarda domina o sumidouro: em todos os caminhos através da âncora, a verificação é executada antes do sumidouro. Mera presença na função não é dominação.
-
Enumere os pares estruturais. Amplie além dos vizinhos do mesmo arquivo - o ponto é parentesco estrutural: funções que alcançam o mesmo sumidouro; sobrescritas da mesma interface/método virtual; manipuladores na mesma tabela de rotas / mapa de dispatch / switch de comandos; membros paralelos de uma classe (
get_x/set_x/delete_x); chamadores de um helper compartilhado onde alguns sanitizam o argumento e outros o passam cru. Pares que alcançam o mesmo sumidouro são o sinal mais forte. -
Diferencie cada par contra a âncora - pelo que a guarda impõe, não por seu nome. Para cada par, pergunte se uma verificação equivalente domina o mesmo sumidouro. Classifique a lacuna:
- ausente - nenhuma verificação equivalente.
- contornável - presente, mas ignorável em algum caminho (retorno precoce, um
||de curto-circuito, um branch que pula a verificação). - mais fraca que o par - um tipo diferente de verificação: autenticação onde a âncora faz autorização; uma verificação de tipo onde a âncora faz limites; um campo onde a âncora verifica também o campo irmão.
- após o sumidouro - a verificação executa, mas após a operação perigosa.
- contexto errado - uma verificação real que não cobre a classe de payload deste sumidouro (um codificador que é errado para a linguagem do sumidouro).
-
Confirme a alcançabilidade com entrada do atacante. Um sumidouro desprotegido é apenas um bug se entrada não confiável o alcança. Rastreie o argumento perigoso do par de volta a uma fonte real (mão para
adjudicating-taint-paths). Um par chamado apenas com constantes não é um achado. -
Registre como um diff - no esquema. Declare como: "âncora A guarda sumidouro S com verificação C (dominando); par B alcança S com
guard_status= <ausente / contornável / mais fraca que o par / após o sumidouro / contexto errado>, e o argumento de B é controlado pelo atacante via ." Emita de acordo com o esquema de achado.
Exemplo prático
Âncora:
delete_own_post(usuário, post_id)- verificapost.proprietario == usuário.id(domina) antes dedb.delete(post). Par:admin_bulk_delete(ids)alcança o mesmo sumidourodb.delete; a única barreira é um@login_requiredno nível de rota, sem verificação de propriedade por objeto. Diff:guard_status = mais fraca que o par- autenticação presente, autorização (propriedade) ausente. Alcançabilidade:idsvêm diretamente do corpo da requisição; qualquer usuário autenticado o atinge. IDOR/BFLA confirmado, gravidade alta - "qualquer usuário logado deleta os posts de qualquer usuário pelo id."
Racionalizações a rejeitar
- "Há uma verificação na função, então está guardada." → Apenas se ela domina o sumidouro. Uma verificação em um branch que o sumidouro ignora não é guarda.
- "Ambas as funções têm autenticação, são equivalentes." → Autenticação não é autorização; uma verificação de tipo não é uma verificação de limites. Compare o que é imposto.
- "O par parece desprotegido - reporte." → Não até que você confirme que entrada do atacante alcança o sumidouro. Inalcançável ≠ achado.
- "Apenas vizinhos do mesmo arquivo." → O par perigoso muitas vezes vive em outro módulo alcançando o mesmo sumidouro. Enumere por estrutura, não por arquivo.
Executando isso na prática
Você precisa de ferramentas que possam agrupar funções pelo sumidouro que alcançam e mostrar o
corpo exato de cada uma, para que você possa difundir guarda por guarda e verificar dominação. Um
grafo de propriedade de código torna "quais pares alcançam este sumidouro, e quais validam primeiro"
diretamente respondível; caso contrário, enumere pares manualmente a partir dos locais de chamada do
sumidouro ou helper compartilhado e leia cada um. A etapa de alcançabilidade reutiliza
adjudicating-taint-paths.
Relacionados
adjudicating-taint-paths- confirme se o sumidouro do par é alcançável com entrada.hunting-bugs-with-a-code-graph- o loop mestre que este alimenta.- FINDING-SCHEMA.md - a forma que cada achado assume.