Showing posts with label regex. Show all posts
Showing posts with label regex. Show all posts

04 January, 2018

Verify password complexity - OS

Not too long ago the news broke that 1.4 billion leaked passwords were being shared all over the place. Obviously, you want to see if your users could be impacted, so one thing that can be done to see if the leaked passwords of users are applicable at all to your environment. If not, then you know that the given password wasn't used in your environment so no need to panic.



Here's an example password policy:
Example password policy with complexity enabled




We would like to see if a given list of passwords conform these settings, mainly the length and complexity. Based on Microsoft's definition a password is complex if contains characters from three of the following five categories: 
  • Uppercase characters of European languages (A through Z, with diacritic marks, Greek and Cyrillic characters)
  • Lowercase characters of European languages (a through z, sharp-s, with diacritic marks, Greek and Cyrillic characters)
  • Base 10 digits (0 through 9)
  • Nonalphanumeric characters: ~!@#$%^&*_-+=`|\(){}[]:;"'<>,.?/


So we want a quick script which verifies if the password is at least 10 characters long and is complex. There are many ways to do it and can be done with hardcore regex patterns or just simple direct checks, here's one not too complicated way to do it, this is the output:


Password complexity check output












the script:
 function checkpwcplx ($passwordString){  
    $pwComplexity = 0  
      
    # checking the minimal length, if it's shorter, no need to continue  
    if($passwordString.length -lt 10){  
       return "length <10"  
    }  
   
    # lowercase  
    if($passwordString -cmatch "[a-z]"){  
       $pwComplexity++  
    }  
   
    # uppercase  
    if($passwordString -cmatch "[A-Z]"){  
       $pwComplexity++  
    }  
   
    # digits  
    if($passwordString -cmatch "[0-9]"){  
       $pwComplexity++  
    }  
   
   
    # special character (not alphabetic characters or numbers)  
    if($passwordString -cmatch "[^a-zA-Z0-9]"){  
       $pwComplexity++  
    }  
   
    # if 3 of the criterias      
    if($pwComplexity -ge 3){  
       return "complex"  
    }  
    else{  
       return "NOT complex"  
    }  
 }  
   
 $list = @($input)  
   
 $list | %{  
    $obj = "" | select Password,Complexity  
    $obj.Password = $_  
    $obj.Complexity = checkpwcplx $_  
    $obj  
 }  






t

28 January, 2017

Parse user name from events - OS

Have you ever needed to parse fields or text from Windows Event Log? Even if the answers is no, read on. You never know when someone stops you on the road and holds a gun to your head and shouts until you parse all the fields from those 100 000 events.


My example was just about parsing user names from events in the Application log in a very friendly situation - no guns and shouting involved...
There were 300 000+ of these events in the last 24 hours and I had to find out from them the list of users with connection to the app and a connection count for each user in the same time frame.


This is how the events I'm parsing look like:

Event with User name to parse and count


First attempt

I used Get-WinEvent and filtered in its hash based switch as much as possible to speed up the query for events. Something like:
$timelimit=(Get-Date).addhours(-24);
Get-WinEvent -ea SilentlyContinue -FilterHashtable @{LogName="Application"; StartTime=$timelimit; id=998}


The events will tell me the SID (Security IDentifier) of the user who generated the event, to look up the user name I used the SecurityIdentifier and then the NTAccount classes of System.Security.Principal:
Select @{e={(New-Object System.Security.Principal.SecurityIdentifier($_.userID)).Translate([System.Security.Principal.NTAccount])}; l="User";}


The trick to make this a one-liner was to generate the user name on the fly when creating a custom object with 'Select' in the command


Full command and the measure of run time:
measure-command {$timelimit=(Get-Date).addhours(-24); $tmp=Get-WinEvent -ea SilentlyContinue -FilterHashtable @{LogName="Application"; StartTime=$timelimit; id=998} | Select @{e={(New-Object System.Security.Principal.SecurityIdentifier($_.userID)).Translate([System.Security.Principal.NTAccount])}; l="User";} | group user}


Days              : 0
Hours             : 0
Minutes           : 41
Seconds           : 0
Milliseconds      : 11

