Verwendung von Low-Level-Funktionalität

Beschreibung

Die Verwendung von Low-Level-Funktionalität ist eine Schwachstelle, bei der Software Low-Level-Funktionalität einsetzt, die durch das Framework oder die Spezifikation, die ihren Betrieb regelt, ausdrücklich verboten oder nicht empfohlen wird. Die Nutzung von Low-Level-Funktionalität kann Spezifikationen auf unerwartete Weise verletzen, potenziell eingebaute Schutzmaßnahmen deaktivieren, ausnutzbare Inkonsistenzen schaffen oder das System Angriffen aussetzen, die das Framework verhindern sollte. Beispiele umfassen die Verwendung nativer Code-Aufrufe aus verwalteten Sprachen, direkte Socket-Operationen in Container-Umgebungen oder das Umgehen von Framework-Abstraktionen, um auf zugrunde liegende Systemressourcen zuzugreifen.

Risiko

Die Verwendung von Low-Level-Funktionalität führt zu Risiken, die das höhere Framework speziell verhindern sollte. Nativer Code, der von Java über JNI aufgerufen wird, kann Buffer Overflows und Speicherkorruption in ansonsten speichersichere Anwendungen einführen. Direkte Socket-Operationen in Servlets umgehen Container-Verbindungspooling und können möglicherweise Ressourcenerschöpfung verursachen. Der Zugriff auf Low-Level-APIs kann Sicherheits-Sandboxes, Protokollierung, Überwachung und Zugriffskontrollen umgehen, die auf Framework-Ebene implementiert sind. Zusätzlich ist Low-Level-Code oft plattformspezifisch, was Portabilitätsprobleme schafft und den Wartungsaufwand erhöht. Der Code wird schwerer zu überprüfen, da Sicherheitsprüfer sowohl das High-Level-Framework als auch die Low-Level-Implementierung verstehen müssen.

Lösung

Verwenden Sie Framework-bereitgestellte APIs und Abstraktionen anstelle von Low-Level-Operationen. Wenn Low-Level-Funktionalität absolut erforderlich ist, isolieren Sie sie sorgfältig und umhüllen Sie sie mit ordnungsgemäßer Validierung und Fehlerbehandlung. Dokumentieren Sie, warum Low-Level-Zugriff notwendig ist. Überprüfen Sie Framework-Spezifikationen, um zu verstehen, welche Operationen verboten sind und warum. Verwenden Sie statische Analysetools, um verbotene API-Nutzung zu erkennen. Wenden Sie zusätzliche Sicherheitsmaßnahmen an, um umgangene Framework-Schutzmaßnahmen zu kompensieren. Erwägen Sie, ob die Anforderung auf andere Weise innerhalb der Framework-Einschränkungen erfüllt werden kann.

Häufige Auswirkungen

AuswirkungDetails
IntegritätBereich: Integrität

Speicher modifizieren - Low-Level-Operationen können Speichersicherheit umgehen und Korruption ermöglichen.
ZugriffskontrolleBereich: Zugriffskontrolle

Umgehung des Schutzmechanismus - Framework-Sicherheitskontrollen können umgangen werden.
VerfügbarkeitBereich: Verfügbarkeit

DoS: Ressourcenverbrauch - Das Umgehen der Ressourcenverwaltung kann zu Erschöpfung führen.

Beispielcode

Verwundbarer Code

// Verwundbar: Java ruft unsicheren C-Code über JNI auf
public class VulnerableJNI {

    // Native Methodendeklaration
    public native void processInput(String input);

    static {
        System.loadLibrary("nativelib");
    }

    public void handleRequest(String userInput) {
        // Verwundbar: Benutzereingabe an nativen Code übergeben,
        // der Speichersicherheitsprobleme haben kann
        processInput(userInput);
    }
}

// Entsprechende verwundbare C-Implementierung
/*
JNIEXPORT void JNICALL Java_VulnerableJNI_processInput(
    JNIEnv *env, jobject obj, jstring input) {

    const char *str = (*env)->GetStringUTFChars(env, input, NULL);

    char buffer[64];
    // Verwundbar: Keine Grenzprüfung - Buffer Overflow!
    gets(buffer);

    // Verwundbar: Format-String-Schwachstelle
    printf(str);

    (*env)->ReleaseStringUTFChars(env, input, str);
}
*/
// Verwundbar: Direkte Socket-Nutzung im Servlet (verletzt J2EE-Spezifikation)
import javax.servlet.http.*;
import java.io.*;
import java.net.*;

public class VulnerableServlet extends HttpServlet {

    private static final String BACKEND_HOST = "backend.internal";

