Thursday, June 7, 2012

Windows DNS logging quirk if you specify alternate path

In Windows DNS, the default logging path is in %SystemRoot%.  However, if you change this path (e.g. D:\DNS\dns.log), you need to make sure you do one critical thing:

CREATE THE NECESSARY FOLDER STRUCTURE!!!

In the case of the path I listed above, it is implied that there is a "D:" drive but you need to create a folder called "DNS" before you cycle the dns service.  Cycling the DNS service will create the "dns.log" file but not the folders in the path.

This may seem obvious to some but there is no indication that this doesn't work.  There is no, "This folder doesn't exist, would you like to create it?" -- or whatever Microsoft usually says whenever you specify a path that doesn't exist.  The service itself doesn't complain, it's just configured to log and simply never does.

In a nuthsell, the service WON'T create any missing folder structure but WILL create the log file itself.

Wednesday, May 30, 2012

PowerShell: Easy ways to get computer names into string arrays

Description:  In Powershell, there are a number of reasons that administrators require the ability to run a script against multiple machines.  In many of the cmdlets, there is a -ComputerName parameter that will usually accept a string[] array to run the command against multiple computers.

This post simply covers easy ways I've come across to get computers into a usable format for this parameter.  They're posted in no real particular order besides maybe most obvious to least obvious.

1.  Import from a basic text file of computer names separated by carriage return/line feeds; e.g.:

[Contents of computers.txt]
server1
server2
server3

[PowerShell command(s)]
  1. PS C:\>Get-Content computers.txt
  2. PS C:\>gc computers.txt
  3. PS C:\>[System.IO.File]::ReadAllLines(computers.txt)
First example uses the full cmdlet name.
Second example uses the alias for the Get-Content cmdlet.
Third example uses .NET.

2.  Import from a comma-separated file (CSV) of computer names and other data; e.g.:

[Contents of computers.csv]
Name,OS,IP
server1,w2k3,192.168.2.1
server2,w2k8,192.168.2.2
server3,w2k8,192.168.2.3

[PowerShell command(s)]
  1. PS C:\>Import-Csv computers.txt | Select-Object -Expand name

Import-Csv essentially reads in a properly formatted CSV file and turns it into a PSCustomObject that has assigns the columns of the CSV to object properties.  Because we only need an array of computer names, we Select only the object-property we care about 'name' and use the -ExpandProperty property to create the required string array [string[]].

3.  Get computer names from Active Directory:

[PowerShell command(s)]
  1. PS C:\>Import-Module ActiveDirectory; Get-ADComputer -Filter * | Select-Object -Expand name
  2. PS C:\>([adsisearcher]'(objectcategory=computer)').FindAll()
First example uses the ActiveDirectory module cmdlets
Second example uses the .NET ADSISearcher class via a type-accelerator ([adsisearcher]) to directly interface with AD via an LDAP search.

4.  Manually build a string[] array from computer names on the clipboard:

[PowerShell command(s)]
PS C:\>$computers = '
>>[ctrl-v to paste the contents of the clipboard here]
>>'
PS C:\>$computers = $computers.Trim().Split()

5.  Use some .NET methods:

PS C:\>$computers = [System.IO.File]::ReadAllLines($PathToFile)

...The System.IO.File class has many other methods for reading in data depending on whether you want to use a StreamReader or get the filebytes instead.

Thursday, May 24, 2012

PowerShell: XML Object Save Method

The .Save() method of an [xml] object appears to default to "C:\Windows" instead of the current working directory. 

PS C:\Users\thepip3r\Desktop\Scripts>[xml]$xml = gc .\myxml.xml

PS C:\Users\thepip3r\Desktop\Scripts>$xml.Save("newxml.xml")


Throws an access-denied error (for my normal user account) about the inability to save the file to "C:\Windows" because Access is Denied.

On another note, you can pass a path with the filename and the Save method will save it to the specified location.

PowerShell: Copy an XML object

This is going to sound incredibly simple and stupid to some but I had a problem when I started working with XML ([xml]) objects in PowerShell regarding the creation of duplicates and/or copying of XML objects.

