Create Adobe GPO templates

Every good IT pro tries to follow best practices by updating and configuring workstations, software installed,…

Let’s consider specifically the PDF reader software provided by Adobe and the fact that the IT pro wants to harden the configuration to have a more resistant endpoint to this attack vector.

Adobe provides some GPO templates that you can find on their FTP website:

Version Reader Acrobat

If you load all these ADMX and ADML files in your policy definitions folder, you’ve got the following in the group policy editor console:

When you start to explore these templates and look for hardening the security settings, you get really disappointed because there’s only between 5 to 7 settings per node 😦

It’s a shame because Adobe took the time to document many registry settings in the Enterprise toolkit pages and for example on these pages:

But they failed to make these settings available in GPO templates 😦

Adobe fails, no problem, PowerShell to the rescue 😎

It appears I’m not the only one who thinks this way. The Information Assurance mission at NSA (iadgov) helps the Department of Defense (DoD) to apply baselines.
They have a huge github repository and even have an Adobe Reader DC template with around 45 settings. It’s not their first attempt. Before that they published recommended Adobe Reader XI settings.

However their single Adobe template has many problems that I won’t detail here when I loaded it on a Windows 7 workstation.
Kudos to iadgov! I’ve used some of their settings when appropriate, their categories but the main difference is that I created a PowerShell module that creates templates on demand for the Reader, Acrobat and their 2005, 2007 or DC versions 😎

I’d like the community to contribute to get more settings,… I’ve uploaded the module on github so that it’s easy to fork, track issues, follow changes.
I’ve also added a documentation of every settings on this page:

I’ve also uploaded the module on the PowerShell Gallery:

Here’s an overview of what you’ll get if you generate all the templates and move them to your local GPO templates folder:

