Skip to content

Instantly share code, notes, and snippets.

@Neo23x0
Last active January 28, 2024 08:19
Show Gist options
  • Save Neo23x0/e4c8b03ff8cdf1fa63b7d15db6e3860b to your computer and use it in GitHub Desktop.
Save Neo23x0/e4c8b03ff8cdf1fa63b7d15db6e3860b to your computer and use it in GitHub Desktop.
Log4j RCE CVE-2021-44228 Exploitation Detection

log4j RCE Exploitation Detection

You can use these commands and rules to search for exploitation attempts against log4j RCE vulnerability CVE-2021-44228

Grep / Zgrep

This command searches for exploitation attempts in uncompressed files in folder /var/log and all sub folders

sudo egrep -I -i -r '\$(\{|%7B)jndi:(ldap[s]?|rmi|dns|nis|iiop|corba|nds|http):/[^\n]+' /var/log

This command searches for exploitation attempts in compressed files in folder /var/log and all sub folders

sudo find /var/log -name \*.gz -print0 | xargs -0 zgrep -E -i '\$(\{|%7B)jndi:(ldap[s]?|rmi|dns|nis|iiop|corba|nds|http):/[^\n]+'

Grep / Zgrep - Obfuscated Variants

These commands cover even the obfuscated variants but lack the file name in a match.

This command searches for exploitation attempts in uncompressed files in folder /var/log and all sub folders

sudo find /var/log/ -type f -exec sh -c "cat {} | sed -e 's/\${lower://'g | tr -d '}' | egrep -I -i 'jndi:(ldap[s]?|rmi|dns|nis|iiop|corba|nds|http):'" \;

This command searches for exploitation attempts in compressed files in folder /var/log and all sub folders

sudo find /var/log/ -name '*.gz' -type f -exec sh -c "zcat {} | sed -e 's/\${lower://'g | tr -d '}' | egrep -i 'jndi:(ldap[s]?|rmi|dns|nis|iiop|corba|nds|http):'" \;

Log4Shell-Rex

A massive regex to cover even the most obfuscated variants: https://github.com/back2root/log4shell-rex

