Tag Archives: Security

Modsecurity for Plesk 2

I already spend some attention on this subject. A customer complained that the webmail did not work no more. The user was able to logon but could not send an e-mail. Obviously this had something to do with Modsecurity. I’m running Plesk on a Ubuntu server. So enabling Modsecurity and enforcing this down on all customers is troublesome. But Plesk does not give you the option to enable or disable Modsecurity on “system” subdomains. Like for example webmail.domain.tld. So this causes an issue when Modsecurity is enabled system wide and webmail is needed. If you were to just read webmail and not answer everything will work just fine. There’re so many rules you’ll need to disable or exclude before it will run fine that it makes no sense to configure this. It’ll practically undermine the sole function of Modsecurity.

Plesk apparently  has a config file for each webmail subdomain. This file is located either here:

/etc/apache2/plesk.conf.d/webmails/roundcube/domain.tld_custom_webmail.conf

or here:

/etc/apache2/plesk.conf.d/webmails/horde/domain.tld__custom_webmail.conf

And now you’ll have to decide whether to exclude a great number of rules or to disabled Modsecurity completely.  If you want to disabled Modsecurity completely and want to enforce security by adding some security headers you can use the code below:

<IfModule mod_security2.c>
  SecRuleEngine Off
</IfModule>
Header always set Strict-Transport-Security "max-age=63072000; includeSubDomains; preload"
Header always set X-Content-Type-Options: nosniff
Header always set X-Frame-Options: SAMEORIGIN
Header always set X-XSS-Protection: "1; mode=block"
Header unset Content-Security-Policy
Header add Content-Security-Policy "default-src https: 'unsafe-inline' 'unsafe-eval';connect-src https: wss:"
Header always set Public-Key-Pins 'pin-sha256="2rpOTT3tKv5TF8/hZGAEvA5aJAwFrXSrEX404CzY9mM="; pin-sha256="gXyS+QHqbu4T0pNQLIgUX+WyMI3ndaViZacoqedaA/o="; max-age=2592000'
Header always edit Set-Cookie (.*) "$1; HTTPOnly; Secure"

If you want to disabled rules by ID (by url is impossible because that changes with every mail) you can use the code below:

<IfModule mod_security2.c>
SecRuleRemoveById 981319 960024 981231
</IfModule>
Header always set Strict-Transport-Security "max-age=63072000; includeSubDomains; preload"
Header always set X-Content-Type-Options: nosniff
Header always set X-Frame-Options: SAMEORIGIN
Header always set X-XSS-Protection: "1; mode=block"
Header unset Content-Security-Policy
Header add Content-Security-Policy "default-src https: 'unsafe-inline' 'unsafe-eval';connect-src https: wss:"
Header always set Public-Key-Pins 'pin-sha256="2rpOTT3tKv5TF8/hZGAEvA5aJAwFrXSrEX404CzY9mM="; pin-sha256="gXyS+QHqbu4T0pNQLIgUX+WyMI3ndaViZacoqedaA/o="; max-age=2592000'
Header always edit Set-Cookie (.*) "$1; HTTPOnly; Secure"

I guess to risk is somewhat moderate if you consider that anyone can send you a message with html content. But sometimes security is a notch below functionality.

Making exclusions is a very normal task when Modsecurity is enabled. So how do you do that? Well next to what’s shown above you’ll have the option to disable rules based on the request url. When using WordPress for example. Many thing do not work very well. This is especially when OWASP is enabled.  So to create some rules you’ll have to edit the config file below:

/etc/apache2/modsecurity.d/rules/modsecurity_crs-plesk/modsecurity_crs_15_custom.conf

If the file does not exist, create it. You can than append to file with the rules and URL that you can find when watching the log file. Enable Modsecurity first in report only. Than add the necessary rules and your site is much safer.

