Wednesday, November 24, 2010

GPO software restrictions

There is a feature in group policy called software restriction policies which allows you to try to block users from running certain specified applications (black listing), or provide a list of applications which are allowed while blocking all others by default(white listing). There are several types of rules you can create:

Hash Rule: Crypto hash of the binary file
Certificate: A software publisher certificate that was used to digitally sign the file
Path: The file path or registry path to the file
Zone: What internet zone is used.

These types of rules can help to guard against predictable malware or certain versions of applications. The problem is that they are frequently very easy to get around.

Hash Rule (avoiding blacklist)
Lets assume our block rule covers txdns.

C:\Users\nl\Downloads>md5sum txdns.exe
f59a3d8e94f0f61130874fd794c38aa7 *txdns.exe

Using a hash rule to block something like this requires some considerations. First, every version and every patch level will have a unique hash. For applications that are frequently updating, you will never be able to keep up with them. If you have a file that has limited versions and no further development, it is still trivial to bypass this. Most files will have some text strings inside the executable. If we open txdns.exe with a hex editor, and immediately we see a string "This program cannot be run in DOS mode". We can change the T in This to lower case without effecting the program.

C:\Users\nl\Downloads>md5sum txdns.exe
42e19fdaeda1a12f21af5512396f37b9 *txdns.exe

Here we can see our hash is radically different than the original. A GPO administrator can't get around this with their blacklists. So what would blacklisting a hash be useful for? There may be a rare case where you encounter a specific and non mutating virus/malware that is in your organization. If you can throw up a hash rule and have systems update group policy, you can help to reduce the spread by this type of policy.

Certificate rules

It would be a bit rare to blacklist something that is signed. Many exe files won't be signed. If they are, and you have a specific one to block, you can look in the file properties to see the signature, and extract the certificate. Load this into a rule to block the software. This may work for something like Stuxnet that is reported to be signed by several stolen certs. Certificate rules would be primarily used in white listing mode, but again many exe's are not signed, or they may be signed with many certs. If you go looking through Microsoft software and downloads you will find either no signing or multiple certs used. Trying to keep up with external vendors' signed applications would take a lot of effort. If you were white listing a limited number of apps for a smaller set of machines, then this may be a useful method for one rule of your whole GPO.

Path rules

Getting path rules to work well in a blacklisting scenario would require a well locked down system to start with. Targeting a specific file using a standard install path would be pointless as the application could be moved or installed to a non standard location. If a user did not have access to create files and folders outside of their own home directory, you could blacklist everything in their profile to prevent execution of all code and scripts. Path rules allow for wildcards. So if you specify c:\documents and settings\*, no executable files can be run in this folder or any subfolders. This also includes attempts to access these files/folders using 8.3 format directory names. In the main policy settings, you can specify the extensions of files to protect against. You can try renaming the extension of a file such as file.exe to file.1 and trying to run it from cmd.exe. This will attempt to execute the file, but the system still sees it as the appropriate file type and blocks it. So, in general, if you need your systems to be extremely locked down, path rules can be a powerful addition.


The software restriction policy mechanism is being replaced by applocker, which is available in windows 7. It is possible to use both in policies, but only the newer OS's can process the applocker rules. I have yet to look at applocker, and I hope it is a step in the right direction for security and manageability.

Friday, October 29, 2010

Personal firewalls

It has been a while since I messed around with firewall rules, and apparently I should get back into it. Recently I had to rebuild one of the home computers to put Windows on it. Initially (pre antivirus install), I shut down all the non-essential windows vista default services that I could think of and left the basic Microsoft provided firewall running. To check for any obvious opening, I ran a basic nmap Syn scan