(?:\$|%(?:25)*24|\\(?:0024|0{0,2}44))(?:{|%(?:25)*7[Bb]|\\(?:007[Bb]|0{0,2}173)).{0,30}?((?:[Jj]|%(?:25)*[46][Aa]|\\(?:00[46][Aa]|0{0,2}1[15]2)).{0,30}?(?:[Nn]|%(?:25)*[46][Ee]|\\(?:00[46][Ee]|0{0,2}1[15]6)).{0,30}?(?:[Dd]|%(?:25)*[46]4|\\(?:00[46]4|0{0,2}1[04]4)).{0,30}?(?:[Ii]|%(?:25)*[46]9|\\(?:00[46]9|0{0,2}1[15]1)|ı).{0,30}?(?::|%(?:25)*3[Aa]|\\(?:003[Aa]|0{0,2}72)).{0,30}?((?:[Ll]|%(?:25)*[46][Cc]|\\(?:00[46][Cc]|0{0,2}1[15]4)).{0,30}?(?:[Dd]|%(?:25)*[46]4|\\(?:00[46]4|0{0,2}1[04]4)).{0,30}?(?:[Aa]|%(?:25)*[46]1|\\(?:00[46]1|0{0,2}1[04]1)).{0,30}?(?:[Pp]|%(?:25)*[57]0|\\(?:00[57]0|0{0,2}1[26]0))(?:.{0,30}?(?:[Ss]|%(?:25)*[57]3|\\(?:00[57]3|0{0,2}1[26]3)))?|(?:[Rr]|%(?:25)*[57]2|\\(?:00[57]2|0{0,2}1[26]2)).{0,30}?(?:[Mm]|%(?:25)*[46][Dd]|\\(?:00[46][Dd]|0{0,2}1[15]5)).{0,30}?(?:[Ii]|%(?:25)*[46]9|\\(?:00[46]9|0{0,2}1[15]1)|ı)|(?:[Dd]|%(?:25)*[46]4|\\(?:00[46]4|0{0,2}1[04]4)).{0,30}?(?:[Nn]|%(?:25)*[46][Ee]|\\(?:00[46][Ee]|0{0,2}1[15]6)).{0,30}?(?:[Ss]|%(?:25)*[57]3|\\(?:00[57]3|0{0,2}1[26]3))|(?:[Nn]|%(?:25)*[46][Ee]|\\(?:00[46][Ee]|0{0,2}1[15]6)).{0,30}?(?:[Ii]|%(?:25)*[46]9|\\(?:00[46]9|0{0,2}1[15]1)|ı).{0,30}?(?:[Ss]|%(?:25)*[57]3|\\(?:00[57]3|0{0,2}1[26]3))|(?:.{0,30}?(?:[Ii]|%(?:25)*[46]9|\\(?:00[46]9|0{0,2}1[15]1)|ı)){2}.{0,30}?(?:[Oo]|%(?:25)*[46][Ff]|\\(?:00[46][Ff]|0{0,2}1[15]7)).{0,30}?(?:[Pp]|%(?:25)*[57]0|\\(?:00[57]0|0{0,2}1[26]0))|(?:[Cc]|%(?:25)*[46]3|\\(?:00[46]3|0{0,2}1[04]3)).{0,30}?(?:[Oo]|%(?:25)*[46][Ff]|\\(?:00[46][Ff]|0{0,2}1[15]7)).{0,30}?(?:[Rr]|%(?:25)*[57]2|\\(?:00[57]2|0{0,2}1[26]2)).{0,30}?(?:[Bb]|%(?:25)*[46]2|\\(?:00[46]2|0{0,2}1[04]2)).{0,30}?(?:[Aa]|%(?:25)*[46]1|\\(?:00[46]1|0{0,2}1[04]1))|(?:[Nn]|%(?:25)*[46][Ee]|\\(?:00[46][Ee]|0{0,2}1[15]6)).{0,30}?(?:[Dd]|%(?:25)*[46]4|\\(?:00[46]4|0{0,2}1[04]4)).{0,30}?(?:[Ss]|%(?:25)*[57]3|\\(?:00[57]3|0{0,2}1[26]3))|(?:[Hh]|%(?:25)*[46]8|\\(?:00[46]8|0{0,2}1[15]0))(?:.{0,30}?(?:[Tt]|%(?:25)*[57]4|\\(?:00[57]4|0{0,2}1[26]4))){2}.{0,30}?(?:[Pp]|%(?:25)*[57]0|\\(?:00[57]0|0{0,2}1[26]0))(?:.{0,30}?(?:[Ss]|%(?:25)*[57]3|\\(?:00[57]3|0{0,2}1[26]3)))?).{0,30}?(?::|%(?:25)*3[Aa]|\\(?:003[Aa]|0{0,2}72)).{0,30}?(?:\/|%(?:25)*2[Ff]|\\(?:002[Ff]|0{0,2}57)|\${)|(?:[Bb]|%(?:25)*[46]2|\\(?:00[46]2|0{0,2}1[04]2)).{0,30}?(?:[Aa]|%(?:25)*[46]1|\\(?:00[46]1|0{0,2}1[04]1)).{0,30}?(?:[Ss]|%(?:25)*[57]3|\\(?:00[57]3|0{0,2}1[26]3)).{0,30}?(?:[Ee]|%(?:25)*[46]5|\\(?:00[46]5|0{0,2}1[04]5)).{2,60}?(?::|%(?:25)*3[Aa]|\\(?:003[Aa]|0{0,2}72))(JH[s-v]|[\x2b\x2f-9A-Za-z][CSiy]R7|[\x2b\x2f-9A-Za-z]{2}[048AEIMQUYcgkosw]ke[\x2b\x2f-9w-z]))

Log4Shell Detector (Python)

Python based scanner to detect the most obfuscated forms of the exploit codes.

https://github.com/Neo23x0/log4shell-detector

Find Log4j on Linux

ps aux | egrep '[l]og4j'
find / -iname "log4j*"
lsof | grep log4j
grep -r --include *.[wj]ar "JndiLookup.class" / 2>&1 | grep matches

Find Vulnerable Log4j on Windows

gci 'C:\' -rec -force -include *.jar -ea 0 | foreach {select-string "JndiLookup.class" $_} | select -exp Path

by @CyberRaiju

YARA

https://github.com/Neo23x0/signature-base/blob/master/yara/expl_log4j_cve_2021_44228.yar

Help

Please report findings that are not covered by these detection attempts.

Credits

I got help and ideas from

@celloza
Copy link

celloza commented Dec 15, 2021

If you have root access to a box containing docker images, based on @umitgunduz 's comment, you can use the following script to iterate through all docker containers, and find usages of JndiLookup.class:

for container in `docker ps -q`; do
# show the name of the container
docker inspect --format='{{.Name}}' $container;
 # find instances of JndiLookup.class in files matching with log4*.jar
docker exec -u root $container find -name "log4*.jar" -exec sh -c 'unzip -l "{}" | grep -i --color=always JndiLookup.class' \; -print
done

@jig-champ
Copy link

Required help please for Solaris 10, getting below

bash-3.2# egrep -I -i -r '$({|%7B)jndi:(ldap[s]?|rmi|dns|nis|iiop|corba|nds|http):/[^\n]+' /var/log
egrep: illegal option -- I
egrep: illegal option -- r
usage: egrep [ -bchilnsv ] [ -e exp ] [ -f file ] [ strings ] [ file ] ...
bash-3.2# find /var/log -name *.gz -print0 | xargs -0 zgrep -E -i '$({|%7B)jndi:(ldap[s]?|rmi|dns|nis|iiop|corba|nds|http):/[^\n]+' find: bad option -print0
find: [-H | -L] path-list predicate-list
xargs: illegal option -- 0
xargs: Usage: xargs: [-t] [-p] [-e[eofstr]] [-E eofstr] [-I replstr] [-i[replstr]] [-L #] [-l[#]] [-n # [-x]] [-s size] [cmd [args ...]]

@carlchan
Copy link

carlchan commented Dec 15, 2021

Here's one I've been working on. So far detects all variations I've seen,even urlencoded. Just make sure to use case-insensitive for grep, or /i for everythign else.

https://gist.github.com/carlchan/916ed5edbef7c8d1c00be67daae8933e

(\$|%(25)*24)(\{|%(25)*7B)(((\$|%(25)*24)(\{|%(25)*7B)[^}]+(j|%[46]a)(n|%[46]e)?(d|%[46]4)?(i|%[46]9)?(%(25)*7(d|%[46]4)|\})|(j|%[46]a)(n|%[46]e)?(d|%[46]4)?(i|%[46]9)?)((\$|%(25)*24)(\{|%(25)*7B)[^}]+(j|%[46]a)?(n|%[46]e)(d|%[46]4)?(i|%[46]9)?(%(25)*7(d|%[46]4)|\})|(j|%[46]a)?(n|%[46]e)(d|%[46]4)?(i|%[46]9)?)((\$|%(25)*24)(\{|%(25)*7B)[^}]+(j|%[46]a)?(n|%[46]e)?(d|%[46]4)(i|%[46]9)?(%(25)*7(d|%[46]4)|\})|(j|%[46]a)?(n|%[46]e)?(d|%[46]4)(i|%[46]9)?)((\$|%(25)*24)(\{|%(25)*7B)[^}]+(j|%[46]a)?(n|%[46]e)?(d|%[46]4)?(i|%[46]9)(%(25)*7(d|%[46]4)|\})|(j|%[46]a)?(n|%[46]e)?(d|%[46]4)?(i|%[46]9))|((\$|%(25)*24)(\{|%(25)*7B)[^}]+(j|%[46]a)?(n|%[46]e)?(d|%[46]4)?(i|%[46]9)?(%(25)*7(d|%[46]4)|\})|(j|%[46]a|n|%[46]e|d|%[46]4|i|%[46]9)+)+)

