Joven hacker sonriendo

Hackeamos su software

cero falsos positivos

Inteligencia experta + Tecnología especializada
DXST - SAST - IAST - SCA - DevSecOps
Caja blanca - Caja gris - Caja negra
Atacando Aplicaciones Web, APIs, Apps Móviles,
Cliente Servidor, Servidores, Redes, Dispositivos IoT
IoT SCI: Sistemas de Control Industrial

Limitar tiempo de vida de recursos

Nuestros ethical hackers explican cómo evitar vulnerabilidades de seguridad mediante la creación, manipulación y eliminación correcta de recursos dentro de un programa Java, evitando que información disponible en memoria pueda ser capturada por usuarios no autorizados.

Necesidad

Eliminar (limpiar) recursos dentro de la aplicación cuando ya no estén en uso.

Contexto

A continuación se describe las circunstancias bajo las cuales la siguiente solución tiene sentido:

  1. Se está desarrollando una aplicación en Java versión 8.0 o superior.

  2. El código debe eliminar información sensible en memoria.

  3. La aplicación debe liberar recursos cuando ya no estén en uso,[1] limitando así su tiempo de vida.

Solución

  • Lectura no segura de ficheros:

    1. El siguiente bloque de código Java especifica el método readData donde se declara un objeto de la clase BufferedReader cuyo constructor recibe como parámetro un objeto InputStreamReader quien a su vez espera por un nuevo objeto FileInputStream. Lo anterior permite leer datos desde un archivo:

      1
      2
      3
      4
      5
      6
      void readData() throws IOException {
        BufferedReader br = new BufferedReader(new InputStreamReader(
        new FileInputStream("file")));
        // Leer desde el fichero
        String data = br.readLine();
      }
      

      El método readLine retorna los datos como un objeto String, los cuales pueden permanecer activos incluso mucho tiempo después de que ya no son necesarios.

  • Lectura segura de ficheros:

    Para solucionar lo anterior, se utiliza un búfer NIO (new I/O) asignado directamente para leer datos confidenciales desde el archivo. Con lo cual, éstos se pueden borrar inmediatamente después de su uso y no se almacenan en la memoria caché ni en el búfer en varias ubicaciones. Existen solo en la memoria del sistema.

    1. En primer lugar, declaramos una variable llamada zeroes de tipo byte cuyo tamaño está determinado por la variable bufferSize:

      test.java
      1
      2
      3
      4
      5
      class Test {
        public static void main (String args[]) throws IOException {
          void readData() {
            int bufferSize = 16 * 1024;
            byte zeroes = new byte[bufferSize];
      
    2. Posterior a ello, asignamos al objeto buffer de tipo ByteBuffer un nuevo búfer de byte directo (NIO), cuya capacidad es determinada por la variable bufferSize:

      1
        ByteBuffer buffer = ByteBuffer.allocateDirect(bufferSize);
      
    3. Mediante el método getChannel de la clase FileInputStream, se retorna un objeto FileChannel asociado al flujo de datos de entrada actual. Dicho objeto es asignado a la variable rdr del mismo tipo:

      1
        try (FileChannel rdr = (new FileInputStream("file.txt")).getChannel()) {
      
    4. Iniciamos la lectura del búfer:

      1
      2
        while (rdr.read(buffer) > 0) {
          buffer.flip();
      
    5. Imprimimos en pantalla el contenido del búfer:

      1
      2
      3
        while(buffer.hasRemaining()){
          System.out.print((char) buffer.get());
        }
      
    6. Por último, limpiamos y sobrescribimos con ceros el contenido del búfer:

       1
       2
       3
       4
       5
       6
       7
       8
       9
      10
              buffer.clear();
              buffer.put(zeroes); // sobrescribir el búfer con ceros
              buffer.clear();
            }
      
          } catch (Throwable e) {
            // Manejar el error
          }
        }
      }
      

      Note que la eliminación manual de los datos del búfer es obligatoria porque los búfer directos no son recogidos por el recolector de basura.

    7. Para ejecutar el anterior programa, desde la terminal se debe ubicar primero en el directorio del proyecto y ejecutar el comando javac:

      1
      2
      3
      4
      5
      $ ls
      file.txt Test.java
      $ javac Test.java
      $ ls
      file.txt Test.class Test.java
      
    8. Luego de compilar el programa correctamente, se obtiene el bytecode (archivo .class) del programa y se procede a ejecutar el mismo mediante el comando Java:

      1
      $ java Test.class
      
    9. La salida en consola luego de ejecutar el programa es la siguiente (en este caso el contenido del fichero son contraseñas de prueba):

      1
      2
      3
      4
      Contrasenas:
      FLUIDtest
      admin123
      javaTest
      

Descargas

Puedes descargar el código fuente pulsando en el siguiente enlace:

  1. Test.java contiene la definición del método readData cuyo fin es la manipulación segura de ficheros.




Haz un comentario

Estado de los servicios - Términos de Uso