¿Cómo limitar setAccessible solo a usos "legítimos"?

100

Cuanto más aprendí sobre el poder de java.lang.reflect.AccessibleObject.setAccessible, más asombrado estoy de lo que puede hacer. Esto está adaptado de mi respuesta a la pregunta ( Uso de la reflexión para cambiar el File.separatorChar final estático para las pruebas unitarias ).

import java.lang.reflect.*;

public class EverythingIsTrue {
   static void setFinalStatic(Field field, Object newValue) throws Exception {
      field.setAccessible(true);

      Field modifiersField = Field.class.getDeclaredField("modifiers");
      modifiersField.setAccessible(true);
      modifiersField.setInt(field, field.getModifiers() & ~Modifier.FINAL);

      field.set(null, newValue);
   }
   public static void main(String args[]) throws Exception {      
      setFinalStatic(Boolean.class.getField("FALSE"), true);

      System.out.format("Everything is %s", false); // "Everything is true"
   }
}

Puedes hacer cosas realmente escandalosas:

public class UltimateAnswerToEverything {
   static Integer[] ultimateAnswer() {
      Integer[] ret = new Integer[256];
      java.util.Arrays.fill(ret, 42);
      return ret;
   }   
   public static void main(String args[]) throws Exception {
      EverythingIsTrue.setFinalStatic(
         Class.forName("java.lang.Integer$IntegerCache")
            .getDeclaredField("cache"),
         ultimateAnswer()
      );
      System.out.format("6 * 9 = %d", 6 * 9); // "6 * 9 = 42"
   }
}

Presumiblemente, los diseñadores de API se dan cuenta de lo abusivo que setAccessiblepuede ser, pero deben haber admitido que tiene usos legítimos para proporcionarlo. Entonces mis preguntas son:

  • ¿Para qué son los usos verdaderamente legítimos setAccessible?
    • ¿Podría Java haber sido diseñado para NO tener esta necesidad en primer lugar?
    • ¿Cuáles serían las consecuencias negativas (si las hubiera) de tal diseño?
  • ¿Puede restringirse setAccessiblea usos legítimos únicamente?
    • ¿Es solo a través SecurityManager?
      • ¿Como funciona? ¿Lista blanca / lista negra, granularidad, etc.?
      • ¿Es habitual tener que configurarlo en tus aplicaciones?
    • ¿Puedo escribir mis clases para que sean a setAccessibleprueba de agua independientemente de la SecurityManagerconfiguración?
      • ¿O estoy a merced de quien gestione la configuración?

Supongo que una pregunta más importante es: ¿DEBO PREOCUPARME POR ESTO ???

Ninguna de mis clases tiene apariencia alguna de privacidad exigible. El patrón singleton (dejando de lado las dudas sobre sus méritos) es ahora imposible de aplicar. Como muestran mis fragmentos anteriores, incluso algunas suposiciones básicas de cómo funciona Java fundamental no están ni siquiera cerca de estar garantizadas.

¿ESTOS PROBLEMAS NO SON REALES ???


Bien, acabo de confirmar: gracias a setAccessible, las cadenas de Java NO son inmutables.

import java.lang.reflect.*;

public class MutableStrings {
   static void mutate(String s) throws Exception {
      Field value = String.class.getDeclaredField("value");
      value.setAccessible(true);
      value.set(s, s.toUpperCase().toCharArray());
   }   
   public static void main(String args[]) throws Exception {
      final String s = "Hello world!";
      System.out.println(s); // "Hello world!"
      mutate(s);
      System.out.println(s); // "HELLO WORLD!"
   }
}

¿Soy el único que piensa que esto es una gran preocupación?