@realtebo
Copy link

In our server there are ZERO software using log4j
We detected a LOT of attempt but how to understand if an attack succeded?

@knightian
Copy link

@realtebo I guess if you have zero software using log4j then it is not possible for an attack to succeed right?

@chetkhatri
Copy link

@alevenelli This is great, can you share some windows or powershall command to delete "JndiLookup.class" file. Thanks much

For Windows Servers and Computers, a simple command to get all the jar files with "jndilookup.class":
for /r %f in (*.jar) do (find /i /c "JndiLookup.class" "%f" 1>nul && echo "%f" >> "c:\lista.txt")

@juliusmusseau
Copy link

juliusmusseau commented Dec 16, 2021

Fantastic resource here. Thanks!

I've put together a free (GPLv3) log4j detector for scanning the file-system. Handy for figuring out exactly what versions are lurking in the folders. Looks for String literals embedded inside the *.class files to determine Log4J version information (not based on hashes at all).

https://github.com/mergebase/log4j-detector

Sample output:

java -jar log4j-detector-2021.12.15.jar ./samples 

/opt/mergebase/log4j-detector/samples/log4j-1.2.17.jar contains Log4J-1.x   <= 1.2.17 _OLD_ :-|
/opt/mergebase/log4j-detector/samples/log4j-core-2.0-beta2.jar contains Log4J-2.x   <= 2.0-beta8 _POTENTIALLY_SAFE_ :-| (or did you already remove JndiLookup.class?) 
/opt/mergebase/log4j-detector/samples/log4j-core-2.0-beta9.jar contains Log4J-2.x   >= 2.0-beta9 (< 2.10.0) _VULNERABLE_ :-(
/opt/mergebase/log4j-detector/samples/log4j-core-2.0.2.jar contains Log4J-2.x   >= 2.0-beta9 (< 2.10.0) _VULNERABLE_ :-(
/opt/mergebase/log4j-detector/samples/log4j-core-2.0.jar contains Log4J-2.x   >= 2.0-beta9 (< 2.10.0) _VULNERABLE_ :-(
/opt/mergebase/log4j-detector/samples/log4j-core-2.10.0.jar contains Log4J-2.x   >= 2.10.0 _VULNERABLE_ :-(
/opt/mergebase/log4j-detector/samples/log4j-core-2.12.2.jar contains Log4J-2.x   >= 2.12.2 _SAFE_ :-)
/opt/mergebase/log4j-detector/samples/log4j-core-2.14.1.jar contains Log4J-2.x   >= 2.10.0 _VULNERABLE_ :-(
/opt/mergebase/log4j-detector/samples/log4j-core-2.15.0.jar contains Log4J-2.x   >= 2.15.0 _OKAY_ :-|
/opt/mergebase/log4j-detector/samples/log4j-core-2.16.0.jar contains Log4J-2.x   >= 2.16.0 _SAFE_ :-)

@jlasti
Copy link

jlasti commented Dec 16, 2021

Thank you for the information in this gist. It was really helpful!
Here is my attempt on detection. It is a little slower, but can be used to extract IoCs as it does the deobfuscation in the beginning:

sed -E -e 's/%24/\$/'g -e 's/%7B/{/'gi -e 's/%7D/\}/'gi -e 's/%3A/:/'gi -e 's/%2F/\//'gi http_requests.log | sed -E -e 's/\$\{(lower:|upper:|::-)//'g | tr -d '}' | egrep -I -i '\$\{jndi:'

Just add your regex to the end of the pipeline, e.g. for IP addresses:

sed -E -e 's/%24/\$/'g -e 's/%7B/{/'gi -e 's/%7D/\}/'gi -e 's/%3A/:/'gi -e 's/%2F/\//'gi http_requests.log | sed -E -e 's/\$\{(lower:|upper:|::-)//'g | tr -d '}' | egrep -I -i '\$\{jndi:' | egrep -o '([0-9]{1,3}\.){3}[0-9]{1,3}' | sort | uniq

@darkpandaman
Copy link

Some more commands to detect log4j loaded in a running JVM (courtesy of Twitter @web_bn), using JDK utils jps and jcmd:

Linux
jps | grep -v " Jps$" | cut -f1 -d " " | xargs -I '{}' jcmd '{}' VM.class_hierarchy | grep logging.log4j

Windows powershell:
jps | select-string -notmatch "jps$" | foreach {jcmd $_.Line.split()[0] VM.class_hierarchy} | select-string "log4j"

@djblazkowicz
Copy link

@TheFiZi it might be beneficial to add support for .war (web application archive) files

for some reason -path and -file does not work together when having an array of file types (at least for me, anyway), so I'm running it like so - it's a bit crude but it seems to work for me:

foreach ($disk in $disks) {
    set-location "$($disk.DriveLetter):\"
    $response+= Get-ChildItem  -File "*.jar","*.war" -Recurse -Attributes !reparsepoint -ErrorAction SilentlyContinue | ForEach-Object {Select-String "JndiLookup.class" $_} | Select-Object -ExpandProperty Path
    if($response){
        $discoveryFlag = $true
    }
    #$response
}

@Elyytscha
Copy link

is there a way to determine if attempts where successful?

@ecki
Copy link

ecki commented Dec 17, 2021

Yes, besides typical signs of compromise you should also be able to detect outgoing and unexpected network connections from your affected servers at the time of attack. Especially if you filter for RMI, LDAP or TLS(ldaps) connections - unfortunately on arbitrary ports.

@TheFiZi
Copy link

TheFiZi commented Dec 17, 2021

@djblazkowicz Thank you! Now I have to re-audit everything for .war file :(

I took your idea and did this instead so I could change as little as possible:

Get-ChildItem "$($disk.DriveLetter):\" -Recurse -Force -Include @("*.jar","*.war") -ErrorAction SilentlyContinue | ForEach-Object { Select-String "JndiLookup.class" $_ } | Select-Object -ExpandProperty Path | Get-Unique

Complete updated script:

<# 
.Synopsis 
    Checks the local system for Log4Shell Vulnerability [CVE-2021-44228]
.DESCRIPTION 
    Gets a list of all volumes on the server, loops through searching each disk for Log4j stuff
    Using base search from https://gist.github.com/Neo23x0/e4c8b03ff8cdf1fa63b7d15db6e3860b#find-vulnerable-software-windows

    Version History
        1.0 - Initial release
        1.1 - Changed ErrorAction to "Continue" instead of stopping the script
        1.2 - Went back to SilentlyContinue, so much noise
        1.3 - Borrowed some improvements from @cedric2bx (https://gist.github.com/Neo23x0/e4c8b03ff8cdf1fa63b7d15db6e3860b#gistcomment-3995092)
                Replace attribute -Include by -Filter (prevent unauthorized access exception stopping scan)
                Remove duplicate path with Get-Unique cmdlet
        1.4 - Added .war support thanks to @djblazkowicz (https://gist.github.com/Neo23x0/e4c8b03ff8cdf1fa63b7d15db6e3860b#gistcomment-3998189)
.EXAMPLE 
    .\check_CVE-2021-44228.ps1
.NOTES 
    Created by Eric Schewe 2021-12-13
    Modified by Cedric BARBOTIN 2021-12-14
#> 

# Get Windows Version string
$windowsVersion = (Get-WmiObject -class Win32_OperatingSystem).Caption

# Server 2008 (R2)
if ($windowsVersion -like "*2008*") {

    $disks = [System.IO.DriveInfo]::getdrives() | Where-Object {$_.DriveType -eq "Fixed"}

}
# Everything else
else {

    $disks = Get-Volume | Where-Object {$_.DriveType -eq "Fixed"}

}

# I have no idea why I had to write it this way and why .Count didn't just work
$diskCount = $disks | Measure-Object | Select-Object Count -ExpandProperty Count

Write-Host -ForegroundColor Green "$(Get-Date -Format "yyyy-MM-dd H:mm:ss") - Starting the search of $($diskCount) disks"

foreach ($disk in $disks) {

    # One liner from https://gist.github.com/Neo23x0/e4c8b03ff8cdf1fa63b7d15db6e3860b#find-vulnerable-software-windows
    # gci 'C:\' -rec -force -include *.jar -ea 0 | foreach {select-string "JndiLookup.class" $_} | select -exp Path

    # Server 2008 (R2)
    if ($windowsVersion -like "*2008*") {

        Write-Host -ForegroundColor Yellow "  $(Get-Date -Format "yyyy-MM-dd H:mm:ss") - Checking $($disk.Name): - $($disk.VolumeLabel)"
        Get-ChildItem "$($disk.Name)" -Recurse -Force -Include @("*.jar","*.war") -ErrorAction SilentlyContinue | ForEach-Object { Select-String "JndiLookup.class" $_ } | Select-Object -ExpandProperty Path | Get-Unique

    }
    # Everything else
    else {

        Write-Host -ForegroundColor Yellow "  $(Get-Date -Format "yyyy-MM-dd H:mm:ss") - Checking $($disk.DriveLetter): - $($disk.VolumeLabel)"
        Get-ChildItem "$($disk.DriveLetter):\" -Recurse -Force -Include @("*.jar","*.war") -ErrorAction SilentlyContinue | ForEach-Object { Select-String "JndiLookup.class" $_ } | Select-Object -ExpandProperty Path | Get-Unique

    }

}

Write-Host -ForegroundColor Green "$(Get-Date -Format "yyyy-MM-dd H:mm:ss") - Done checking all drives"

@najx
Copy link

najx commented Dec 28, 2021

Thanks for the script @TheFiZi

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment