Finding and Fixing Unquoted Service Paths, Pt2

In my last post I showed you a script I wrote to find all the service paths on a computer, or group of computers, and package them into a collection of objects. That was in preparation for a second script that would then evaluate the paths the determine if any need to be fixed for this vulnerability.

This could be useful for a dry-run of an upcoming fix script, or in place of a scanner if you do not have one telling you about this problem.


I’ve posted updated copies of all 3 scripts with progress output and instructions for executing in parallel here. The explanation below is still valid, but I have adjusted the handling of some output to clean up the pipeline.

Finding Bad Service Paths

So once we’ve gotten all our service paths packaged into objects, I can pipe them to a new script that will examine each one and determine if they are bad. If it finds a bad path, it will recommended a corrected version.

First things first, we’re taking objects on the pipeline so we need something to handle input. Since we are only accepting objects from the pipeline and not manual input, I do not need the string array input or the ForEach loop in the body of the script.

#Find-BADSVCPath.ps1
[cmdletbinding()]
	Param ( #Define a Mandatory input
	[Parameter(
	 ValueFromPipeline=$true,
	 ValueFromPipelinebyPropertyName=$true,
	 Position=0)] $obj
	) #End Param
 
Process
{ #Process Each object on Pipeline
} #End Process

That was easy enough. Now to the meat of the problem. We need to evaluate each object in the pipeline from our previous script to determine if the key is bad. Since the last script was configured to create an object for those machines that didn’t respond to the REG command, we can handle those first.