poligenelubricantes
fuente
30
¡Debería ver lo que pueden hacer en C ++! (Oh, y probablemente C #.)
Tom Hawtin - tackline

Respuestas:

105

¿DEBO PREOCUPARME POR ESTO ???

Eso depende completamente de qué tipo de programas esté escribiendo y para qué tipo de arquitectura.

Si está distribuyendo un componente de software llamado foo.jar a la gente del mundo, de todos modos está completamente a su merced. Podrían modificar las definiciones de clase dentro de su .jar (mediante ingeniería inversa o manipulación directa del código de bytes). Podrían ejecutar su código en su propia JVM, etc. En este caso, preocuparse no le servirá de nada.

Si está escribiendo una aplicación web que solo interactúa con personas y sistemas a través de HTTP y controla el servidor de aplicaciones, tampoco es una preocupación. Seguro que los compañeros programadores de su empresa pueden crear código que rompa su patrón singleton, pero solo si realmente lo desean.

Si su trabajo futuro es escribir código en Sun Microsystems / Oracle y tiene la tarea de escribir código para el núcleo de Java u otros componentes confiables, es algo que debe tener en cuenta. Sin embargo, preocuparse solo le hará perder el cabello. En cualquier caso, probablemente le obligarán a leer las Directrices de codificación segura junto con la documentación interna.

Si va a escribir subprogramas de Java, el marco de seguridad es algo que debe conocer. Encontrará que los applets sin firmar que intentan llamar a setAccessible solo darán como resultado una SecurityException.

setAccessible no es lo único que evita las comprobaciones de integridad convencionales. Hay una clase central de Java sin API llamada sun.misc.Unsafe que puede hacer prácticamente cualquier cosa que quiera, incluido acceder a la memoria directamente. El código nativo (JNI) también puede sortear este tipo de control.

En un entorno de espacio aislado (por ejemplo, Java Applets, JavaFX), cada clase tiene un conjunto de permisos y el acceso a Unsafe, setAccessible y las implementaciones nativas definidas están controladas por SecurityManager.

"Los modificadores de acceso de Java no están destinados a ser un mecanismo de seguridad".

Eso depende en gran medida de dónde se esté ejecutando el código Java. Las clases principales de Java utilizan modificadores de acceso como mecanismo de seguridad para hacer cumplir la zona de pruebas.

¿Cuáles son los usos verdaderamente legítimos de setAccessible?

Las clases principales de Java lo usan como una forma fácil de acceder a cosas que deben permanecer privadas por razones de seguridad. Como ejemplo, el marco de serialización de Java lo usa para invocar constructores de objetos privados al deserializar objetos. Alguien mencionó System.setErr, y sería un buen ejemplo, pero curiosamente los métodos de la clase System setOut / setErr / setIn usan código nativo para establecer el valor del campo final.

Otro uso legítimo obvio son los marcos (persistencia, marcos web, inyección) que necesitan echar un vistazo al interior de los objetos.

Los depuradores, en mi opinión, no entran en esta categoría, ya que normalmente no se ejecutan en el mismo proceso JVM, sino en la interfaz con la JVM utilizando otros medios (JPDA).

¿Podría Java haber sido diseñado para NO tener esta necesidad en primer lugar?

Esa es una pregunta bastante profunda para responder bien. Me imagino que sí, pero necesitaría agregar algún otro mecanismo (s) que podría no ser tan preferible.

¿Puede restringir setAccessible solo para usos legítimos?

La restricción OOTB más sencilla que puede aplicar es tener un SecurityManager y permitir setAccessible solo para el código que proviene de ciertas fuentes. Esto es lo que ya hace Java: las clases estándar de Java que provienen de su JAVA_HOME pueden hacer setAccessible, mientras que las clases de subprograma sin firmar de foo.com no pueden hacer setAccessible. Como se dijo antes, este permiso es binario, en el sentido de que uno lo tiene o no. No hay una forma obvia de permitir que setAccessible modifique ciertos campos / métodos mientras rechaza otros. Sin embargo, al usar SecurityManager, podría impedir que las clases hagan referencia a ciertos paquetes por completo, con o sin reflejo.

¿Puedo escribir mis clases para que se establezcan a prueba de accesibilidad independientemente de la configuración de SecurityManager? ... ¿O estoy a merced de quien gestione la configuración?

No puedes y ciertamente lo eres.

Sami Koivu
fuente
"Si estás distribuyendo un componente de software llamado foo.jar a la gente del mundo, estás completamente a su merced de todos modos. Ellos podrían modificar las definiciones de clase dentro de tu .jar (mediante ingeniería inversa o manipulación directa de códigos de bytes). podría ejecutar su código en su propia JVM, etc. En este caso, preocuparse no le servirá de nada ". ¿Sigue siendo así? ¿Algún consejo sobre cómo dificultar la manipulación del software (con suerte, de manera significativa)? Es difícil simplemente tirar las aplicaciones de escritorio Java por la ventana debido a esto.
Sero
@naaz Tendría que decir que nada ha cambiado. Es solo que la arquitectura trabaja en tu contra. Cualquier pieza de software que se ejecute en mi máquina sobre la que tenga control total, puedo modificarla. El elemento de disuasión más fuerte podría ser legal, pero no creo que funcione en todos los escenarios. Creo que los binarios de Java se encuentran entre los más fáciles de revertir, pero la gente con experiencia alterará cualquier cosa. La ofuscación ayuda al dificultar la realización de grandes cambios, pero cualquier tipo de booleano (como una verificación de derechos de autor) aún es trivial de evitar. En su lugar, podría intentar colocar las partes clave en un servidor.
Sami Koivu
¿Qué tal denuvo?
Sero
15
  • ¿Para qué son los usos verdaderamente legítimos setAccessible?

Pruebas unitarias, componentes internos de la JVM (por ejemplo, implementación System.setError(...)), etc.

  • ¿Podría Java haber sido diseñado para NO tener esta necesidad en primer lugar?
  • ¿Cuáles serían las consecuencias negativas (si las hubiera) de tal diseño?

Muchas cosas serían imposibles de implementar. Por ejemplo, varias inyecciones de persistencia, serialización y dependencia de Java dependen de la reflexión. Y prácticamente cualquier cosa que se base en las convenciones de JavaBeans en tiempo de ejecución.

  • ¿Puede restringirse setAccessiblea usos legítimos únicamente?
  • ¿Es solo a través SecurityManager?

Si.

  • ¿Como funciona? ¿Lista blanca / lista negra, granularidad, etc.?

Depende del permiso, pero creo que el permiso de uso setAccessiblees binario. Si desea granularidad, debe usar un cargador de clases diferente con un administrador de seguridad diferente para las clases que desea restringir. Supongo que podría implementar un administrador de seguridad personalizado que implemente una lógica más fina.

  • ¿Es habitual tener que configurarlo en tus aplicaciones?

No.

  • ¿Puedo escribir mis clases para que sean a setAccessibleprueba de agua independientemente de la SecurityManagerconfiguración?
    • ¿O estoy a merced de quien gestione la configuración?

No, no puedes, y sí lo eres.

La otra alternativa es "hacer cumplir" esto mediante herramientas de análisis de código fuente; por ejemplo, costumbre pmdo findbugsreglas. O revisión de código selectiva del código identificado por (digamos) grep setAccessible ....

En respuesta al seguimiento

Ninguna de mis clases tiene apariencia alguna de privacidad exigible. El patrón singleton (dejando de lado las dudas sobre sus méritos) es ahora imposible de aplicar.

Si eso le preocupa, supongo que debe preocuparse. Pero realmente no debería intentar obligar a otros programadores a respetar sus decisiones de diseño. Si las personas son lo suficientemente estúpidas como para usar la reflexión para crear gratuitamente múltiples instancias de sus singletons (por ejemplo), pueden vivir con las consecuencias.

Por otro lado, si se refiere a "privacidad" para abarcar el significado de proteger la información confidencial de la divulgación, está ladrando al árbol equivocado. La forma de proteger los datos confidenciales en una aplicación Java es no permitir que el código que no es de confianza ingrese al entorno limitado de seguridad que trata con datos confidenciales. Los modificadores de acceso de Java no están destinados a ser un mecanismo de seguridad.

<Ejemplo de cadena> - ¿Soy el único que piensa que esto es una preocupación ENORME?

Probablemente no sea el único :-). Pero en mi opinión, esto no es motivo de preocupación. Es un hecho aceptado que el código que no es de confianza debe ejecutarse en una caja de arena. Si tiene un código de confianza / un programador de confianza que hace cosas como esta, entonces sus problemas son peores que las cadenas inesperadamente mutables. (Piense en bombas lógicas, exfiltración de datos a través de canales encubiertos , etcétera)