My scenario was that I was reading in an XML config file for Microsoft NAP, altering some values for different sites/domains, and then outputting a modified version of the XML that was specific for each site/domain that could then be imported to keep the configs identical between each site/domain.  My problem is that I was taking my local NAP server and using those values as the expected values so I didn't want it to change with each iteration of the domain/site so I thought to create a "temp" copy after reading the XML file in:

$xml = gc .\myxmlfile.xml

$sites | ForEach-Object {

   $xml2 = $xml

   ...

}


...For normal PowerShell objects, this would work fine to create a duplicate object and assign it to $xml2.  For some reason that is beyond me, this doesn't work for XML objects.  What it does is create a reference to the original object.  So if you modify the XML in $xml2 using the assignment method above, you will actually be modifying $xml. 

What I ended up discovering is that if you do a Get-Member on an [xml] object, you'll see a method called Clone().  The proper assignment in my loop that resolved this issue was:

$xml2 = $xml.Clone()

Wednesday, May 16, 2012

PowerShell: Useful Active Directory .NET Methods


  
[Active Directory Forest]:
  • Name:
    • [System.DirectoryServices.ActiveDirectory.Forest]::GetCurrentForest().Name
  • Application Partitions: 
    • [System.DirectoryServices.ActiveDirectory.Forest]::GetCurrentForest().ApplicationPartitions
  • Sites: 
    • [System.DirectoryServices.ActiveDirectory.Forest]::GetCurrentForest().Sites
  • Global Catalogs:  
    • [System.DirectoryServices.ActiveDirectory.Forest]::GetCurrentForest().GlobalCatalogs
  • Domains: 
    • [System.DirectoryServices.ActiveDirectory.Forest]::GetCurrentForest().Domains
  • Functional Level: 
    • [System.DirectoryServices.ActiveDirectory.Forest]::GetCurrentForest().ForestMode  
  • Root Domain: 
    • [System.DirectoryServices.ActiveDirectory.Forest]::GetCurrentForest().RootDomain
  • Schema DN/Role: 
    • [System.DirectoryServices.ActiveDirectory.Forest]::GetCurrentForest().Schema
  • FSMO - Schema Master: 
    • [System.DirectoryServices.ActiveDirectory.Forest]::GetCurrentForest().SchemaRoleOwner
  • FSMO - Domain Naming Master:  
    • [System.DirectoryServices.ActiveDirectory.Forest]::GetCurrentForest().NamingRoleOwner
[Active Directory Domain]:

 * If you need this information from the computer's domain you're running this on, switch out GetCurrentDomain() for GetComputerDomain() and all of these will work as well.
  • FSMO - Infrastructure Master:  
    • [System.DirectoryServices.ActiveDirectory.Domain]::GetCurrentDomain().InfrastructureRoleOwner
  • FSMO - PDC Emulator: 
    • [System.DirectoryServices.ActiveDirectory.Domain]::GetCurrentDomain().PdcRoleOwner
  • FSMO - RID Master: 
    • [System.DirectoryServices.ActiveDirectory.Domain]::GetCurrentDomain().RidRoleOwner
  • Domain Controllers: 
    • [System.DirectoryServices.ActiveDirectory.Domain]::GetCurrentDomain().DomainControllers
  • Functional Level: 
    • [System.DirectoryServices.ActiveDirectory.Domain]::GetCurrentDomain().DomainMode
  • Domain Parent:
    • [System.DirectoryServices.ActiveDirectory.Domain]::GetCurrentDomain().Parent
  • Functional Level:
    • [System.DirectoryServices.ActiveDirectory.Domain]::GetCurrentDomain().DomainMode
  • Child Domains:
    • [System.DirectoryServices.ActiveDirectory.Domain]::GetCurrentDomain().Children
  • Forest:
    • [System.DirectoryServices.ActiveDirectory.Domain]::GetCurrentDomain().Forest
  • Name:
    • [System.DirectoryServices.ActiveDirectory.Domain]::GetCurrentDomain().Name
[Computer Specific]:
  • Site: 
    • [System.DirectoryServices.ActiveDirectory.ActiveDirectorySite]::GetComputerSite()
  • User Domain: 
    • [System.DirectoryServices.ActiveDirectory.Domain]::GetCurrentDomain().Name
  • Computer Domain:  
    • [System.DirectoryServices.ActiveDirectory.Domain]::GetComputerDomain().Name