Process
{ #Process Each object on Pipeline
	if ($obj.key -eq "Unavailable")
	{ #The keys were unavailable, I just append object and continue
	$obj | Add-Member -MemberType NoteProperty -Name BadKey -Value "Unknown"
	$obj | Add-Member -MemberType NoteProperty -Name FixedKey -Value "Can't Fix"
	Write-Output $obj
	$obj = $nul #clear $obj
 
	} #end if
	else
	{ ..........

Now all we have left is evaluating the keys that were returned. We need to do a couple of things here:

We need to check if the value starts with a quote. Since the path is the first element of data in the value, starting with a quote would indicate the path is quoted.

$examine = $obj.ImagePath
if (!($examine.StartsWith('"'))) { #Doesn't start with a quote

If it doesn’t start with a quote, we need to check for this strange character string ( \?? ) that I discovered on some Microsoft services during my testing. The paths resulting from these do not appear vulnerable, so I’ve configured the script to treat these as good paths.

if (!($examine.StartsWith("\??"))) { #Some MS Services start with this but don't appear vulnerable

Next we need to look for spaces in the path.

if ($examine.contains(" ")) { #If contains space

Once we’ve done that, it’s important to remember that services with arguments as part of their path will trip this check, so we’re going to need to deal with those too.

if ($examine.contains("-") -or $examine.contains("/")) { #found arguments, might still be bad

In my research these two flags precede over 99% of the arguments found. (Ref: few hundred computers with anywhere from 200-400 services each) We need to split them out so we can check the path without them.

$split = $examine -split " -", 0, "simplematch"
$split = $split[0] -split " /", 0, "simplematch"
$newpath = $split[0].Trim(" ") #Now we have just path

With that $newpath variable, I can now examine if the path contains the space. If it does, I’m simply going to replace it in the original variable surrounded in quotes. If it doesn’t, this path is okay.

if ($newpath.contains(" ")){
$examine = $examine.Replace($newpath, '"' + $newpath + '"')
$badpath = $true
} #end if newpath
else { #if newpath doesn't have spaces, it was just the argument tripping the check
$badpath = $false
} #end else

With all that done, I also need to write in what I do if I found just a path with spaces and no argument, so I’ll use else on the block where I checked for parameters.

else
{#just a simple bad path
#surround path in quotes
$examine = $examine.replace($examine, '"' + $examine + '"')
$badpath = $true
}#end else

I’ve nested all of these if’s under each other so they check successively. I need to go back and close them all. I’ve been using the badpath variable to detect how I need to update the object when I get there, so I’ll go ahead and use else on each close to setup that variable when an evaluation leaves the nest.

}#end if contains space
else { $badpath = $false }
} #end if starts with \??
else { $badpath = $false }
} #end if startswith quote
else { $badpath = $false }

With that done, I just need to look at else, update the obj, put it back on the pipeline, and clear the object for the next one. We’ll close out that top else (where I checked if the key was available to be evaluated or not) and close the process loop while we’re here.

	#Update Objects
	if ($badpath -eq $false){
		$obj | Add-Member -MemberType NoteProperty -Name BadKey -Value "No"
		$obj | Add-Member -MemberType NoteProperty -Name FixedKey -Value "N/A"
		Write-Output $obj
		$obj = $nul #clear $obj
		}
	if ($badpath -eq $true){
		$obj | Add-Member -MemberType NoteProperty -Name BadKey -Value "Yes"
		if ($examine.EndsWith('""')){ $examine = $examine.Replace('""','"') }
		$obj | Add-Member -MemberType NoteProperty -Name FixedKey -Value $examine
		Write-Output $obj
		$obj = $nul #clear $obj
		}	
	} #end top else
} #End Process

Alibi: I found a condition in which you could end with double quotes through more testing. Consider the following path:

C:\program files\some cool service\service.exe unflaggedargument “C:\program files\some cool service\somedatafile.dat”

This would result in:

“C:\program files\some cool service\service.exe unflaggedargument “C:\program files\some cool service\somedatafile.dat””

The script has been modified to get rid of that double quote. It’s still wrong because I’m not catching the unflagged argument, but I’m working on it and it’s slightly less wrong and one less thing I have to catch.  (All of this has been corrected in the new copy below, update 8 April)

With all that done, I now have a second script that will take the output of the first one, and parse through it looking for bad paths. This can also be pipelined, meaning I could run Get-SVCPath | Find-BADSVCPath | Where {$_.Badkey -eq “Yes”} and get a list of ONLY those keys that evaluate as bad.

Spoiler Alert: This will may give you some bad recommended fixes. Not many but it will happen. You should REALLY REALLY review your results before you take any automated approach to fixing this vulnerability.

Consider this call for a moment:

C:\Program Files\Cool Service\agent.exe parameter.ini

This is calling agent.exe and the argument for agent.exe is parameter.ini. The way this script is currently configured, it will recommend fixing this to “C:\Program Files\Cool Service\agent.exe parameter.ini” – Windows will read this as a single executable path and will fail on error.

That’s not what we wanted. We wanted “C:\Program Files\Cool Service\agent.exe” parameter.ini

I haven’t figured out how to catch it, and this type of argument appears very rare, but I have encountered it (all encounters were SQL and Postgre database servers). I’m sure it will come to me eventually. When it does I will update this post.

If you’ve got an idea on how to catch those, throw me a comment.  (This has also been fixed, update 8Apr)

Here’s the whole script for your enjoyment.

(Note, this script now contains more content than what I’ve explained above.  The script now controls for unquoted arguments and catches/quotes paths correctly.  I have validated this also catches combinations of flagged and un-flagged arguments, in any order or quantity.  Remember to check your results but I think I’ve got this one solved. If you happen to find a path this doesn’t handle leave me a message so I can update this. The only outlier is an unquoted path as an argument. i.e. C:\Program Files\Cool Service\Service.exe C:\somefolder\datafile.dat – I have not encountered and would not anticipate any like this. )

(By the way, I’m still learning PowerShell, so I’m sure there’s a cleaner way to do this text parsing than what I’m doing here. Leave me a suggestion if you’ve got an alternative)

#Find-BADSVCPath.ps1
[cmdletbinding()]
	Param ( #Define a Mandatory input
	[Parameter(
	 ValueFromPipeline=$true,
	 ValueFromPipelinebyPropertyName=$true,
	 Position=0)] $obj
	) #End Param
 
Process
{ #Process Each object on Pipeline
	if ($obj.key -eq "Unavailable")
	{ #The keys were unavailable, I just append object and continue
	$obj | Add-Member -MemberType NoteProperty -Name BadKey -Value "Unknown"
	$obj | Add-Member -MemberType NoteProperty -Name FixedKey -Value "Can't Fix"
	Write-Output $obj
	$obj = $nul #clear $obj
 
	} #end if
	else
	{
	#If we get here, I have a key to examine and fix
	#We're looking for keys with spaces in the path and unquoted
	#the Path is always the first thing on the line, even with embedded arguments
	$examine = $obj.ImagePath
	if (!($examine.StartsWith('"'))) { #Doesn't start with a quote
		if (!($examine.StartsWith("\??"))) { #Some MS Services start with this but don't appear vulnerable
			if ($examine.contains(" ")) { #If contains space
				#when I get here, I can either have a good path with arguments, or a bad path
				if ($examine.contains("-") -or $examine.contains("/")) { #found arguments, might still be bad
					#split out arguments
					$split = $examine -split " -", 0, "simplematch"
					$split = $split[0] -split " /", 0, "simplematch"
					$newpath = $split[0].Trim(" ") #Path minus flagged args
					if ($newpath.contains(" ")){
						#check for unflagged argument
						$eval = $newpath -Replace '".*"', '' #drop all quoted arguments
						$detunflagged = $eval -split "\", 0, "simplematch" #split on foler delim
							if ($detunflagged[-1].contains(" ")){ #last elem is executable and any unquoted args
								$fixarg = $detunflagged[-1] -split " ", 0, "simplematch" #split out args
								$quoteexe = $fixarg[0] + '"' #quote that EXE and insert it back
								$examine = $examine.Replace($fixarg[0], $quoteexe)
								$examine = $examine.Replace($examine, '"' + $examine)
								$badpath = $true
							} #end detect unflagged
						$examine = $examine.Replace($newpath, '"' + $newpath + '"')
						$badpath = $true
					} #end if newpath
					else { #if newpath doesn't have spaces, it was just the argument tripping the check
						$badpath = $false
					} #end else
				} #end if parameter
				else
					{#check for unflagged argument
					$eval = $examine -Replace '".*"', '' #drop all quoted arguments
					$detunflagged = $eval -split "\", 0, "simplematch"
					if ($detunflagged[-1].contains(" ")){
						$fixarg = $detunflagged[-1] -split " ", 0, "simplematch"
						$quoteexe = $fixarg[0] + '"'
						$examine = $examine.Replace($fixarg[0], $quoteexe)
						$examine = $examine.Replace($examine, '"' + $examine)
						$badpath = $true
					} #end detect unflagged
					else
					{#just a bad path
						#surround path in quotes
						$examine = $examine.replace($examine, '"' + $examine + '"')
						$badpath = $true
					}#end else
				}#end else
			}#end if contains space
			else { $badpath = $false }
		} #end if starts with \??
		else { $badpath = $false }
	} #end if startswith quote
	else { $badpath = $false }
	#Update Objects
	if ($badpath -eq $false){
		$obj | Add-Member -MemberType NoteProperty -Name BadKey -Value "No"
		$obj | Add-Member -MemberType NoteProperty -Name FixedKey -Value "N/A"
		Write-Output $obj
		$obj = $nul #clear $obj
		}
	if ($badpath -eq $true){
		$obj | Add-Member -MemberType NoteProperty -Name BadKey -Value "Yes"
		#sometimes we catch doublequotes
		if ($examine.endswith('""')){ $examine = $examine.replace('""','"') }
		$obj | Add-Member -MemberType NoteProperty -Name FixedKey -Value $examine
		Write-Output $obj
		$obj = $nul #clear $obj
		}	
	} #end top else
} #End Process

Jeff is a system administrator for an enterprise of over 4000 devices. He is comfortable on Windows and *nix and participates in everything from desktop to network support. In addition, he develops in PowerShell to aid in the automation of administrative tasks.

When not at work, Jeff is adjunct faculty at the University of Alaska Anchorage, where he teaches PC Architecture to first year students in the Computer and Networking Technology Program. He is also working on new curriculum in PowerShell and Virtualization as well as leading department technology deployments.


View Jeff Liford's profile on LinkedIn



Posted in Computer Security, Desktop, PowerShell, Server