CERTCC / CVE-2021-44228_scanner

Scanners for Jar files that may be vulnerable to CVE-2021-44228
BSD 2-Clause "Simplified" License
344 stars 89 forks source link

PowerShell script finds nothing if access denied error occurs #5

Closed endnil closed 2 years ago

endnil commented 2 years ago

As described in this reddit thread, Get-ChildItem can return nothing when an "Access is denied" error (System.UnauthorizedAccessException) occurs, despite -ErrorAction Ignore. I found that this happened on some systems when scanning a whole drive from the root directory.

The solution suggested by OPconfused, i.e. building the list of files manually, worked in my case. The "Access is denied" error is still shown, but the script proceeds to check the files that are found.

$jars = @();
Get-ChildItem -Path $topdir -File -Recurse -Force -Include "*.jar","*.war","*.ear","*.zip" -ErrorAction Ignore | % { $jars += $_ };
wdormann commented 2 years ago

I've found that setting a directory to be not accessible is silently ignored, and setting a file in an accessible directory to be not readable reports an error, but also continues. Can you be specific as to what circumstances can result in the scanner not behaving as expected Screen Shot 2021-12-17 at 9 29 16 AM ?

no-identd commented 2 years ago

@wdormann did you explicitly (deny) or implicitly (no matching allow) provoke the access denied message, and did you do so on the file, or on the folder level?

wdormann commented 2 years ago

The above screenshot is the result of two filesystem operations:

  1. Explicitly deny access to a file in a subdirectory.
  2. Explicitly deny access to a subdirectory. I suppose I need more info from the OP as to what led to the problem experienced, and what the problem was.
Munmun6392 commented 2 years ago

Hello @wdormann I have checked the syntax there is issue with the syntax. make changes in the syntax as per standard syntax, it will fix the issue.

jmbking commented 2 years ago

JndiLookup error I get no errors running the script in Powershell, but if I run in ISE some servers complete it without displaying the Green play button at completion.

If I run gci 'c:\' -rec -force -include *.jar -ea 0 |foreach {select-string "JndiLookup.class" $_} | select -exp Path it returns the attached error saying access is denied, but I have access and assume it's due to a character limit in the file path.

wdormann commented 2 years ago

I can't claim to know much of anything about PowerShell, but I've committed the indirect handling of files to look at as 7bab00c4c5da27bdf10533c4eea94e9e9652a267 Since I don't really know what the underlying cause may be, I'll have to rely on anybody who has spoken up here to see if it has changed anything.

bewell commented 2 years ago

Capture I get this error while running script. I am remote signed into the server. Edit: I am logged in as Administrator and running PS in Administrator.

wdormann commented 2 years ago

The important question is: Did the scanner stop at the point of encountering the file, or is it simply letting you know that it encountered a file that it cannot read along the way?

bewell commented 2 years ago

The important question is: Did the scanner stop at the point of encountering the file, or is it simply letting you know that it encountered a file that it cannot read along the way?

It stopped and came back with the PS C:\tmp> prompt.

wdormann commented 2 years ago

As an experiment, try changing "-ErrorAction Ignore" to "-ErrorAction SilentlyContinue" in the ps1 script.

bewell commented 2 years ago

As an experiment, try changing "-ErrorAction Ignore" to "-ErrorAction SilentlyContinue" in the ps1 script.

Same error as before. No change.

JaapSchram commented 2 years ago

When the Force parameter is removed its working, however that's NOT the goal. Anyone has a suggestion? Its breaking my scan as well, same as @bewell...

JaapSchram commented 2 years ago

This is shown (removed the path At):

Get-ChildItem : Access is denied

TecharyJames commented 2 years ago

The script still runs and detects exploitable log4j instances, tested this by placing a log4j jar in multiple places on the C drive of a server and running the script.

I do still see the error, but each time it scans it does still detect the vulnerable .jar You can uncomment line 55 to see the scan running on all files.

If it returns to the console line with no warning, that would suggest the script didn't find any vulnerable jars.

JaapSchram commented 2 years ago

That might be what your observing James however for me its crashing on my own Windows 10 machine with access denied (when adding the -Force parameter to Get-ChildItem).

TecharyJames commented 2 years ago

Have you uncommented line 55? You may see it scan through files.

@wdormann might be worth adding a line to output if there are no vulnerable log4j files found.

JaapSchram commented 2 years ago

Yes I tried, odd thing is that it doesn't throw the access denied but it stops scanning after a certain point, i know that cause ive added some additional logging. And I'm able to compare between scans when the Force parameter is removed. The list of files that it scans through is much longer without the force parameter, whilst it should be the other way around...