Starting Nmap 4.75 ( http://nmap.org ) at 2010-10-24 14:46
Initiating ARP Ping Scan at 14:46
Scanning 192.168.2.3 [1 port] Completed ARP Ping Scan at 14:46, 0.01s elapsed (1 total hosts)
Initiating Parallel DNS resolution of 1 host. at 14:46
Completed Parallel DNS resolution of 1 host. at 14:46, 0.02s elapsed
Initiating SYN Stealth Scan at 14:46 Scanning 192.168.2.3 [1000 ports]
Completed SYN Stealth Scan at 14:47, 21.88s elapsed (1000 total ports)
Host 192.168.2.3 appears to be up ... good.
All 1000 scanned ports on 192.168.2.3 are filtered MAC Address: 00:23:AE:1B:1F:6E (Unknown)

Read data files from: /usr/share/nmap
Nmap done: 1 IP address (1 host up) scanned in 22.08 seconds
Raw packets sent: 2001 (88.042KB) | Rcvd: 2 (98B)


Looks ok to me. So after buying antivirus (customer wanted Kaspersky), we got the Kaspersky Internet Security 2011 suite. It looked interesting for features, and the firewall component took over for windows firewall. Using the standard config, I ran the scan again afterwards.


Starting Nmap 4.75 ( http://nmap.org ) at 2010-10-24 14:20
Initiating ARP Ping Scan at 14:20 Scanning 192.168.2.3 [1 port]
Completed ARP Ping Scan at 14:20, 0.11s elapsed (1 total hosts)
Initiating Parallel DNS resolution of 1 host. at 14:20
Completed Parallel DNS resolution of 1 host. at 14:20, 2.52s elapsed
Initiating SYN Stealth Scan at 14:20 Scanning 192.168.2.3 [1000 ports]
Discovered open port 49156/tcp on 192.168.2.3
Discovered open port 49153/tcp on 192.168.2.3
Increasing send delay for 192.168.2.3 from 0 to 5 due to 53 out of 176 dropped probes since last increase.
Increasing send delay for 192.168.2.3 from 5 to 10 due to max_successful_tryno increase to 4 Discovered open port 49155/tcp on 192.168.2.3
Increasing send delay for 192.168.2.3 from 10 to 20 due to 12 out of 39 dropped probes since last increase.
Discovered open port 49152/tcp on 192.168.2.3
Increasing send delay for 192.168.2.3 from 20 to 40 due to 14 out of 45 dropped probes since last increase.
Discovered open port 139/tcp on 192.168.2.3
Discovered open port 135/tcp on 192.168.2.3
Discovered open port 49154/tcp on 192.168.2.3
Discovered open port 1110/tcp on 192.168.2.3
Completed SYN Stealth Scan at 14:20, 38.70s elapsed (1000 total ports) Host 192.168.2.3 appears to be up ... good.
Interesting ports on 192.168.2.3:
Not shown: 992 closed ports
PORT STATE SERVICE
135/tcp open msrpc
139/tcp open netbios-ssn
1110/tcp open nfsd-status
49152/tcp open unknown
49153/tcp open unknown
49154/tcp open unknown
49155/tcp open unknown
49156/tcp open unknown
MAC Address: 00:23:AE:1B:1F:6E (Unknown)

Read data files from: /usr/share/nmap
Nmap done: 1 IP address (1 host up) scanned in 41.58 seconds
Raw packets sent: 1154 (50.774KB) | Rcvd: 1009 (40.612KB) dfkj123:/home


Well that's interesting. Basically the suite sees several zones. Anything on your local network, it seems to be happy with. So if you're on some public wifi spot, you're screwed. Also, most common Microsoft and commercial apps get whitelisted and all hosts (internet wide) are allowed full access. Seriously? Trying to shut all that down and fix it to something sane was looking like a near impossible task, so for simplicity I actually had to disable that and fall back to Microsoft. I hope their AV component doesn't act as badly.

Thursday, October 7, 2010

99 names. Creating quick powershell GUI applications.

I thought it would be nice to try creating a GUI application in Powershell. During the Microsoft scripting games, I came across PrimalForms from Sapien. This simple tool allows you to drag and drop window elements and create a basic shell of a form that you can export for use in Powershell. From here you can add code for event handlers that were defined in your form. So as a demonstration of a very basic usage of this, I created a simple application to show the 99 names of Allah in transliterated form and English. The form allows for simple forward, backwards and random name features.




The Code:


########################################################################
# Code Generated By: SAPIEN Technologies PrimalForms (Community Edition) v1.0.8.0
# Generated On: 10/7/2010 1:21 PM
# Generated By: Nathan_Linley
########################################################################

 
$transliterations = ("Allah","Al-'Aziz","Al-'Alim","Al-'Azim","Al-'Aliyy","Al-Ahad","Al-Awwal","Al-Akhir",
 "Al-'Afuw","Al-Akram","Al-A'la","Al-'Allam","Al-Bari'","Al-Basir","Al-Batin","Al-Barr","Al-Badi'","Al-Ba'ith",
 "Al-Baqi","Al-Fattah","Al-Fatir","Al-Ghaffar","Al-Ghafur","Al-Ghani","Al-Ghafir","Ghalib","Al-Halim",
 "Al-Hafiiz","Al-Hasib","Al-Hakim","Al-Haqq","Al-Hameed","Al-Hayy","Al-Hadi","Al-Haafiz","Al-Hafiyy",
 "Al-Ilah","Al-Jabbar","Al-Jame'","Al-Jaleel","Al-Khaliq","Al-Khabir","Al-Kabir","Al-Kafil","Al-Khallaq",
 "Al-Kafi","Al-Latif","Al-Malik","Al-Mu'min","Al-Muhaimin","Al-Mutakabbir","Al-Musawwir","Al-Muqit",
 "Al-Mujib","Al-Majid","Al-Matin","Al-Muqtadir","Al-Muta'Ali","Al-Mubin","Al-Maula","Al-Maliik",
 "Al-Muhit","Al-Musta'an","Al-Mannan","Al-Muhyi","Al-Mumit","An-Nur","Al-Nasir","Al-Quddus",
 "Al-Qahhar","Al-Qarib","Al-Qawi","Al-Qayyum","Al-Qaadir","Al-Qadiir","Al-Qahir","Ar-Rahman",
 "Ar-Rahim","Ar-Razzaq","Ar-Raqeeb","Ar-Ra'uf","Ar-Rabb","Ar-Rafi'","As-Salam","As-Sami'","Ash-Shakur",
 "Ash-Shahid","As-Samad","Ash-Shaakir","At-Tawwab","Al-Wahhab","Al-Wasi","Al-Wadud","Al-Wakil","Al-Wali",
 "Al-Waahid","Al-Waali","Al-Waarith","Az-Zahir")

$translations = ( "God","The Mighty One","The All-Knowing","The Great One","The Sublime","The One","The First",
 "The Last","The Pardoner","The Most Bounteous","The Most High","The Omniscient","The Maker","The All seeing One",
 "The Hidden","The Source of All Goodness","The Originator","The Awakener","The Everlasting One","The Judge",
 "The Creator","The Great Forgiver","The All Forgiving","The Self Sufficient","The Forgiver","The Predominant",
 "The Clement","The Preserver","The Reckoner","The Wise","The Truth","The Praiseworthy","The Alive",
 "The Guide","The Protector","The Gracious","God","The Compeller","The Gatherer","The Glorious","The Creator",
 "The Aware","The Most Great","The Surety","The Creator","The Sufficient One","The Subtle One","The Sovereign",
 "The Giver of Peace","The Protector","The Majestic","The Fashioner","The Maintainer","The Responsive",
 "The Most Glorious One","The Firm One","The Powerful","The Most Exalted","The Manifest","The Patron",
 "The King, The Sovereign","All Pervading","One Who is Called Upon For Help","The Gracious","The Giver of Life",
 "The Giver of Death","The Light","The Helper","The Holy","The Dominant","The Nigh","The Most Strong",
 "The Self-Subsisting","The Able","The Mighty","The Omnipotent","The Compassionate","The Merciful",
 "The Provider","The Watchful","The Compassionate","The Sustainer","The Sublime","The Provider of Peace",
 "The All-Hearing","The Appreciative","The Witness","The Eternal","The Appreciative","The Acceptor of Repentance",
 "The Bestower","The All-Embracing","The Loving","The Trustee","The Protecting Friend","The One","The Governor",
 "The Inheritor","The Manifest")

 

function getName([int]$nameval) {
 $result = New-Object PSobject
 Add-Member -InputObject $result Noteproperty Transliteration $transliterations[$nameval-1]
 Add-Member -InputObject $result Noteproperty Translation $translations[$nameval-1]
 return $result
 
}


#Generated Form Function
function GenerateForm {


#region Import the Assemblies
[reflection.assembly]::loadwithpartialname("System.Drawing") | Out-Null
[reflection.assembly]::loadwithpartialname("System.Windows.Forms") | Out-Null
#endregion

#region Generated Form Objects
$form1 = New-Object System.Windows.Forms.Form
$lbl99 = New-Object System.Windows.Forms.Label
$lblCounter = New-Object System.Windows.Forms.Label
$label2 = New-Object System.Windows.Forms.Label
$lblTranslit = New-Object System.Windows.Forms.Label
$txtTranslation = New-Object System.Windows.Forms.TextBox
$txtTransliteration = New-Object System.Windows.Forms.TextBox
$btnRandom = New-Object System.Windows.Forms.Button
$btnNext = New-Object System.Windows.Forms.Button
$btnPrev = New-Object System.Windows.Forms.Button
$InitialFormWindowState = New-Object System.Windows.Forms.FormWindowState
#endregion Generated Form Objects

#----------------------------------------------
#Generated Event Script Blocks
#----------------------------------------------
#Provide Custom Code for events specified in PrimalForms.

$handler_btnNext_Click= 
{
 $counter = [int]$lblCounter.text
 if ($counter -eq 99) {
  $counter = 1
 } else { $counter += 1 }
 $name = &getname $counter
 $txtTransliteration.text = $name.Transliteration
 $txtTranslation.text = $name.Translation
 $lblCounter.text = $counter
 $form1.refresh()
}

$handler_btnRandom_Click= 
{

 $rnd = New-Object system.Random
 $counter = $rnd.next(1,99)
 $name = &getname $counter
 $txtTransliteration.text = $name.Transliteration
 $txtTranslation.text = $name.Translation
 $lblCounter.text = $counter
 $form1.refresh()
}


$handler_btnPrev_Click= 
{

 $counter = [int]$lblCounter.text
 if ($counter -eq 1) {
  $counter = 99
 } else { $counter -= 1 }
 $name = &getname $counter
 $txtTransliteration.text = $name.Transliteration
 $txtTranslation.text = $name.Translation
 $lblCounter.text = $counter
 $form1.refresh()
}

$OnLoadForm_StateCorrection=
{#Correct the initial state of the form to prevent the .Net maximized form issue
 $form1.WindowState = $InitialFormWindowState
}

#----------------------------------------------
#region Generated Form Code
$form1.Text = "99 Names of Allah"
$form1.Name = "form1"
$form1.CausesValidation = $False
$form1.DataBindings.DefaultDataSourceUpdateMode = 0
$System_Drawing_Size = New-Object System.Drawing.Size
$System_Drawing_Size.Width = 284
$System_Drawing_Size.Height = 230
$form1.ClientSize = $System_Drawing_Size

$lbl99.TabIndex = 9
$System_Drawing_Size = New-Object System.Drawing.Size
$System_Drawing_Size.Width = 48
$System_Drawing_Size.Height = 23
$lbl99.Size = $System_Drawing_Size
$lbl99.Text = "of 99"

$System_Drawing_Point = New-Object System.Drawing.Point
$System_Drawing_Point.X = 93
$System_Drawing_Point.Y = 170
$lbl99.Location = $System_Drawing_Point
$lbl99.DataBindings.DefaultDataSourceUpdateMode = 0
$lbl99.Name = "lbl99"

$form1.Controls.Add($lbl99)

$lblCounter.TabIndex = 8
$System_Drawing_Size = New-Object System.Drawing.Size
$System_Drawing_Size.Width = 20
$System_Drawing_Size.Height = 23
$lblCounter.Size = $System_Drawing_Size
$lblCounter.Text = "1"

$System_Drawing_Point = New-Object System.Drawing.Point
$System_Drawing_Point.X = 67
$System_Drawing_Point.Y = 170
$lblCounter.Location = $System_Drawing_Point
$lblCounter.DataBindings.DefaultDataSourceUpdateMode = 0
$lblCounter.Name = "lblCounter"

$form1.Controls.Add($lblCounter)

$label2.TabIndex = 7
$System_Drawing_Size = New-Object System.Drawing.Size
$System_Drawing_Size.Width = 100
$System_Drawing_Size.Height = 16
$label2.Size = $System_Drawing_Size
$label2.Text = "Translation"

$System_Drawing_Point = New-Object System.Drawing.Point
$System_Drawing_Point.X = 13
$System_Drawing_Point.Y = 40
$label2.Location = $System_Drawing_Point
$label2.DataBindings.DefaultDataSourceUpdateMode = 0
$label2.Name = "label2"

$form1.Controls.Add($label2)

$lblTranslit.TabIndex = 6
$System_Drawing_Size = New-Object System.Drawing.Size
$System_Drawing_Size.Width = 100
$System_Drawing_Size.Height = 16
$lblTranslit.Size = $System_Drawing_Size
$lblTranslit.Text = "Transliteration"

$System_Drawing_Point = New-Object System.Drawing.Point
$System_Drawing_Point.X = 13
$System_Drawing_Point.Y = 110
$lblTranslit.Location = $System_Drawing_Point
$lblTranslit.DataBindings.DefaultDataSourceUpdateMode = 0
$lblTranslit.Name = "lblTranslit"

$form1.Controls.Add($lblTranslit)

$txtTranslation.CausesValidation = $False
$System_Drawing_Size = New-Object System.Drawing.Size
$System_Drawing_Size.Width = 248
$System_Drawing_Size.Height = 20
$txtTranslation.Size = $System_Drawing_Size
$txtTranslation.DataBindings.DefaultDataSourceUpdateMode = 0
$txtTranslation.Name = "txtTranslation"
$System_Drawing_Point = New-Object System.Drawing.Point
$System_Drawing_Point.X = 13
$System_Drawing_Point.Y = 10
$txtTranslation.Location = $System_Drawing_Point
$txtTranslation.Enabled = $False
$txtTranslation.TabIndex = 5
$txtTranslation.text = $translations[0]

$form1.Controls.Add($txtTranslation)

$txtTransliteration.CausesValidation = $False
$System_Drawing_Size = New-Object System.Drawing.Size
$System_Drawing_Size.Width = 249
$System_Drawing_Size.Height = 20
$txtTransliteration.Size = $System_Drawing_Size
$txtTransliteration.DataBindings.DefaultDataSourceUpdateMode = 0
$txtTransliteration.Name = "txtTransliteration"
$System_Drawing_Point = New-Object System.Drawing.Point
$System_Drawing_Point.X = 13
$System_Drawing_Point.Y = 80
$txtTransliteration.Location = $System_Drawing_Point
$txtTransliteration.Enabled = $False
$txtTransliteration.TabIndex = 4
$txtTransliteration.text = $transliterations[0]

$form1.Controls.Add($txtTransliteration)

$btnRandom.TabIndex = 3
$btnRandom.Name = "btnRandom"
$System_Drawing_Size = New-Object System.Drawing.Size
$System_Drawing_Size.Width = 75
$System_Drawing_Size.Height = 23
$btnRandom.Size = $System_Drawing_Size
$btnRandom.UseVisualStyleBackColor = $True

$btnRandom.Text = "Random"

$System_Drawing_Point = New-Object System.Drawing.Point
$System_Drawing_Point.X = 197
$System_Drawing_Point.Y = 200
$btnRandom.Location = $System_Drawing_Point
$btnRandom.DataBindings.DefaultDataSourceUpdateMode = 0
$btnRandom.add_Click($handler_btnRandom_Click)

$form1.Controls.Add($btnRandom)

$btnNext.TabIndex = 2
$btnNext.Name = "btnNext"
$System_Drawing_Size = New-Object System.Drawing.Size
$System_Drawing_Size.Width = 75
$System_Drawing_Size.Height = 23
$btnNext.Size = $System_Drawing_Size
$btnNext.UseVisualStyleBackColor = $True

$btnNext.Text = "Next"

$System_Drawing_Point = New-Object System.Drawing.Point
$System_Drawing_Point.X = 93
$System_Drawing_Point.Y = 200
$btnNext.Location = $System_Drawing_Point
$btnNext.DataBindings.DefaultDataSourceUpdateMode = 0
$btnNext.add_Click($handler_btnNext_Click)

$form1.Controls.Add($btnNext)

$btnPrev.TabIndex = 1
$btnPrev.Name = "btnPrev"
$System_Drawing_Size = New-Object System.Drawing.Size
$System_Drawing_Size.Width = 75
$System_Drawing_Size.Height = 23
$btnPrev.Size = $System_Drawing_Size
$btnPrev.UseVisualStyleBackColor = $True

$btnPrev.Text = "Previous"

$System_Drawing_Point = New-Object System.Drawing.Point
$System_Drawing_Point.X = 12
$System_Drawing_Point.Y = 200
$btnPrev.Location = $System_Drawing_Point
$btnPrev.DataBindings.DefaultDataSourceUpdateMode = 0
$btnPrev.add_Click($handler_btnPrev_Click)

$form1.Controls.Add($btnPrev)


#endregion Generated Form Code

#Save the initial state of the form
$InitialFormWindowState = $form1.WindowState
#Init the OnLoad event to correct the initial state of the form
$form1.add_Load($OnLoadForm_StateCorrection)
#Show the Form
$form1.ShowDialog()| Out-Null

} #End Function

#Call the Function
GenerateForm



NOTE: This post was updated from its original form to use a more authentic list of the 99 names. (Aug 2011) You may want to verify this list if you wish to use this application for learning. Also note, the arabic text has been removed as I couldn't find a non-graphical copy of the names to use.

Friday, September 24, 2010

Remedy Password Obfuscation

Recently I was checking around for possible sites that were sending plaintext passwords in the clear. One such application I tested was BMC remedy 7.x. Although it doesn't send completely clear passwords, it might as well. Here is the javascript code on the login form for password handling prior to a POST operation.

function getScrambledPassword(pwd) {
   var cipher = ['k', 's', 'z', 'h', 'x', 'b', 'p', 'j', 'v', 'c', 'g', 'f', 'q', 'n', 't', 'm'];
   var result="";
   if (pwd == null)
       pwd = "";
   pwd = encodeURIComponent(pwd);
   //alert("encoded password: " + pwd);
   for(var i=0;i < pwd.length;i++) 
       var cc="pwd.charCodeAt(i);
       result += cipher[Math.floor(cc/16)] + cipher[cc%16];
   }
   return result
}


The login page uses this function to "Scramble" the password using a fixed set of constants in the array. If you search around google for remedy pages "inurl:arsys inurl:login.jsp", you will find the same set of ciphers on every page you check. Some sites have https pages, while others do not. In case, the scrambled password above uses a simple operation to convert to numeric values, and creates to characters for every one character of original password. Lets assume our password is "Password". When you post the form, the value sent is bkpsjhjhjjpmjzpx. Since we have 2 characters of cipher text for each one of non-cipher, lets take the first letter: bk

From the calculations, first we have a divide by 16 which equals 5 according to our array. X/16 = 5 -> 5*16 = X = 80. This is rounded down, so we have a original value of anything from 80 to (80 + 16 -1 = 95). Now the second value k is a 0. This is a modulus of the original number and 16. This value is added to our first character to get the original. 80 + 0 = 80, which our handy ASCII table says is a P.

Keep going with the remaining characters and you get the full original password. So, always use https. If you can come up with a better authentication method, do it.

Wednesday, September 22, 2010

Simple powershell Hijra date converter

I was recently looking for something to display Hijra calendar format in windows. If you have the system set up with specific locales then it should be an option to display on your system time. But if you want the gregorian date displayed as standard, but want to do quick Hijra date conversions, there is a .NET object to assist in that. System.Globalization.HijraCalendar assists with converting the dates, but of course the day value may not always be accurate, or agreed upon. I wanted to put together a simple powershell script for converting the current date, or any date provided to its appropriate day/month/year, including a text display of the name. So I have thrown together this very basic script for this:



#get-hijradate

if ($args[0] -eq $null) {
$mydt = [datetime]::Now
} else {
if ($args[0].gettype() -eq [datetime]) {
$mydt = $args[0]
} else {
$mydt = [datetime]::Now
}
}

$hjdt = New-Object System.Globalization.HijriCalendar

$hjday = $hjdt.getdayofmonth($mydt)
$hjmonth = $hjdt.getmonth($mydt)
$hjyear = $hjdt.getyear($mydt)
$hj_day_of_week = $hjdt.getdayofweek($mydt)

switch ($hjmonth) {
"1" {$hjmontxt = "Muharram"}
"2" {$hjmontxt = "Safar"}
"3" {$hjmontxt = "Rabi I"}
"4" {$hjmontxt = "Rabi II"}
"5" {$hjmontxt = "Jumada I"}
"6" {$hjmontxt = "Jumada II"}
"7" {$hjmontxt = "Rajab"}
"8" {$hjmontxt = "Shaban"}
"9" {$hjmontxt = "Ramadan"}
"10" {$hjmontxt = "Shawwal"}
"11" {$hjmontxt = "Zulkadah"}
"12" {$hjmontxt = "Zulhijjah"}
}
switch ($hj_day_of_week) {
"Monday" { $hjdaytxt = "Yawm al-Ithnayn"}
"Tuesday" { $hjdaytxt = "Yawm ath-Thalaathaa'"}
"Wednesday" { $hjdaytxt = "Yawm al-Arba'aa" }
"Thursday" { $hjdaytxt = "Yawm al-Khamīs"}
"Friday" {$hjdaytxt = "Yawm al-Jumu'ah"}
"Saturday" { $hjdaytxt = "Yawm as-Sabt"}
"Sunday" { $hjdaytxt = "Yawm al-Aḥad"}
}

$hjdisplay = [string]$hjday + " " + $hjmontxt + " " + [string]$hjyear
$result = New-Object PSObject
Add-Member -InputObject $result NoteProperty Day $hjday
Add-Member -InputObject $result NoteProperty Month $hjmonth
Add-Member -InputObject $result NoteProperty Year $hjyear
Add-Member -InputObject $result NoteProperty DayName $hjdaytxt
Add-Member -InputObject $result Noteproperty Display $hjdisplay
return $result


PS C:\> .\get-hijradate.ps1
Day Month     Year DayName             Display
--- -----     ---- -------             -------
16   10       1431 Yawm Al-Jumu'ah     14 Shawwal 1431

Tuesday, September 14, 2010

SPN attribute limits

In cases where a lot of ServicePrincipalNames are tied to a commonly used service account (yes I know there's no good reason to do this), I thought it may be a good idea to check what the maximum number of SPN's on a single account was. The method used for this was adding random generated strings of 10 characters with the parent domain dns suffix added as a HOST/ SPN record. I did try the same test with longer random generated strings, but I came up with the same results.

I have not yet tested in 2008 functional levels, but I was able to test 2000 and 2003 Native.

The numbers I am getting are:

2000 Native: 831 spn's
2003 Native: 1249 spn's

Tuesday, August 10, 2010

Managing odd services in group policy

If you feel that using group policy to manage system services for security purposes is a good idea, and you want to block certain services that typically won't be in the list of services in the edit console, there are several ways to get around this. One suggestion (the hard way, and bad way), is to have the service installed on a machine, and edit the GPO from that machine. That will work, but it is time consuming, and may open up security problems just to set up the GPO.

The easier way is to create a GPO and pick any service from the list. Use this service to set whatever permissions, start type, and other options you want. From here you can either backup the GPO, or just directly edit the files in sysvol. In the policy you will see 3 folders:

Adm
Machine
User

The services are under machine, so open this. Drill down through Microsoft-> Windows NT->SecEdit and open the GptTmpl.inf in notepad. From here you can see a section called [Service General Settings]. One example line here for a vnc service:

"WinVNC4",4,"D:AR(D;;DCRPWPDTSDRC;;;BA)(A;;CCLCSWLOCRRC;;;BA)(A;;CCLCSWLOCRRC;;;IU)(A;;CCDCLCSWRPWPDTLOCRSDRCWDWO;;;SY)S:(AU;FA;CCDCLCSWRPWPDTLOCRSDRCWDWO;;;WD)"

Here we have the service name (this is not the display name you see in the services MMC. It is the service name you use with "net start" or how you find it in the registry. You can get this with psservice or sc.exe). Since we already set up all the other settings that we want, we can keep all the ACL information in place, and just change the name of the service that is in the first set of quotations. Edit what you want, add more services if you need and save.

If you backed up the GPO before editing it, you can just import this information into a new GPO object. If you are directly editing sysvol files you may need to update the version details in the same file (and possibly in other files and the GPO AD object), so it is best to go with the backup/restore method and create a new group policy object. This gives versioning flexibility and an easier backout path in case of problems.

Finding the distinguishedName of an AD integrated DNS zone

As a precursor to some work I want to do for standard users to be able to create DNS records in AD integrated zones, I threw together some started code to find the actual location of a DNS zone. If you read my previous post looking at DNS without the DNS protocol, I mentioned the various places zones can be located. When you have more than one domain in a forest, this increases the number of possible places to look. This sample code is a basic starter for looking at anything outside of specialized application partitions, to find a specified zone inside the current forest.
#get-dnspartition
#
#Searches different paritions for the distinguished name of a dns zone
#V1.0  Aug 2010.  Checks the forest dns, domain dns and legacy domain dns locations for the existence of a zone
#and returns the distinguished name of all results.
#
# Future work possibilities.  Looking for application partitions.  Working with reverse zones in a way that does
# more of a wildcard search to get a better match when the network ID is unknown or scope of the reverse zone is unknown.  
#Search capability for subzones that are in the same parent zone
# object.  In AD these just show up as part of the record's Name attribute..recordname.subzone

#note, some partitions may not allow authenticated users read access to view the base cn=microsoftDNS containers
#but opening the parent container and doing a subtree search for the zone bypasses this restriction.


$zonename = $args[0]

$forest = [System.DirectoryServices.ActiveDirectory.Forest]::GetCurrentForest()
$mytopleveldomain = $forest.schema.name
$mytopleveldomain = $mytopleveldomain.substring($mytopleveldomain.indexof("DC="))
 
$Arrmyresults = @()

function search-dns-partition($partion, $domainDN, $zonename) {
 $de = new-object DirectoryServices.DirectoryEntry("LDAP://" + $partition + $domaindn)
 $ds = new-object DirectoryServices.DirectorySearcher($de)
 $ds.filter = "(&(objectclass=dnszone)(name=" + $zonename + "))"
 $fu = $ds.findone()
 if ($fu -ne $null) { 
  return $fu.properties.distinguishedname 
 } else { return $null }
}

function dns-to-dn ([string]$dnsname) {
  #may 09
  
  #this is a helper function which will convert a dns name to a distinguished name dc= 
  #type of result by breaking off each piece of the dns name to become a DC entry
  
  $domainDNnamearr = $dnsname.split(".")
  $domainDNname = ""
    
  foreach ($component in $domainDNnamearr) {
     $domainDNname = $domainDNname + "DC=" + $component + ","
  }
  $domainDNnamearr = $null
     
  #remove trailing , off
  $domainDNname = $domainDNname.substring(0,$domainDNname.length -1)
  return $domainDNname

}

#check domain and domain legacy partitions
foreach ($dom in $forest.domains) {
 foreach ($partition in ("cn=System,", "dc=domaindnszones,")) {
  $domainDNval = dns-to-dn $dom.name
  $myresult = search-dns-partition $partition $domainDnVal $zonename
  if ($myresult -ne $null) {
   $Arrmyresults += $myresult
  }
 } 
}

#Check forest partition
$myresult = search-dns-partition "dc=forestdnszones" $mytopleveldomain $zonename
if ($myresult -ne $null) {
 $Arrmyresults += $myresult
}

return $arrmyresults

Monday, August 9, 2010

Getting the NIC card binding order

I was playing around with some ideas today for identifying what is the primary NIC card of a Windows system, so I could add that to some Dns client setting monitoring I'm working on. The problem of NIC bindings has come up in the past during some troubleshooting on servers that were plugging into trunk ports and had a lot of virtual nic's for different subnets. For those that do no know about binding order, when you are in windows, on the network properties page (for newer OS versions that is "Change adapter settings"). There is a menu item, though it may be hidden to make your life more difficult in new OS's. Try alt-N to pull up the network Advanced menu, and select "Advanced Settings". In this dialog box, you will see a list of connections available and their order. You can move NIC's up and down to set what is the primary NIC for a system. On some Os's this requires reboot to take effect. The binding order is stored in the registry under HKLM\System\CurrentControlSet\Services\TcpIP\Linkage key with a multistring value called Bind. This lists devices by GUID. You can match up the guid values here to the interfaces in other registry locations, or read them through WMI. The reason NIC binding can be important can include, selection of default gateway (windows only supports 1 at a time), and determining what interface traffic originating from the machine uses. In the case of my previously mentioned problems, the problem was DNS lookups were going out the wrong interface, into a secluded subnet to DNS servers that could not reply back due to firewall restrictions. Moving the order will allow you to control what NIC is your source IP. In any case, I though it would be useful to throw together a basic script to list out the binding order of multihomed machines. This script returns an array of PSObjects with the following: BindingOrder, Description, GUID, IP, SubnetMask, Gateway; where description is the text value that shows up in your list of network adapters, and GUID is what is used to reference it in the registry.
#get-binding order
#Check a remote machine to see NIC binding order.

$server = $args[0]

if ($server -eq $null -or $server -match "\?") {
 Write-Host -ForegroundColor "yellow" "Usage:  Get-NicOrder "
 Write-Host -ForegroundColor "yellow" "    Enter the name of a system to connect to.  This script will"
 Write-Host -ForegroundColor "yellow" "    provide the network card binding order of a remote machine."
 exit
}

$key = "System\CurrentControlSet\Services\Tcpip\Linkage"
$type = [Microsoft.Win32.RegistryHive]::LocalMachine
$regkey = [Microsoft.win32.registrykey]::OpenRemoteBaseKey($type,$server)

if (-not $?) {
 Write-Host -ForegroundColor "red"  "Cannot check remote machine, exiting...."
 exit
}

$regkey = $regkey.opensubkey($key)
$ArrBindingGUIDs = $regkey.getvalue("Bind")
 
$ArrNicList = Get-WmiObject -ComputerName $server -Query "select Description,settingid,ipaddress,ipsubnet,defaultipgateway from win32_networkadapterconfiguration"

$ArrResults = New-Object collections.ArrayList

for ($i = 0; $i -lt $ArrBindingGUIDs.length; $i++) {
    if ($arrbindingguids[$i].contains("{") {
  $guid = $ArrBindingGUIDS[$i].substring($ArrBindingGUIDs[$i].indexof('{'))
  foreach ($nic in $ArrNicList) {
   if ($nic.settingid -eq $guid) {
    $result = New-Object psobject
    Add-Member -InputObject $result NoteProperty BindingOrder $i
    Add-Member -InputObject $result NoteProperty Description $nic.Description
    Add-Member -InputObject $result NoteProperty GUID $nic.SettingID
    Add-Member -InputObject $result NoteProperty IP $nic.ipAddress
    Add-Member -InputObject $result NoteProperty SubnetMask $nic.ipsubnet
    Add-Member -InputObject $result NoteProperty Gateway $nic.defaultIpGateway
    $Arrresults.add($result) >$null 
   }
  }
 }
}

Return $arrResults
 

The ISATAP/WPAD query patch (WINS and DNS)

While working on my powershell netbios name library, I came across the bulletin for blocking by default, all replies for WPAD and ISATAP to Windows WINS and DNS servers. Given that you can register arbitrary DNS host records and wins records without any validation that your source machine has that name, this is a good thing. The problem that this bulletin didn't think to address is the netbios node types of the client machines. Typically clients will be configured as hybrid nodes, which means that if they have netbios enabled, and DNS does not resolve a name for them, they will attempt name resolution with WINS, followed by a IP directed broadcast to their subnet looking for that name. I noticed in netmon captures, that machines are certainly looking for ISATAP and WPAD with great frequency. Some cases show broadcasted queries with FQDN formats of these entries as well. This should be related to the period being a special character in netbios names. I suspect that at some point in the search list suffixes being appended, the resolver passes a DNS format name to netbios resolution and it goes out on the wire like that.




In any case, the fact that these requests are being broadcast out, leaves the original issue open as a security problem. It would be quite trivial to have a malicious machine responding to clients and offering to be their web proxy. Inject some browser exploit of your choice and start pwning machines.

As WINS and netbios is getting quite old, if you really really still need WINS on your network, why not switch your clients to P-Nodes, so they don't broadcast?

Friday, July 30, 2010

Dynamic DNS without the [DNS]

I have been poking around at Microsoft name resolution system investigations that became a side project of computer account security research. I was looking at the ability of computer systems to use the DNS protocol to register names other than their own to see if any additional records could be created for a specific host name. The findings there are that hosts can register names other than their own, but if there is a record already present for the name they are trying to register, and that computer account (or user account) doesn't have rights to update, then it fails. This is good from a security perspective, but shows some limitations for usability and advance configurations provided from a level closer to the user or machine.

In the process, I took a deeper look at Active Directory integrated DNS zones to see how everything is represented. First off, DNS zones can be found in several different locations inside of Active directory. This is based on their replication scopes. For those not too familiar with AD and AD DNS, briefly, the advantages of Active Directory DNS is that it is made highly available through the fact that it is stored in the Active directory database, and all changes are replicated as part of standard AD replication. This allows records to be updated with small updates instead of larger zone file transfers that work on a scheduled timeout period. Additionally, in secure dynamic updates, updates are authenticated through Kerberos and the records are owned by the updater.

Back to replication... There are 4 options for Dns zone replication scopes in AD. This is Legacy, Domain, Forest, and application partition. Here is some information about each:

1) Legacy. This zone will be stored in the standard domain partition, under the Cn=Microsoft DNS,CN=System organizational unit in the dc=yourdomain,dc=com partition. A zone will appear as a container icon, and is of the class dnsZone. The problem with this type of zone is although it is only available in the scope of its own domain, it is part of the domain's system contain and will replicate as part of the global catalog to other domains. So you waste space and replication traffic sending information to other domain controllers that cannot use this information.

2) Domain. This zone is stored in a separate partition DC=DomainDnsZones,dc=yourdomain,dc=com. This partition is only available on domain controllers that are running the DNS service. So, replication is limited to only where the records are needed, and it only stays in that one domain.

3) Forest. This zone is stored in a separate partition DC=ForestDnsZones,dc=yourdomain,dc=com. This partition will replicate to all dns servers in the forest. It is only hosted on domain controllers with dns installed.

