Auditoría de brechas de guardia: el par sin guardia es el bug
La mayoría de los bugs de control de acceso y validación faltante no son exóticos: son un
manejador en una familia que olvidó la comprobación que sus hermanos realizan. El
admin_delete que verifica un rol y el bulk_delete que no lo hace. El
read_bounded que valida la longitud y el read_fast que confía en ella. Leer
archivos de arriba a abajo oculta esto; comparar pares los revela. Esta es una de las
movidas de caja blanca de mayor rendimiento.
La asimetría que estás buscando
guardia → sumidero (el camino seguro e intencionado)
??? → sumidero (un camino par con la guardia faltante - el bug)
Cuándo usarlo
- Un sumidero es alcanzable desde varios sitios de llamada y sospechas que uno omite una comprobación.
- Estás auditando un modelo de autorización, una familia de analizadores o un grupo de manejadores por una comprobación olvidada.
- Un hallazgo necesita su "por qué esto está mal" enmarcado como una diferencia concreta contra un par correcto.
Verificación de alcance
Solo fuente autorizada. Si no puedes nombrar la autorización, detente.
El bucle
-
Elige un ancla con guardia. Encuentra una función que sí valide antes de un sumidero sensible: una comprobación de autorización/propiedad, una comprobación de límites, una lista blanca, un sanitizador. Esta es tu referencia de "cómo se ve lo correcto aquí".
-
Fija la guardia al sumidero que protege. Nombra la comprobación exacta y el sumidero exacto. "Correcto" significa que la guardia domina el sumidero: en cada camino a través del ancla, la comprobación se ejecuta antes del sumidero. La mera presencia en la función no es dominación.
-
Enumera los pares estructurales. Amplía más allá de los vecinos del mismo archivo: el punto es el parentesco estructural: funciones que alcanzan el mismo sumidero; anulaciones de la misma interfaz/método virtual; manejadores en la misma tabla de rutas / mapa de despacho / conmutador de comandos; miembros paralelos de una clase (
get_x/set_x/delete_x); llamadores de un ayudante compartido donde algunos sanitizan el argumento y otros lo pasan crudo. Los pares que alcanzan el mismo sumidero son la señal más fuerte. -
Diferenciación de cada par contra el ancla - por lo que la guardia impone, no su nombre. Para cada par, pregúntate si una comprobación equivalente domina el mismo sumidero. Clasifica la brecha:
- ausente - ninguna comprobación equivalente en absoluto.
- eludible - presente pero evitable en algún camino (retorno temprano, un
||de cortocircuito, una rama que salta la comprobación). - más débil que el par - un tipo diferente de comprobación: autenticación donde el ancla hace autorización; una comprobación de tipo donde el ancla hace límites; un campo donde el ancla también verifica el campo hermano.
- posterior al sumidero - la comprobación se ejecuta, pero después de la operación peligrosa.
- contexto incorrecto - una comprobación real que no cubre la clase de carga útil de este sumidero (un codificador que es incorrecto para el lenguaje del sumidero).
-
Confirma la alcanzabilidad con entrada del atacante. Un sumidero sin guardia solo es un bug si la entrada no confiable lo alcanza. Rastrea el argumento peligroso del par hasta una fuente real (entrega a
adjudicating-taint-paths). Un par que solo se llama con constantes no es un hallazgo. -
Registra como una diferencia - en el esquema. Enúncialo como: "el ancla A protege el sumidero S con la comprobación C (dominante); el par B alcanza S con
guard_status= <ausente / eludible / más débil que el par / posterior al sumidero / contexto incorrecto>, y el argumento de B está bajo control del atacante a través de ." Emitir según el esquema de hallazgos.
Ejemplo práctico
Ancla:
delete_own_post(usuario, post_id)- verificapost.propietario == usuario.id(domina) antes dedb.delete(post). Par:admin_bulk_delete(ids)alcanza el mismo sumiderodb.delete; la única puerta de entrada es un@login_requireda nivel de ruta, sin comprobación de propiedad por objeto. Diferencia:guard_status = más débil que el par- autenticación presente, autorización (propiedad) ausente. Alcanzabilidad:idsproviene directamente del cuerpo de la solicitud; cualquier usuario autenticado lo alcanza. IDOR/BFLA confirmado, gravedad alta - "cualquier usuario conectado elimina los posts de cualquier usuario por id."
Racionalizaciones para rechazar
- "Hay una comprobación en la función, por lo tanto está protegida." → Solo si domina el sumidero. Una comprobación en una rama que el sumidero omite no es una guardia.
- "Ambas funciones tienen autenticación, son equivalentes." → La autenticación no es autorización; una comprobación de tipo no es una comprobación de límites. Compara lo que se impone.
- "El par parece desprotegido - repórtalo." → No hasta que confirmes que la entrada del atacante alcanza el sumidero. Inalcanzable ≠ hallazgo.
- "Solo vecinos del mismo archivo." → El par peligroso a menudo vive en otro módulo que alcanza el mismo sumidero. Enumera por estructura, no por archivo.
Ejecutando esto en la práctica
Necesitas herramientas que puedan agrupar funciones por el sumidero que alcanzan y mostrar el
cuerpo exacto de cada una, para que puedas comparar guardia por guardia y verificar la dominación. Un
grafo de propiedades de código hace que "qué pares alcanzan este sumidero, y cuáles validan primero"
sea directamente respondible; de lo contrario, enumera los pares manualmente desde los sitios de llamada del
sumidero compartido o ayudante y lee cada uno. El paso de alcanzabilidad reutiliza
adjudicating-taint-paths.
Relacionados
adjudicating-taint-paths- confirma que el sumidero del par es alcanzable con entrada.hunting-bugs-with-a-code-graph- el bucle maestro del que se alimenta esto.- FINDING-SCHEMA.md - la forma que toma cada hallazgo.