SecRule REQUEST_FILENAME "@streq /wp-admin/post.php" "id:88261001,phase:1,t:none,nolog,pass,ctl:ruleRemoveById=973332"
SecRule REQUEST_FILENAME "@streq /wp-admin/post.php" "id:88261002,phase:1,t:none,nolog,pass,ctl:ruleRemoveById=970903"
SecRule REQUEST_FILENAME "@streq /wp-admin/post.php" "id:88261003,phase:1,t:none,nolog,pass,ctl:ruleRemoveById=973344"
SecRule REQUEST_FILENAME "@streq /wp-admin/post.php" "id:88261004,phase:1,t:none,nolog,pass,ctl:ruleRemoveById=973333"
SecRule REQUEST_FILENAME "@streq /wp-admin/post.php" "id:88261005,phase:1,t:none,nolog,pass,ctl:ruleRemoveById=973306"
SecRule REQUEST_FILENAME "@streq /wp-admin/post.php" "id:88261006,phase:1,t:none,nolog,pass,ctl:ruleRemoveById=973304"
SecRule REQUEST_FILENAME "@streq /wp-admin/post.php" "id:88261007,phase:1,t:none,nolog,pass,ctl:ruleRemoveById=973300"
SecRule REQUEST_FILENAME "@streq /wp-admin/post.php" "id:88261008,phase:1,t:none,nolog,pass,ctl:ruleRemoveById=973338"
SecRule REQUEST_FILENAME "@streq /wp-admin/post.php" "id:88261009,phase:1,t:none,nolog,pass,ctl:ruleRemoveById=981243"
SecRule REQUEST_FILENAME "@streq /wp-admin/post.php" "id:88261010,phase:1,t:none,nolog,pass,ctl:ruleRemoveById=981245"
SecRule REQUEST_FILENAME "@streq /wp-admin/post.php" "id:88261011,phase:1,t:none,nolog,pass,ctl:ruleRemoveById=981317"
SecRule REQUEST_FILENAME "@streq /wp-admin/post.php" "id:88261012,phase:1,t:none,nolog,pass,ctl:ruleRemoveById=950901"
SecRule REQUEST_FILENAME "@streq /wp-admin/post.php" "id:88261013,phase:1,t:none,nolog,pass,ctl:ruleRemoveById=981231"
SecRule REQUEST_FILENAME "@streq /wp-admin/post.php" "id:88261014,phase:1,t:none,nolog,pass,ctl:ruleRemoveById=950120"
SecRule REQUEST_FILENAME "@streq /wp-admin/post.php" "id:88261015,phase:1,t:none,nolog,pass,ctl:ruleRemoveById=960024"

 

HPKP explained

The CA system we currently have is insecure. Governments try to hack CA’s, hacker try to hack CA’s, Criminals try to hack CA’s basically anyone tries to hack CA’s. So if one of these hackers succeed that person can issue a certificate that is valid for whatever domain he want. No browser or client will ever doubt that certificate as it is issued by a trusted CA. This occurred when a Dutch CA Diginotar was hacked. Hackers used that certificate for gmail and so forth.

In defence for this security issue a RFC was created. Here’s where HKPK (HTTP Public Key Pinning)  is introduced. This technique is quite simple. A pin can be generated with a certificate. This pin is always the same with that certificate. You’ll need to have 2 certificates per CN or SAN. So if you have a certificate from let’s say StartSSL for the domain www.example.com you’ll need an additional certificate for www.example.com or let’s say a wildcard certificate for *.example.com. Next you generate a pin for these certificates. You enter the pin into a HTTPS header and your secure.

When a clients connects to your site it saves this pin. Lets say a CA gets hacked and a certificate is generated for your domain. Then that hacker uses your certificate for example in a MITM attack of if it is a government it can alter DNS for your domein. Then the client connect to your site again and the certificate does not match the saved pin. The client browser present you with an error that someone is trying to intercept the traffic and further processing will be stopped.

For now only a maintained list by Google is being used for HKPK. On this list are only large sites like Facebook, Twitter and Google. But soon this RFC will be implemented and available for anyone who uses a SSL certificate. When HKPK is being used a hacker or government will have to burn a CA every time they try to intercept traffic. A very nice result. Yet in the world before HKPK we have more than 1500 certificates.

By the end of 2015 HKPK is already proven to be valuable. Only for google.com Symantec issued a certificate while Google did not request the certificate. Symantec fired those employees. But this must only be the tip of the iceberg. Image how much HKPK can contribute to a safer net. I can only imagine that some government already are deleting certificates

But what if the certificate expires? Well that one is easy. You have two certificates in the header. Than just use the not expired certificate. Request a new certificate generate the new pin. Enter this in the header and remove the old pin. The only thing important here is that you must use a shorter period than the time between the expiration of certificate1 and certificate2.

So how do you make this work? First you’ll need to generate a SHA256 pin. You can do this for an online certificate like this:

Non-SNI

openssl s_client -connect www.bexit.nl:443 | openssl x509 -pubkey -noout | openssl rsa -pubin -outform der | openssl dgst -sha256 -binary | openssl enc -base64

SNI

openssl s_client -servername www.bexit.nl -connect www.bexit.nl:443 | openssl x509 -pubkey -noout | openssl rsa -pubin -outform der | openssl dgst -sha256 -binary | openssl enc -base64

Or for a certificate by running this command

openssl x509 -in certificate.pem -pubkey -noout | openssl rsa -pubin -outform der | openssl dgst -sha256 -binary | openssl enc -base64

When you’ve generated the pins you can add them to the header by using a string like this:

Header always set Public-Key-Pins 'pin-sha256="hOKXjMHg/QuZ2q6n6Hzp6j+P+217O6Mzd70WJ2LgEBw="; pin-sha256="t3EN6WT7PvqYPL0af/BoG4YAjVucNaIhg+CkT/sDExM="; max-age=15768000'

Be careful not to enter a very long timeframe as you might run in to trouble later on.

Export all tasks in the domain

In the spirit of controlling all tasks you can do an export of them all in your domain. To do this you can use to following script. This script uses the schtasks.exe. It exports the scheduled tasks on any server in the domain. The list of those servers is generated based on when the have logged on to the domain. I explained that in this post.

