Secure Coding: CWE 1123 – Avoid self-modifying code

The Common Weakness Enumeration CWE-1123 warns against the excessive use of self-modifying code. Java developers should act with caution.

listen Print view
Man points to spiral of geometric bodies

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

14 min. read
By
  • Sven Ruppert
Contents

Self-modifying code changes its own instructions during execution. In practice, this approach certainly offers advantages, for example in terms of adaptability or code optimization. It is not generally recommended without reservation due to the associated – sometimes considerable – risks and challenges. The use of self-modifying code is particularly problematic for Java developers, as it impairs the predictability, readability and maintainability of the code base. In addition, the Java programming language does not natively support self-modifying code. The Common Weakness Enumeration CWE-1123 (Excessive Use of Self-Modifying Code) describes the resulting risks.

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.

Unpredictable behavior: Self-modifying code can lead to unexpected program behavior, which makes it difficult to diagnose and correct errors.

Security vulnerabilities: Code that changes itself can be an attack vector for various attacks, including injection attacks and malware.

Maintenance difficulty: Such code is difficult to read and understand, making it difficult to maintain and update.

Performance issues: Self-modifying code can lead to performance degradation due to the additional effort required to modify and interpret it at runtime.

Videos by heise

Dynamic class loading: Java allows classes to be loaded at runtime using mechanisms such as reflection or custom class loaders. While dynamic loading of classes is not inherently problematic or even wrong, its excessive or overt use can lead to self-sustaining behavior.

Bytecode manipulation: The use of libraries such as ASM or Javassist (Java Programming Assistant) to modify the Java bytecode at runtime can cause the code itself to change. This practice is strongly discouraged unless it is absolutely necessary.

Reflection: Although Reflection is a powerful function, it can also be misused, for example to change private fields, methods or classes. This leads to behavior that is difficult to track and debug.

Code example of risky, self-modifying behavior in Java using 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 this example, the TargetClass method targetMethod is modified at runtime to include an additional print statement. Such a change can lead to the risks described above.

Avoid runtime code changes: Design the system to minimize or eliminate the need for runtime code changes.

Use design patterns: Use design patterns such as strategy or state patterns that allow behavioral changes without changing the code at runtime.

Correct use of reflection: Use reflection sparingly – ideally only when no other practicable solution exists. Document the use carefully.

Static code analysis: Use static code analysis tools to detect and prevent the introduction of self-modifying code.

Excessive use of self-modifying code in Java poses risks that can affect the security, maintainability and performance of applications. By following the best practices presented and using design patterns that promote flexibility and adaptability without changing the code at runtime, the pitfalls associated with CWE-1123 can be avoided.

Reflection in Java enables the introspection and manipulation of classes, fields, methods and constructors at runtime. Heavy, excessive or improper use of reflection can lead to self-modifying code in the sense of CWE-1123. The possible consequences of this are: unpredictable behavior, security vulnerabilities and maintenance problems.

The following code example demonstrates the excessive use of reflection to change the behavior of a class at runtime – which can be considered a form of self-modifying code:

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;
 }
}

The ReflectionExample class: it creates an instance of MyClass and prints the original message. The example uses reflection to customize the private field message and the private method setMessage of MyClass. The value of the message field is then changed and the modified message is printed.

This example shows how reflection can change the behavior and state of an object at runtime, which leads to the problems described in CWE-1123.

Mitigation strategies for Reflection

Minimize the use of reflection: Avoid reflection unless absolutely necessary. Prefer alternative design patterns that allow flexibility without changing the code at runtime.

Access control: Ensure that fields and methods that should not be changed remain private and final wherever possible to prevent unintended access.

Static analysis tools: Use static analysis tools to detect excessive use of reflection and other risky practices in the codebase.

Code reviews: Perform thorough code reviews to detect and mitigate the use of self-modifying code through reflection.

Reflection is a powerful tool in Java, but misuse can lead to the risks associated with CWE-1123. By following best practices and minimizing the use of reflection to modify code at runtime, developers can maintain the security, predictability and maintainability of their applications.

Dynamic loading of classes in Java refers to the ability to load and unload classes at runtime. While this can be useful in certain scenarios, excessive or inappropriate use may lead to self-modifying code – and result in the risks described in CWE-1123: unpredictable behavior, security vulnerabilities and maintenance issues.

The code example demonstrates the excessive use of dynamic loading to change the behavior of a class at runtime:

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

The DynamicClassLoadingExample class: it loads an original MyClass class and calls its printMessage method. Dynamically loads a modified version of the class using a custom class loader: ModifiedClass. Now it creates an instance of the modified class and calls its printMessage method, which prints a different message.

Mitigation strategies for dynamic loading of classes

Avoid dynamic loading of classes: Use dynamic class loading only when it is absolutely necessary and cannot be circumvented by other design patterns.

Secure class loaders: Ensure that custom class loaders are secure and do not load untrusted or malicious classes.

Static analysis tools: Use static analysis tools to detect overuse of dynamic class loading and other risky practices in the codebase.

Code reviews: Perform thorough code reviews to identify and mitigate the use of self-changing code through dynamic class loading.

Don't miss any news – follow us on Facebook, LinkedIn or Mastodon.

This article was originally published in German. It was translated with technical assistance and editorially reviewed before publication.