4) Application. If you create your own custom application partitions, you can use them to store DNS. Also you can chose where the application partitions replicate. For DNS, this may be useful if you want specific zones only located in DC's in a certain city, but it requires more manual work to manage it.

If you look inside adsiedit at the dnsZone containers in these partitions, you will see dnsNode objects for records. The object will be named DC=. Example, if you have the zone mycompany.com. The record www.mycompany.com will have the name DC=www. Inside each of these objects is a dnsRecord attribute. This is multivalued, so if www.mycompany.com had several records for 3 different host machines, you will see 3 dnsRecord values. The dnsRecord itself is all in hex. The type of Record (A, CNAME, SRV, etc) is all within the hex, along with the values, timestamps, ttl, etc. (Hopefully I will get back on track to decoding these on of these days)

The interesting point to all of this is that we are looking at the records in LDAP. Typically records are generated through the DNS service using authentication from a workstation. The DHCPClient service registers the hostnames of the machine, and uses the computer account to own the record. When you look at the permissions of the dnsZone object's in LDAP, authenticated users has Create All Child Objects rights. This gives any user or computer the ability to go and create their own records outside of the whole DNS protcol, and built in methods. So, if you needed to create additional dnsRecords on a dnsNode object that you own, you can do that. If you want to dynamically create CNAME, SRV or other records, you can. If you want to dynamically create a DNS record that can't be scavenged, no problems.

