How to protect Azure Virtual Machines from WannaCry Ransomware with Powershell?

It is no doubt that you haven’t heard all over the news about the WannaCry Ransomware attacks. Let’s check what wikipedia gives us about the Ransomware:

Ransomware is a type of malicious software that threatens to publish the victim’s data or perpetually block access to it unless a ransom is paid. <..> Ransomware attacks are typically carried out using a Trojan that is disguised as a legitimate file that the user is tricked into downloading or opening when it arrives as an email attachment. However, one high-profile example, the “WannaCry worm“, traveled automatically between computers without user interaction.

So you can be pretty sure that it is serious when every channel in the media escalating about this type of attack. Anyway, coming back to the point. As you know, if you decide to go with IaaS, that means that if you deploy virtual machines you are fully responsible of protecting and securing them. It means that you and/or your team has to do planned maintenance in order to patch the updates.

So let’s imagine the situation that there are bunch of VMs in the Azure subscription that you need to patch with security updates. VMs are joined to the domain and you don’t have access to it. What would you do? How do you force the security update to be patched on the VMs?

The answer is – with custom script extension! So how does it work? You have a script which is made to execute locally on the VM and custom script extension downloads and executes that script on Azure VM. This type of functionality is a great way to manage your VMs, do post deployment configuration or perform any other activities that you need to perform without logging in to the VM itself. The requirement is, that script needs to be stored and accessed from URL. It can be github, Azure blob storage or any other location which can be accessed via valid URL link. Custom script extension can be added on the following Azure VM operating systems:

  • Windows Server 2008 R2
  • Windows Server 2012/2012 R2
  • Windows Server 2016

So OK, we have the way how to install the security update on the VM. Now let’s check which security update we need to install that will protect our VMs from WannaCry Ransomware? We are looking at Microsoft Security Update MS17-010

The logic of the powershell script that I am going to execute on each VM is going to be pretty simple. Based on the security update we need to know the OS and then download needed files and execute. In the following script I am getting the OS version using WMI object, creating new directory where I want to store the security update on the VM and then downloading the security update based on the operating system. Finally I am executing the patch using Windows Standalone Installer for Windows – wusa.exe

$OS = (Get-WmiObject -class Win32_OperatingSystem).caption
New-Item -ItemType Directory c:\EB-SecurityUpdate

#Windows Server 2012R2

If ($OS -like '*Windows Server 2012 R2 Datacenter*')
{
  Invoke-WebRequest http://download.windowsupdate.com/d/msdownload/update/software/secu/2017/03/windows8.1-kb4012216-x64_cd5e0a62e602176f0078778548796e2d47cfa15b.msu -OutFile c:\EB-SecurityUpdate\WinServer2012R2-kb4012216.msu
  wusa.exe c:\EB-SecurityUpdate\WinServer2012R2-kb4012216.msu /quiet /norestart /log:C:\EB-SecurityUpdate\SecurityUpdateOutput.evt
}

#Windows Server 2012
If ($OS -like '*Windows Server 2012 Datacenter*')
{
  Invoke-WebRequest http://download.windowsupdate.com/d/msdownload/update/software/secu/2017/03/windows8-rt-kb4012217-x64_96635071602f71b4fb2f1a202e99a5e21870bc93.msu -OutFile c:\EB-SecurityUpdate\WinServer2012-kb4012217.msu
  wusa.exe c:\EB-SecurityUpdate\WinServer2012-kb4012217.msu /quiet /norestart /log:C:\EB-SecurityUpdate\SecurityUpdateOutput.evt
}


#Windows Server 2016

If ($OS -like '*Windows Server 2016 Datacenter*')
{
  Invoke-WebRequest  http://download.windowsupdate.com/d/msdownload/update/software/secu/2017/03/windows10.0-kb4013429-x64_ddc8596f88577ab739cade1d365956a74598e710.msu -OutFile c:\EB-SecurityUpdate\WinServer2016-kb4013429.msu
  wusa.exe c:\EB-SecurityUpdate\WinServer2016-kb4013429.msu /quiet /norestart /log:C:\EB-SecurityUpdate\SecurityUpdateOutput.evt
}

Together with wusa.exe I am using few switches:

  • /quiet – runs in the quiet mode, without user interaction
  • /norestart – prevents restarting a computer. Mainly because if I have production systems running I don’t want to restart it during business hours. I could use /forcerestart option to make sure that VMs are restarted after the security patch installation
  • /log – to store the outcome of the installation into the log file

Now as we have the the script that we will use it as a custom script extension we need to store it somewhere. Let’s create the blob storage account. If you want to access the URL without any security keys, you need to create the storage account container with the access type – Container.

After the storage account and container is created we need to upload the script file from the example above and it will be used as a custom script extension on the VM level.

Now we need to execute the powershell script from our local pc. We will login, using Add-AzureRmAccount and execute the following logic:

  1. Will get all the resource groups from subscription and will assign to the variable
  2. Use foreach loop to go through all the resource groups and get all the VMs within the resource group
  3. Use another foreach loop and go though all the VMs within the resource group
  4. Remove the custom script extension on the VM. Now we do not need to perform any if checks, because we can use only one Custom script extension. So in any case if there is any, we will have to remove and apply our own with the security update
  5. Set the custom script extension

The whole script looks as per below.

Add-AzureRmAccount

$fileURL = 'https://secdeploy.blob.core.windows.net/scripts/SecurityInstall.ps1'
$newCustomExt = 'SecurityPatching'

$RG = Get-AzureRmResourceGroup 

foreach ($Resource in $RG.resourcegroupname)
{

 $VMs = Get-AzureRmVM -ResourceGroupName $Resource

if ($vms)
{
    ForEach ($VM in $VMs)
    {
    $AzureRmVMExt = Get-AzureRmVM -ResourceGroupName $resource -Name $vm.Name
    $CustomExt = $AzureRmVMExt.Extensions | Where-Object {$_.VirtualMachineExtensionType -eq "CustomScriptExtension"} 
    
    Write-Host "Removing Custom Script Extension $($CustomExt.Name) on the VM: $($VM.Name) in the Resource Group: $resource"
    Remove-AzureRmVMCustomScriptExtension -ResourceGroupName $Resource -VMName $VM.Name -Name $CustomExt.Name -Force -ErrorAction Stop
    
    Write-Host "Installing Custom Script Extension $newCustomExt on the VM: $($VM.Name) in the Resource Group: $Resource"
    Set-AzureRmVMCustomScriptExtension -ResourceGroupName $Resource -VMName $VM.Name -FileUri $fileURL -Name $newCustomExt -Location $VM.location -Verbose -Run SecurityInstall.ps1 -ErrorAction Stop
    
    Write-Host "Custom Script Extension installed successfully on $($VM.Name) in the Resource Group: $Resource" -BackgroundColor DarkRed

    } 
}

else { Write-Host "There are no VMs in Resource Group: $Resource"}

}

Let’s check on the VM if extension has been installed successfully.

And here we go. Now we need to arrange the planned VM reboot and we are ready.

What I have noticed that to this type of solution I need to implement parallel execution. Because if I have lots of VMs it will take a while till it get done one by one.

In the next blog post I will show you how you can leverage Operations Management Tool and automate the patching to your VMs. Stay tuned!

Leave a Reply

Your email address will not be published.