Second attempt - with regex parsing


Well, 41 minutes doesn't look like a lot of time when you are spending with your loved ones, but it's way above the limit of the patience of an average IT guy... Remember the first part of this article? I was lucky enough with this application that it logged the user name into the message of the event (with some other random text as well).


So let's try something different:
measure-command {$timelimit=(Get-Date).addhours(-24); $tmp=Get-WinEvent -ea SilentlyContinue -FilterHashtable @{LogName="Application"; StartTime=$timelimit; id=998} | Select @{e={$matches=$null;$a=($_.message -match "UserName: (?<users>[^`n]+)"); $matches.user}; l="User";} | group user}


Days              : 0
Hours             : 0
Minutes           : 8
Seconds           : 13
Milliseconds      : 54


Very nice, 80% reduction on run time. What's different on this run?
The reason why this is quicker because the command does not read into Active Directory to look up a SID and translate it to user name, instead, it just parses the user name from the message field of the event with a regex pattern:
$matches=$null;$a=($_.message -match "UserName: (?<users>[^`n]+)"); $matches.user



Hope this helps saving a bit of time for some of you and can spend this away from the computer thinking about the next powershell trick!


t

08 November, 2015

List DNS scavenging settings on multiple servers remotely - AD

DDNS (Dynamic DNS where clients register their own DNS records) was a very good idea when it was published in RFC2136 and had been missing like a slice of bread, but it inevitably left some questions on the table. For example, if I let 100 000 hosts register their own records, who will tell them to clean-up their stuff if they don't need it anymore? On the other hand, if I don't use DDNS and I have only one DHCP server registering addresses, I can just regulate that one guy and tell it off if it doesn't cleanup its rubbish.

The answer is DNS scavenging which would be the butter on that slice of bread just to make it taste better and proper. But we want to make sure that the butter we put on the bread is not rotten. Otherwise we would need to throw the bread to the bin with the butter... ok, enough of this nonsense.

DNS scavenging essentially deletes stale / old records from the given DNS Zone. What we want to make sure that the scavenging process is properly configured otherwise we could end up losing very valuable DNS records and cause outages - believe me, you don't want an outage caused by something as fundamental as DNS.

There are a couple of rules we need to keep in mind:
  • The scavenging intervals have to be thought through - I'd go with the default settings 7+7 days
  • There should be only 1 DNS server scavenging the zone regularly even if we have lots of e.g. Domain Controllers hosting the zone.
  • The zone should be restricted and only that one server should be allowed to scavenge the zone. You can read more about scavenging e.g. here and here 
Question: if I have 100 domain controllers hosting an AD integrated zone how can I check if there's only one set to scavenge the zone. To get these settings from one server, you can use dnscmd /info. To do it on multiple servers you can do some dnscmd output parsing in powershell, e.g.:

Create an object where you will store the name of the DNS host, the scavenging interval set on that server and the default aging state on that server:
$sObject = "" | select hostname,ScavengingInterval,LastScav,DefaultAgingState