Someday I hope to work out a basic tool or script for some of this, but presently I'm still too lazy to finish up my nbtns powershell library for doing all of the netbios name and WINS protocol functions through powershell.

The DNS tunneling possibilities to this are interesting, if you have AD DNS exposed to the outside world.

Wednesday, July 14, 2010

Finding account lockout source (general practice)

A lot of escalations come my way regarding difficult account lockout issues, and repeat lockout events. Over time, I took some of the methodology I used in tracking down the root source of the failed logon and put it into an auto tracker script. This runs quite quickly using WMI's WIN32_NtlogEvent class with a very tight filter for the timegenerated attribute. When providing and upper and lower limit, searching event logs can be done very quickly on remote machines. This is important as in most cases you may traverse through 2 or 3 machines to get to the originating machine.

When starting to track down a lockout source, either manually or through automated practice, you will check the bad password timestamps for all domain controllers [this is an ldap query to all writable domain controllers so it can be slow. Alternatively a much faster approach is to read the metadata of that attribute on the PDC only to get the time and source]. You can do this with ldap queries to each one, or use the microsoft lockoutstatus tool. Ldap queries will allow you to script it. The key point here is that the PDC will always have the most recent bad password time, even if the events are not located on that machine. So for automation purposes, I simplified the process by ignoring the PDC. You could always find the most recent reported event and compare it to see if the PDC is more recent by at least a few seconds to see if the PDC is where you should look. It is also important to note that you are assuming the last bad password time that you are looking at is related to the problem and is not something else like the user mistyping a password. This may require several runs of an automated script to see if you get different sources.

From the bad password time stamp, you can go to the security log for that domain controller, filtering for failure audits and check the event text or InsertionStrings to get the source machine or source IP. Different event ID's have different information. Some failed logons will not show up in the log, or may have a blank source. In this case it is important to have netlogon debugging enabled so you can check the netlogon.log, as it may contain more details for the source. In netlogon, you want to look at the entry in parenthesis that shows via XXXXXX. Ensure the code in the line is a failure, and not success, so you don't chase the wrong entries. For automating, it is helpful to have some code that decodes the various security event hex codes and integer values to plain text for display purposes (my code).

When looking at sources in the event log, you need to carefully check source IP and source workstation if both are present. Sometimes they differ, and the source workstation may be the machine you are already on. Sometimes it is best to use the IP as a preferred value for source. Go to the source machine and look at the security logs there for additional information. When you get down to workstations and member servers, you may get process id's and port information that can help narrow down to a process. Checking logon type codes can be a dead giveaway for scheduled tasks and services with bad cached passwords.  On some occasions you will not see a source machine in the security log.  If this is the case, you will want to enabled netlogon debugging (from command prompt use: nltest /dbflag:0x2080ffff.  Some logon events will show up there, and you can get further source information from the events.  Just search the text files in c:\windows\debug\netlogon* for the user name and look for other machine names in any event that doesn't have a 0x0 status.

You can look at my high level overview for how I wrote my tracking script. For security events, I had to identify some standards between each event ID's I wanted to work with and use insertionstrings to provide a standardized generic PSObject that can be provided by parsing the available information.



Some useful commands to find uses of an account on a local machine (open powershell as administrator, set the target account name without domain)

$targetaccount = "johndoe"

write-host "`n checking services"
gwmi win32_service |select name,startname | where {$_.startname -match $targetaccount}

write-host "`n checking processes"
gwmi win32_process |select name,@{name="ownerdomain"; exp={$_.getowner().domain}},@{name="owneruser" ; expr={$_.getowner().user}}  | where {$_.owneruser -match $targetaccount}

write-host "`n dumping any IIS appPools"
c:\windows\system32\inetsrv\appcmd.exe list apppool /text:* |where {$_ -match "APPPOOL\.NAME|userName"}

write-host "`n checking remote desktop connections"
qwinsta

write-host "`n checking scheduled tasks"
schtasks /query /v -fo CSV | where {$_ -match $targetaccount}

write-host "`n checking mapped network drives for currently logged on user"
dir hkcu:\network

Tuesday, July 13, 2010

How to validate client dns settings for a few thousand systems

Over the last year or so, I have been working on correcting and ensuring correct DNS settings on clients. Inconsistency of documentation between build and support teams, or mistyped entries can prove to be a large scale problem as well as outages waiting to happen. To create a centralized store of DNS client settings is always a challenge, especially when not everyone is in close communication, and you want automation involved. So being an Active Directory guy, I thought of ways to involve AD in this. There is always GPO settings, however these are not permanent, and GPO's can't differentiate between NIC cards that should and shouldn't be touched. So, to get around this problem, I wanted something that was location specific and smart enough to be able to tell what a valid internal IP address was. Having SCOM and SCCM as the tools to help implement this, I came up with a set of scripts for various OS's to parse reports, set client settings, and provide monitoring of client settings to track any deviation from the published standards. The trick to this was stashing DNS client setting information in AD. Since the settings were site specific, tying the information to the site object seemed like a natural choice. From here you can either extend the schema to include an attribute for this, or use one that is available but not in use. The best path seemed to be using one not in use, and what appears to be perfect is the location attribute. Since it is a text field, and is editable in the AD Sites MMC, it is easy to manage. Once that is in place, vbscript's are easy to produce to check the client machine's site, pull out domain specific information out of the location field, compare the approved settings against all "valid IP" NIC's for any problems. Different issues provide different results to the scripts for responsible teams to resolve. There are always some exceptions to the rule that are hard to code for, so not getting to overzealous in the configuration scripts, and providing an exception capability to monitoring scripts is a must.