You can adjust this code to your needs.

Ipmo ActiveDirectory
$Date = (Get-Date).AddDays(-15)
$list = Get-ADComputer -Filter 'OperatingSystem -like "Windows Server 20*" -and LastLogonDate -gt $Date' -Properties LastLogonDate,Operatingsystem
Foreach ($Computer in $list){$Export = $Null
$Export = schtasks /query /FO CSV /V /S $Computer.DNSHostName
$Path = ".\"+$Computer.Name+".csv"
If ($Export -ne $Null){Foreach ($Line in $Export){AC -Value $Line -Path $Path}}
If ($Export -eq $Null){AC -Value $Computer.DNSHostName -Path ".\Error.csv"}}
$CSV = gci .\* -Include *.csv | ? {$_.FullName -notmatch "Error.csv"}
$List = @()
$List += Import-CSV $CSV -Delimiter ","
$List | Export-CSV ".\CompleteExport.csv" -Delimiter ";" -Append -NoTypeInformation

 

Administrator account monitor script

The Administrator account is an account which you must monitor in a managed environment. You do not want this account to be exploited. For example I recently encountered an Administrator account which had a mailbox. To make matters worse the account had ActiveSync enabled. So if someone would authenticate with the Administrator account to Activesync it would become visible when you have the password right. A blackhat hacker could do a long term deployment in which he would try to authenticate multiple times per hour. He would be unnoticed in his attempts as long as he keeps the pace low. Imagine what a massive number of passwords you can try in two years time. If you have chosen a bad Administrator password you could end up being compromised. 105120 may seem like a low number but in reality many companies do not change this password frequently and if those companies started out with a weak password, the chance of being compromised is quite large.

What most companies also don’t do is monitor when someone logged on with this account recently.  Or more importantly when someone reset the password. This information is very important to keep an eye on. I made a script which can be planned to report any of those events by mail. You may adjust as you wish.

I highly recommend to sign this script to prevent rogue administrator scenarios. If you want to monitor any powerful group in your domain I have made a script that does that job similar to to one below

<#Administrator Test Script#>
$MailServer = "mailserver"
$Recipients = "some@one.com","any@body.com"
cd "Path-to-Script..."

$Domaincontrollers = [system.directoryservices.activedirectory.domain]::GetCurrentDomain() | ForEach-Object {$_.DomainControllers} | ForEach-Object {$_.Name} 
$Errors =@()
Foreach ($Domaincontroller in $DomainControllers){$Path = ".\"+$Domaincontroller+".csv"
If ((Test-Path $Path) -eq $False){
Get-ADUser Administrator -Properties * -Server $DomainController | Select Enabled,LastBadPasswordAttempt,LastLogon,LastLogonDate,LastLogonTimeStamp,LockedOut,PasswordLastSet | Export-CSV -Delimiter ";" -Path $Path
}}
$CSV = GCI ".\*" -Include *.csv
Foreach ($Item in $CSV){
$StoredValues = Import-CSV $Item.FullName -Delimiter ";"
$DC = $Item.Name.Replace(".csv",'')
Get-ADUser Administrator -Properties * -Server $DC | Select Enabled,LastBadPasswordAttempt,LastLogon,LastLogonDate,LastLogonTimeStamp,LockedOut,PasswordLastSet | Export-CSV -Delimiter ";" -Path ".\Temp.csv"
$ActualValues = Import-CSV ".\Temp.csv" -Delimiter ";"

$CompareResult =@(Compare -ReferenceObject $StoredValues -DifferenceObject $ActualValues -Property Enabled)
If ($CompareResult -ne $Null){$Errors += "Enabled"+";"+"$DC"+";"+$ActualValues.Enabled+";"+$StoredValues.Enabled}
$CompareResult =@(Compare -ReferenceObject $StoredValues -DifferenceObject $ActualValues -Property LastBadPasswordAttempt)
If ($CompareResult -ne $Null){$Errors += "LastBadPasswordAttempt"+";"+"$DC"+";"+$ActualValues.LastBadPasswordAttempt+";"+$StoredValues.LastBadPasswordAttempt}
$CompareResult =@(Compare -ReferenceObject $StoredValues -DifferenceObject $ActualValues -Property LastLogon)
If ($CompareResult -ne $Null){$Errors += "LastLogon"+";"+"$DC"+";"+$ActualValues.LastLogon+";"+$StoredValues.LastLogon}
$CompareResult =@(Compare -ReferenceObject $StoredValues -DifferenceObject $ActualValues -Property LastLogonTimeStamp)
If ($CompareResult -ne $Null){$Errors += "LastLogonTimeStamp"+";"+"$DC"+";"+$ActualValues.LastLogonTimeStamp+";"+$StoredValues.LastLogonTimeStamp}
$CompareResult =@(Compare -ReferenceObject $StoredValues -DifferenceObject $ActualValues -Property LockedOut)
If ($CompareResult -ne $Null){$Errors += "LockedOut"+";"+"$DC"+";"+$ActualValues.LockedOut+";"+$StoredValues.LockedOut}
$CompareResult =@(Compare -ReferenceObject $StoredValues -DifferenceObject $ActualValues -Property PasswordLastSet)
If ($CompareResult -ne $Null){$Errors += "PasswordLastSet"+";"+"$DC"+";"+$ActualValues.PasswordLastSet+";"+$StoredValues.PasswordLastSet}
Remove-Item ".\Temp.csv" -Force
}

