Secure Coding: CWE 1123 – Sich selbst modifizierenden Code vermeiden

Die Common Weakness Enumeration CWE-1123 warnt vor dem übermäßigen Einsatz von sich selbst modifizierendem Code. Java-Entwickler sollten mit Bedacht agieren.

vorlesen Druckansicht 15 Kommentare lesen
Mann zeigt auf Spirale aus geometrischen Körpern

(Bild: erstellt mit Chat-GPT / DALL-E)

Lesezeit: 14 Min.
Von
  • Sven Ruppert
Inhaltsverzeichnis
close notice

This article is also available in English. It was translated with technical assistance and editorially reviewed before publication.

Sich selbst modifizierender Code ändert seine eigenen Anweisungen während der Ausführung. Diese Vorgehensweise bietet in der Praxis durchaus Vorteile, etwa im Hinblick auf die Anpassungsfähigkeit oder die Optimierung des Codes. Sie ist aufgrund der damit verbundenen – zum Teil erheblichen – Risiken und Herausforderungen im Allgemeinen nicht uneingeschränkt empfehlenswert. Insbesondere für Java-Entwicklerinnen und -Entwickler ist die Verwendung von sich selbst modifizierendem Code problematisch, denn sie beeinträchtigt die Vorhersagbarkeit, Lesbarkeit und Wartbarkeit der Codebasis. Außerdem unterstützt die Programmiersprache Java sich selbst modifizierenden Code nicht nativ. Die Common Weakness Enumeration CWE-1123 (Excessive Use of Self-Modifying Code) beschreibt die daraus resultierenden Gefahren.

Secure Coding – Sven Ruppert
Sven Ruppert

Seit 1996 programmiert Sven Java in Industrieprojekten und seit über 15 Jahren weltweit in Branchen wie Automobil, Raumfahrt, Versicherungen, Banken, UN und Weltbank. Seit über 10 Jahren ist er von Amerika bis nach Neuseeland als Speaker auf Konferenzen und Community Events, arbeitete als Developer Advocate für JFrog und Vaadin und schreibt regelmäßig Beiträge für IT-Zeitschriften und Technologieportale. Neben seinem Hauptthema Core Java beschäftigt er sich mit TDD und Secure Coding Practices.

Unvorhersehbares Verhalten: Sich selbst ändernder Code kann zu unerwartetem Programmverhalten führen, was die Diagnose und das Beheben von Fehlern erschwert.

Sicherheitslücken: Code, der sich selbst verändert, kann ein Angriffsvektor für verschiedene Attacken sein, einschließlich Injektionsangriffen und Malware.

Wartungsschwierigkeit: Solcher Code ist schwer zu lesen und zu verstehen, was die Wartung und Aktualisierung erschwert.

Leistungsprobleme: Sich selbst ändernder Code kann aufgrund des zusätzlichen Aufwands für die Änderung und deren Interpretation zur Laufzeit zu Leistungseinbußen führen.

Videos by heise

Dynamisches Klassenladen: Java ermöglicht das Laden von Klassen zur Laufzeit mithilfe von Mechanismen wie Reflection oder benutzerdefinierten Class Loader. Während das dynamische Laden von Klassen an sich nicht grundsätzlich problematisch oder gar falsch ist, kann dessen übermäßige oder offensichtliche Verwendung zu einem sich verselbständigenden Verhalten führen.

Bytecode-Manipulation: Die Verwendung von Bibliotheken wie ASM oder Javassist (Java Programming Assistant) zur Änderung des Java-Bytecodes zur Laufzeit kann dazu führen, dass sich der Code selbst ändert. Von dieser Praxis wird dringend abgeraten, es sei denn, sie ist unbedingt erforderlich.

Reflection: Obwohl Reflection eine leistungsstarke Funktion ist, kann sie auch missbraucht werden, etwa um private Felder, Methoden oder Klassen zu ändern. Das führt zu einem Verhalten, das schwer nachzuverfolgen und zu debuggen ist.

Codebeispiel fĂĽr riskantes, selbstmodifizierendes Verhalten in Java mithilfe der Bytecode-Manipulation:

import javassist.ClassPool;
import javassist.CtClass;
import javassist.CtMethod;
public class SelfModifyingExample {
 public static void main(String[] args) {
   try {
     ClassPool pool = ClassPool.getDefault();
     CtClass cc = pool.get("TargetClass");
     CtMethod m = cc.getDeclaredMethod("targetMethod");
     m.insertBefore("{ System.out.println(\"Method modified at runtime\"); }");
     cc.toClass();
     TargetClass target = new TargetClass();
     target.targetMethod();
   } catch (Exception e) {
     e.printStackTrace();
   }
 }
}
class TargetClass {
 public void targetMethod() {
   System.out.println("Original method execution");
 }
}

