Saltar a contenido

Insecure Dynamic Library Loading

Carga insegura de bibliotecas dinámicas

Descripción

La carga dinámica de bibliotecas, cuando no se implementa de manera segura, puede introducir una serie de vulnerabilidades significativas que plantean graves riesgos para la seguridad y la integridad de una aplicación:

Ruta de carga insegura: Esta vulnerabilidad surge cuando una aplicación carga bibliotecas desde ubicaciones no confiables o manipuladas. Si una aplicación carga involuntariamente bibliotecas desde fuentes maliciosas, abre la puerta a posibles ataques. Los atacantes pueden explotar esta debilidad colocando bibliotecas maliciosas en ubicaciones no confiables, lo que lleva a la ejecución de código con privilegios elevados o al acceso no autorizado a recursos sensibles.

Secuestro de biblioteca (Library Hijacking): El secuestro de biblioteca ocurre cuando un atacante sustituye una biblioteca legítima por una maliciosa. Al alterar el proceso de carga de la biblioteca, el atacante puede engañar a la aplicación para que cargue la biblioteca maliciosa en su lugar. Esto puede permitir una amplia gama de ataques, incluida la inyección de código, la escalada de privilegios y el acceso no autorizado a datos sensibles.

Estas vulnerabilidades, si no se abordan, pueden tener graves consecuencias. Los atacantes pueden inyectar código malicioso en una aplicación, comprometer su integridad, obtener acceso no autorizado a recursos críticos y, potencialmente, explotar otras vulnerabilidades de seguridad.

Ejemplos de implementaciones vulnerables:

void loadDependency(String unsanitized_user_input) {
  // Dynamically load the library without sanitizing the input
  final dylib = DynamicLibrary.open(unsanitized_user_input);

  // Resolve and call a function from the loaded library that contains malicious code
  final libraryMethod =
      dylib.lookupFunction<Pointer<Utf8> Function(), Pointer<Utf8> Function()>(
          'getSensitiveData');
  final result = libraryMethod();
}
func loadDynamicLibrary(unsanitized_user_input: String) {

    // Attempt to dynamically load the library without sanitizing user input 
    if let libraryHandle = dlopen(unsanitized_user_input, RTLD_NOW) {
        // Library loaded successfully, resolve a function that could contain malicious code
        if let method = dlsym(libraryHandle, "libraryMethod") {
            typealias FunctionType = @convention(c) () -> String
            let function = unsafeBitCast(libraryMethod, to: FunctionType.self)
            let result = function()
        }
        dlclose(libraryHandle)
    }
}
fun loadDependency(unsanitized_user_input : String) {

    // Load the library dynamically without sanitizing the input
    System.load(unsanitized_user_input)

    // Perform some operation using the dynamically loaded library
    val result = libraryMethod()
}

Recomendación

Al tratar con la carga de bibliotecas dinámicas, un desarrollador debe:

  • Implementar técnicas sólidas de validación y sanitización de entradas para prevenir ataques de inyección y salto de directorio (directory traversal).
  • Cargar bibliotecas únicamente de fuentes confiables y verificadas, asegurando la integridad y autenticidad de las bibliotecas.
  • Aplicar controles de acceso estrictos para evitar la carga no autorizada de bibliotecas.
  • Actualizar y parchear periódicamente las bibliotecas para solucionar vulnerabilidades conocidas.
  • Emplear mecanismos sólidos de firma de código (code signing) y verificación de integridad para asegurar la integridad de las bibliotecas cargadas dinámicamente.

Ejemplos de implementaciones seguras:

void validation(String input){

    // Verify library file integrity
    Uint8List fileBytes = libraryFile.readAsBytesSync();
    Digest fileDigest = SHA256Digest().process(fileBytes);
    Uint8List expectedChecksum = Uint8List.fromList([...]);

    if (fileDigest.bytes != expectedChecksum) {
        return false; // Invalid file checksum
    }

    if (/* checks for .. patterns */ )
        return false; // Invalid path

    // Add additional validation checks specific to your implementation
    return true;
}

void loadDependency(String unsanitized_user_input) {

    // sanitize user input
    if (validation(unsanitized_user_input) == false)
        throw FormatException("invalid input");

    // Dynamically load the library with a validated input
    final dylib = DynamicLibrary.open(unsanitized_user_input);

    // Resolve and call a function from the loaded library
    final libraryMethod = 
    dylib.lookupFunction<Pointer<Utf8> Function(), Pointer<Utf8> Function()>(
        'getSensitiveData');
    final result = libraryMethod();
}
func validation(unsanitized_user_input: String){

    guard let fileData = fileManager.contents(atPath: libraryURL.path) else {
        return false // Failed to read file data
    }
    let fileDigest = SHA256.hash(data: fileData)

    // Compare the calculated checksum with the expected checksum
    let expectedChecksum: [UInt8] = [ /* Replace with your expected checksum */ ]
    if fileDigest != expectedChecksum {
        return false // Invalid file checksum

    if (/* checks for .. patterns */ )
        return false; // Invalid path

    // Add additional validation checks specific to your implementation
    return true;
  }
}

func loadDynamicLibrary(unsanitized_user_input: String) {

    // Validate your user input
    if !validation(unsanitized_user_input){
      throw CustomValidationError.invalidInput
    }

    // Attempt to dynamically load the library with validated user input 
    if let libraryHandle = dlopen(unsanitized_user_input, RTLD_NOW) {
        // Library loaded successfully, resolve a function that could contain malicious code
        if let method = dlsym(libraryHandle, "libraryMethod") {
            typealias FunctionType = @convention(c) () -> String
            let function = unsafeBitCast(libraryMethod, to: FunctionType.self)
            let result = function()
        }
        dlclose(libraryHandle)
    }
}
fun validation(input : String){

    // Verify library file integrity
    val fileBytes = Files.readAllBytes(libraryFile.toPath())
    val digest = MessageDigest.getInstance("SHA-256")
    val fileDigest = digest.digest(fileBytes)
    val expectedChecksum = byteArrayOf(...)

    if (!fileDigest.contentEquals(expectedChecksum)) {
        return false
    }

    if (/* checks for .. patterns */ )
        return false  // Invalid path

    // Add additional validation checks specific to your implementation
    return true
}

fun loadDependency(unsanitized_user_input : String) {

    // validate your uses input
    if (!validation(unsanitized_user_input))
        throw IllegalArgumentException("Invalid input")

    // Load the library dynamically with the validated input
    System.load(unsanitized_user_input)

    // Perform some operation using the dynamically loaded library
    val result = libraryMethod()
}

Enlaces

Estándares

  • OWASP_MASVS_L1:
    • MSTG_PLATFORM_3
  • OWASP_MASVS_L2:
    • MSTG_PLATFORM_3
  • PCI_STANDARDS:
    • REQ_6_2
    • REQ_6_3
    • REQ_11_3
  • OWASP_MASVS_v2_1:
    • MASVS_CODE_4
  • SOC2_CONTROLS:
    • CC_2_1
    • CC_4_1
    • CC_7_1
    • CC_7_2
    • CC_7_4
    • CC_7_5
  • HIPAA_CONTROLS:
    • SECURITY212
    • SECURITY213
    • SECURITY255