If ($Errors.length -ne 0){$Body = "Er heeft een wijziging plaats gevonden op het account Administrator. De Fout/Fouten zijn:"+"`n`n"
Foreach ($item in $Errors){
$Array = $Item.Split(";")
If ($Array[0] -Match "Enabled"){$Body = $Body + "Op de domaincontroller: "+$Array[1]+" is de status van het account gewijzigd van Enabled: "+$Array[3]+" naar Enabled: "+$Array[2]+".`n"}
If ($Array[0] -Match "LastBadPasswordAttempt"){$Body = $Body + "Op de domaincontroller: "+$Array[1]+" is er een BadPasswordAttempt uitgevoerd op "+$Array[2]+". De waarde was hiervoor: "+$Array[3]+".`n"}
If ($Array[0] -Match "LastLogon"){$StoredValue = $Array[3] ; $StoredValue = [datetime]::fromfiletime($StoredValue) ; $ActualValue = $Array[2] ; $ActualValue =  [datetime]::fromfiletime($ActualValue) ; $Body = $Body + "Op de domaincontroller: "+$Array[1]+" is de waarde van LastLogon gewijzigd van "+$StoredValue+" naar de waarde: "+$ActualValue+".`n"}
If ($Array[0] -Match "LastLogonTimeStamp"){$StoredValue = $Array[3] ; $StoredValue = [datetime]::fromfiletime($StoredValue) ; $ActualValue = $Array[2] ; $ActualValue =  [datetime]::fromfiletime($ActualValue) ; $Body = $Body + "Op de domaincontroller: "+$Array[1]+" is de waarde van LastLogonTimeStamp gewijzigd van "+$StoredValue+" naar de waarde: "+$ActualValue+".`n"}
If ($Array[0] -Match "LockedOut"){$Body = $Body + "Op de domaincontroller: "+$Array[1]+" is de status van het account gewijzigd van LockedOut: "+$Array[3]+" naar LockedOut: "+$Array[2]+".`n"}
If ($Array[0] -Match "PasswordLastSet"){$Body = $Body + "Op de domaincontroller: "+$Array[1]+" is er een PasswordLastSet uitgevoerd op "+$Array[2]+". De waarde was hiervoor: "+$Array[3]+".`n"}
}
Send-MailMessage -From "AdministratorRoles@Domain.com" -Subject "Er heeft een wijziging plaatsgevonden op het Administrator account" -To $Recipients  -Body $Body -SmtpServer $MailServer}

Foreach ($item in $CSV){Remove-item $Item -Force}
Foreach ($Domaincontroller in $DomainControllers){$Path = ".\"+$Domaincontroller+".csv"
Get-ADUser Administrator -Properties * -Server $DomainController | Select Enabled,LastBadPasswordAttempt,LastLogon,LastLogonDate,LastLogonTimeStamp,LockedOut,PasswordLastSet | Export-CSV -Delimiter ";" -Path $Path}




EDIT 30 April 2015

Since the number of e-mails sent is very high, I was asked to change the script to show where the error originated from. I found that in the Security event log on the specified domain controller an error is written with that information. The problem is that it will not export very well. If you select the Message string of that Event log item you can not simply select the originating address.

Screen Shot 2015-04-30 at 10.53.50

The EventID number is 4771. You can see the additional information in the image above. So I changed the script so that it will search the event log of the domain controller on which the authentication failure occurred. You’ll have to make sure that the event log is not too big. I have used this script with an event log that was 1 GB of size. I have had serious problems with the script because it cannot index anywhere from 1.8 million records in the memory. You can get by this to select -Newest 200000 in the Get-EventLog cmdlet. However you’ll have less accuracy as the event log item can be outside that limit in some cases. I do not know this for certain but I have a hunch that Get-EventLog will search from bottom to top. We have used the script successfully with an event log size of 128 MB. Even an event log of that size does pose a problem as querying that does take a lot of time. The best thing to do is the run the script like every 2 minutes and specify the first 50K records.

Anyhow I noticed that there is some difference between that event log per domain controllers. This is just like the case further above that when you query for when the last badpassword attempt has occurred there’s a difference per domain controller. So just like that I made sure that the domain controller on which the bad password attempt occurred is being queried. You need to have Powershell version 3 . To check which version of Powershell is installed enter this code.

<#Administrator Test Script#>
$MailServer = "mailserver"
$Recipients = "some@one.com","any@body.com"
cd "Path-to-Script..."