Hay formas de abordar (o mitigar) el problema de un "mal actor" en su equipo de desarrollo u operaciones. Pero son costosos y restrictivos ... y excesivos para la mayoría de los casos de uso.

Stephen C
fuente
1
También es útil para cosas como implementaciones de persistencia. Reflections no es realmente útil para los depuradores (aunque originalmente estaba destinado a serlo). No puede cambiar (subclase) de manera útil SecurityManagerpara esto, porque todo lo que obtiene es la verificación de permisos; todo lo que realmente puede hacer es mirar el acc y tal vez recorrer la pila para verificar el hilo actual). grep java.lang.reflect.
Tom Hawtin - tackline
@Stephen C: +1 ya, pero también agradecería su opinión sobre una pregunta más que acabo de agregar. Gracias por adelantado.
polygenelubricants
Tenga en cuenta que grep es una idea terrible. Hay tantas formas de ejecutar código dinámicamente en Java que es casi seguro que perderá una. El código de la lista negra en general es una receta para el fracaso.
Antimony
"Los modificadores de acceso de Java no están destinados a ser un mecanismo de seguridad". - en realidad, sí lo son. Java está diseñado para permitir la coexistencia de código confiable y no confiable en la misma máquina virtual; esto fue parte de sus requisitos básicos desde el principio. Pero solo cuando el sistema se está ejecutando con un SecurityManager bloqueado. La capacidad para el código de la zona de pruebas depende completamente de la aplicación de especificadores de acceso.
Jules
@Jules - La mayoría de los expertos en Java no estarían de acuerdo con eso; por ejemplo, stackoverflow.com/questions/9201603/…
Stephen C
11