wdormann commented 2 years ago

There's now a note at the end to distinguish between completing and finding nothing, and not completing. I'll probably need a test case to confirm what several seem to be seeing if I want any hope of fixing it, though. That is, for any specific files/directories that cause the script to unexpectedly terminate, I'd need to know what the underlying filesystem properties are that cause it.

JaapSchram commented 2 years ago

Yeah I understand @wdormann, its something ive read before. Also ive written a script myself to tackle the log4net issue with a similar command and that doesnt stop:

$files = Get-Childitem –Path C:\ -Recurse –Force -ErrorAction SilentlyContinue | where-object { $.Name -match 'log4net.dll' -and $.DirectoryName -like 'C:*' } | Select-Object -Property DirectoryName

The difference I don't understand. What's the best way to make the folder (filesystem properties) visible causing this issue?

JaapSchram commented 2 years ago

BTW I have built a script based on checkjndi.ps1 and tested it with Intune as proactive remediation script (running daily). Works fine as long as the Force parameter isnt used. Im managing +700 machines, at least we have something running.

wdormann commented 2 years ago

I suppose the benefits of using -Force (don't ignore hidden files) don't outweigh the risks (potential incomplete scan). If anybody in this thread is still seeing trouble after it's been removed, speak up now.

jledoux1962 commented 2 years ago

I have tried removing the -Force and the script only scans a dozen files before it stops. The scan is being done on the C:\ which definitely has more than a dozen files on it. This is happening on one machine running Windows Server 2012 R2. Any suggestions?

jsmartbnl commented 2 years ago

I'm working on a patch - there's some potential for the script to eat accessdenied exceptions at https://github.com/CERTCC/CVE-2021-44228_scanner/blob/8052f456987db1d2e18bcbf95cbc7ee87e9dabfe/checkjndi.ps1#L50 and https://github.com/CERTCC/CVE-2021-44228_scanner/blob/main/checkjndi.ps1#L76 (and anywhere else there's a catch {})

jsmartbnl commented 2 years ago

This should, at the very least, provide some diagnostic information when the scanner fails to enumerate folders. https://github.com/CERTCC/CVE-2021-44228_scanner/pull/23

JaapSchram commented 2 years ago

I got the latest code, looks like its failing for me when using the force is true param at: "21-12-2021 17:57","Warning","Unable to scan C:\$Recycle.Bin\S-1-5-21-505421461-1408518508-922709458-44569\$IKUETHS.zip : InvalidDataException". Output window shows Get-ChildItem : Access is denied.

jsmartbnl commented 2 years ago

That indicates that C:$Recycle.Bin\S-1-5-21-505421461-1408518508-922709458-44569$IKUETHS.zip can't be extracted by System.IO.Compression.ZipFile - which I think is a good indication that it can't be executed by Java, but I'm not sure. That warning means the exception has been handled, and probably isn't causing the crash. Can you run $Error[-1] | select * and see if there is any detail available in the exception?

wdormann commented 2 years ago

The thing is, the System.IO.Compression.ZipFile stuff is already wrapped in a try/catch block. So a file not being a valid zip should be just fine. I suspect that there's something tricky going on. Perhaps the $ is implying that the "file" is really an NTFS ADS?

jsmartbnl commented 2 years ago

The thing is, the System.IO.Compression.ZipFile stuff is already wrapped in a try/catch block. So a file not being a valid zip should be just fine. I suspect that there's something tricky going on. Perhaps the $ is implying that the "file" is really an NTFS ADS?

I think the zip file is a red herring. The exception is happening elsewhere, hopefully it's being captured by $error.

jledoux1962 commented 2 years ago

Same issue as before using the new script.

jsmartbnl commented 2 years ago

Same issue as before using the new script.

Run with -Verbose and see what it's telling you it scanned.

JaapSchram commented 2 years ago

Like such: Checkjndi.ps1 C:\ -verbose -force?

jsmartbnl commented 2 years ago

Like such: Checkjndi.ps1 C:\ -verbose -force?

$error.clear() 
./Checkjndi.ps1 C:\ -verbose -force
$error[-1] | select *

Should give clear out any errors in the session, invoke the script with verbose output and then show the last logged error on the console. This will give you the best shot at finding out what when wrong with the script.

JaapSchram commented 2 years ago

Ive added the latest code to my Intune Remediation script, tweaked it a little, added a logging function, and its working however without the force param but its fine for now. Thanks for all the hard work guys!!

JaapSchram commented 2 years ago

PSMessageDetails : Exception : System.UnauthorizedAccessException: Access is denied ---> System.ComponentModel.Win32Exception: Access is denied --- End of inner exception stack trace --- at System.Management.Automation.Utils.NativeDirectoryExists(String path) at System.Management.Automation.SessionStateInternal.IsItemContainer(CmdletProvider provider Instance, String path, CmdletProviderContext context) TargetObject : CategoryInfo : NotSpecified: (:) [Get-ChildItem], UnauthorizedAccessException FullyQualifiedErrorId : System.UnauthorizedAccessException,Microsoft.PowerShell.Commands.GetChildItemCommand ErrorDetails : InvocationInfo : System.Management.Automation.InvocationInfo ScriptStackTrace : at Get-Files, <...path exhibited...>\Checkjndi.ps1: line 47 at , C:\Users\<...path exhibited...>\Checkjndi.ps1: line 147 at , : line 1 PipelineIterationInfo : {}

JaapSchram commented 2 years ago

Another example but I think the same:

PSMessageDetails : Exception : System.UnauthorizedAccessException: Access is denied ---> System.ComponentModel.Win32Exception: Access is denied --- End of inner exception stack trace --- at System.Management.Automation.Utils.NativeDirectoryExists(String path) at System.Management.Automation.SessionStateInternal.IsItemContainer(CmdletProvider provider Instance, String path, CmdletProviderContext context) TargetObject : CategoryInfo : NotSpecified: (:) [Get-ChildItem], UnauthorizedAccessException FullyQualifiedErrorId : System.UnauthorizedAccessException,Microsoft.PowerShell.Commands.GetChildItemCommand ErrorDetails : InvocationInfo : System.Management.Automation.InvocationInfo ScriptStackTrace : at Get-Files, C:\Temp\Checkjndi.ps1: line 47 at , C:\Temp\Checkjndi.ps1: line 147 at , : line 1 PipelineIterationInfo : {}

jsmartbnl commented 2 years ago

OK, took another crack at it with #27. I know get-childitem can be on the finnicky side (it also fails with very long paths). I think a lot of that is fixed in PowerShell Core - but I really don't know for sure.

JaapSchram commented 2 years ago

now were getting somewhere, the error output is different:

PSMessageDetails : Exception : System.UnauthorizedAccessException: Access to the path 'C:\Documents and Settings' is denied. at System.IO.__Error.WinIOError(Int32 errorCode, String maybeFullPath) at System.IO.FileSystemEnumerableIterator1.CommonInit() at System.IO.FileSystemEnumerableIterator1..ctor(String path, String originalUserPath, Stri ng searchPattern, SearchOption searchOption, SearchResultHandler`1 resultHandler, Boolean check Host) at System.IO.DirectoryInfo.EnumerateDirectories() at Microsoft.PowerShell.Commands.FileSystemProvider.Dir(DirectoryInfo directory, Boolean rec urse, UInt32 depth, Boolean nameOnly, ReturnContainers returnContainers) TargetObject : C:\Documents and Settings CategoryInfo : PermissionDenied: (C:\Documents and Settings:String) [Get-ChildItem], UnauthorizedAccessExcepti on FullyQualifiedErrorId : DirUnauthorizedAccessError,Microsoft.PowerShell.Commands.GetChildItemCommand ErrorDetails : InvocationInfo : System.Management.Automation.InvocationInfo ScriptStackTrace : at Get-Files, C:\Temp\Checkjndi.ps1: line 50 at , C:\Temp\Checkjndi.ps1: line 148 at , : line 1 PipelineIterationInfo : {0, 1}

JaapSchram commented 2 years ago

C:\Document and Settings is a junction point, a file system shortcut to c:\users. So let me do explain that im running the script, like I always do, under 'NT authority\system' to simulate it running via Intune (or SCCM). From an elevated command prompt: psexec.exe -i -s %SystemRoot%\system32\cmd.exe.

JaapSchram commented 2 years ago

So how do we bypass this problem :-)

JaapSchram commented 2 years ago

For the Intune remediation script, the base code is perfect, I just add this to the bottom:

try { if ($global:foundvulnerable -eq $false){ Write-Log -Message "No issue found, exit with code 0" -Severity Information exit 0 } else { Write-Log -Message "Issue found, exit with code 1 " -Severity Error exit 1 }

} catch { $errMsg = $_.Exception.Message return $errMsg exit 1 }

Simple logging function:

function Write-Log { [CmdletBinding()] param( [Parameter()] [ValidateNotNullOrEmpty()] [string]$message,

    [Parameter()]
    [ValidateNotNullOrEmpty()]
    [ValidateSet('Information','Warning','Error')]
    [string]$severity = 'Information'

)

[pscustomobject]@{
    Time = (Get-Date -f g)
    Severity = $severity
    Message = $message
} | Export-Csv -Path "C:\Windows\Logs\Software\Check_JNDI_Intune_REV2.log" -Append -NoTypeInformation

}

endnil commented 2 years ago

It looks like it's antivirus software interfering in our case (problems even with 7bab00c). With it disabled, $f = get-childitem -path "c:\" -file -recurse -force -erroraction ignore -include "*.zip" raised no exception, and $f contained the list of matched files. With the antivirus enabled, the same invocation raised UnauthorizedAccessException, and $f was null. Having toggled the antivirus a couple of times, it started working every time, though! This is also only happening on some machines.

Interestingly, removing -include "*.zip" allowed the call to complete without raising an exception, and a list of files was returned. Replacing -include "*.zip" with -filter "*.zip" also worked, although -filter is more limited.

The best solution for us at this point seems to be to filter manually, with something like $f = get-childitem -path "c:\" -file -recurse -force -erroraction ignore | where { $_.fullname.endswith(".zip") }.

This SO question talks about problems with Get-ChildItem and reparse points. I initially tried using Get-ChildItem -Force -Recurse -Attributes !Hidden, !System, !ReparsePoint as suggested in the second answer, but that didn't solve the problem. The approach using Get-WmiObject does avoid the exception, but it seems dramatically slower than Get-ChildItem.

Using the Windows Search Index could be nice, since it returns results almost instantaneously, but Windows Search only includes a limited set of paths by default. Anyway, here's the code I tried, based on that article:

$query = "SELECT System.ItemName, System.ItemPathDisplay, System.ItemTypeText, System.Size FROM SystemIndex WHERE System.FileExtension = '.zip'"
$objConnection = new-object -comobject adodb.connection
$objrecordset = new-object -comobject adodb.recordset
$objconnection.open("Provider=Search.CollatorDSO;Extended Properties='Application=Windows';")
$objrecordset.open($query, $objconnection)

Try { $objrecordset.MoveFirst() }
Catch [system.exception] { "no records returned" }
do
{
 Write-host ($objrecordset.Fields.Item("System.ItemName")).value `
 ($objrecordset.Fields.Item("System.ItemPathDisplay")).value `
 ($objrecordset.Fields.Item("System.ITemTypeText")).value `
 ($objrecordset.Fields.Item("System.Size")).value
 if(-not($objrecordset.EOF)) {$objrecordset.MoveNext()}
} Until ($objrecordset.EOF)

$objrecordset.Close()
$objConnection.Close()
$objrecordset = $null
$objConnection = $null
[gc]::collect()

Hopefully one of these things helps others.

no-identd commented 2 years ago

Does GCI create read-locks prior to attempting to read?

+Since @JaapSchram reported that this happens even when running as NT-AUTHORITY\SYSTEM, this makes me suspect we run into something like Integrity Level Control violations—there's only one thing more powerful than Local System (aka NT-AUTHORITY\SYSTEM) in terms of Integrity Level, and that's TrustedInstaller. (don't get the idea of starting to mess with taking Ownership to bypass that however.)

Also, it should be noted that folders such as C:\Documents and Settings\ aren't 'normal' NTFS junctions. UAC compatibility shimming acts on those folders iirc. (if they were simple junctions, you could browse into them in explorer.) Unfortunately I have very little time to contribute to this, otherwise I'd research this more properly so this is all from hazy memories from years ago, and might have misstated a few things.

@ednil can you attach procmon to powershell while the AV is active? (make sure your user actually has debug permissions, many enterprise environments remove this permission from the administrators group, which makes tools like procmon behave strangely, but without reporting errors.)

JaapSchram commented 2 years ago

Back online, ill test the latest code in a moment or two..

JaapSchram commented 2 years ago

When running with a domain admin account the script runs but with a lot of 'WARNING: Unable to scan C:\Documents and Settings : DirUnauthorizedAccessError,Microsoft.PowerShell.Commands.GetChildItemCommand'.

With NT-AUTHORITY\SYSTEM im getting still access denied, and stops running unfortunately.

So indeed @no-identd, I agree with your statement. Ill do one more test and run the script from Intune with the force param enabled.

NT-AUTHORITY\SYSTEM error output:

PSMessageDetails : Exception : System.UnauthorizedAccessException: Access to the path 'C:\Documents and Settings' is denied. at System.IO.__Error.WinIOError(Int32 errorCode, String maybeFullPath) at System.IO.FileSystemEnumerableIterator1.CommonInit() at System.IO.FileSystemEnumerableIterator1..ctor(String path, String originalUserPath, Stri ng searchPattern, SearchOption searchOption, SearchResultHandler`1 resultHandler, Boolean check Host) at System.IO.DirectoryInfo.EnumerateDirectories() at Microsoft.PowerShell.Commands.FileSystemProvider.Dir(DirectoryInfo directory, Boolean rec urse, UInt32 depth, Boolean nameOnly, ReturnContainers returnContainers) TargetObject : C:\Documents and Settings CategoryInfo : PermissionDenied: (C:\Documents and Settings:String) [Get-ChildItem], UnauthorizedAccessExcepti on FullyQualifiedErrorId : DirUnauthorizedAccessError,Microsoft.PowerShell.Commands.GetChildItemCommand ErrorDetails : InvocationInfo : System.Management.Automation.InvocationInfo ScriptStackTrace : at Get-Files, C:\Temp\Checkjndi.ps1: line 49 at , C:\Temp\Checkjndi.ps1: line 154 at , : line 1 PipelineIterationInfo : {0, 1}

jsmartbnl commented 2 years ago

I really think this should be closed. The original issue (no output if there's an access denied) seems to be resolved. @JaapSchram's testing seems to confirm this. There are going to be a lot of edge cases here, they should probably be tracked individually.

wdormann commented 2 years ago

Agreed.

JaapSchram commented 2 years ago

Enough work has been done indeed. I'm able to use the Force param running under NT-AUTHORITY\SYSTEM with the below changed code. It will throw quite often for example - Unable to scan C:\Documents and Settings : DirUnauthorizedAccessError,Microsoft.PowerShell.Commands.GetChildItemCommand" But it wont stop, thanks all.

function Get-Files { $jars = @();

        Get-Childitem –Path C:\ -File -Recurse -Force -ErrorAction SilentlyContinue -ErrorVariable UnscannablePaths | where-object { ($_.Name -like '*.jar' -or $_.Name -like '*.war' -or $_.Name -like '*.ear' -or $_.Name -like '*.zip' -or $_.Name -like 'JndiLookup.class' ) -and $_.DirectoryName -like 'C:\*' } | % { $jars += $_.DirectoryName + '\' + $_.Name };

        foreach ($Exception in $UnscannablePaths) {
            Write-Log -Message "Unable to scan $($Exception.TargetObject) : $($Exception.FullyQualifiedErrorID)" -Severity Warning
        }

        return $jars
}
jsmartbnl commented 2 years ago

Just for posterity, support for accepting input from the pipeline was added in #25, so you can use your own method (or even non-PowerShell software) to enumerate paths, without modifying the script. If it's text, make sure it's one path per line. If it's PowerShell objects, use the parameter name toplevel or the alias PSPath, and pass that into checkjndi.ps1 on the pipeline.

no-identd commented 2 years ago

I agree that the initial issue was solved but maybe we should track this in some follow up issue ticket (even if the original participants might lack time to continue working on the deep end of it), I still find this very odd, albeit not quite unexpected. Tagging @markrussinovich & @ionescu007 cuz they're the first two people my brain goes to when thinking about debugging garbage like this before begrudgingly opening an MS call instead (which I won't do here, because ugh MS calls+I lack the time anyway)

tdoan1 commented 2 years ago

Have you guys considered any methods other than GCI for locating target files? You might have a look at robocopy. As surprising as it may seem, robocopy can be an order of magnitude faster than GCI at simply locating files and cataloging them for processing, especially on very large volumes.

$Drives = ([System.IO.DriveInfo]::GetDrives() | Where-Object {$_.DriveType -eq 'Fixed'}).Name ForEach ($drive -in $Drives) { $robocopyExitCode = (Start-Process robocopy -ArgumentList "$drive C:\DOESNOTEXIST *.jar *.war *.ear /S /XJ /L /FP /NS /NC /NDL /NJH /NJS /r:0 /w:0 /UNILOG+:$env:temp\log4jfilescan.csv" -WindowStyle Hidden -Wait).ExitCode }

In fact, there's an example of using robocopy specifically for log4j research here: https://github.com/N-able/ScriptsAndAutomationPolicies/blob/master/Vulnerability%20-%20CVE-2021-44228%20(Log4j)/get-log4jrcevulnerability.ps1