Tuesday, June 1, 2010

The user profile service failed the login

I was trying to connect to a relatively newly imaged 2008R2 domain controller today and was getting booted each time due to the user profile service failing to create my profile, and a temp profile. I had never logged into this machine in the past, so I was curious as to what the problem was.

The application logs were showing multiple events at that time in this order:

  • Event 6004 Winlogon: The winlogon notification subscriber failed a critical notification event.
  • Event 1509 User profile general: Windows cannot copy file C:\Users\Default\ntuser.dat to location C:\Users\nlinley\ntuser.dat. This error may be caused by network problems or insufficient security rights. DETAIL - Access is denied.
  • Event 1511 User profile service: Windows cannot find the local profile and is logging you on with a temporary profile. Changes you make to this profile will be lost when you log off.
  • Event 1509 User Profile general: Windows cannot copy file C:\Users\Default\ntuser.dat to location C:\Users\TEMP\ntuser.dat. This error may be caused by network problems or insufficient security rights. DETAIL - Access is denied.
  • Event 1500 User Profile General: Windows cannot log you on because your profile cannot be loaded. Check that you are connected to the network, and that your network is functioning correctly. DETAIL - Only part of a ReadProcessMemory or WriteProcessMemory request was completed.
  • Event 6001 Winlogon: The winlogon notification subscriber failed a notification event.
  • Event 6001 Winlogon: The winlogon notification subscriber failed a notification event.
The user profile service log was not showing anything useful, just logoff events.

From here, I was looking at the security log and found a few failure audits from base filtering engine for domain controller to domain controller on standard domain controller ports. Although this was odd, it didn't seem to explain the issue and the ports did not seem to significant for this point of the login.

Checking the access issue and permissions showed normal permissions on c:\users\default folder (Everyone:read, System: full, Administrators:full, Users:read), and on ntuser.dat it was set to inherit. The odd part with this it the ACL was only showing (Admistrators:full, System:full). If it is inheriting, where was everyone and users? I manually added Everyone:read to the ACL and this solved the problem of the loading profile. Going back again to check the permissions shows that the Users group permission has also inherited and is displaying now.

Root cause: unknown

Update Nov 2015
Just for additional reference, other problems can cause profile failures, such as partially deleted profiles. User profiles consist of several components. There are the files and folders in c:\users\username, of which the ntuser.* files are the core of the profile. The user's account will need full control NTFS permissions to the folder (top level folder owner is SYSTEM but files and folders below that are usually owned by the user). In addition to this, there is a list of user profiles in the registry at HKLM\Software\Microsoft\Windows NT\CurrentVersion\ProfileList. This is a listing of SID values, which will also point to the profile folder in the value ProfileImagePath. This key can be access with WMI using win32_userprofile. This WMI namespace also has a delete method, which should be a safe way of clearing up unwanted profiles. This needs to be used with care, as you will want to ignore certain built in profiles. I have also noticed on at least one test case where a machine has 400+ profiles, trying to delete a profile via WMI was not working, and just seemed to hang. Another registry key that needs to be looked at is HKLM\Software\Microsoft\Windows NT\CurrentVersion\ProfileGUID. This contains a guid to SID mapping for what is in the ProfileList. If you are manually deleting profiles, you need to remove the folder, and both registry keys that relate to the user. Another problem that can come up, is if a user is logged in with a temporary profile, but the profile folder doesn't get cleaned up property. The user account will be the owner of the files and the system may try to use this same temp profile folder on subsequent logons.

Friday, May 21, 2010

Creating infinite semi-anonymous accounts in Active Directory as a standard user

I have been looking at computer accounts on and off over the last few months for various security risks associated with them, and found a few interesting things. One of which is a default configuration for active directory that allows users to join a limited number of computers to a domain without having any specific delegated rights to do so.

Note: if the domain has been modified to drop the quota of allowed joined computers to zero, and a few other methods can block this, so it will only work in a Active Directory environment where low level customizations have not been done. In all versions of windows 2000 - 2008RC2, by default, users have a quota of 10 computers accounts which is controlled by the ms-DS-MachineAccountQuota attribute in the Domain's top level object. If you are an AD administrator, go set this to zero right now.

There are a few interesting points to this. When I say that a user has rights to join a machine, they cannot directly create a computer account in active directory using management tools as they don't have the rights. However, the act of joining a machine to the domain works (obviously), or you can just use netdom to create the computer account without joining any real machine. You may be saying, so what, its just a computer object. If you read my previous post about accounts that can't be locked out (computer accounts), you will also see the default permissions on computer objects give excessive rights to the creator/owner of the object. This allows for some continued problems.

When the end user creates a computer object, I refer to it as being capable of being a semi-anonymous generic account. The reason for semi-anonymous is that the user is the owner of the object and they don't have permissions to change the permissions, also there is an attribute mS-DS-CreatorSID which will be filled in with the creating user's SID. To create a layer between one of these computer accounts and a user object, if you have Admin rights to a machine you can schedule tasks to run under the BUILTIN\System context. In this case, the computer running the task will own the computer object. If a machine has multiple administrators, this creates a deniability layer between the user and the computer object. Otherwise, you could always try social engineering, or some other method to get someone that does have the rights to remove some of the traces of ownership, or grant you rights to the object so you can make it anonymous (not counting backups, systems that record object changes, and forensics that may get the original owner information).

To have a fully anonymous computer account, you can create your first level account, modify some attributes in that, log in with the computer account, and create more computer accounts that are now no longer tied to the original human user account object. (As mentioned early, every user has a 10 machine quota by default. Computer accounts are users, and can have 10 of their own, making the infinite generic accounts possible). This is where the bad default permissions on computer objects comes in. The create/owner has rights to edit the UserAccountControl attribute and set the password to the account. Using a helpful tool called admod (joeware), you can set the password and change the UserAccountControl values to allow interactive logons.

Putting it together, lets assume we have a domain called CompSecTest.2k3.local.


C:\Documents and Settings\nathan>netdom add maliciousAccount /domain:compsectest.2k3.local
The command completed successfully.

C:\Documents and Settings\nathan\Desktop>admod -b "cn=maliciousAccount,cn=computers,dc=compsectest,dc=2k3,dc=local" "unicodepwd::Password1" -optenc

AdMod V01.13.00cpp Joe Richards (joe@joeware.net) April 2010

DN Count: 1
Using server: DC2K3-1.compsectest.2k3.local:389
Directory: Windows Server 2003

Modifying specified objects...
DN: cn=maliciousAccount,cn=computers,dc=compsectest,dc=2k3,dc=local...

The command completed successfully

###SET UAC to 512 to act like a normal user and bypass interactive logon restrictions

C:\Documents and Settings\nathan\Desktop>admod -b "cn=malicious,cn=computers,dc=compsectest,dc=2k3,dc=local" "useraccountcontrol::512"

AdMod V01.13.00cpp Joe Richards (joe@joeware.net) April 2010

DN Count: 1
Using server: DC2K3-1.compsectest.2k3.local:389
Directory: Windows Server 2003

Modifying specified objects...
DN: cn=malicious,cn=computers,dc=compsectest,dc=2k3,dc=local...

The command completed successfully


C:\Documents and Settings\nathan\Desktop>runas /user:compsectest1\maliciousAccount$ cmd
Enter the password for compsectest1\MaliciousAccount$:
Attempting to start cmd as user "compsectest1\MaliciousAccount$" ...


C:\WINDOWS\system32>whoami
compsectest1\maliciousAccount$


C:\WINDOWS\system32>netdom add malicious2 /domain:compsectest.2k3.local
The command completed successfully.


C:\WINDOWS\system32>admod -b "cn=malicious2,cn=computers,dc=compsectest,dc=2k3,dc=local" -optenc "unicodepwd::Password1"

AdMod V01.13.00cpp Joe Richards (joe@joeware.net) April 2010

DN Count: 1
Using server: DC2K3-1.compsectest.2k3.local:389
Directory: Windows Server 2003

Modifying specified objects...
DN: cn=malicious2,cn=computers,dc=compsectest,dc=2k3,dc=local...

The command completed successfully


C:\temp>admod -b "cn=malicious2,cn=computers,dc=compsectest,dc=2k3,dc=local" -optenc
"useraccountcontrol::512"

AdMod V01.13.00cpp Joe Richards (joe@joeware.net) April 2010

DN Count: 1
Using server: DC2K3-1.compsectest.2k3.local:389
Directory: Windows Server 2003

Modifying specified objects...
DN: cn=malicious2,cn=computers,dc=compsectest,dc=2k3,dc=local...

The command completed successfully


Now that you are here, you have a generic computer account with interactive logon capabilities that is one level removed from a standard user account. This can continue until there are so many computer accounts owned, that stopping the use of them may require a mass deletion of objects in the Computers container which could have other impact on legitimate accounts. So, as administrators we should take steps to prevent this type of activity. This can include redirecting the default computers container elsewhere and putting restricted permissions there, dropping the default quota value to zero, changing the schema to reduce the creator/owner level default permissions on computer objects, and editing the default group policy rights to remove the Authenticated Users object in the Add machines to the domain right. These steps can reduce risks from non-privileged users, but for higher privileged users that need the ability to manage computer accounts, and combination of schema permissions changes and a tight least privilege method of rights to computer accounts can help prevent the interactive capability of a computer account. When a computer account is at its base UserAccountControl value it can't log in interactively, be used for services, or scheduled tasks, but it can be used to access network shares. Monitoring the environment for any computer objects with UAC bits flipped to the wrong values could also be a good idea.

One way to find this type of account is with another joeware utility (adfind):

adfind -gc -b dc=compsectest,dc=2k3,dc=local -s subtree -bit -f "(&(objectcategory=computer)(objectclass=computer)(useraccountcontrol:OR:=512))" -t 15000 distinguishedname

Tuesday, April 20, 2010

UserAccountControl

Write-line "Hello World"

I have been distracted from some work I was doing for computer account security research, so I thought I would get back to it by sharing some of limited fuzzing results for UAC values of objects use this attribute (anything using the User class, such as user accounts, and computer accounts).

The various bit values are shown in this table from kb305144
























PropertyHexadecimalDecimal
SCRIPT0x00011
ACCOUNTDISABLE0x00022
HOMEDIR_REQUIRED0x00088
LOCKOUT0x001016
PASSWD_NOTREQD0x002032
PASSWD_CANT_CHANGE0x004064
ENCRYPTED_TEXT_PWD_ALLOWED0x0080128
TEMP_DUPLICATE_ACCOUNT0x0100256
NORMAL_ACCOUNT0x0200512
INTERDOMAIN_TRUST_ACCOUNT0x08002048
WORKSTATION_TRUST_ACCOUNT0x10004096
SERVER_TRUST_ACCOUNT0x20008192
DONT_EXPIRE_PASSWORD0x1000065536
MNS_LOGON_ACCOUNT0x20000131072
SMARTCARD_REQUIRED0x40000262144
TRUSTED_FOR_DELEGATION0x80000524288
NOT_DELEGATED0x1000001048576
USE_DES_KEY_ONLY0x2000002097152
DONT_REQ_PREAUTH0x4000004194304
PASSWORD_EXPIRED0x8000008388608
TRUSTED_TO_AUTH_FOR_DELEGATION0x100000016777216



