Skip to content

Insecure Dynamic Library Loading

Insecure Dynamic Library Loading

Description

Dynamic loading of libraries, when not implemented securely, can introduce a range of significant vulnerabilities that pose serious risks to the security and integrity of an application:

Insecure Loading Path: This vulnerability arises when an application loads libraries from untrusted or manipulated locations. If an application unwittingly loads libraries from malicious sources, it opens the door to potential attacks. Attackers can exploit this weakness by placing malicious libraries in untrusted locations, leading to code execution with elevated privileges or unauthorized access to sensitive resources.

Library Hijacking: Library hijacking occurs when an attacker substitutes a legitimate library with a malicious one. By tampering with the library loading process, the attacker can deceive the application into loading the malicious library instead. This can enable a wide range of attacks, including code injection, privilege escalation, and unauthorized access to sensitive data.

These vulnerabilities, if left unaddressed, can have severe consequences. Attackers can inject malicious code into an application, compromise its integrity, gain unauthorized access to critical resources, and potentially exploit other security vulnerabilities.

Examples of vulnerable implementations:

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 malicous 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 malicous 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()
}

Recommendation

When dealing with dynamic library loading, A developer should:

  • Implement robust input validation and sanitization techniques to prevent directory traversal and injection attacks.
  • Only load libraries from trusted and verified sources, ensuring the integrity and authenticity of the libraries.
  • Enforce strict access controls to prevent unauthorized library loading.
  • Regularly update and patch libraries to address known vulnerabilities.
  • Employ strong code signing and integrity verification mechanisms to ensure the integrity of dynamically loaded libraries.

Examples of secure implementations :

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 .. paterns */ )
        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 .. paterns */ )
        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 malicous 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 .. paterns */ )
        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()
}

Standards

  • OWASP_MASVS_L1:
    • MSTG_PLATFORM_3
  • OWASP_MASVS_L2:
    • MSTG_PLATFORM_3
  • PCI_STANDARDS:
    • REQ_6_2
    • REQ_6_3
    • REQ_11_3