    @Override
    protected void doGet(HttpServletRequest request,
                        HttpServletResponse response)
            throws ServletException, IOException {

        // Verwundbar: Socket direkt im Servlet erstellen
        // Dies umgeht Container-Verbindungspooling
        // Verletzt J2EE-Spezifikation
        Socket sock = new Socket(BACKEND_HOST, 3000);

        try {
            PrintWriter out = new PrintWriter(sock.getOutputStream());
            BufferedReader in = new BufferedReader(
                new InputStreamReader(sock.getInputStream()));

            // Socket verwenden...
            out.println("REQUEST");
            String result = in.readLine();

            response.getWriter().write(result);
        } finally {
            sock.close();
        }
        // Probleme:
        // - Kein Verbindungspooling - ineffizient
        // - Umgeht Container-Überwachung
        // - Ressourcenleck bei Ausnahme
        // - Kein Timeout-Management
    }
}

// Verwundbar: Thread direkt in EJB verwenden (verletzt Spezifikation)
@Stateless
public class VulnerableEJB {

    public void processAsync(String data) {
        // Verwundbar: Thread-Erstellung in EJB verletzt Spezifikation
        // Container kann Thread-Lebenszyklus nicht verwalten
        Thread t = new Thread(() -> {
            processData(data);
        });
        t.start();
        // Thread entkommt Container-Kontrolle
    }
}
# Verwundbar: ctypes verwenden, um Python-Sicherheit zu umgehen
import ctypes

def vulnerable_native_call(user_input):
    # Verwundbar: Native Bibliothek direkt laden und aufrufen
    libc = ctypes.CDLL("libc.so.6")

    # Verwundbar: strcpy ohne Grenzprüfung verwenden
    buffer = ctypes.create_string_buffer(64)

    # Wenn user_input > 64 Bytes, tritt Buffer Overflow auf
    libc.strcpy(buffer, user_input.encode())

    return buffer.value

# Verwundbar: Direkte Speichermanipulation
def vulnerable_memory_access():
    # Verwundbar: Beliebigen Speicher lesen
    libc = ctypes.CDLL("libc.so.6")

    # Dies umgeht Pythons Speichersicherheit
    ptr = ctypes.c_void_p(0x12345678)  # Beliebige Adresse
    value = ctypes.c_int.from_address(ptr.value)  # Absturz oder Sicherheitsproblem
// Verwundbar: Unsafe-Code in C# verwenden
using System;
using System.Runtime.InteropServices;

public class VulnerableUnsafe {

    // Verwundbar: Unverwaltete Funktion importieren
    [DllImport("vulnerable.dll")]
    private static extern void ProcessBuffer(IntPtr buffer, int size);

    public unsafe void VulnerableProcess(byte[] data) {
        // Verwundbar: Unsafe-Pointer-Operationen verwenden
        fixed (byte* ptr = data) {
            // Umgeht .NET-Speichersicherheit
            // Aufgerufener nativer Code kann Schwachstellen haben
            ProcessBuffer((IntPtr)ptr, data.Length);
        }
    }

    public unsafe void PointerArithmetic() {
        int* numbers = stackalloc int[10];

        // Verwundbar: Keine Grenzprüfung in unsafe-Block
        for (int i = 0; i < 20; i++) {  // Schreibt über Array hinaus!
            numbers[i] = i;
        }
    }
}

Korrigierter Code

// Korrigiert: JNI vermeiden oder mit Validierung umhüllen
public class SecureJNI {

    // Wenn JNI absolut erforderlich ist, Eingaben validieren
    private native void processInputNative(byte[] input, int length);

    static {
        System.loadLibrary("nativelib_secure");
    }

    public void handleRequest(String userInput) {
        // Korrigiert: Eingabe vor nativem Aufruf validieren und begrenzen
        if (userInput == null) {
            throw new IllegalArgumentException("Eingabe darf nicht null sein");
        }

        if (userInput.length() > MAX_INPUT_LENGTH) {
            throw new IllegalArgumentException("Eingabe zu lang");
        }

        // Eingabe bereinigen
        String sanitized = sanitizeForNative(userInput);
        byte[] bytes = sanitized.getBytes(StandardCharsets.UTF_8);

        // Begrenztes Array mit expliziter Länge übergeben
        processInputNative(bytes, bytes.length);
    }

    // Besser: Pure Java-Alternative verwenden
    public void handleRequestPureJava(String userInput) {
        // Javas eingebaute Funktionalität anstelle von nativem Code verwenden
        processInJava(userInput);
    }
}

// Korrigierte C-Implementierung mit Grenzprüfung
/*
JNIEXPORT void JNICALL Java_SecureJNI_processInputNative(
    JNIEnv *env, jobject obj, jbyteArray input, jint length) {

    if (length > MAX_BUFFER_SIZE) {
        // Ausnahme werfen statt Overflow
        (*env)->ThrowNew(env,
            (*env)->FindClass(env, "java/lang/IllegalArgumentException"),
            "Eingabe zu groß");
        return;
    }

    jbyte *buffer = (*env)->GetByteArrayElements(env, input, NULL);
    if (buffer == NULL) return;

    // Mit Grenzprüfung verarbeiten
    char localBuffer[MAX_BUFFER_SIZE + 1];
    memcpy(localBuffer, buffer, length);
    localBuffer[length] = '\0';

    // Sichere Funktionen verwenden
    processDataSafe(localBuffer, length);

    (*env)->ReleaseByteArrayElements(env, input, buffer, JNI_ABORT);
}
*/
// Korrigiert: Container-verwaltete Ressourcen verwenden
import javax.servlet.http.*;
import javax.annotation.Resource;
import javax.sql.DataSource;
import java.io.*;