$Domaincontrollers = [system.directoryservices.activedirectory.domain]::GetCurrentDomain() | ForEach-Object {$_.DomainControllers} | ForEach-Object {$_.Name} 
$Errors =@()
Foreach ($Domaincontroller in $DomainControllers){$Path = ".\"+$Domaincontroller+".csv"
If ((Test-Path $Path) -eq $False){
Get-ADUser Administrator -Properties * -Server $DomainController | Select Enabled,LastBadPasswordAttempt,LastLogon,LastLogonDate,LastLogonTimeStamp,LockedOut,PasswordLastSet | Export-CSV -Delimiter ";" -Path $Path
}}
$CSV = GCI ".\*" -Include *.csv
Foreach ($Item in $CSV){
$StoredValues = Import-CSV $Item.FullName -Delimiter ";"
$DC = $Item.Name.Replace(".csv",'')
Get-ADUser Administrator -Properties * -Server $DC | Select Enabled,LastBadPasswordAttempt,LastLogon,LastLogonDate,LastLogonTimeStamp,LockedOut,PasswordLastSet | Export-CSV -Delimiter ";" -Path ".\Temp.csv"
$ActualValues = Import-CSV ".\Temp.csv" -Delimiter ";"

$CompareResult =@(Compare -ReferenceObject $StoredValues -DifferenceObject $ActualValues -Property Enabled)
If ($CompareResult -ne $Null){$Errors += "Enabled"+";"+"$DC"+";"+$ActualValues.Enabled+";"+$StoredValues.Enabled}
$CompareResult =@(Compare -ReferenceObject $StoredValues -DifferenceObject $ActualValues -Property LastBadPasswordAttempt)
If ($CompareResult -ne $Null){$Errors += "LastBadPasswordAttempt"+";"+"$DC"+";"+$ActualValues.LastBadPasswordAttempt+";"+$StoredValues.LastBadPasswordAttempt}
$CompareResult =@(Compare -ReferenceObject $StoredValues -DifferenceObject $ActualValues -Property LastLogon)
If ($CompareResult -ne $Null){$Errors += "LastLogon"+";"+"$DC"+";"+$ActualValues.LastLogon+";"+$StoredValues.LastLogon}
$CompareResult =@(Compare -ReferenceObject $StoredValues -DifferenceObject $ActualValues -Property LastLogonTimeStamp)
If ($CompareResult -ne $Null){$Errors += "LastLogonTimeStamp"+";"+"$DC"+";"+$ActualValues.LastLogonTimeStamp+";"+$StoredValues.LastLogonTimeStamp}
$CompareResult =@(Compare -ReferenceObject $StoredValues -DifferenceObject $ActualValues -Property LockedOut)
If ($CompareResult -ne $Null){$Errors += "LockedOut"+";"+"$DC"+";"+$ActualValues.LockedOut+";"+$StoredValues.LockedOut}
$CompareResult =@(Compare -ReferenceObject $StoredValues -DifferenceObject $ActualValues -Property PasswordLastSet)
If ($CompareResult -ne $Null){$Errors += "PasswordLastSet"+";"+"$DC"+";"+$ActualValues.PasswordLastSet+";"+$StoredValues.PasswordLastSet}
Remove-Item ".\Temp.csv" -Force
}

If ($Errors.length -ne 0){$Body = "Er heeft een wijziging plaats gevonden op het account Administrator. De Fout/Fouten zijn:"+"`n`n"
Foreach ($item in $Errors){
$Array = $Item.Split(";")
If ($Array[0] -Match "Enabled"){$Body = $Body + "Op de domaincontroller: "+$Array[1]+" is de status van het account gewijzigd van Enabled: "+$Array[3]+" naar Enabled: "+$Array[2]+".`n"}
If ($Array[0] -Match "LastBadPasswordAttempt"){
$Resultlist = @()
$Eventlog=@( Get-EventLog -LogName Security -ComputerName $Array[1] | ?{$_.entrytype -eq "FailureAudit" -and $_.message -match "Administrator"})
Foreach ($Event in $Eventlog){$Message = $Event.message.Split("`n")
Foreach ($Line in $Message){
If ($Line -match "Client Address:"){
[String]$Result = $Line.replace("`t",'').Replace("Client Address:",'').Replace(" ",'').Replace("::ffff:",'')}}
$ReverseName = $Null
[string]$ReverseName = [System.Net.Dns]::GetHostbyAddress($Result.Trim()).hostname
If ($ReverseName -eq $Null){$ReverseName = "Naam kon niet opgehaald worden"}
$Value = "- IP-Adres: "+($Result.replace("`n",'')) + " welke resolved naar $ReverseName`n"
$Resultlist += $Value}
$Resultlist = $Resultlist | Sort -Unique
$Body = $Body + "Op de domaincontroller: "+$Array[1]+" is er een BadPasswordAttempt uitgevoerd op "+$Array[2]+". De waarde was hiervoor: "+$Array[3]+". De attempts kwamen vanaf de volgende computer/Computers:`n`r"
$Body = $Body + $Resultlist + "`n`n"}
If ($Array[0] -Match "LastLogon"){$StoredValue = $Array[3] ; $StoredValue = [datetime]::fromfiletime($StoredValue) ; $ActualValue = $Array[2] ; $ActualValue =  [datetime]::fromfiletime($ActualValue) ; $Body = $Body + "Op de domaincontroller: "+$Array[1]+" is de waarde van LastLogon gewijzigd van "+$StoredValue+" naar de waarde: "+$ActualValue+".`n"}
If ($Array[0] -Match "LastLogonTimeStamp"){$StoredValue = $Array[3] ; $StoredValue = [datetime]::fromfiletime($StoredValue) ; $ActualValue = $Array[2] ; $ActualValue =  [datetime]::fromfiletime($ActualValue) ; $Body = $Body + "Op de domaincontroller: "+$Array[1]+" is de waarde van LastLogonTimeStamp gewijzigd van "+$StoredValue+" naar de waarde: "+$ActualValue+".`n"}
If ($Array[0] -Match "LockedOut"){$Body = $Body + "Op de domaincontroller: "+$Array[1]+" is de status van het account gewijzigd van LockedOut: "+$Array[3]+" naar LockedOut: "+$Array[2]+".`n"}
If ($Array[0] -Match "PasswordLastSet"){$Body = $Body + "Op de domaincontroller: "+$Array[1]+" is er een PasswordLastSet uitgevoerd op "+$Array[2]+". De waarde was hiervoor: "+$Array[3]+".`n"}
}
Send-MailMessage -From "AdministratorRoles@Domain.com" -Subject "Er heeft een wijziging plaatsgevonden op het Administrator account" -To $Recipients  -Body $Body -SmtpServer $MailServer}

