Actually, the page should not go online at all. At least not yet.
But because of the Log4j exploit (or Log4shell) I decided to at least write this post. So please forgive the default template and everything else that needs to be done.
In the end I just want to offer a small PowerShell solution that can help with the analysis of log4j. Sometimes it is not known in networks, which systems are really affected or use log4j at all.
I would like to offer the script expressly as untested!
It should also be ensured that reliable backups exist! A test run with me has worked wonderfully - but that does not necessarily speak for other systems.
It should only be used by experienced administrators. It is also certainly good to have a look at it beforehand.
You should also evaluate if you really want to use the "automatic" cleanup, - or if you prefer to do it manually based on the results.
What exactly does the Powershell script do?
Finally it traverses a whole network area (must currently be a /24 network) and checks if a system is accessible behind it.
If it is a Windows system, the drives are queried via WMI.
These drives are then searched individually for log4j* files. If something is found, the .jar archive is unpacked into a temporary directory and the version is read from the manifest file.
And if it turns out that it is a version 2.0 or higher - the .jar archive is searched for the jdni class.
Please note that it also finds a potentially patched version, as for now it just looks for a version <= 2.0!
If this search is also positive, the feedback is sent directly to the Powershell, as well as to a log file.
Additionally there is the possibility to delete the file or class automatically (which is one of the suggested mitigations). The .jar file is then repacked and the original file is replaced.
You run the file from a system that has at least PowerShell v5.0 installed. But that should'n be a problem nowadays. The system should have access to SMB and WMI of the remote systems. - And finally it should run under a suitable account (e.g. member of the domain admins).
To make this work, you should create a new directory, e.g. C:\log4j - if it does not exist, the script will create it for you, though.
There you can put your log4j-scanner.ps1 (or whatever you want to call it).
Run the script as local admin, otherwise you may get some access permission errors.
In the last step you have to edit the file:
Under the file header you will find the entry:
$ipnet = "192.168.1" - you have to change this entry. If you want to scan e.g. "192.168.200.0/24", you have to enter "192.168.200" - without the last octet.
You should also change $workdir, if you didn't follow my suggestion and used C:\log4j. Hopefully the file is also commented sufficiently.
But as I said: Use it at your own risk! I do not take any responsibility!
Update 15.12.2021:
- Version 2.16.0 is considered safe
- working directory will be created automatically
- the temporary working dir will have a random suffix. So it is possible to run this script simultanously from the same host (e.g. for different subnets)
- basic error handling for wmic. It shouldn't spam the powershell console with error messages about systems without working WMIC
- some NTFS permission fixes which prevented the script from deleting some temporary files
And here's the script, visible below - and of course available as Download.
# Simple Log4j affected version scanner
#
# --- UNTESTED! USE AT OWN RISK! ---
#
# Description: Runs through network segment (/24) and scans Windows Servers using the admin shares (C$, D$, ...) (if enabled)
# Uses WMIC to get a list of all physical drives.
# .jar files will be unpacked and searched for the exploitable class
# if $fix is $true, it will delete the class, re-pack the .jar and replace the original one
#
# Basic Usage:
# - run this script with local admin rights
# - the user itself should have approriate access to the remote systems (e.g. domain admin)
# - the script can be placed anywhere, the working directory (default: c:\log4j) will be created automatically
# - have a look at the section "Adjustable Variables" to set the IP address to scan, as well as the working directoy and log file
# - to have log4j exploit class removed from the .jar file, set $fix to $true in the settings below (USE AT OWN RISK)
#
# PRECAUTIONS:
#
# When you keep "$fix" at the default value "$false", this script shouldn't do any harm to remote systems, but nevertheless:
# !!! PLEASE ENSURE ON YOUR OWN THAT THIS WILL NOT BREAK YOUR APPLICATION !!!
# !!! PLEASE ENSURE THAT YOU HAVE BACKUPS OR SNAPSHOTS YOU CAN RELY ON !!!
# !!! DON'T BLAME THE AUTHOR IF IT BREAKS YOUR SYSTEM !!!
#
# Copyright (c) 2021 by Hellstorm.De
#
#
# Changelog
# =================================================================
#
# v1.2 - 2021-12-15
# -------------------
# - fix for missing permission when removing copied archive
# - basic error handling for failed WMIC query
# - script now detects fixed version 2.16.0 and marks as "safe"
#
# v1.1 - 2021-12-15
# -----------------
# - using randomized temp directory for running script multiple times (e.g. on different networks)
#
# ===== Adjustable Variables =====
# Network (must be /24) to scan, no trailing ".0" or similar:
$ipnet = "192.168.1"
# network range, normally we want to skip x.x.x.1 (gateway) and x.x.x.255 (broadcast address)
$netstart = 2
$netend = 254
# where to store working files
$workdir = "E:\log4j"
# apply mitigation by removing the affected class from .jar file?
$fix = $false
#generate random temp directory
$randstr = ''
Get-Random -InputObject @('a','b','c','d','e','f','g','h','i','j','k','l','m','n','o','p','q','r','s','t','u','v','w','x','y','z') -Count 16 | % {$randstr+=$_}
$temp = "temp-$($randstr)"
# create working directory if not exist
New-Item -Path $workdir -ItemType "directory" -ErrorAction SilentlyContinue > $null
# create temp dir
New-Item -Path $workdir -Name $temp -ItemType "directory" > $null
# working files/dir, can be, but shouldn't be changed
$inzip = "$($workdir)\$($temp)\tmp.zip"
$outzip = "$($workdir)\$($temp)\new.zip"
$unpdir = "$($workdir)\$($temp)\unpacked"
# logging file, normally stored in workdir
$logfile = "$($workdir)\results-$($randstr).txt"
Write-Host -Fore Cyan "Temporary directory: $($workdir)\$($temp)"
Write-Host -Fore Cyan "Log file: $($logfile)"
# run through the network, skip Gateway .1 and broadcast .255
for($i=$netstart;$i -le $netend; $i++) {
$full = $ipnet+"."+$i
# is the host up?
if($(Test-Connection -ComputerName $full -count 1 -Delay 1 -ErrorAction SilentlyContinue)) {
Write-Host "Scanning $($full)..."
$isclean = $true
$haveaccess = $true
# get the list of physical disks with WMIC; more relieable than Get-PSDDrive
$gwmi = gwmi Win32_LogicalDisk -ComputerName $full -Filter DriveType=3
if(-not $?) {
Write-Host "No WMI access!"
$haveaccess = $false
"NO WMI ACCESS: $full" | Out-File -Append -FilePath $logfile
} else {
$gwmi | Select-Object DeviceID | % {
$netdrive = $_.DeviceID -replace ":","$"
Write-Host "Scanning drive $($_.DeviceID)..."
# Search all local drives for log4j* files
Get-ChildItem -Path "\\$($full)\$($netdrive)" -Filter 'log4j*.jar' -Recurse -File -ErrorAction SilentlyContinue | % {
$isclean = $false
$_.FullName
$file = $_.FullName
# if .jar is found, copy to temp directiry
Copy-Item -Path $file -Destination $inzip
# ... and unzip
Expand-Archive -Path $inzip -DestinationPath $unpdir
# Get version from Manifest file
Get-Content -Path "$($unpdir)\META-INF\MANIFEST.MF" | % {
if($_ -match "Implementation-Version") {
$ver = $_ -replace "^.*: ",""
}
}
# split version string into separate numbers
$vertok = $ver -split "\."
$unsafe = $true
if(($vertok[0].ToInt32($null) -eq 2) -and ($vertok[1].ToInt32($null) -le 15)) {
Write-Host -Fore Red "UNSAFE Version: $($ver)"
} else {
Write-Host -Fore Gree "Safe Version: $($ver)"
$unsafe = $false
}
# if we found a potentially risky version (Major version = 2)
if($unsafe) {
# look for JndiLookup class and notify user/logfile
Get-ChildItem -Path $unpdir -Filter "JndiLookup.class" -Recurse -ErrorAction SilentlyContinue | % {
Write-Host -Fore Red "POTENTIAL EXPLOIT: $($_.FullName)"
"AFFECTED: $($file)" | Out-File -Append -FilePath $logfile
}
# delete JndiLookup class if $fix is $true
if($fix) {
Get-ChildItem -Path $unpdir -Filter "JndiLookup.class" -Recurse -ErrorAction SilentlyContinue | % {
Write-Host "Removing $($_.FullName)..."
Remove-Item -Path $($_.FullName)
"REMOVED: $($_.FullName)" | Out-File -Append -FilePath $logfile
}
# write new .jar file
Compress-Archive -Path "$($unpdir)\*" -DestinationPath $outzip
# and copy back to original location replacing the old file
Copy-Item -Path $outzip -Destination $file -Force
# cleanup
Remove-Item $outzip
}
}
# further cleanup
Remove-Item $inzip -Force
Remove-Item -Recurse -Path $unpdir
}
}
# no findings? Also log. Please note, that this is no warranty for a really unaffected system!
if($isclean -and $haveaccess) {
Write-Host "$($full) seems to be clean"
"NO RESULT: $full" | Out-File -Append -FilePath $logfile
}
}
}
}
#final cleanup
Remove-Item -Recurse -Path "$($workdir)\$($temp)"