In diesem Beispiel wird die TargetClass-Methode targetMethod zur Laufzeit geändert, um eine zusätzliche print-Anweisung einzuschließen. Eine solche Änderung kann zu den oben beschriebenen Risiken führen.

Vermeiden von Änderungen am Laufzeitcode: Gestalte das System so, dass die Notwendigkeit von Änderungen am Laufzeitcode minimiert oder ganz eliminiert wird.

Designmuster verwenden: Setze Entwurfsmuster wie Strategie- oder Zustandsmuster ein, die Verhaltensänderungen ermöglichen, ohne den Code zur Laufzeit zu ändern.

Richtiger Einsatz von Reflection: Setze Reflection sparsam ein – idealerweise nur dann, wenn keine andere praktikable Lösung existiert. Dokumentiere die Verwendung sorgfältig.

Statische Code-Analyse: Verwende statische Code-Analysetools, um die EinfĂĽhrung von sich selbst modifizierendem Code zu erkennen und zu verhindern.

Die übermäßige Verwendung von sich selbst modifizierendem Code in Java birgt Risiken, die die Sicherheit, Wartbarkeit und Leistung von Anwendungen beeinträchtigen können. Durch die Einhaltung der vorgestellten Best Practices und das Verwenden von Entwurfsmustern, die Flexibilität und Anpassungsfähigkeit fördern, ohne den Code zur Laufzeit zu ändern, lassen sich die mit CWE-1123 verbundenen Fallstricke vermeiden.

Reflection in Java ermöglicht die Selbstbeobachtung und Manipulation von Klassen, Feldern, Methoden und Konstruktoren zur Laufzeit. Ein starker, übermäßiger oder unsachgemäßer Einsatz von Reflection kann im Sinne von CWE-1123 zu sich selbst modifizierendem Code führen. Die möglichen Folgen davon sind: unvorhersehbares Verhalten, Sicherheitslücken und Wartungsproblemen.

Das folgende Codebeispiel demonstriert den übermäßigen Einsatz von Reflection zur Änderung des Verhaltens einer Klasse zur Laufzeit – was als eine Form von sich selbst änderndem Code betrachtet werden kann:

import java.lang.reflect.Field;
import java.lang.reflect.Method;
public class ReflectionExample {
 public static void main(String[] args) {
   try {
// Original object creation
     MyClass original = new MyClass();
     original.printMessage();
// Using reflection to modify the behavior at runtime
     Class<?> clazz = Class.forName("MyClass");
     Method method = clazz.getDeclaredMethod("setMessage", String.class);
     method.setAccessible(true);
// Modify the private field value using reflection
     Field field = clazz.getDeclaredField("message");
     field.setAccessible(true);
     field.set(original, "Modified message");
// Verify the modification
     original.printMessage();
   } catch (Exception e) {
     e.printStackTrace();
   }
 }
}
class MyClass {
 private String message = "Original message";
 public void printMessage() {
   System.out.println(message);
 }
 private void setMessage(String message) {
   this.message = message;
 }
}

Die ReflectionExample-Klasse: Sie erstellt eine Instanz von MyClass und druckt die ursprüngliche Nachricht. Das Beispiel verwendet Reflection, um die private Feldnachricht und die private Methode setMessage von MyClass anzupassen. Anschließend wird der Wert des Nachrichtenfelds geändert und die geänderte Nachricht gedruckt.

Dieses Beispiel zeigt, wie Reflection das Verhalten und den Zustand eines Objekts zur Laufzeit ändern kann, was zu den in CWE-1123 beschriebenen Problemen führt.

Minderungsstrategien zu Reflection

Verwenden von Reflection minimieren: Vermeiden Sie Reflection, es sei denn, dies ist unbedingt erforderlich. Bevorzugen Sie alternative Entwurfsmuster, die Flexibilität ermöglichen, ohne den Code zur Laufzeit zu ändern.

Zugangskontrolle: Stellen Sie sicher, dass Felder und Methoden, die nicht geändert werden sollten, nach Möglichkeit privat und endgültig bleiben, um unbeabsichtigten Zugriff zu verhindern.

Statische Analysetools: Verwenden Sie statische Analysetools, um übermäßigen Einsatz von Reflection und andere riskante Praktiken in der Codebasis zu erkennen.

Codeüberprüfungen: Führen Sie gründliche Prüfungen des Codes durch, um die Verwendung von sich selbst modifizierendem Code durch Reflection zu erkennen und einzudämmen.

Reflection ist ein leistungsstarkes Tool in Java, aber Missbrauch kann zu den mit CWE-1123 verbundenen Risiken führen. Durch die Einhaltung von Best Practices und die Minimierung des Einsatzes von Reflection zum Ändern von Code zur Laufzeit können Entwicklerinnen und Entwickler die Sicherheit, Vorhersehbarkeit und Wartbarkeit ihrer Anwendungen aufrechterhalten.

Unter dynamischem Laden von Klassen versteht man in Java die Möglichkeit, Klassen zur Laufzeit zu laden und wieder zu entladen. Während dies in bestimmten Szenarien nützlich sein kann, führt eine übermäßige oder unsachgemäße Verwendung gegebenenfalls zu sich selbständig modifizierendem Code – und zieht dann die CWE-1123 beschriebenen Risiken nach sich: unvorhersehbares Verhalten, Sicherheitslücken und Wartungsprobleme.

Das Codebeispiel demonstriert den übermäßigen Einsatz des dynamischen Ladens zur Änderung des Verhaltens einer Klasse zur Laufzeit:

public class DynamicClassLoadingExample {
 public static void main(String[] args) {
   try {
// Load the original class
     ClassLoader classLoader = DynamicClassLoadingExample.class.getClassLoader();
     Class<?> loadedClass = classLoader.loadClass("MyClass");
// Create an instance of the loaded class
     Object instance = loadedClass.getDeclaredConstructor().newInstance();
// Invoke the original method
     loadedClass.getMethod("printMessage").invoke(instance);
// Dynamically load the modified class
     classLoader = new CustomClassLoader();
     loadedClass = classLoader.loadClass("ModifiedClass");
// Create an instance of the modified class
     instance = loadedClass.getDeclaredConstructor().newInstance();
// Invoke the modified method
     loadedClass.getMethod("printMessage").invoke(instance);
   } catch (Exception e) {
     e.printStackTrace();
   }
 }
}
// Original class definition
class MyClass {
 public void printMessage() {
   System.out.println("Original message");
 }
}
// Custom class loader to simulate loading a modified class
class CustomClassLoader extends ClassLoader {
 @Override
 public Class<?> loadClass(String name) throws ClassNotFoundException {
   if ("ModifiedClass".equals(name)) {
// Define a modified version of MyClass at runtime
     String modifiedClassName = "ModifiedClass";
     String modifiedClassBody = "public class " + modifiedClassName + " {" +
         "    public void printMessage() {" +
         "        System.out.println(\"Modified message\");" +
         "    }" +
         "}";
     byte[] classData = compileClass(modifiedClassName, modifiedClassBody);
     return defineClass(modifiedClassName, classData, 0, classData.length);
   }
   return super.loadClass(name);
 }
 private byte[] compileClass(String className, String classBody) {
// Simulate compiling the class body into bytecode (in a real scenario, use a compiler API)
// This is a placeholder for demonstration purposes
   return classBody.getBytes();
 }
}

Die DynamicClassLoadingExample-Klasse: Sie lädt eine Originalklasse MyClass und ruft deren printMessage-Methode auf. Lädt mithilfe eines benutzerdefinierten Class Loader dynamisch eine geänderte Version der Klasse: ModifiedClass. Nun erstellt sie eine Instanz der geänderten Klasse und ruft deren printMessage-Methode auf, die eine andere Nachricht druckt.

Minderungsstrategien fĂĽr dynamisches Laden von Klassen

Vermeiden des dynamischen Ladens von Klassen: Verwenden Sie das dynamische Laden von Klassen nur, wenn es unbedingt erforderlich ist und sich nicht durch andere Entwurfsmuster umgehen lässt.

Sichere Class Loader: Stellen Sie sicher, dass benutzerdefinierte Class Loader sicher sind und keine nicht vertrauenswürdigen oder bösartigen Klassen laden.

Statische Analysetools: Verwenden Sie statische Analysetools, um den übermäßigen Einsatz des dynamischen Ladens von Klassen und anderer riskanter Praktiken in der Codebasis zu erkennen.

Codeüberprüfungen: Führen Sie gründliche Prüfungen des Codes durch, um die Verwendung von sich selbst änderndem Code durch dynamisches Laden von Klassen zu identifizieren und einzudämmen.