Foreach ($item in $CSV){Remove-item $Item -Force}
Foreach ($Domaincontroller in $DomainControllers){$Path = ".\"+$Domaincontroller+".csv"
Get-ADUser Administrator -Properties * -Server $DomainController | Select Enabled,LastBadPasswordAttempt,LastLogon,LastLogonDate,LastLogonTimeStamp,LockedOut,PasswordLastSet | Export-CSV -Delimiter ";" -Path $Path}


Export services

Recently I needed to have an export of all services. To be more specific I needed to have an export of Log On As field of all services. I wanted to have this export for alle computers in de domain. I wanted only servers but you can modify the script as you wish. For this to work you’ll need to have PSRemoting enabled on all servers. I wrote a script that does that job.

I added a LastLogonDate as that can confirm that the specified server is logged on lately. I used 15 days because the LastLogonTimeStamp isn’t a hard value. The Timestamp can fall behind anywhere from 14 to 8 days. So by specifying 15 days you are safe.

Ipmo ActiveDirectory
$Date = (Get-Date).AddDays(-15)
$list = Get-ADComputer -Filter 'OperatingSystem -like "Windows Server 20*" -and LastLogonDate -gt $Date' -Properties LastLogonDate,Operatingsystem

Foreach ($Computer in $List){$Export = $Null
$Export = Get-WmiObject win32_service -ComputerName $Computer.DnsHostName | select Name,Startname,Systemname
If ($Export -ne $Null){$Export | Export-CSV ".\Services.csv" -Delimiter ";" -Append -NoTypeInformation}
If ($Export -eq $Null){AC -Value $Computer.DNSHostName -Path ".\Error.csv"}}

Mod Security for Plesk

One thing that I have learned is that no security measure is solid other than a computer without any ports, no screen and no NIC. If you want to have a connection to the internet you will be exposed to a massive amount of attacks. Therefore it is more important to detect attacks other then desperately trying to avoid vulnerability. You should be well protected but noticing that you have been hacked is just as important.

Plesk 12 has a nice addition as it comes to security. I’ve talked about security in this and this post. Now I will continue with some nice features of Plesk 12.

Modsecurity

If you upgraded your version of Plesk from 11.5, Mod Security will probably be optional. Mod Security is an application firewall that detects malicious code and prevent it from being ran. If you want this protection enable and install Mod Security. You’ll have three options: Disabled, Detect and Prevent.

Disabled needs no explanation

Detect Only will log any detected incident.

Prevent will actively try to prevent code from being run. It has impact on performance so be sure that you’ll enable it only if you server can handle it. You can tweak the settings so that there is a fast check if you have a lot of visitors. If you however want a thorough scan you can select that also.

Mod security works with a set of rules. This ruleset is updated regularly. By default the set of rules present in Plesk is a simple. This makes the effectiveness a little less but  performance is maintained and security is improved over having no mod security. If want a very secure environment you can select to OWASP core set. This is a more thorough set but none the less a very good protection. You can optionally pay for a subscription for a professional service. Use this only if the basic ruleset is not good enough for you.

It is wise to update the ruleset once a day. I often see new updates and 0-day vulnerabilities will be patched quicker. The impact of the update process is small and the gain in security is high.

Fail2Ban