Having rights to write to this value does not guarantee all values can be written. There are two values related to delegation, which were smartly locked down to Domain administrator edits only. Some of the bit values are not in use, but can be used, while others are not in use and cannot be used. I break these down as follows:

M = Mandatory entry, but can only contain one type. Used for account type
N = Not possible to edit (note: domain administrator level accounts may be able to try to edit these values without error, but it won't write the value)
P = Possible to edit when write UAC property permission or write all properties permissions is possessed by editor
A = Domain admin level rights required to edit:
- = Can be used in a modify operation, but will not be written

Bits: 0000 0011 0111 1111 0011 1011 1010 1010
Oper: ---- --PA -PPP APPP --MM M-MM PNPN P-PN

When trying to write values to UAC, you can provide in hex format or integer format. Other entries or invalid characters will throw errors. Negative numbers can be provided, but they will be handled with the appropriate bit values and hold to the same restrictions.

As for computer account security, I find it interesting that the creator of a computer account can manipulate some of these values that would be better left to an administrator. One in specific would be the account type values allowing more interactive access like I mentioned in my avoiding account lockout article. Luckily, delegation enable/disable is restricted, so if you have Domain admins that understand the implications of this and restrict its implementation, you have some degree of safety. Once enabled though, maybe another story.

Monday, April 5, 2010

Managing large sets of scripts

Over the last year I have been rolling out a lot of Powershell functions that I like to have readily available in my profile. Originally this was one large block over roughly 2000 lines of code, which gets a bit unmanageable. Eventually I created a base profile, a secondary profile with reference functions, and multiple executable and header files to split up functions based on usage. This has become 42 files and 5300 lines of self updating and centrally managed code with stable and beta sets. As this set of files continues to grow rapidly, I am evaluating the design principles again to see if there are any better solutions out there. This is what I have at the moment:

1) Base profile. Changes between beta copy and production copy on a function call that overwrites the file at $profile with the appropriate version. This base profile checks all files at a specific network share, either prod share or beta share, compares a set of files on the local system with the ones on the share for any changes/new files that need updating, updates and applies the secondary profile with the "." operator.

2) Secondary profile. Contains a few complete functions for parsing system names, command line options, and a few other functions that don't fit well in other files. Besides this there are pointer functions that call external scripts at ($localpath + "extFile.ps1"). These calls can directly call a stand alone script, or call a script that contains multiple executable functions. In this case, the name of the function + arguments is passed and interpreted by something like this:

$server = $args[0]
$function = $args[1]
&$function $server

3) External scripts. Stand alone that manage arguments passed. Options passed are general hash table's with switches or valued arguments passed. External scripts may load header type files with the "." operator. Since the files are separate from the profile, the scripts may be updated by another process without reloading the profile.

4) Header files. Contain a range of functions for a specific area (hardware, directory, security, etc) that would not be very useful when used directly. Functions here may be required for several different executable functions. They also allow for rapid development of future executable functions. The problem here is designing functions that are useful for what you are doing when you write them, and still usable for other code without having to be rewritten, or written with excessive capabilities the first time.

Tuesday, March 30, 2010

Tired of looking up security codes in events?

Hello, I have put together a few Powershell functions for this lockout tool I am working on, which help decode some of the various Kerberos and logon codes in security event log events. They are not full tested, but hopefully should work fine as is.
function decode-krbTktOpts([string]$code) {
 #code provided is in hex format
 if (-not($code -match "0x\d{8}")) {
   write-error "Invalid entry sent to decode-krbTktOpts function : value was : $code"
   return $null
 }
 
 
 ##########
 #Ticket options are 32 bits of flags.  Code comes in as hex.  Only bits 0-1,3-5,16-17,20,23,25-31 are used
 ##########
 $results = new-object collections.arraylist
 $code = [convert]::toint32($code.substring($code.indexof("x")+1), 16)
 if ($code -and 1) {
  $results.add("Validate") >$null
 } else if ($code -and 2) {
  $results.add("Renew") >$null
 } else if ($code -and 8) {
  $results.add("EncTktInSKey") >$null 
 } else if ($code -and 16) { 
  $results.add("RenewableOK") >$null
 } else if ($code -and 32) {
  $results.add("DisableTransitedCheck") >$null
 } else if ($code -and 65536) {
  $results.add("Canonicalize") >$null
 } else if ($code -and 131072) {
  $results.add("CNameInAddlTkt") >$null
 } else if ($code -and 1048576) {
  $results.add("OptHardwareAuth") >$null
 } else if ($code -and 8388608) {
  $results.add("Renewable") >$null
 } else if ($code -and 33554432) {
  $results.add("AllowPostDate") >$null
 } else if ($code -and 67108864) {
  $results.add("Proxy") > $null
 } else if ($code -and 134217728) {
  $results.add("Proxiable") > $null
 } else if ($code -and 268435456) {
  $results.add("Forwarded") > $null
 } else if ($code -and 536870912) {
  $results.add("Forwardable") > $null
 } 
 return $results

}

function decode-LogonErrorCode([string]$code) {
 #decode 32 bit microsoft logon error codes from Hex format (32 bit)
 if (-not($code -match "0x\d{8}")) {
   write-error "Invalid entry sent to decode-krbTktOpts function : value was : $code"
   return $null
 }
 
 switch($code.tolower()) {
  "0x0" { return  "Successful login" }
  "0xC0000064" { return "The specified user does not exist" }
  "0xC000006A" { return "The value provided as the current password is not correct" }
  "0xC000006C" { return "Password policy not met" }
  "0xC000006D" { return "The attempted logon is invalid due to a bad user name"}
  "0xC000006E" { return "User account restriction has prevented successful login"}
  "0xC000006F" { return "The user account has time restrictions and may not be logged onto at this time"}
  "0xC0000070" { return "The user is restricted and may not log on from the source workstation"}
  "0xC0000071" { return "The user account's password has expired"}
  "0xC0000072" { return "The user account is currently disabled"}
  "0xC000009A" { return "Insufficient system resources"}
  "0xC0000193" { return "The user's account has expired"}
  "0xC0000224" { return "User must change his password before he logs on the first time"}
  "0xC0000234" { return "The user account has been automatically locked" }
  default {return "Unknown code provided, unable to translate" }
 }
 
}

function decode-krbErrCode([string]$code) {
 #code provided is required to be in the hex format provided in the system event logs  ex: 0x2
 if (-not($code -match "x")) {
  #if we receive something in invalid format, try to convert to hex
  if ($code -match "\d+") {
   $code = "0x" + [string]::format("{0:x}",$code)
  } else {
   write-error "Invalid entry sent to decode-krbErrCode function : value was : $code"
   return $null
  }
 }
  
 switch($code.tolower()) {
  "0x0" { return ("KDC_ERR_NONE","No Error") }
  "0x1" { return ("KDC_ERR_NAME_EXP","Clients entry in Database has Expired") }
  "0x2" { return ("KDC_ERR_SERVICE_EXP","Servers entry in Database has Expired") }
  "0x3" { return ("KDC_ERR_BAD_PVNO","Request protocol version number not supported") }
  "0x4" { return ("KDC_ERR_C_OLD_MAST_KVNO","Client's key encrypted in old master key") }
  "0x5" { return ("KDC_ERR_S_OLD_MAST_KVNO","Servers key encrypted in old master key") }
  "0x6" { return ("KDC_ERR_C_PRINCIPAL_UNKNOWN","Client not found in Kerberos Database") }
  "0x7" { return ("KDC_ERR_S_PRINCIPAL_UNKNOWN","Server not found in Kerberos Database") }
  "0x8" { return ("KDC_ERR_PRINCIPAL_NOT_UNIQUE","Multiple principal entries in database") }
  "0x9" { return ("KDC_ERR_NULL_KEY", "The client or server has a null key") }
  "0xa" { return ("KDC_ERR_CANNOT_POSTDATE", "Ticket not eligible for postdating") }
  "0xb" { return ("KDC_ERR_NEVER_VALID","Requested start time is later than end time") }
  "0xc" { return ("KDC_ERR_POLICY","KDC policy rejects request") }
  "0xd" { return ("KDC_ERR_BADOPTION","KDC cannot accomodate requested option") }
  "0xe" { return ("KDC_ERR_ETYPE_NOSUPP","Kerberos server has no support for this encryption type") }
  "0xf" { return ("KDC_ERR_SUMTYPE_NOSUPP","Kerberos server has no support for checksum type") }
  "0x10" { return ("KDC_ERR_PADATA_TYPE_NOSUPP","Kerberos server has no support for PADATA type") }
  "0x11" { return ("KDC_ERR_TRTYPE_NOSUPP", "Kerberos server has no support for transited type") }
  "0x12" { return ("KDC_ERR_CLIENT_REVOKED","Clients credentials have been revoked") }
  "0x13" { return ("KDC_ERR_SERVICE_REVOKED","Credentials for server have been revoked") }
  "0x14" { return ("KDC_ERR_TGT_REVOKED","TGT has been revoked") }
  "0x15" { return ("KDC_ERR_CLIENT_NOTYET","Client not yet valid") }
  "0x16" { return ("KDC_ERR_SERVICE_NOTYET", "Server not yet valid") }
  "0x17" { return ("KDC_ERR_KEY_EXPIRED", "Password has expired - change password to reset") }
  "0x18" { return ("KDC_ERR_PREAUTH_FAILED","Preauthentication is invalid, bad password") }
  "0x19" { return ("KDC_ERR_PREAUTH_REQUIRED","Additional Preauthentication required") }
  "0x1f" { return ("KRB_AP_ERR_BAD_INTEGRITY","Integrity check on decrypted field failed") }
  "0x20" { return ("KRB_AP_ERR_TKT_EXPIRED","Ticket expired")}
  "0x21" { return ("KRB_AP_ERR_TKT_NYV","Ticket not yet valid")}
  "0x22" { return ("KRB_AP_ERR_REPEAT","Request is a replay")}
  "0x23" { return ("KRB_AP_ERR_NOT_US","The ticket isn't for us")}
  "0x24" { return ("KRB_AP_ERR_BADMATCH", "Ticket and authenticator do not match") }
  "0x25" { return ("KRB_AP_ERR_SKEW", "Clock skew is too big")}
  "0x26" { return ("KRB_AP_ERR_BADADDR", "Incorrect net address") }
  "0x27" { return ("KRB_AP_ERR_BADVERSION", "Protocol version mismatch") }
  "0x28" { return ("KRB_AP_ERR_MSG_TYPE", "Invalid message type") }
  "0x29" { return ("KRB_AP_ERR_MODIFIED", "Message stream modified") }
  "0x2a" { return ("KRB_AP_ERR_BADORDER", "Message out of order") }
  "0x2c" { return ("KRB_AP_ERR_BADKEYVER","Specified version of key is not available") }
  "0x2d" { return ("KRB_AP_ERR_NOKEY", "Service key not available") }
  "0x2e" { return ("KRB_AP_ERR_MUT_FAIL", "Mutual authentication failed") }
  "0x2f" { return ("KRB_AP_ERR_BADDIRECTION", "Incorrect message direction") }
  "0x30" { return ("KRB_AP_ERR_METHOD", "Alternative authentication method required") }
  "0x31" { return ("KRB_AP_ERR_BADSEQ", "Incorrect sequence number in message") }
  "0x32" { return ("KRB_AP_ERR_INAPP_CKSUM", "Inappropriate type of checksum in message") }
  "0x3c" { return ("KRB_ERR_GENERIC", "Generic error") }
  "0x3d" { return ("KRB_ERR_FIELD_TOOLONG","Field is too long for this implementation") }
  default { return ("Invalid code", "not in RFC") }
 }
  
}