Monday, May 14, 2012

PowerShell - Reorder/Rename CDROM/DVDROM devices in reverse alphabetical order

Title:  Reorder CDROM/DVDROM Drive Letters in Reverse Alpha
Description:

If you reorder your CDROM/DVDROM drives in reverse alphabetical order in your environment, you may have use for this script.  If you have one or more "disk drives" on a system, you can run this script and it will reorder them in reverse alphabetical order. 

Example:
  1. If you have a single drive on a single system, if you run this script, it will be relablelled as the "Z:" drive.
  2. If you have multiple drives on a single system (e.g. E:, F:, G:), they will be relabelled as Z:, Y:, X:, respectively.
Known Issues/Limitations:
  1. Does not account for drives already in use.
  2. Does not account for the potential of a large number of disk drives that would be greater than 26.
Script:

$CDROMs = @(Get-WmiObject Win32_Volume -Filter "DriveType=5")
    
$Alpha = @()
65..90 | % { $Alpha += [char]$_ }
    
for ($x=0; $x -lt $CDROMs.Count; $x++) {
    $idx = 25 - $x
        
    $CDROMs[$x] | Set-WmiInstance -Arguments @{DriveLetter = "$($Alpha[$idx])`:" }
} 

Friday, May 4, 2012

DNS Views/BIND Views on Windows DNS?

In order to understand why BIND administrators want this functionality, we'll spend some time explaining what BIND Views offer, what Windows offers, and how they differ.  Then we'll look at an example to try and help bring it all together.
What is DNS Views/BIND Views?

BIND Views is a feature of BIND that allows you to create records with a configuration such that, when the name server responds to a lookup, it will return the IP address of the host relative to the configuration of the "view".  To be more specific, you can make BIND provide network-specific IP address answers based off of the requester's IP address.

Why would you want to do this?

Let's say you have a web farm across a global enterprise and you have a corporate web portal that is rather complex.  Instead of simply pointing everyone to http://www.randomcorpname.com/, you decide that you want to use a moniker like "my.randomcorpname.com".  This "my" moniker configured as a "view" in BIND can default to the Hong Kong web farm and specific portion of the portal for it's Hong Kong users, the San Francisco portion of the portal and web farm for the San Francisco users, and London portion of the portal and web farm for the London users -- simply by users at each location typing in the same value:  "my.randomcorpname.com."

Can you perform this same action in Windows DNS?

The answer is a mixed-bag of yes and no:
  1. Is the robustness of BIND Views identical in Windows?  No.  
  2. Is it even close?  No.
  3. Can I perform similar, network-centric DNS answers using Windows DNS?  Yes -- Windows actually does this by default on Windows 2003 DNS servers and beyond but for it to work properly, your network has to be IP'd in such a way to support it or Windows DNS will simply default to round-robining.
How can I implement network-centric answers similar to BIND Views in Windows?

The short answer is Windows netmask ordering

How do BIND Views and Windows netmask ordering differ?

In BIND Views, you can create View records that define network-by-network who will recieve what IP address as an answer.  The configuration of the networks can be any number of network addresses across any number of different classes of network (A,B,C, etc.)

In Windows netmask ordering, the DNS server gets configured to "look" at a particular set of bits on the network mask of the requestor, and then provide the appropriate A-record relative the class configuration of the DNS server and there being an A-record for the hostname with that same "class-value" of the requester existing in DNS.  By default, Windows DNS defaults to a "Class-C"-style matching.

Let's look at an example:

We'll shift our example using BIND above to the internal enclave since Windows netmask ordering would be so painful to manage in the same fashion as described above and because I'd never have a Windows DNS server on a non-internal boundary of my network.  So in our shifted example, we are a sprawling company with offices all over the world.  We have a portal website where we want people being directed to specific servers/farms based off of their location in the world but each location have the ability to use the same moniker to access the intranet portal:  intranet.randomcorpname.local.