When you have a small server like me, with only a few site and look at the logs, you see a lot malicious traffic. All sorts of port scanning, robots, crawlers and hackers. This traffic only increases on server with a high user load. There are lists of username, password combination that bots are trying at any site. If you have an account on a hacked site and your username and password combination is exposed you do not want this to actually be exploited. Let alone your users. Not all companies disclose security incidents which makes it hard to know if your credentials are on some list. If you don’t have a unique set of username/password combination for every site that risk is real.

Plesk 12 has this incredible feature call Fail2Ban. This extension automatically bans these threats by IP-address. The default settings are very loose but this can be improved.  Fail2Ban can built jails to detect login attempts and so forth. First install this feature and see my config below.

First you’ll have to enable jails. Jails are no more than automatically looking at logs. The logs are scanned and if an IP-address attempts multiple malicious actions in a certain period, that IP gets blocked. I would recommend to enable jails on all available services:

Jails Fail2Ban

Apache jails will check of failed authentication or time traveling. Badbot detects fraudulent bots. You can check what a jail does by looking at the Filters tab.

By default Fail2Ban will detect 7 attempts within 10 minutes and then bans that ip for a period of 15 minutes. I personally think that a 15 minute break is way to weak. Also the 10 minute range is to short. Will users will keep on trying to logon after 7 attempts? They will contact you anyway to reset their passwords. So releasing their IP is then just part of the deal. As I basically work alone on this server I lowered the number of failures. Next I have expanded the time frame in which to look for a attempt to 900 seconds. The ban time should be at least a year. Imagine that those bots are built to try every 10 minutes with 7 login attempts. That will be 144 times 7 login attempts a day. That will be a 367.920 attempts a year when with my settings you’ll only have a small number of no more than 5. Imagine that such a organization has 10 ip-addresses. That is a massive number of attempts without you probably ever noticing one of them if you do not look at the logs. That’s why I configured a ban period of  31536000 seconds. That is precisely one year. Even for a server that has only been up for one week there are already 10 ip-addresses banned. That is a quite large attack surface been taken care of. You can configure a ban period that is there forever to make it negative. So if you configure a ban period of -1 seconds the IP address will not be released.

Of course this impacts performance. You’ll have to decide for yourself if this is worth it for you. In my case it is. But I do not host any high profile sites.

There is another downside for the implementation of Fail2Ban in Plesk. If you whitelist an IP-Address that has been blocked all blocked IP-Addresses will be released. Furthermore if you restart your server the blacklist will be release also.

WordPress toolkit

You may have noticed the new WordPress toolkit icon in the Plesk Admin panel:

Wordpres Toolkit

 

This is a vital new function of Plesk 12. Security flaws in common web application can be exploited because they are not patched. With WordPress Toolkit you can update version of WordPress. You can also update plugins and themes. But it doesn’t stop there. You can also enhance security by removing the built numbers from all relevant files. You can tighten security on the wp-include and wp-content directories. The attack surface will be tremendously reduced. And the best thing is that you can see all installations even though you did not install them using Plesk. Having said that you can thus manage all your WordPress installation in one console. It is an incredible powerful addition and I wouldn’t want to miss it again.

Strict security on a Plesk Apache web server 2

In an addition to my previous post I would like to add a few updates and improvements.

HSTS

While it is possible to add a line in the conf file. You can also use a nifty function in Plesk. Since Plesk 11.5  configuring the web server per site is possible. But before I take you to heaven I would like to add a few side notes. Be sure that if you use the includesubdomains flag, that all your subdomains support HTTPS. You can of course configure this per site, but if a browser connects to the parent site and receives the HSTS header, it will automatically connect for a long time to all subdomain using HTTPS. If one of your subdomain doesn’t support HTTPS and the user visited the parent site you’ll have a problem. You can of course exclude the IncludeSubDomains flag.

When logging in the Plesk Panel site you can configure the webserver. Go to Websites and Domains en click on Expand. Next click on configure webserver.

Schermafbeelding 2014-10-01 om 11.18.55

 

Scroll down to the end of the page and add the following line in the HTTPS settings:

Schermafbeelding 2014-10-01 om 11.22.50

Please mind the includeSubDomains switch

Header always set Strict-Transport-Security "max-age=63072000; includeSubDomains"

CSP

I recently learned about CSP. This make it impossible to have malicious  XSS or other code being run from your site. For example if you have a script that is being referred to in the index of you site, it is harder to exploit the trust that you browser has in your site. You can very basically configure this using the same steps as above by adding another line in you HTTPS config

 

Header unset Content-Security-Policy
Header add Content-Security-Policy "default-src https: 'unsafe-inline' 'unsafe-eval';connect-src https: wss:"

Please note that this is a very basic way to configure CSP. To further tighten CSP you can read this site. To enable full CSP you can replace about code with:

Header unset Content-Security-Policy
Header add Content-Security-Policy "default-src 'self'"

Additional headers

Other headers that can strengthen the security are:

Header always set X-Content-Type-Options: nosniff
Header always set X-Frame-Options: deny
Header always set X-XSS-Protection: "1; mode=block"

