En Java, las comprobaciones de permisos normalmente son manejadas por el Administrador de Seguridad. Para evitar que el código no confiable invoque código privilegiado y explote algún error en el código privilegiado, SecurityManager comprueba toda la pila de llamadas; Si alguna de las personas que llaman en el seguimiento de la pila no tiene privilegios, de forma predeterminada, se deniega la solicitud. Al menos, así es cómo funcionan las comprobaciones estándar de SecurityManager .
Sin embargo, algunas API de Java especiales siguen reglas diferentes. Pasan por alto las comprobaciones estándar de SecurityManager y sustituyen a las comprobaciones más débiles. En particular, verifican solo al llamante inmediato, no a toda la pila de llamadas. (Consulte Pauta 9-8 de las Pautas de codificación segura de Java para obtener más información. Las API especiales incluya, por ejemplo, Class.forName()
, Class.getMethod()
, y más.)
¿Por qué? ¿Por qué estas API especiales omiten las comprobaciones estándar y sustituyen a las débiles? Y, ¿por qué es esto seguro? En otras palabras, ¿por qué es suficiente para ellos verificar solo a la persona que llama de inmediato? ¿Esto no reintroduce todos los riesgos contra los cuales se diseñaron las comprobaciones estándar de SecurityManager?
Me enteré de esto al leer un análisis de la reciente exploit de día cero en Java (CVE-2012-4681). Ese análisis deconstruye cómo funciona el exploit. Entre otras cosas, el ataque implica aprovechar la comprobación más débil realizada por estas API especiales. En particular, el código Java malintencionado se las arregla para obtener una referencia a una clase de sistema confiable (a través de un error separado), luego engaña a esa clase de sistema confiable invocando una de estas API especiales. La verificación de permisos resultante solo se ve en su interlocutor inmediato, ve que el interlocutor inmediato es de confianza y permite la operación, aunque la operación se inició originalmente con un código que no es de confianza. Por lo tanto, las comprobaciones más débiles no detienen el ataque, pero, por lo que puedo ver, este ataque se habría prevenido mediante las comprobaciones estándar de SecurityManager (ya que la persona que llama no es confiable). En otras palabras, este ataque reciente parece un ejemplo de por qué el cheque más débil es riesgoso.
Sin embargo, sé que los diseñadores de Java son gente inteligente. Sospecho que los diseñadores de Java deben haber considerado estos problemas y tener una buena razón para eludir las comprobaciones estándar y sustituir las débiles de estas API especiales, o, al menos, pensaron que tenían una buena razón por la que esto era seguro. Entonces, tal vez me esté perdiendo algo.
¿Alguien puede arrojar alguna luz sobre esto? ¿Se equivocaron los diseñadores de Java con estas API especiales o hubo razones válidas para sustituir los cheques más débiles?
Editar 9/1: no pregunto cómo funciona el exploit; Creo que entiendo cómo funciona el exploit. Tampoco estoy preguntando por qué, en este ejemplo en particular, el código de confianza que invocó estas API especiales tenía errores. Más bien, me pregunto por qué las API especiales, como Class.forName()
, Class.getMethod()
, etc., se especifican e implementan para usar la verificación de permisos más débiles no estándar (solo busque en el llamante inmediato) en lugar de Verificación de permisos estándar de SecurityManager (mire la pila de llamadas completa). Esta decisión de diseño (de utilizar comprobaciones de permisos más débiles para esas API especiales) permitió la reciente vulnerabilidad, por lo que sería fácil criticar la decisión de diseño. Sin embargo, me imagino que podría haber algunas buenas razones para hacer las cosas de esta manera, y me pregunto cuáles podrían ser.