Take the output of dnscmd /info and go through each line:
dnscmd $srv /info | %{

If it's the line where scavenging info is stored, do some regex matching to take out the bits you need:
if($_ -imatch "last scav"){
$value = ([regex]::Match($_, "= .+$")).value -replace "= ",""

Add it to your output object:
$sObject.LastScav = $value

It will show you an output like this:
Note the date and result of the last scavenging run









The full script:
 # get the list of Windows DNS servers from the pipe  
 $hostlist = @($Input)  
   
 $hostlistlength = ($hostlist | measure).count  
   
 # go through each host  
 foreach($srv in $hostlist){  
    $sObject = "" | select hostname,ScavengingInterval,LastScav,DefaultAgingState  
   
    # run dnscmd to get the detailed info of each DNS server  
    dnscmd $srv /info | %{  
       $value = $null  
   
       # pick out the data from dnscmd output with regex matches  
       if($_ -imatch "last scav"){  
          $value = ([regex]::Match($_, "= .+$")).value -replace "= ",""  
          $sObject.LastScav = $value  
          $value = $null  
       }  
       elseif($_ -imatch "ScavengingInterval"){  
          $value = ([regex]::Match($_, "= .+$")).value -replace "= ",""  
          $sObject.ScavengingInterval = $value  
          $value = $null  
       }  
       elseif($_ -imatch "DefaultAgingState"){  
          $value = ([regex]::Match($_, "= .+$")).value -replace "= ",""  
          $sObject.ScavengingInterval = $value  
          $value = $null  
       }  
    }  
    $sObject  
 }  
   




23 February, 2014

Parse OU location from DistinguishedName - AD

This post is just a bit of breadcrumb of Powershell bits. I've got some scripts which run regularly and have to analyse 100 000+ AD objects. It can take hours to run them, so every bit of code that can make one iteration in the loop a couple of milliseconds quicker can pay significant dividends when running against many objects.

As I was looking through my 3 years old code, I noticed an ugly solution (we all do these things, don't we). I needed to get the OU location of each object, so I decided to take the DistinguishedName attribute and drop the name of the object from the beginning of string therefore I end up with the full LDAP formatted path of the object (could have taken the CanonicalName attibute in reverse order and replace '\' with 'cn=' or 'dn=' or 'ou=', but then I would have to lookup each of those elements to figure if they are OUs or containers...etc.)

Let's take an example, the dinstinguishedName of an object is "CN=DroidServer,OU=ChalmunsCantina,OU=MosEisley,DC=tatooine,DC=com", so the LDAP path of the object can be determined by dropping the first part of this string before the first comma which leaves us with: "OU=ChalmunsCantina,OU=MosEisley,DC=tatooine,DC=com".

First attempt - original code in my script

Easy, lets split the string based on commas, put the elements into an array and drop the first element, then join the elements into a string again (now without the cn=objectname piece):
 $distinguishedName = "CN=DroidServer,OU=ChalmunsCantina,OU=MosEisley,DC=tatooine,DC=com"  
 $arrDN = New-Object System.Collections.ArrayList  
 $tmparr = $distinguishedName.Split(",")  
 $tmparr | %{[void]$arrDN.add($_)}  
 $arrDN.RemoveAt(0)  
 $accLocation = [string]::join(",",$arrDN)  
 $accLocation  

This will take 96.5 milliseconds on my machine.
96 milliseconds, fair enough, it's quicker than me doing this on paper.

Second attempt

Let's get rid of the foreach-object (%) when adding elements to $tmpArr and use the .AddRange method of the ArrayList instead - this will just add all elements in one go instead of going through element by element:
 $distinguishedName = "CN=DroidServer,OU=ChalmunsCantina,OU=MosEisley,DC=tatooine,DC=com"  
 $arrDN = New-Object System.Collections.ArrayList  
 $tmparr = $distinguishedName.Split(",")  
 [void]$arrDN.addrange($tmparr)  
 $arrDN.RemoveAt(0)  
 $accLocation = [string]::join(",",$arrDN)  
 $accLocation  


25 milliseconds, not bad, 4 times quicker.
 

Third attempt

To see if it can be even quicker, we'll need to "thinking outside the box" and see if there's any simpler solution than working with arrays and instead do this in one step and drop the first bit of the string which we don't need.
It's not obvious in PowerShell because the -replace operator does not support the regular expressions which refer only to the first occurrence in a string. What we can do is make it drop all characters which are not commas and they are followed by a comma, that would make sure the "cn=computername," string is dropped and we end up with the full LDAP path of the object:
 $distinguishedName = "CN=DroidServer,OU=ChalmunsCantina,OU=MosEisley,DC=tatooine,DC=com"  
 $accLocation = $distinguishedName -creplace "^[^,]*,",""  
 $accLocation  

Explanation for the regex pattern:
  • ^       start of the string
  • [^,]*   match one or more non-comma characters
  • ,       match a comma character
 
0.4669 milliseconds!
200 times quicker than the first solution! With 100 000 objects, originally it takes 160 minutes (obviously in real life it will be less because of caching...etc.) and with the 3rd solution it should take a bit less than a minute. Maybe it can be quicker with some better trick, but I'm not greedy, I've shaved off ~2.5 hours runtime, it's good enough for me... for today...

t