Please note that these headers may alter you website functionality. For example X-Frame-Options: limits frame redering. So if you have code that runs in a frame, there will be no output. My advise is to play around with these headers in your test environment. Above headers may be also used in HTTP. So you can add them to the HTTP headers. This is unlike HTST which of course has something to do with secure transfer.

You configure them in the same way as the headers above:

Schermafbeelding 2014-10-01 om 11.43.50

In my next post I will get into HTTP Public Key Pinning. This I still have to figure out myself so if I have some spare time I will test around a little bit. The support for this feature isn’t very broad. Only predefined list are currently supported. As always IE doesn’t support it yet. Firefox supports it since version 32. Chrome supports it since 2011. However there is no mainstream support for it as of now. HPKP will however be supported in the future so I want to be sure that by the time it is supported, I already comply 🙂

The complete header looks like this:

Strict security on a Plesk Apache web server

There is an update for this post

To ensure secure SSL traffic you should always protect you server properly. Plesk isn’t really the best solution to handle such tasks. I found a way to protect a Plesk server better using SSL. My configuration is a Ubuntu 14.04 LTS server in combination with Plesk 12. If you have Ubuntu 12.04 LTS you’ll have problem protecting such a server. I’ll discuss this later on.

What you first need to do is to ensure that only secure ciphers are being used. A secure cipher is one that enables Forward Secrecy. If someone captured SSL traffic and succeeds to brute force it’s way into one package, he cannot use that key to decipher all the other packages. In other words if someone cracks open one package he would have to do the same thing for each package. If you use ciphers that don not  enable forward secrecy and someone cracks open one package he can open all the packages. Which makes it highly insecure.

Create a file on the following location:

/etc/apache2/conf-enabled/zz050-psa-disable-weak-ssl-ciphers.conf

Enter the text below to disable the unwanted ciphers. Please note that by disabling SSLv3 your IE6 users will not be able to connect anymore.

SSLProtocol all -SSLv2 -SSLv3
SSLHonorCipherOrder on
SSLCipherSuite "EECDH+ECDSA+AESGCM EECDH+aRSA+AESGCM EECDH+ECDSA+SHA384 EECDH+ECDSA+SHA256 EECDH+aRSA+SHA384 EECDH+aRSA+SHA256 EECDH+aRSA+RC4 EECDH EDH+aRSA RC4 !aNULL !eNULL !LOW !3DES !MD5 !EXP !PSK !SRP !DSS"

Please also note that you need to have Apache 2.3 for the EECDH algorithm. Apache 2.3 is not by default installed on Ubuntu 12.04 LTS. So I would recommend to install Ubuntu 14.04 LTS on you server or any other distribution with Apache 2.4 support.

Next you should enable OCSP stapling. This is a lot quicker to check for revocations than CRL. If your server handles a lot of traffic I would definitely  enabled this feature. Open the file:

/etc/apache2/conf-enabled/security.conf

And append the following text:

SSLUseStapling on
SSLStaplingResponderTimeout 5
SSLStaplingReturnResponderErrors off
SSLCACertificateFile /etc/ssl/certs/StartCom_Certification_Authority.pem
SSLStaplingCache shmcb:/var/run/ocsp(128000)

I only use one CA. If you have more than one CA your at the moment limited to only enter one. OCSP Stapling doesn’t support more than one CA. There is a new standard that will get rid of this problem. It is however not very well supported. Both clients and servers do rarely support it. You’ll have to wait for a bit.

Next you can enable HSTS (HTTP Strict Transport Security). This only work with Apache 2.2.3 and above. The way Plesk handles website isn’t really efficient for HSTS. Don’t get me wrong I absolutely love the way Plesk handles SNI and other offloading  and features but this is one that could be improved. You have to open each conf file for a website in Plesk.

Please note the update stated above!

First I’ll explain what HSTS is.  Imagine that you site enables HTTP requests and HTTPS requests. You have HTTP enabled to redirect to HTTPS. One of you users browses to you site and fails to directly go to secure site. Someone have created a fraudulent access point and redirects the site to another site. The user doesn’t mind the changes and enters his credentials. Well that account is compromised. Now HSTS tells the browser to connect to the https:// site for the upcoming year. Each time the user browses to that site the timer wil be reset to another year for that date.

Each new website gets a .conf file in the system directory. You will have to make sure that a certain text is added to that file.

If you have a default website for an IP address a file is created like to one below:

/var/www/vhosts/system/domain.tld/conf/httpd_ip_default.conf

If you use SNI Plesk creates the file below for you:

/var/www/vhosts/system/domain.tld/conf/httpd.conf

In the portion of the container below add the following line

 

<VirtualHost x.x.x.x:443 >

</VirtualHost>
Header always set Strict-Transport-Security "max-age=63072000; includeSubDomains"

This enables HSTS. But please not any change to you website settings and this file get overwritten. I created a shell script that checks the existence of the text and if it is not enabled it will enable it for you. I post that later on.