La reflexión es de hecho ortogonal a la seguridad / protección bajo esta perspectiva.

¿Cómo podemos limitar la reflexión?

Java tiene administrador de seguridad y ClassLoadercomo base para su modelo de seguridad. En tu caso, supongo que debes mirar java.lang.reflect.ReflectPermission.

Pero esto no resuelve por completo el problema de la reflexión. Las capacidades de reflexión que están disponibles deben estar sujetas a un esquema de autorización detallado, lo que no es el caso ahora. Por ejemplo, permitir que cierto marco use la reflexión (por ejemplo, Hibernate), pero no el resto de su código. O para permitir que un programa se refleje solo en modo de solo lectura, con fines de depuración.

Un enfoque que puede convertirse en la corriente principal en el futuro es el uso de los llamados espejos para separar las capacidades reflectantes de las clases. Consulte Espejos: principios de diseño para instalaciones de metanivel . Sin embargo, existen otras investigaciones que abordan este problema. Pero estoy de acuerdo en que el problema es más grave para los lenguajes dinámicos que para los estáticos.

¿Deberíamos preocuparnos por el superpoder que nos da la reflexión? Si y no.

Sí, en el sentido de que se supone que la plataforma Java está protegida con Classloaderun administrador de seguridad. La capacidad de meterse con la reflexión puede verse como una brecha.

No, en el sentido de que la mayoría de los sistemas no son del todo seguros. Muchas clases se pueden subclasificar con frecuencia y potencialmente ya podría abusar del sistema con solo eso. Por supuesto, las clases se pueden hacer finalo sellar para que no se puedan subclasificar en otro frasco. Pero solo unas pocas clases están aseguradas correctamente (por ejemplo, String) de acuerdo con esto.

Vea esta respuesta sobre la clase final para una buena explicación. Consulte también el blog de Sami Koivu para obtener más información sobre la piratería de Java en torno a la seguridad.

El modelo de seguridad de Java puede considerarse insuficiente hasta cierto punto. Algunos lenguajes como NewSpeak adoptan un enfoque aún más radical de la modularidad, donde solo tiene acceso a lo que se le otorga explícitamente mediante la inversión de dependencias (por defecto, nada).

También es importante tener en cuenta que la seguridad es relativa de todos modos . A nivel de idioma, por ejemplo, no puede evitar que un formulario de módulo consuma el 100% de la CPU o consuma toda la memoria hasta un OutOfMemoryException. Estas inquietudes deben abordarse por otros medios. Quizás veamos en el futuro Java extendido con cuotas de utilización de recursos, pero no es para mañana :)

Podría ampliar más el tema, pero creo que he dejado claro mi punto.

ewernli
fuente