Friday, March 26, 2010

Auditing Kerberos Delegation

Hello,

In the last few weeks I was doing some audits in my environment for accounts that were trusted for delegation. I thought I would share some LDAP searches with everyone, using Joe's great ADFIND tool. For those that are not too familiar with delegation, there are two different bits in the UserAccountControl attribute that are related to delegation. These are TRUSTED_FOR_DELEGATION (0x80000) which uses kerberos forwardable tickets, and TRUSTED_TO_AUTH_FOR_DELEGATION (0x1000000) which allows the delegated person/computer to request a ticket on a user's behalf. Both of these options should be used with extreme caution when the accounts are unconstrained, the second option even more so. If systems that are delegated in an unconstrained manner get compromised, anyone accessing them is basically giving up their account for any purpose to the compromised machine.



Finding all unconstrained delegated computer and user accounts, ignoring domain controllers.

adfind -h DCservername -b dc=mydomain,dc=com -s subtree -bit -f "(&(|(objectcategory=user)(objectcategory=computer))(|(userAccountControl:OR:=16777216)(userAccountControl:OR:=524288))(!(iscriticalsystemobject=TRUE))(!(msds-allowedtodelegateto=*)))" -t 9000 distinguishedname serviceprincipalname useraccountcontrol

Finding all constained delegated computer and user accounts, ignoring domain controllers.

adfind -h DCservername -b dc=mydomain,dc=com -s subtree -bit -f "(&(|(objectcategory=user)(objectcategory=computer))(|(userAccountControl:OR:=16777216)(userAccountControl:OR:=524288))(!(iscriticalsystemobject=TRUE))(msds-allowedtodelegateto=*))" -t 9000 distinguishedname serviceprincipalname useraccountcontrol

Thursday, March 25, 2010

.NET and NetBIOS name resolution

Recently I was looking at what it would take to throw together some code to chase failed logon events through multiple servers and workstations down to the source machine, source process, and/or source network connection. One of the problems that I encounter in security event logs, the source machines are often IP addresses instead of the system name. When dealing with reverse DNS records in dynamic environments, there are problem subnets in which hosts change IP's often. Reverse DNS allows multiple hosts to record the same record with a maximum per entry limit so high that name resolution is worthless. Even if you have a short scavenging period, your data may not be very accurate, or perhaps the source machine did not register a record. One idea that came to mind was doing a WMI lookup against the remote IP to pull the computer name, but that requires the appropriate level of access for remote WMI (if it is enabled), so it may not be as reliable. Being a sysadmin from the pre-windows 2000 period, I like to use nbtstat against the IP address to see the name of remote windows machines. I tried looking around the various classes of .NET and could find anything for netbios style name resolution, so to save myself the trouble of trying to parse through nbtstat command output strings, and dealing with that slowness (when using multiple NIC's and virtualization NIC's), I decided to roll my own solution. The code below was expanded more later for some additional functionality such as flag parsing. This is the simplified version that returns an array of PSObjects containing the various netbios records. For those not familiar with Netbios, it is the 15 character name format used in windows. The 16th byte of the name is a type value. Type 0x00 will give you the workstation name and domain name (group flag is enabled). The netbios query packet is pretty standard other than perhaps the transaction ID. The replied records are all fixed length and names are padded with 0x20 up to the 15 characters for the names. There is a number of records value that tells how many records were returned, so pulling the results is pretty basic.   (NOTE: the script opens a privileged port, so it requires admin rights on the local machine that you are running it on)


Function convert-netbiosType([byte]$val) {
 #note netbios type codes are usually in decimal, but .net likes to deal with bytes
 #as integers.
    
 $myval = [int]$val
 switch($myval) {
  0 { return "Workstation" }
  1 { return "Messenger service" }
  3 { return "Messenger" }
  6 { return "RAS" }
  32 { return "File Service" }
  27 { return "Domain Master Browser" }
  28 { return "Domain Controller" }
  29 { return "Master Browser" }
  30 { return "Browser election" }
  31 { return "NetDDE" }
  33 { return "RAS Client" }
  34 { return "Exchange MS mail connector" }
  35 { return "Exchange Store" }
  36 { return "Exchange Directory" }
  48 { return "Modem sharing service Server"}
  49 { return "Modem sharing service Client"}
  67 { return "SMS client remote control" }
  68 { return "SMS client remote transfer" }
  135 { return "Exchange MTA" }
  default { return "unk" }
 }
 
}



function get-netbios-name ([string]$ip) {
 #Function:  Get netbios name of the remote machine by IP address provided
 #  result:  Error = $null, positive result is hashtable of names
 if (-not ($ip -match "\d{1,3}\.\d{1,3}\.\d{1,3}\.\d{1,3}")) {
  write-error "The Ip address provided: $ip  is not a valid IPv4 address format"
  return $null
 }
 #ping first for reachability check
 $po = New-Object net.NetworkInformation.PingOptions
 $po.set_ttl(64)
 $po.set_dontfragment($true)
#alexander ping
 [Byte[]] $pingbytes = (65,72,79,89)
 $ping = new-object Net.NetworkInformation.Ping
 $pingres = $ping.send($ip, 1000, $pingbytes, $po)
    
if ($pingres.status -eq "Success") {
    
#netbios name query  NBTNS
 $port=137
 $ipEP = new-object System.Net.IPEndPoint ([system.net.IPAddress]::parse($ip),$port)
 $udpconn = new-Object System.Net.Sockets.UdpClient
 [byte[]] $sendbytes = (0xf4,0x53,00,00,00,01,00,00,00,00,00,00,0x20,0x43,0x4b,0x41,0x41,0x41,0x41,0x41,0x41,0x41,0x41,0x41,0x41,0x41,0x41 ,0x41,0x41,0x41,0x41,0x41,0x41,0x41,0x41,0x41,0x41,0x41,0x41,0x41,0x41,0x41,0x41,0x41,0x41,00,00,0x21,00,01)
 $udpconn.client.receivetimeout=1000
 $bytesSent = $udpconn.Send($sendbytes,50,$ipEP)
 $rcvbytes = $udpconn.Receive([ref]$ipEP)
 if ($? -eq $false -or $rcvbytes.length -lt 63) {
  write-error "System is not responding to netbios traffic on port 137, system is not a windows machine, or other error has occurred."
  return $null
    
 } else {
  [array]$nbnames = $null
  #nbtns query results have a number of returned records field at byte #56 of the returned
  #udp payload.  Read this value to find how many records we have
  $startptr = 56
  $numresults = [int]$rcvbytes[$startptr]
  $startptr++
  $namereclen = 18
  #loop through the number of results and get the names + data
  #  NETBIOS result =  15 byte of name (padded if shorted 0x20)
  #                     1 byte of type
  #                     2 byte of flags
    
    
  for ($i = 0; $i -lt $numresults; $i++) {
   $nbname = new-object PSObject
   $tempname = ""
   #read the 15 byte name and convert to human readable string
   for ($j = 0; $j -lt $namereclen -3; $j++) {
    $tempname += [char]$rcvbytes[$startptr + ($i * $namereclen) + $j]
       
   }
   add-member -input $nbname NoteProperty NetbiosName $tempname
   $rectype = convert-netbiosType $rcvbytes[$startptr + ($i * $namereclen) + 15]
   add-member -input $nbname NoteProperty  RecordType $rectype
   if (($rcvbytes[$startptr + ($i * $namereclen) + 16] -band 128) -eq 128 ) {
  #in the flags field, only the high order byte of the 2 is used
  #the left most bit is the Group name flag which can be used for domain
  #name type identification to differentiate the 0x00 type names
    $groupflag = 1
   } else { $groupflag = 0 }
    add-member -input $nbname NoteProperty IsGroupType $groupflag
    $nbnames += $nbname
   }
   return $nbnames
    
  }
 } else {
  write-error "System not pinging: $ip"
  #prompt for another ip to be inputted?
  return $null
 }
  
}

$ip = args[0]
if ($ip -eq $null -or $ip -eq "") {
  write-host "You need to provide an ip address to check"
  exit
}
get-netbios-name $ip