public class SecureServlet extends HttpServlet {

    // Korrigiert: Container-verwalteten Verbindungspool verwenden
    @Resource(name = "jdbc/BackendDB")
    private DataSource dataSource;

    // Für HTTP-Aufrufe Container-verwalteten HTTP-Client verwenden
    @Override
    protected void doGet(HttpServletRequest request,
                        HttpServletResponse response)
            throws ServletException, IOException {

        // Korrigiert: Framework-bereitgestellten HTTP-Client verwenden
        // Viele Container bieten dies an
        try (java.net.http.HttpClient client =
                 java.net.http.HttpClient.newHttpClient()) {

            java.net.http.HttpRequest req = java.net.http.HttpRequest.newBuilder()
                .uri(URI.create("http://backend.internal:3000/"))
                .build();

            java.net.http.HttpResponse<String> resp =
                client.send(req, java.net.http.HttpResponse.BodyHandlers.ofString());

            response.getWriter().write(resp.body());
        }
    }
}

// Korrigiert: Container-verwaltetes Async verwenden (EJB 3.1+)
@Stateless
public class SecureEJB {

    @Asynchronous  // Korrigiert: Container verwaltet asynchrone Ausführung
    public Future<String> processAsync(String data) {
        String result = processData(data);
        return new AsyncResult<>(result);
    }

    // Oder ManagedExecutorService verwenden
    @Resource
    private ManagedExecutorService executor;

    public void processWithManagedExecutor(String data) {
        // Korrigiert: Container-verwalteter Thread-Pool
        executor.submit(() -> processData(data));
    }
}
# Korrigiert: ctypes für sicherheitskritische Operationen vermeiden
import subprocess

def secure_process(user_input):
    # Korrigiert: Pythons subprocess mit ordnungsgemäßem Escaping verwenden
    # anstatt native Bibliotheken direkt aufzurufen
    result = subprocess.run(
        ['safe_processor', user_input],
        capture_output=True,
        text=True,
        timeout=30
    )
    return result.stdout

# Wenn native Interaktion erforderlich, ordnungsgemäße FFI-Bibliothek verwenden
import cffi

ffi = cffi.FFI()

# Schnittstelle explizit definieren
ffi.cdef("""
    int safe_process(const char* input, size_t input_len,
                     char* output, size_t output_len);
""")

lib = ffi.dlopen("libsafe.so")

def secure_native_call(user_input):
    # Korrigiert: Ordnungsgemäße Grenzbehandlung mit cffi
    input_bytes = user_input.encode('utf-8')
    output_buffer = ffi.new("char[1024]")

    result = lib.safe_process(
        input_bytes, len(input_bytes),
        output_buffer, 1024
    )

    if result < 0:
        raise RuntimeError("Verarbeitung fehlgeschlagen")

    return ffi.string(output_buffer).decode('utf-8')
// Korrigiert: Sicheren verwalteten Code verwenden
using System;
using System.Security;

public class SecureManaged {

    // Korrigiert: Verwaltete Alternativen verwenden
    public void SecureProcess(byte[] data) {
        // Verwaltete APIs anstelle von P/Invoke verwenden
        using var stream = new MemoryStream(data);
        using var reader = new BinaryReader(stream);

        // Mit verwaltetem Code verarbeiten
        // Volle Speichersicherheit und Grenzprüfung
    }

    // Wenn P/Invoke erforderlich, SafeHandle verwenden
    public void SecureInterop() {
        using var handle = new SafeFileHandle(/* ... */);
        // SafeHandle stellt ordnungsgemäße Bereinigung sicher
        // und verhindert Handle-Recycling-Angriffe
    }

    // Unsafe vermeiden, außer wenn absolut notwendig
    // Wenn erforderlich, Umfang minimieren
    public int SafeSum(int[] numbers) {
        // Span<T> anstelle von unsafe-Pointern verwenden
        Span<int> span = numbers.AsSpan();
        int sum = 0;
        foreach (int n in span) {
            sum += n;
        }
        return sum;
    }
}

CVE-Beispiele

  • CVE-2008-0657: Java-Anwendung, die JNI verwendete, rief verwundbaren C-Code mit Buffer Overflow auf.
  • CVE-2006-3747: Anwendung umging Framework-Sicherheit durch Verwendung von Low-Level-Dateioperationen.

Referenzen

  1. MITRE Corporation. "CWE-695: Use of Low-Level Functionality." https://cwe.mitre.org/data/definitions/695.html
  2. Oracle. "Java EE Specification Restrictions."
  3. CAPEC-36: Using Unpublished Interfaces or Functionality.