That looks better, isn’t it? And there are more than 40 settings for each version of Adobe Software. πŸ˜€

  • How to start and create these templates?
    • Download the module
    • Find-Module -Name AdobeGPOTemplates -Repository PSGallery
      Save-Module -Name AdobeGPOTemplates -Path ~/Downloads -Repository PSGallery
      $HT = @{
       CatalogFilePath = "~/Downloads/AdobeGPOTemplates/1.0.0/"
       Path = "~/Downloads/AdobeGPOTemplates/1.0.0"
       Detailed = $true
       FilesToSkip = 'PSGetModuleInfo.xml'
      Test-FileCatalog @HT

    • Import the module, create templates and copy them to your local GPO templates folder
    • Import-Module ~/Downloads/AdobeGPOTemplates/1.0.0/AdobeGPOTemplates.psd1 -Force
      Get-Command New-AdobeGPOTemplate -Syntax
      # Get-Help New-AdobeGPOTemplate  -Examples
      New-AdobeGPOTemplate -Product Reader,Acrobat -Version DC,2017,2015
      copy .\*.admx -Destination C:\Windows\PolicyDefinitions\
      copy .\*.adml -Destination C:\Windows\PolicyDefinitions\en-US\

  • What’s the bare minimum config?
  • If you omit the first rule of hygiene that states that you need to update your software and the fact that the Adobe Reader has many “cloud-focused” features, I’d say that the 3 minimum settings to configure are:

    1. Disable JavaScript
    2. Disable the ability to execute any embedded object
    3. Have the protected view turned on for anything

    I know we may not agree and if you’ve an opinion about the bare minimum config, please share it in the comments.

    • Example of minimum config

    Let’s say you just want to change the following default settings at the user level without locking down everything based on the above 3 recommendations:

    After you configured the following GPO settings:

    You get this in the Reader UI:


If you’ve seen the following 3 words in recent updates like KB4025342, don’t panic,…

…keep calm and read the CVE-2017-8565 on the new security updates guide portal

Yes, CVE-2017-8565 was published on July 11, 2017.

Oleksandr Mirosh and Alvaro MuΓ±oz from Hewlett-Packard Enterprise Security reported this vulnerability as far as we can see on the acknowledgments page.

On the following page, you can read a good description of the issue:

A remote code execution vulnerability exists in PowerShell when PSObject wraps a CIM Instance. An attacker who successfully exploited this vulnerability could execute malicious code on a vulnerable system.

In an attack scenario, an attacker could execute malicious code in a PowerShell remote session.

The update addresses the vulnerability by correcting how PowerShell deserializes user supplied scripts.

The above page states it’s a Remote Code Execution (RCE) vulnerability and I guess it’s because Remoting is involved in the attack scenario.

Here’s why I don’t 100% agree with this classification:
First, let’s quickly examine who can access remoting on a Windows 10 1703 workstation with the Get-PSSessionConfiguration cmdlet

  • NT AUTHORITY\INTERACTIVE (S-1-5-4) are Users who log on for interactive operation. This is a group identifier added to the token of a process when it was logged on interactively. The corresponding logon type is LOGON32_LOGON_INTERACTIVE. (source)
  • BUILTIN\Administrators (S-1-5-32-544)
    A built-in group. After the initial installation of the operating system, the only member of the group is the Administrator account. When a computer joins a domain, the Domain Admins group is added to the Administrators group. When a server becomes a domain controller, the Enterprise Admins group also is added to the Administrators group (source)
  • BUILTIN\Remote Management Users (S-1-5-32-580) A Builtin Local group. Members of this group can access WMI resources over management protocols (such as WS-Management via the Windows Remote Management service). This applies only to WMI namespaces that grant access to the user. (source)

Based on the 3 groups listed above who can connect via remoting, either you already are an admnistrator of the box or are logged interactively (you’re an admin or a standard user) and/or belong to the local BUILTIN\Remote Management Users group.

If you’re not already an administrator, you could eventually become one by exploiting the CVE-2017-8565 vulnerability through remoting sessions.
In this case, I’m more likely to call this kind of a vulnerability an Elevation of Privilege (EoP) than a Remote Code Execution (RCE).


Invoking code via PowerShell Remoting is the prime purpose of PowerShell Remoting


Now let’s say, other remoting configurations were added because you’ve implemented JEA (Just Enough Administration) or constrained remoting endpoints with a RunAs account (a privileged account) to delegate access to some (less privileged) remote users (typically helpdesk members).
Again a less privileged remote user who was given access to a (vulnerable) remoting configuration/session could gain more privileges by exploiting this vulnerability.

We can see more details about this vulnerability in PowerShell Core. The beauty of open source projects is that they tend to be more transparent and agile in fixing bugs.

The issue with CIM deserializer was introduced in the PowerShell Core github repository the day after the security updates were released for regular Windows versions of PowerShell.
And it was fixed in about 3 days.
You can see the related merged Pull Request using this link.

I’ve tested this vulnerability on Windows 10 1703 fully patched where KB4025342 was installed. Importing the corrupted CIM class didn’t spawn a calculator process.

I’ve removed KB4025342 and restarted the computer

wusa.exe /uninstall /kb:4025342 /norestart

Importing the corrupted CIM class launched a calculator process. Its integrity was labeled “AppContainer” and has actually a lower integrity level than its parent process (wsmprovhost.exe) level set to medium (as I run as a standard user).

Importing the corrupted CIM class has the same result as doing the following already allowed:

Invoke-Command -ComputerName . -EnableNetworkAccess { calc.exe }

No elevation of privilege so far.

Now, let’s say I add a somehow quite restricted endpoint (allowing only import-clixml and access to the filessystem provider)

$HT = @{
 SchemaVersion = '1.0'
 ExecutionPolicy = 'Restricted'
 SessionType = 'RestrictedRemoteServer'
 LanguageMode = 'NoLanguage'
 VisibleCmdlets = 'Import-Clixml'
 VisibleProviders = 'FileSystem'
 RunAsVirtualAccount = $true
New-PSSessionConfigurationFile -Path C:\config.pssc @HT
Register-PSSessionConfiguration -Path 'C:\config.pssc' -Name "Test" -Force

Importing the corrupted CIM class results in the ability to escape the constrained endpoint restrictions – the “sandbox” – and run arbitrary code under a more privileged account. You can see that wsmprovhost.exe runs under a WinRM virtual user, has a High integrity level and has two child processes that inherited from its security context (cmd.exe and openwith.exe triggred by trying to run calc):

In my opinion, there’s no need to panic.
Arbitrary code can only run in a higher security context only in some specific scenarios.
This vulnerability can be easily remediated by just applying already available Windows security updates.

On a Windows 7, only Administrators can connect through remoting by default:

WSUS (bundled or installable) child updates

This morning there was question on about how to find if the following .Net Rollup update (4019112) has been installed.

The person approved 4019112 but noticed that the WMI Win32_QuickFixEngineering class didn’t list it.
Instead it contained the following updates KB4014504 and KB4014511 that are actually bundled inside the rollup 4019112

The person was also asking if there’s another way to find what child updates an update installs instead of what’s listed on its support page.

Yes, there is one. If you use the WSUS API GetInstallableItems method. It will list what child updates an update will install (more precisely, it will get the directly bundled, non-explicitly-deployable child updates of this update. Creates an installable item on behalf of this update if the update itself has files.)

(Get-WsusServer).SearchUpdates('4019112') |
Out-GridView -PassThru -Title 'Select an update'| 
ForEach-Object { 
} | ft -AutoSize

As you can see the KB4019112 (logically) bundles an update for every supported version of .Net.

Unfortunately, we couldn’t use the GetRelatedUpdates method and its enum UpdatesBundledByThisUpdate that should also list child updates that are bundled in this update.

# what's the numeric value of the UpdatesBundledByThisUpdate enum

(Get-WsusServer).SearchUpdates('4019112') | 
ForEach-Object {
 $_ | 
 Add-Member -MemberType ScriptProperty -Name UpdatesBundledByThisUpdate -Value {
 } -Force -PassThru
} | Select Title,UpdatesBundledByThisUpdate | 
Out-GridView -PassThru | Select -ExpandProperty UpdatesBundledByThisUpdate

As you can see, it doesn’t return anything. Why?


Well, because the above two searches also don’t return anything.
It means that these updates don’t exist as individual updates in WSUS.

To find out if the .Net rollup 4019112 has been installed on a target computer you can either:

# use the Get-HotFix cmdlet
Get-HotFix -Id 'kb4014504','kb4014508','kb4014511','kb4014514'
# or the  Get-WmiObject cmdlet
Get-WmiObject -Query 'Select * FROM Win32_QuickFixEngineering WHERE HotFixID = "KB4014504" OR HotfixID = "KB4014508" OR HotfixID = "KB4014511" OR HotFixID = "KB4014514"' 

WMI subclasses

One of my colleague provided the following screenshot

and asked where he could have found that “ChangeStartMode” method.

Get-CimInstance -Class CIM_Service -Filter "Name='w32time'"|
Get-Member -MemberType Methods

Notice that he asked for instances from the CIM_Service class and got an Win32_Service instance back.
You may wonder why.

  • What’s the difference between CIM_Service and Win32_Service class ?

Win32_service is actually a subclass of the CIM_Service class.
CIM_ based classes are “schema” classes in WMI while Win32_ classes inherit from them (source)

  • How could you find that information using PowerShell?
([wmiclass]"CIM_Service").GetSubclasses() | 
Select Name,Methods | 
ft -AutoSize

There’s a child class named Win32_BaseService that inherits from the CIM_Service schema class.

If I get the subclasses from it, I can finally see the Win32_Service class.

([wmiclass]"Win32_BaseService").GetSubclasses() | 
Select Name,Methods | 
ft -AutoSize

If I want to see the methods, I should simply get them from the Win32_Service class.
In this case, you use Get-CIMClass cmdlet instead of the Get-CIMInstance cmdlet.

Get-CimClass -ClassName Win32_Service | 
Select -Expand CimClassMethods | ft

Bonus: If you want to see the MOF defintion of a WMI class, you can do:

([wmiclass]'Win32_Service').gettext('mof') -replace '\;',"`r"

VideoLan Player (vlc) update status

When I launch the Videolan Player (vlc), I get the following update prompt whenever a new version is available.

I was actually using the following detection based on the target link of the “last” version redirect:

$foundLatestOnlineVersion = $latestOnlineVer  = $null
$HT = @{
 Uri = '' ;
 UseBasicParsing = $true ;
 ErrorAction = 'SilentlyContinue'
if ( 
 ($latestOnlineVer = (Invoke-WebRequest @HT).Links | 
 Where href -match '\.sha256$')
) { 
 $foundLatestOnlineVersion = (
 ).Match($latestOnlineVer.href).Groups | Select -Last 1 -Expand Value
 Write-Verbose -Message "Found latest VLC version online: $foundLatestOnlineVersion" -Verbose

As you can see my version detection approach fails. It shows instead of 2.2.6 😦

I started looking around what vlc does to check when a new version has been released.

Fortunately, I found their updater source code on this page:

The interesting line is where they show how they build the target status url based on the platform.


I tried to do the same in PowerShell but the reply from their web server is an octet-stream

I can quickly convert the bytes array into something meaningful like this:

$HT.Uri = ''
[char[]][int[]](Invoke-WebRequest @HT).Content  -join ''

Yeah, you can do this, it’s quick and dirty πŸ˜›

But, it would be nice to get an object returned instead of a multi line string.
I’ll use the char 10 (a line feed) as a separator.
The first line is the version, the second line the URL, the third line the title and the rest is the full text.

(Invoke-WebRequest @HT).Content | 
ForEach-Object -Begin {
 $Version = $URI = $Title = $null
 $i = 0
 $sb  = New-Object System.Text.StringBuilder
} -Process {
 if ($_ -eq 10) {
  Switch ($i) {
   1 {
    $Version = $sb.ToString() ;
    break ;
   2 {
    $URI = $sb.ToString() ;
    break ;
   3 {
    $Title = $sb.ToString() ;
    break ;
   4 {
    $Text = $sb.ToString() ;
    break ;
   default {}

  # reset the string builder
  $sb  = New-Object System.Text.StringBuilder
 } else {
  $null = $sb.Append([char][int]$_)
} -End {
  Version = [version]$Version ;
  URI = [uri]$URI ;
  Title = $Title ;
  Text = $Text ;
} | fl

Let me quickly explain this approach. Btw, if you’ve got a better one please share it πŸ™‚
I use the Begin block of the Foreach-Object cmdet to initialize a counter and an empty string builder.
Inside the Process block, I test if the encountered byte is char 10, the line feed separator.
If it is, it means I reached a milestone. I increment the counter to know if it’s the first, second, third or fourth line. Then inside the switch block, based on this counter value, I create temporary variables where I store the result of the string builder.
If the character isn’t 10, I just append it to the string I’m building.
In the End block of the Foreach-Object cmdet, I can now create a single custom object using the temporary variables used in the process block.
This approach is rock solid until they change lines returned when you query the status URL πŸ™‚

What’s the last Sunday?

In October 2012, I published a function to find the last Sunday or another day of a month and based on some comments, I decided to revisit the code and improve it.
I started to improve the discovery of expected values by some parameters like the day, the month and year.

To achieve this I started to use a Dynamic parameter block and had the following code:

I wasn’t satisfied although it worked as expected. It’s hard to read, there are too many variables…

I remember I’ve already seen someone who wrote helper functions to avoid writing so many redundant code in the dynamic parameter block. (I cannot remember who. If you do, please post a link in the comments).

I wrote a quick helper function. I didn’t want to go further in order to keep the complexity quite low.

It’s around 50 lines.
Here’s an example to create a ValidateRange parameter attribute for the “Month” parameter.

Let’s see how this helper function is used in practice:

I first create the Dictionary object in the dynamic parameter block.
I load the helper function.
I then create the 3 dynamic parameters for the day, month and year.
Notice that I’ve also used some hash-table and splatting separated by a semicolon inside the same script block for a better readability.
There are only two variables left my dynamic parameter block ($Dictionary and $HT).

Now, let’s see how this function performs.
I’ve added a new feature where you can actually tell what day position in the month you want.
You can get the First, Second, Third, Fourth or Last.
You might be scratching your head why such options.
Well, let’s take a concrete example to illustrate why.
Let’s say Sunday.
In February 2016, you’ve got 4 Sundays but in April 2017, you’ve got 5.
One of the purpose of this function is to find out the last Sunday in a month to run a scheduled task.
Or you may want to run your task every fourth Sunday in a month.
Sometimes, the fourth Sunday is the last Sunday like it’s in February 2016.

But that’s not true in April 2017

I’ve got another code snippet to demo how this function can be used:

I can display the first, last, fourth Sunday in every month in 2016 and the number of days between the first and last 😎