For those that use nbtstat, you will also know that it returns the MAC address, however not in the best or most efficient way. Since nbtstat tries to use every network adapter on the machine, if you have multiple nics and virtualization nics, then it is slow. The benefit of the powershell method above is that you only send out from one NIC. To get the MAC address, the code can be modified to pull it out of the received packets. MAC address is the last useful 6 bytes of the packet (which may be padded with extra 0's at the end). So you can use this to grab those bytes and format it to a mac address string.

$mac = (0,0,0,0,0,0)
$j = 5
for ($i = $rcvbytes.length - 1; $i -gt 0; $i--) {
   if ($rcvbytes[$i] -ne 0x0) {
      $mac[$j] = $rcvbytes[$i]
      $j--
      if ($j -eq -1) { $i = -1 }
   }
}
$macstring = ""
foreach ($byte in $mac) {
  $macstring += ("{0:X2}" -f $byte) + "-"
}
new-object psobject -property @{
   IP = $ip
   MacAddress = $macstring.trim("-")
}

Thursday, March 18, 2010

Fixing scattered non-permission inherting objects

Occasionally in our environment we come across problems with user management access due to inherited permissions not being enabled. This appears to be related to accounts that were once members of protected groups like server operators, backup operators, account operators, etc and the AdminSDHolder process unchecking inheritance. Over time as the users changed roles and were removed from these groups, inheritance was not manually enabled. There are scripts available that show how to edit this ACL attribute with vbscript for a single object, but I'm a Powershell guy and I want to hit a few thousand accounts at once. For the sake of simplicity, let us assume all of the accounts are in the same OU location: OU=admins,dc=mydomain,dc=com. Also note that OU=admins has inheritance disabled for security reasons and cannot be changed. If it could we could fix all objects in the OU with a single command.

If we are unconcerned with checking to see which accounts are set to not inherit, but we don't want to temporarily expose any current AdminSDHolder accounts to a short term reduction in security, we can do this:

$de = new-object DirectoryServices.DirectoryEntry("LDAP://ou=admins,dc=mydomain,dc=com")
$ds = new-object DirectoryServices.DirectorySearcher($de)
$ds.filter = "(&(objectclass=user)(!(admincount=1)))"
$ds.propertiestoload.add("distinguishedname")
$users = $ds.findall()

Now our variable (array) $users contains a list of LDAP results that can be used to provide distinguished names to the DSACLS command.

Loop through the results and enable permissions inheritance for all objects.

foreach ($user in $users) {
$dn = $user.properties.distinguishedname
dsacls $dn /P:n > $null
}

This will loop through all users found in the query and enable inheritance. If the top level OU did not need to be protected you could accomplish the same type of result with dsacls /T option to run the change on a whole tree of objects from the OU level down. The Powershell way allows more granular control and is helpful if you have several OU levels under the Admins OU that you don't want impacted. The DirectorySearcher class allows you to specify the search scope to limit the results.

In AD Powershell, the get-acl/set-acl options can provide similar capabilities, example here.

Thursday, March 11, 2010

Avoiding account lockout policy (pre Windows 2008)

Over the last year or so a few instances have come up with service accounts getting locked out and the owners being unhappy. Usually everyone wants to know if it is possible to set their account so it can't be locked out. Prior to Windows 2008 AD fine grained password policies and managed service accounts, the answer typically is No. The security guy in me says No way should this be done even if it is possible. But to be fair, lets look at technical feasibility.

Lockout policy/Password policy is a domain wide setting, for domain accounts it follows what policy is applying to the domain controllers. For domain member local accounts it follows whatever policy is pushed to the client system. But, there is in fact a way to avoid lockouts, though it is a bit unorthodox. Let me give some background then I will mention the how-to.

A few weeks ago, one morning I had this idea pop into my head that it would be interesting to explorer the security implications of computer accounts in the enterprise (This means computer objects...people like to mix "computer accounts" to mean user accounts). Since computer accounts have passwords and can access remote resources, I thought there may be some interesting things to find.

So on this mission, I first looked at what can you do with computer accounts. Normal tools, like ADUC, dsmod, etc don't let you set a computer account password manually. NET USER and admod will allow this however. So I created my object, reset the password and started messing around. I first found that you can't log in interactively with computer accounts and it gives you an error saying that a policy is blocking this. I granted all possible user rights to the account, and added to administrators group, but no help there. If you look at the UserAccountControl attribute, there are flags that indicate the type of account. There is also a SamAccountType attribute on users and computers. The value of this is slightly different, but you can't edit this attribute as it is controlled by the SAM. UserAccountControl however can be edited. There is some security implications with this related to computers that I will get into some other time.

Anyways, if you look at the values in the linked article above you can see these flags. You can't have both set at the same time, but you can change the computer object's useraccesscontrol level to that of a normal user. Coincidentally, this causes the SamAccountType value to be updated to match a user object as well.

Values:
0x1000 workstation trust account
0x0200 normal account

So I changed my test object's UAC to 0x0200, I retried local login and it worked fine. Also using it for scheduled tasks, services, etc were all working now as well. Previously I could only use the account to map network drives.

So this was an interesting discovery, but I took this a little further on login attempts. I noticed with repeat failed passwords my bad password count kept going up, well over the lockout threshold set in my test domain. When my normal user objects were getting locked, my computer account kept taking attempts over and over. Providing the correct password after multiple failures that should have resulted in lockout showed that I could still log in. So apparently the mechanism used to lock accounts ignores computer objects, doesn't look at UAC for them, and doesn't look at the SamAccountType. Given this, we can create an unlockable "user" by creating a computer object.

For those who may worry now about brute force password attempts on computer objects, given that most forms of logins are blocked for a standard account, and the auto generated password length is significantly high, the risk is low. For those that have pretty loose computer object creation policies, it may be time to start thinking in a different way. In the near future, I hope to provide some better details on the overall security implications of computer objects.

Monday, March 8, 2010

Converting time formats in Powershell

Since I am spending the morning looking at solutions to track down lockout events to source in a more automated fashion, I'm stuck looking at how to dig through event log events. Using the Active Directory values for bad password time helps to locate a domain controller with useful log events, but the format needs some adjustment. When looking at AD, the format can look like this:

lastlogontimestamp {129121380931028667}
badpasswordtime {129115363568077019}

In powershell it is very easy to convert this to a datetime object for further use. If you want to just display the value in a readable format, you can just use this command. If you want to work with it some more, assign it to a variable:

[datetime]::FromFileTime($mydatetime)

ex:

PS C:\> [datetime]::fromfiletime(129121380931028667)

Wednesday, March 03, 2010 7:01:33 PM


Now when looking at event logs with something such as WMI queries, the date time format is different.
Format: YYYYMMDDHHMMSS.000000-UUU
Reference

The System.Management.ManagementDateTimeConverter class can be used to convert between WMI's format and DateTime.

Once the value is in WMI format, into a wmi queries. If you are searching logs in a timewritten range you may want to adjust the seconds of this value +/- a few to help ensure that everything links up. You can also drop the milliseconds as the AD time stamp may not perfectly match up to a system event log time stamp. In any case, having a tight time range and very specific WMI query will help keep remote event log search queries fast and leave you will less results to process in any addition work you plan to do.

Some basic code using these conversions and providing a time range for event log searches:


function wmiTime-toDateTime([string]$wmitime){
#YYYYMMDDHHMMSS.000000-UUU where -UUU is the three-digit offset
return [system.management.managementdatetimeconverter]::todatetime($wmitime)

}

function dt-toWmiTime([system.datetime]$dt) {
#YYYYMMDDHHMMSS.000000-UUU where -UUU is the three-digit offset
$dt = $dt.touniversaltime()
$wmiformat = [System.Management.ManagementDateTimeConverter]::ToDmtfDateTime($dt)
return $wmiformat

}

function adtime-toWMItime([string]$mydatetime) {
$dt = [datetime]::FromFileTime($mydatetime) 
$wmitime =  dt-toWmiTime $dt
return $wmitime
}


function adjust-wmitime ([string]$wmiOrigTime, [double]$secondOffset) {
#WMI time format: YYYYMMDDHHMMSS.000000-UUU where -UUU is the three-digit offset
#This function takes an original time and provides an array of two values, search start time and
#search end time

$mybasetime = wmiTime-toDateTime $wmiOrigTime
$myadjustedDT = $mybasetime.addseconds($secondoffset)
$myResultingWMI = dt-toWmiTime $myadjustedDT
return $myResultingWMI

}

function compare-wmiTimes($startTime,$endTime) {
 $dtS = wmiTime-toDateTime $startTime
 $dtE = wmiTime-toDateTime $endTime
 $timediff = new-timespan -start $dtS -end $dtE
 if ($timediff.TotalMilliseconds -gt 0) {
  #second date is later than the first
  return 1
 } elseif ($timediff.TotalMilliseconds -eq 0) {
  #time is the same
  return 0
 } else {
  #first date is later than the second
  return -1
 }

}

function wmitime-timerange ([string]$wmiOrigTime, $secondOffset) {
$secondoffset = [double]$secondoffset
$mystartWMI = adjust-wmitime $wmiOrigTime -$secondoffset
$myendWmi = adjust-wmitime $wmiOrigTime $secondoffset
$myResult = ($mystartWMI, $myendWMI)
return $myResult
}



Wednesday, March 3, 2010

Checking SSL certificate values with Powershell

For anyone that needs to check SSL certificates in a simple way from Powershell, I created something for this purpose a while back. It works for most SSL connections using .NET code and will throw exceptions if the name on the cert you provide is not valid, or the cert is expired.

Check-sslcert.ps1 (Updated Jan 15, 2013)


#Requires -version 2.0
 
param(
 [parameter(mandatory=$true,helpmessage="IP address or hostname to resolve remote system")][string]$ipaddr,
 [parameter(mandatory=$true,helpmessage="TCP port number that SSL application is listening on")][int]$port,
 [parameter(helpmessage="Hostname on certificate")][string]$myhostname=$ipaddr,
 [parameter(helpmessage="Verbose")][alias('fulldetail')][switch]$V
 
)


function stripcomma([string]$tempstring) {
 write-debug "In Function StripComma $($tempstring)"
 return $tempstring.replace(',',';') 
 
}

function convertoid([string]$oid) {
 write-debug "In function ConvertToOID: $($oid)"
 #strip off oid component common to all crypto types
 $oidstr = $oid.replace("1.2.840.113549.1.","")
 
 #pull out first number
 $firstval = $oidstr.substring(0,$oidstr.indexof('.'))
 
 #pull out second number for more detail
 $sub = $oidstr.substring(2)
 if ($sub.indexof('.') -gt 0) {
  $sub = $sub.substring(0,$sub.indexof('.')) 
 }
 
 if ($firstval -eq "1") {
  $format = "PKCS-1"
  switch ($sub) {
   "1" { return ($format + " RSA Encryption") }
   "2" { return ($format + " MD2 with RSA") }
   "3" { return ($format + " rsadsi md4 with RSA")}
   "4" { return ($format + " MD5 with RSA") }
   "5" { return ($format + " SHA-1 with RSA") }
   "6" { return ($format + " rsaOAEPEncryptionSet")}
   "11" { return ($format + " sha256 with RSA") }
  }
 } elseif ($firstval -eq "5") {
  $format = "RSA PKCS5"
  switch ($sub) {
   "1" { return ($format + " rsadsi pbe with MD2 DES-CBC")}
   "3" { return ($format + " rsadsi pbe with MD5 DES-CBC")}
   "4" { return ($format + " pbe with MD2 and RC2_CBC")}
   "6" { return ($format + " pbe with MD5 and RC2_CBC")}
   "9" { return ($format + " pbe with MD5 and XOR")}
   "10" { return ($format + " pbe with SHA1 and DES-CBC")}
   "11" { return ($format + " pbe with SHA1 and RC2_CBC")}
   "12" { return ($format + " id-PBKDF2 key derivation function")}
   "13" { return ($format + " id-PBES2  PBES2 encryption")}
   "14" { return ($format + " id-PBMAC1 message auth scheme")}
   
  }
 } elseif ($firstval -eq "7" ) {
  $format = "PKCS-7"
  switch ($sub) {
   "1" { return ($format + " data")}
   "2" { return ($format + " signed data")}
   "3" { return ($format + " enveloped data")}
   "4" { return ($format + " signed and enveloped data")}
   "5" { return ($format + " digested data")}
   "6" { return ($format + " encrypted data")}
  }
 } elseif ($firstval -eq "12") {
  return ("PKCS-12")
 } elseif ($firstval -eq "15") {
  return ("PKCS-15") 
 } else {
  return $oid 
 }
   
 
}

######
#MAIN#
######

#open TCP connection
try {
 $conn = new-object system.net.sockets.tcpclient($ipaddr,$port) 
 
 try {
  #create ssl stream on existing tcp connection
  $stream = new-object system.net.security.sslstream($conn.getstream())
  #send hostname on cert to try SSL negotiation
  $stream.authenticateasclient($myhostname) 
  
  $cert = $stream.get_remotecertificate()
  $cert2 = New-Object system.security.cryptography.x509certificates.x509certificate2($cert)    #can get much more information with this class    

  $validto = [datetime]::Parse($cert.getexpirationdatestring())
  $validfrom = [datetime]::Parse($cert.geteffectivedatestring())
  
  if ($V) {
   new-object psobject -property @{ 
    Connection = "Success"
    Machine = $ipaddr
    CertFormat = ($cert.getformat())
    CertExpiration = $validto
    CertIssueDate = $validfrom
    CertIssuer = ($cert.get_issuer())
    SerialNumber = ($cert.getserialnumberstring())
    CertSubject = (stripcomma $cert.get_subject())
    CertType =  (convertoid $cert.getkeyalgorithm())
   }
  } else {
   #non verbose
   New-Object psobject -Property @{
    Connection = "Success"
    Machine = $ipaddr
    CertExpiration = $validto
   }
  }

 } catch {
  #if SSL connection failed, cert may be invalid or name on cert didn't match, fails either way
  throw $_
 } finally {
  Write-Debug "In finally: closing connection"
  $conn.close()
 }
} catch {
 Write-Verbose "Error occurred connecting to $($ipaddr)"
 New-Object PSObject -Property @{
  Machine = $ipaddr
  Connection = "Failure"
  Status = $_.exception.innerexception.message
 }
 
}