Hostname/A-Record, IP Address
hk-web01.randomcorpname.local., 10.0.1.11
hk-web02.randomcorpname.local., 10.0.1.12
sf-web01.randomcorpname.local., 10.1.1.11
sf-web02.randomcorpname.local., 10.1.1.12
lon-web01.randomcorpname.local., 10.2.1.11
lon-web02.randomcorpname.local., 10.2.1.12

Additional A-Records for Netmask Ordering
intranet, 10.0.1.11
intranet, 10.0.1.12
intranet, 10.1.1.11
intranet, 10.1.1.12
intranet, 10.2.1.11
intranet, 10.2.1.12

In this example and in most medium-to-large networks, the default configuration for netmask ordering in Windows DNS will not work for proximity-based resolution because you typically segregate clients/workstations by subnet.  Any machines that are NOT on the same /24 as the web server, will ignore netmask ordering and proceed to just round-robin.  In our example with the current IP configuration, you would need to adjust the netmask ordering mask from the default Class-C to a Class-B (or something in between) in order to make this work.

If we were to adjust the netmask ordering mask of our DNS servers to a Class B (0x0000ffff), then our Windows DNS server would properly respond as desired.  In our example with our Windows DNS server configured with a netmask ordering mask set to 0x0000ffff, then a client in Hong Kong (let's say, 10.0.2.10) who does a lookup on "intranet" will properly round-robin between 10.0.1.11 and 10.0.1.12.  Similarly, a client with address 10.2.2.10 (London), will appropriately round-robin between 10.2.1.11 and 10.2.1.12.

Notice that we've changed the comparison of the third octet (Class-C) to the second octet (Class-B).  Also notice how static this configuration is and how dependent it is upon a very strict and consistent IP addressing scheme.

Relevant Command-Line Utilities/Commands to Netmask Ordering:
  1. dnscmd /Info /LocalNetPriorityNetmask
  2. dnscmd /Config /LocalNetPriorityNetmask 0x0000ffff
If you run the command in item number 1, you should get back:  "Dword:  255 (000000FF)" -- this is the default configuration.  Also notice that if you do a 'dnscmd /Info /?', the /LocalNetPriorityNetmask parameter is not documented.  I don't know if this means Microsoft is deprecating this functionality or just that it's hidden for some reason.

References:
  1. http://support.microsoft.com/kb/842197
  2. http://technet.microsoft.com/en-us/library/cc940789.aspx

Thursday, May 3, 2012

RDP Assistant v1.0


Program Name:     RDP Assistant
Program Version:  v1.0
Language:              PowerShell v2.0
Description:

This tool was developed for people who need to RDP frequently in their jobs.  The tool is nothing more than a quick way to execute multiple RDP windows simultaneously.  You can add computers individually by hostname and domain or by using an Active Directory distinguishedName value to an OU/Container that has computer objects in it.




Example: 

You need to update all domain controllers in all domains you manage [assumes you have all servers loaded in the list]:
  1. Use your system naming nomenclature to filter the list of servers to only domain controllers (e.g. "dc").
  2. Select all objects.
  3. Hit, "Go".
This will open RDP windows to all of the selected hosts.

Monday, March 5, 2012

Windows Web Security: Why you DON'T run IIS AppPools with user accounts that have elevated rights to a domain.

Title:  Windows Web Security:  Why you DON'T run IIS AppPools with user accounts that have elevated rights to a domain.

Description:  Windows AppPool account passwords are exposable as clear-text to a local Administrator of an IIS server.

Vulnerability:  If your IIS machine is compromised and an AppPool running on that machine is running with a domain account that has elevated user rights, the attacker simply needs to run a PowerShell command to see the username and password of an account with domain credentials.  This also applies in an administratively segregrated environment where you have an Administrator of an IIS server who should not have domain-level rights.

Requirements:  at least IIS 7/Windows 2008 R2 if not before.  I haven't tested this on previous versions but should still be testable via the root\MicrosoftIISv2 namespace.

Proof of Concept:


You can see the AppPool Name as well as the configured domain username and password.  This was run using a local Administrator account on an IIS server.

Wednesday, February 15, 2012

Windows Service Security Descriptors: MOF Changes

Back to Windows Service Security Descriptors Project


// Author:  Cameron Wilson
// Date:  9/27/2011
// Description:  A custom class created to inventory Windows Service Vulnerabilities

[ SMS_Report(TRUE), 
 SMS_Group_Name("Service Vulnerabilities"), 
 SMS_Class_ID("Custom|ServiceVulnerabilities|1.0"),
 SMS_Namespace  (False),
 Namespace      ("\\\\\\\\localhost\\\\root\\\\Custom") ]
 
class ServiceVulnerabilities : SMS_Class_Template
{
 [SMS_Report(TRUE),key] uint32 ServiceID;
 [SMS_Report(TRUE) ] string   ServiceName;
 [SMS_Report(TRUE) ] string   ServiceDisplayName;
 [SMS_Report(TRUE) ] string   AccessMask;
 [SMS_Report(TRUE) ] string   AceFlags;
 [SMS_Report(TRUE) ] string   AceType;
 [SMS_Report(TRUE) ] string   Trustee;
};

Windows Service Security Descriptors

Project:  Windows Service Security Descriptors

Overview:  A project aimed at tracking and mitigating the vulnerabilities in ACLs on Windows services in your network.

Requirements:
  1. Windows Clients - Windows Vista+
  2. Microsoft SMS/SCCM 2003+
Why Should I Care?:
  1. Windows services run with the NT Authority\System account, unless otherwise specified.
  2. The application of Windows service ACLs is left up to the vendor who installs the service.
  3. Many Windows services (especially driver services) are poorly written so that the service allows Everyone, Full Control access.
  4. The exploitation of vulnerable service ACLs is very common part of a hacker's escalation path in your network.
In a nutshell, any normal user account that is on a system with this vulernable ACL on a service can very easily elevate their credentials to that of a local Administrator.  This is a key part of the escalation path because once a hacker has local Administrator access, they can run/install any program they want on a system to start stealing domain-level user credentials.

Scope:

This project is nothing more than addressing one facet of a single part of a hacker's entire escalation path.  Some might argue that this is more effort than its worth and depending on your network's security requirements, and for some, it absolutely is.  However, if your goal is a high security environment or you simply want to prevent a hacker from penetrating your network by any and all means, this information would be of interest to you.

How it Works:
  1. Host
    • A script runs on each host that queries the Windows services for ACL information.  The data is filtered, formatted, and then written to a custom WMI namespace and class.  The SMS/SCCM client accounts for the new class and sends the data up to the SMS/SCCM server with the frequency of its own hardware inventory.
  2. SMS/SCCM Server
    • The SMS_DEF.MOF file must be updated to tell the SCCM clients to start accounting for the new WMI class in the new namespace. 
Now that the information is being aggregated by the SMS/SCCM client natively, you can leverage SMS/SCCM collections, reports, advertisements, etc. to:
  1. Provide reports on vulnerable services in your network.
  2. Create collections of "like" services with the same vulnerability.
  3. Use advertisements to fix vulnerable services throughout your network in a targetted way. 
Installation Aspects:
  1. Security_Descriptor_Service_WMI.vbs
    • This script creates a custom WMI namespace and class and then populates that namespace and class with the data from the vulnerable services on the host.  This script should run at system startup or through an SCCM package.  I'd highly recommend the SCCM package over a startup script because of the integrated ability to enforce that the script runs and it runs with any kind of frequency.  A startup script requires the machine be rebooted before it will run.
  2. SMS_DEF.MOF Modifications
    • Append the mof file definitions to this file so the SMS/SCCM clients will start aggregating this information up to the SMS/SCCM database.  If you have multiple site servers, these changes must be done to each SMS_DEF.MOF file on each server.
Example Report:

Windows Service Security Descriptors: Client Script

Back to Windows Service Security Descriptors Project


' Author:  Cameron L. Wilson (thepip3r)
' Date:   9/25/2011
' Description:  Track vulnerable services on Windows systems
' References:
' - Win32_ACE:    http://msdn.microsoft.com/en-us/library/aa394063.aspx
' - Win32_SecurityDescriptor: http://msdn.microsoft.com/en-us/library/aa394402.aspx
' - Win32_Service:   http://msdn.microsoft.com/en-us/library/aa394418(v=VS.85).aspx




' ****************************************************************************************************************************************
' Begin:  Create Custom WMI Namespace/Class
' ****************************************************************************************************************************************

Const wbemCimtypeSint16 = 2
Const wbemCimtypeSint32 = 3
Const wbemCimtypeReal32 = 4
Const wbemCimtypeReal64 = 5
Const wbemCimtypeString = 8
Const wbemCimtypeBoolean = 11
Const wbemCimtypeObject = 13
Const wbemCimtypeSint8 = 16
Const wbemCimtypeUint8 = 17
Const wbemCimtypeUint16 = 18
Const wbemCimtypeUint32 = 19
Const wbemCimtypeSint64 = 20
Const wbemCimtypeUint64 = 21
Const wbemCimtypeDateTime = 101
Const wbemCimtypeReference = 102
Const wbemCimtypeChar16 = 103


Set objLocator = CreateObject("WbemScripting.sWbemLocator") 
Set objSvc = objLocator.ConnectServer(".", "root") 
Set objNamespace = objSvc.Get("__namespace") 
Set objCustomNameSpace = objNamespace.SpawnInstance_ 

objCustomNamespace.name = "custom" ' Create the "Custom" namespace
objCustomNamespace.Put_() 

Set objWMI = GetObject("winmgmts:root\custom") 
Set objClass = objWMI.Get()

strClass = "ServiceVulnerabilities"

' Look for existing custom classes on this host and wipe them out if they exist
On Error Resume Next
Set objServiceVulnerabilities = objWMI.Get(strClass)

If Err.Number = 0 Then
 objServiceVulnerabilities.Delete_
End If
On Error Goto 0

' Create the custom class with all of its properties
objClass.Path_.Class = strClass 
objClass.Properties_.add "ServiceID", wbemCimtypeUint32 
objClass.Properties_("ServiceID").Qualifiers_.add "key", true 
objClass.Properties_.add "ServiceName", wbemCimtypeString 
objClass.Properties_.add "ServiceDisplayName", wbemCimtypeString 
objClass.Properties_.add "Trustee", wbemCimtypeString 
objClass.Properties_.add "AccessMask", wbemCimtypeUint32 
objClass.Properties_.add "AceType", wbemCimtypeUint32
objClass.Properties_.add "AceFlags", wbemCimtypeUint32

objClass.Put_() 

' Apply a pre-defined security string to the new namespace/class
strSD = "1,0,4,128,148,0,0,0,164,0,0,0,0,0,0,0,20,0,0,0,2,0,128,0,4,0,0,0,0,18,24,0,63,0,6,0,1,2,0,0,0,0,0,5,32,0,0,0,32,2,0,0,0,18,20,0,19,0,0,0,1,1,0,0,0,0,0,5,20,0,0,0,0,18,20,0,19,0,0,0,1,1,0,0,0,0,0,5,19,0,0,0,0,18,20,0,19,0,0,0,1,1,0,0,0,0,0,5,11,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,1,2,0,0,0,0,0,5,32,0,0,0,32,2,0,0,1,2,0,0,0,0,0,5,32,0,0,0,32,2,0,0" 
arrSD = Split(strSD,",") 

Set objSS = objWMI.Get("__SystemSecurity=@") 
objSS.SetSD(arrSD)

' ****************************************************************************************************************************************
' End:  Create Custom WMI Namespace/Class
' ****************************************************************************************************************************************




' ****************************************************************************************************************************************
' Begin:  Query Service Security Descriptors for User-Defined Criteria
' ****************************************************************************************************************************************

Const SE_DACL_PRESENT = &h4  
Const ACCESS_ALLOWED_ACE_TYPE = &h0  
Const ACCESS_DENIED_ACE_TYPE  = &h1   
 
strExclusions = "BUILTIN\Administrators, NT SERVICE\TrustedInstaller, NT AUTHORITY\SYSTEM, NT SERVICE\IPBusEnum, BUILTIN\Domain Administrators"
arrExclusions = Split(strExclusions, ",")
 
strComputer = "."  
Set objWMIService = GetObject("winmgmts:{impersonationLevel=impersonate, (Security)}!\\" & strComputer & "\root\cimv2")   
Set colServices =  objWMIService.ExecQuery("Select * from Win32_Service")   

Set objData = objWMI.Get(strClass) 
Set objInstance = objData.SpawnInstance_ 

y = 0
For Each objService in colServices    
 
 WScript.Echo objService.Name

 iReturn = objService.GetSecurityDescriptor( objSD )   
 
 If ( iReturn <> 0 ) Then   
  WScript.Echo "Could not get security descriptor: " & iReturn
 End If  
 
 ' Extract the security descriptor flags   
 intControlFlags = objSD.ControlFlags  
 
 If intControlFlags AND SE_DACL_PRESENT Then 
  WScript.Echo "   Control Flags:" & vbTab & intControlFlags
  ' Get the ACE entries from security descriptor    
  colACEs = objSD.DACL  

  For Each objACE in colACEs  
   strTrustee = objACE.Trustee.Domain & "\" & objACE.Trustee.Name

   If Trim(strTrustee) = "\" Then
    strTrustee = objACE.Trustee.SIDString
   End If
   
   ' Make sure the ACEType is an "allow", make sure the accessmask gives the group some kind of "write" permissions, and the trustee group isn't predefined as "good"
   If objACE.AceType = ACCESS_ALLOWED_ACE_TYPE And objACE.AccessMask >= 262144 And Not InArray(arrExclusions, strTrustee) Then 
    ' Get all the trustees and determine which have access to the service
    strTrustees = strTrustees & strTrustee & vbCrLf
    strAccessMasks = strAccessMasks & objACE.AccessMask & vbCrLf
    strAceFlags = strAceFlags & objACE.AceFlags & vbCrLf
    strAceTypes = strAceTypes & objACE.AceType & vbCrLf
    
    WScript.Echo "   Trustee:" & vbTab & strTrustee
    WScript.Echo "   Trustee SID:" & vbTab & objACE.Trustee.SIDString
    WScript.Echo "   Trustee SIDLength:" & vbTab & objACE.Trustee.SIDLength
    WScript.Echo "   Trustee SID:"
    For Each strSIDpart in objACE.Trustee.SID
     strSID = strSID & strSIDPart
    Next
    WScript.Echo "     " & vbTab & strSID
    WScript.Echo "   Access Mask:" & vbTab & objACE.AccessMask
    WScript.Echo "   Ace Flags:" & vbTab & objACE.AceFlags
    WScript.Echo "   Ace Types:" & vbTab & objACE.AceType
    
   ElseIf objACE.AceType = ACCESS_DENIED_ACE_TYPE Then     
    ' Don't care about Denies
   End If  
  Next
  
  If strTrustees <> "" Then
   WScript.Echo "   Class Index:" & vbTab & y
   
   arrTrustees = Split(strTrustees, vbCrLf)
   arrAccessMasks = Split(strAccessMasks, vbCrLf)
   arrAceFlags = Split(strAceFlags, vbCrLf)
   arrAceTypes = Split(strAceTypes, vbCrLf)
   
   ' Loop through the found ACE entries and write those to our custom class
   For x=0 to (Ubound(arrTrustees)-1)
    objInstance.ServiceID = y
    objInstance.ServiceName = objService.Name
    objInstance.ServiceDisplayName = objService.DisplayName
    objInstance.Trustee = arrTrustees(x)
    objInstance.AccessMask = arrAccessMasks(x)
    objInstance.AceType = arrAceTypes(x) 
    objInstance.AceFlags = arrAceFlags(x)
    
    objInstance.Put_() 
    y = y + 1
   Next
   
   strTrustees = ""
   strAccessMasks = ""
   strAceFlags = ""
   strAceTypes = ""
  End If

  
 Else   
  ' No DACL found; we don't care about the security descriptor.
  WScript.Echo "   Control Flags(F):" & vbTab & intControlFlags
 End If  
Next

' ****************************************************************************************************************************************
' End:  Query Service Security Descriptors for User-Defined Criteria
' ****************************************************************************************************************************************



' Checks for the existence of a string in an array
Function InArray(arrName, strValue)
 For Each strItem in arrName
  If Trim(LCase(strItem)) = Trim(LCase(CStr(strValue))) Then
   InArray = TRUE
   Exit Function
  End If
 Next
 InArray = FALSE 
End Function

   

Tuesday, February 7, 2012

PowerShell: Create-ComplexPassword v2

Description: Generates complex passwords based off of the criteria given. Able to create passwords of any length and any complexity given the following standard options:

  • length
  • lowercase
  • uppercase
  • numbers
  • special characters

Acknowledgements: The script should run efficiently no matter how complex of a password is requested.  The most visible limitation is that the script could take a second or two to complete an abnormally long password ( > 2000 chars) because of the way the script generates and shuffles the characters.

Logic Overview:

  • Determine supplied options
  • Build out all the requirements specified sequentially in a string.
  • Loop ($PasswordLength * 5) times to shuffle each character in the string to guarantee the password is randomized.  As an example, if the password requested is 20 characters long, the password's characters are shuffled 100 (20*5) times to produce a "randomizing" effect.

Example Syntax:

Create-ComplexPassword -PasswordLength 20 -LowerAlphas 2 -UpperAlphas 2 -Numbers 2 -SpecialCharacters 2
Create-ComplexPassword -PasswordLength 30 -SpecialCharacters 20
Create-ComplexPassword -PasswordLength 25 -LowerAlphas 2 -UpperAlphas 2

Function Syntax:

Function Create-ComplexPassword {
#  http://msdn.microsoft.com/en-us/library/60ecse8t(VS.80).aspx
Param (
        [parameter()] [ValidateNotNullOrEmpty()] [int] [ValidateScript({$_ -gt 0})] $PasswordLength = 8,
        [parameter()] [ValidateNotNullOrEmpty()] [int] [ValidateScript({$_ -ge 0})] $LowerAlphas = 0,
        [parameter()] [ValidateNotNullOrEmpty()] [int] [ValidateScript({$_ -ge 0})] $UpperAlphas = 0,
        [parameter()] [ValidateNotNullOrEmpty()] [int] [ValidateScript({$_ -ge 0})] $Numbers = 0,
        [parameter()] [ValidateNotNullOrEmpty()] [int] [ValidateScript({$_ -ge 0})] $SpecialCharacters = 0
    )
  
    If (($LowerAlphas + $UpperAlphas + $Numbers + $SpecialCharacters) -gt $PasswordLength) { 
        Throw "The specified sum of the number of upper, lower, numeric, and special characters cannot be greater than the desired length of the password."
    }
    
    # Creates random object with seed to guarantee uniqueness
 $objRnd = New-Object System.Random((Get-Random))
    
    $strPassword = $Null
    $arrSpecials = @(33,34,35,36,37,38,39,40,41,42,43,44,45,46,47,58,59,60,61,62,63,64,91,92,93,94,95,96,123,124,125,126)
    
    # Add the specified number of lowercase values to the string
    For ($i=0; $i -lt $LowerAlphas; $i++) {
        $strPassword += [char]$objRnd.next(97,122)
    }

 # Add the specified number of uppercase values to the string
    For ($i=0; $i -lt $UpperAlphas; $i++) {
        $strPassword += [char]$objRnd.next(65,90)
    }
    
    # Add the specified number of number values to the string
    For ($i=0; $i -lt $Numbers; $i++) {
        $strPassword += [char]$objRnd.next(48,57)
    }
    
    # Add the specified number of special character values to the string
    For ($i=0; $i -lt $SpecialCharacters; $i++) {
        $strPassword += [char]$arrSpecials[$objRnd.next(0,$arrSpecials.length)]
    }
    
    # Add the remaining number of random characters for those not specified to fill the rest of the length
    For ($i = $strPassword.length; $i -lt $PasswordLength; $i++) {
  $strPassword += [char]$objRnd.next(33,126)
    }
 
 For ($i = 0; $i -lt ($PasswordLength * 5); $i++) {
  $pos = $objRnd.Next(0, ($strPassword.length - 1))
  
  $lpart = $strPassword.Substring(0,$pos)
  $hpart = $strPassword.Substring($pos + 1, ($strPassword.Length - ($pos + 1)))

  $strPassword = "$lpart$hpart" + $strPassword[$pos]
 }

 $strPassword
}