February 3, 2022

    How to Build a Log4Shell Detector with ProGuardCORE

    Log4Shell (CVE-2021-44228) is a zero-day vulnerability in Log4J, a popular open-source Java logging framework used by many organizations around the world. Though the vulnerability has been patched, and upgrading to a newer Log4J version solves the problem, not everyone has completed the necessary upgrade.

    A simple technique to detect the Log4Shell vulnerability is to find something unique in the vulnerable versions that is not in the patched version. That’s exactly the approach used by this Yara rule which detects a particular constructor that only appears in the vulnerable versions.

    In this blog post, we will show you how to build a Log4Shell detector using ProGuardCORE to determine if applications are using an older Log4J version that is susceptible to the vulnerability.

    Using ProGuardCORE to Detect Log4Shell

    ProGuardCORE is a library to parse, modify and analyze Java class files upon which the well-known shrinker, optimizer, and obfuscator ProGuard, and the compatible Android security solution DexGuard, are built.

    Using the ProGuardCORE toolbox, we can easily:

    • Read Java class files and extract classes from Jar files
    • Filter those classes for a specific class
    • Filter that specific class for a specific constructor

    We’ll concentrate the discussion on the last two points: these implement the actual detection logic. The input reading code will return aClassPoolinstance containing a collection ofProgramClassinstances given an input file or directory, which we can use to look for the constructor.

    Applying actions on a specific class

    ProGuardCORE uses the visitor pattern to interact with the model classes and provides many useful built-in visitors which allow traversing and filtering.

    The class we are interested in finding isorg.apache.logging.log4j.core.net.JndiManager. To apply a visitor to this class, we can call theclassesAcceptmethod on the program class pool with the class name as the first parameter (note that ProGuardCORE uses internal naming with/instead of.):

    programClassPool.classesAccept(
      "org/apache/logging/log4j/core/net/JndiManager",
      classVisitor
    )
    
    classVisitorin this snippet is an instance ofClassVisitor: it can be implemented by combining some of ProGuardCORE’s built-in visitors to delegate to aMemberVisitorthat can be applied to the constructor we’re interested in.

    Applying actions on a specific member

    TheJndiManager constructor we’re looking for has the following signature:

    private <init>(Ljava/lang/String;Ljavax/naming/Context;)V

    There are a few built-in ProGuardCORE visitors we can use to visit a specific constructor of a class:

    • AllMemberVisitor: aClassVisitorthat applies aMemberVisitorto all members (methods and fields) of a class.
    • MethodFilter: aMemberVisitorthat delegates to anotherMemberVisitorif the member is a method (i.e. not a field).
    • ConstructorMethodFilter: aMemberVisitor that delegates to anotherMemberVisitorif the method is a constructor.
    • MemberAccessFilter: aMemberVisitor that delegates to anotherMemberVisitorif the access flags match those given.
    • MemberDescriptorFilter: aMemberVisitorthat delegates to anotherMemberVisitorif the member descriptor matches the given descriptor.

    Putting these together, we can construct aClassVisitorthat delegates to aMemberVisitorif the member matches the specificJndiManagerconstructor signature:

    val classVisitor = AllMemberVisitor(
        MethodFilter(
            ConstructorMethodFilter(
                MemberAccessFilter(
                    /* requiredSetAccessFlags = */ PRIVATE, 
                    /* requiredUnsetAccessFlags = */ 0,
                    MemberDescriptorFilter(
                        "(Ljava/lang/String;Ljavax/naming/Context;)V",
                         memberVisitor
                    )
                )
            )
        )
    )

    Counting visits to members

    The main logic of our Log4Shell detector involves finding a specificJndiManagerconstructor. Now that we’ve constructed aClassVisitorthat will delegate to aMemberVisitorif this constructor is found - the implementation of thatMemberVisitorshould be as simple as knowing whether or not it was applied to any member of the visited class.

    ProGuardCORE contains a built-inMemberVisitorthat can be used for this purpose:
    • MemberCounter: aMemberVisitorthat counts the number of class members that have been visited.

    Using this, we can create thememberVisitorto be used with ourclassVisitorfrom the previous section:

    val memberVisitor = MemberCounter()
    val classVisitor = AllMemberVisitor(
        MethodFilter(
            ConstructorMethodFilter(
                MemberAccessFilter(
                    /* requiredSetAccessFlags = */ PRIVATE,
                    /* requiredUnsetAccessFlags = */ 0,
                    MemberDescriptorFilter(
                        "(Ljava/lang/String;Ljavax/naming/Context;)V",
                         memberVisitor
                    )
                )
            )
        )
    )

    After theclassVisitoris applied to the program class pool we can check how many times thememberVisitorwas applied. It should be once if this is an application using a vulnerable Log4J version.

    programClassPool.classesAccept(
      "org/apache/logging/log4j/core/net/JndiManager",
      classVisitor
    )
    println(memberVisitor.count) // prints 1 if vulnerable to Log4Shell

    Putting it all together

    Putting all this together, we can construct a function that takes aClassPoolof a given application as a parameter and returnstrueorfalsebased on whether or not the application is vulnerable to Log4Shell.

    There are some improvements we can make, as well:
    • The class and member filters in ProGuardCORE accept wildcards. This allows us to take into account shadow packing by prefixing the class name with a wildcard that matches any package: **org/apache/logging/log4j/core/net/JndiManager
    • A workaround for protecting against Log4Shell is to remove the class
      org/apache/logging/log4j/core/lookup/JndiLookupso we can first check if this class exists before we check if theJndiManage constructor exists.
    fun check(programClassPool: ClassPool): Boolean {
        val jndiLookupCounter = ClassCounter()
        val jndiManagerOldConstructorCounter = MemberCounter()
    
        programClassPool.classesAccept(
          "**org/apache/logging/log4j/core/lookup/JndiLookup",
          jndiLookupCounter)
    
        if (jndiLookupCounter.count == 0) return false
    
        programClassPool.classesAccept(
            "**org/apache/logging/log4j/core/net/JndiManager",
            AllMemberVisitor(
                MethodFilter(
                    ConstructorMethodFilter(
                        MemberAccessFilter(
                            /* requiredSetAccessFlags = */ PRIVATE,
                            /* requiredUnsetAccessFlags = */ 0,
                            MemberDescriptorFilter(
                                "(Ljava/lang/String;Ljavax/naming/Context;)V",
                                jndiManagerOldConstructorCounter
                            )
                        )
                    )
                )
            )
        )
    
        return jndiManagerOldConstructorCounter.count > 0
    }

    Conclusion

    We’ve shown how ProGuardCORE  provides a toolbox to easily implement a Log4Shell detector based on pattern matching classes and members. ProGuardCORE provides even more features not demonstrated here such as partial evaluation, Kotlin metadata support, and powerful instruction sequence matching and replacement.

    These powerful features are used at Guardsquare to build software, including ProGuard, the Kotlin metadata printer used in ProGuard Playground, and the Android security solution DexGuard.

    James Hamilton - Software Engineer

    Interested in learning more about ProGuardCORE?

    Start a conversation with our experts on the Guardsquare community >

    Other posts you might be interested in