Tuesday, January 24, 2006

Script to Check an Exchange 2003 Server against the OVAL vulnerabilities definitions file

A few weeks ago while looking for some information on something else I was working on I came across the Open Vulnerability and Assessment Language (OVAL) website. Now these’s type of certification organization are nothing new and there are many around we have one locally in Australia based at the Queensland University. The big difference with this site is that they are publishing the vulnerabilities in a XML file in a format that you can basically plug straight into a testing tool. They have a sample test tool already which looks good but didn’t really give the sort of result I was after. With the schema that is used this XML file really lends itself to scripting so I thought I’d try to put together a script that would first separated out all the Exchange specific tests within the OVAL definition file and then perform the tests using the appropriate API via a script. This turned out to prove challenging and the script I ended up with was on the large side and covered a gamete of different API’s and VBS functions including Xpath, ADSI, Regular Expressions, Bitwise Operations, WMI, FSO and the registry provider.

OVAL definition file

The current OVAL definition file has a lot of vulnerabilities listed for Exchange 2003 at the moment which is what I targeted this script at. Most of the vulnerabilities appear to come from the Exchange Benchmark produced by the Centre for Internet Security. You can download the benchmark document for there site its well worth a read it contains a lot of good information and explanations that describe the reasoning behind some the vulnerabilities that are included in the OVAL definition file (and some of these vulnerabilities do need a little bit of explanation as they don’t all initial make sense).These guys are also building a scoring tool for there benchmark which should be interesting once it gets released.

To navigate the OVAL definition file in a script I’ve used Xpath via the Microsoft XMLDOM object. To limit it to only testing for vulnerabilities on Exchange 2003 it does a wildcard search of nodes that have a text value equal to “Microsoft Exchange Server 2003”. It’s important to note this script doesn’t test for Windows 2003/2000 vulnerabilities listed in the OVAL file only the Exchange specific ones.

OVAL test

The OVAL definition file lists a number of tests to be performed to work out if the current server’s configuration is vulnerable. Because some tests in the file are listed as unknown and some of the results are open to a little interpretation what I designed the script to do was just attempt the test if it can and display the result. Its doesn’t analyse wether it was a pass or fail (which I did have it doing initially but dropped)

Basically there are 5 types of tests

File Test looks at files to see if they are below a certain version meaning that a patch(hotfix) or service pack may not have been applied. To do this test via a script I’ve used WMI to first retrieve a registry entry that helps in finding the right filepath and then it uses the CIM_DataFile WMI class to look at the actual file version.

Registry Test looks at particular registry entires to determine if a patch is installed or different configuration or version information. To perform the registry test via a script I’ve used the WMI registry provider.

Active Directory Test looks at the value of particular Active Directory attributes. The OVAL definition provides a regular expression of the Distinguished Name of the object to look for and the attribute and value to evaluate. Because of this the Active Directory test isn’t 100%. To perform the test ADSI is used, a search is first performed for all objects in Active directory that have the attribute being search for set. A list of objects that matches this criteria is then evaluated against the Regex expression from the OVAL definition. Any objects that match then are then examined to see if these are server specific configuration setting. If they are then a test is performed to see if the configuration relates to the server that the script is testing. Non server specific keys such as global setting are always checked regardless. Because of the potential results that are returned for some Active Directory attributes it’s not possible to display the results of all the evaluations. Eg Keys that have a value type of Octent_string are sometimes stored in there own binary format which needs further decoding. Because of a few irregularities in the OVAL definitions there is a fix up section that reformats some of the Regular expressions so they can be searched successfully using VBS.

WMI_test looks at particular server configuration setting that are accessible via the WMI classes provided by Exchange or Windows. Currently there are 2 WMI tests in the current definitions that tests for IIS directory security settings. These 2 tests both use a WMI class that is only available on a Windows 2003 server.

The last type of test is the Unknown_test which means the script will just list a description of what should be tested but does not provide any means of carrying out the test


The script is very verbose and outputs most of the actions it’s performing to the command line as it runs. It also builds a XML of the results of the entire test as it goes along. With the OVAL specification there is a result file format you can used but I decided to not use this and build my own mainly because of time and flexibility. To read the results I have a HTM page that runs another VBS script that uses Xpath again to navigate the result xml file and then display the results formatted in a HTML table.

Running the script

Because the script uses remote-able API’s this script can be run from any machine as long as the users you are logging on as has the necessary rights to query the file and WMI classes required. There are few hardcoded paths set in the script that point to a c:\oval directory which is where the results of the scan are saved to and the definition file is read from. Before you can use this script you need to download the OVAL definition file from http://oval.mitre.org/oval/download/datafiles/index.html . The file that contains the Exchange definitions is called windows.definitions.xml and needs to be downloaded to the c:\oval directory. The script takes one commandline parameter which is the servername of the server you want to run this script against. So to run the script use something like cscript.exe exovalchk.vbs servername

After the script has run you can then use the DisplayOvalResults.htm file to view the results of the scan. This file contains a script which you need to allow to run to parse out the ovaltestresult.xml file and display the result in a HTML Table. The script looks for the result file in the c:\oval directory. When viewing the result if you want to view the OVAL definition itself the name of the definition is hyperlinked to the definition description on the OVAL site.

Interpreting the results

The most time consuming thing you need to do with security (and this is probably the way it should be) is to interpret the results there is no real simple pass /fail result. Even vulnerability does need some level of human consideration and interpretation behind it to consider if this really does apply to you and weather you should make changes. Making the changes prescribed in some of these vulnerabilities will break things like OWA and OMA and these type of effects are not very clearly defined in the OVAL definition itself so always research any changes you are going to make before you make them (eg use Microsoft or the Exchange Benchmark which does list the reasons and effects of the changes.).

The script is very beta-ish because of the potential for errors there is no guarantee that this script will scan all the Exchange 2003 vulnerabilities in the OVAL definition file or display the results correctly. This script should be only viewed as a starting point

The script itself is a bit large to post verbatim I’ve put a downloadable copy of the script and display webpage here

Thursday, January 12, 2006

Creating Summary Emails for Inbox, Sub or Public Folders

This is one script that has a number of different uses the basic premise of this script is that it will produce a summary email of all the email that exists in a folder for a certain time period. For example if you have a public folder where mail may be arriving in and you want people to receive a summary once a day of all the email that was received in this folder for the past 24 hours or . I also use this for different listservs I subscribe to where I have some rules moving email from different lists to a certain folders than I run the script to summarize mail that was received in that folder over the last 24 hours . The summary includes who the message was from what time it was received and also an Oulook link so if you do want to view the mail you can click on the link and it will take me to the appropriate message in Outlook.

The script works by querying a folder using exoledb for messages that arrived after a certain date. Because Exchange stores the received time in UTC there’s some code to first convert the query time to UTC and then also format the date/time into ISO format that is required by Exoledb. The code builds a HMTL table that is going to be used as the body of the message to provide the outlook links PR_EntryID is included in the Exoledb query and a function is used to convert this into a Hex String. The Hex String can then be used in a hyperlink in the Outlook:entryID format. The last part of the code is responsible for sending the message. The send is done using CDOEX sending via SMTP port 25 on the server.

To use this script the following lines need to be customised.

Mailboxurl = http://mailbox/exchange/mailbox/inbox

Use your own mailbox or public folder URL (just cut and past the OWA URL)

dtListFrom = DateAdd("d",-1,dtListFrom)

This controls the time period for the query the default is 1 day (24 hours)

objEmail.From = "source@yourdomai.com"
objEmail.To = target@yourdomain.com

Set this to valid source and target email address's

objEmail.Configuration.Fields.Item("http://schemas.microsoft.com/cdo/configuration/smtpserver") = "servername"

Set this to a valid servername of the Exchange server you running it on.

Because the code uses Exoledb it must be run locally on an Exchange server with an account that has rights to the mailbox or public folder you’re querying. If you want to run it regularly then a scheduled task can be used locally on the server.
I’ve put a downloadable copy of the script here the code itself looks like

Mailboxurl = "http://server/exchange/mailbox/inbox"

Emailreport = "<table border=""1"" cellpadding=""0"" cellspacing=""0""
width=""100%"">" & vbcrlf
Emailreport = Emailreport & "<table border=""1"" cellpadding=""0"" cellspacing=""0""
width=""100%"">" & vbcrlf
Emailreport = Emailreport & " <tr>" & vbcrlf
Emailreport = Emailreport & "<td align=""center"">Time</td>" & vbcrlf
Emailreport = Emailreport & "<td align=""center"">From</td>" & vbcrlf
Emailreport = Emailreport & "<td align=""center"">Subject</td>" & vbcrlf
Emailreport = Emailreport & "<td align=""center"">Attachment</td>" & vbcrlf
Emailreport = Emailreport & "</tr>" & vbcrlf

strComputer = "."
set shell = createobject("wscript.shell")
strValueName = "HKLM\SYSTEM\CurrentControlSet\Control\TimeZoneInformation\ActiveTimeBias"
minTimeOffset = shell.regread(strValueName)
toffset = datediff("h",DateAdd("n", minTimeOffset, now()),now())
dtListFrom = DateAdd("n", minTimeOffset, now())
dtListFrom = DateAdd("d",-1,dtListFrom)

set rs = createobject("ADODB.Recordset")
set conn = createobject("ADODB.Connection")
conn.Provider = "ExOLEDB.Datasource"
conn.Open Mailboxurl, "", "", -1
Set Rs.ActiveConnection = conn
Rs.Source = "SELECT ""DAV:href"", " & _
" ""urn:schemas:mailheader:subject"", " & _
" ""urn:schemas:httpmail:fromname"", " & _
" ""urn:schemas:httpmail:datereceived"", " & _
" ""urn:schemas:httpmail:hasattachment"", " & _
" ""http://schemas.microsoft.com/mapi/proptag/0x0FFF0102"" " & _
"FROM scope('shallow traversal of """ & Mailboxurl & """') " & _
"WHERE (""urn:schemas:httpmail:datereceived"" >= CAST(""" & isodateit(dtListFrom)
& """ as 'dateTime'))" & _
" AND ""DAV:contentclass"" = 'urn:content-classes:message'"
If Not (Rs.EOF) Then
Do Until Rs.EOF
Emailreport = Emailreport & " <tr>" & vbcrlf
Emailreport = Emailreport & "<td align=""center"">" &
& "</td>" & vbcrlf
Emailreport = Emailreport & "<td align=""center"">" &
rs.fields("urn:schemas:httpmail:fromname") & "</td>" & vbcrlf
Emailreport = Emailreport & "<td align=""center""><a href=""outlook:" &
Octenttohex(rs.fields("http://schemas.microsoft.com/mapi/proptag/0x0FFF0102")) &
""">" & rs.fields("urn:schemas:mailheader:subject") & "</a></td>" & vbcrlf
Emailreport = Emailreport & "<td align=""center"">" &
rs.fields("urn:schemas:httpmail:hasattachment") & "</td>" & vbcrlf
Emailreport = Emailreport & "</tr>" & vbcrlf
end if

Emailreport = Emailreport & "</table>" & vbcrlf
REm Email Bit
Set objEmail = CreateObject("CDO.Message")
objEmail.From = "source@yourdomain.com"
objEmail.To = "target@yourdomain.com"
objEmail.Subject = "Email Summary for " & formatdatetime(now(),2)
objEmail.htmlbody = Emailreport
= 2
= "servername"
= 25

wscript.echo "Report Sent"

function isodateit(datetocon)
strDateTime = year(datetocon) & "-"
if (Month(datetocon) < 10) then strDateTime = strDateTime & "0"
strDateTime = strDateTime & Month(datetocon) & "-"
if (Day(datetocon) < 10) then strDateTime = strDateTime & "0"
strDateTime = strDateTime & Day(datetocon) & "T" & formatdatetime(datetocon,4) &
isodateit = strDateTime
end function

Function Octenttohex(OctenArry)
ReDim aOut(UBound(OctenArry))
For i = 1 to UBound(OctenArry) + 1
if len(hex(ascb(midb(OctenArry,i,1)))) = 1 then
aOut(i-1) = "0" & hex(ascb(midb(OctenArry,i,1)))
aOut(i-1) = hex(ascb(midb(OctenArry,i,1)))
end if
Octenttohex = join(aOUt,"")
End Function

Adding the Creator of Calendar appointments to appointments that already exist in a public calendar

A while ago I posted this event sink that can be used in a public calendar folder that will add the person who created the appointment so that it is visible when the appointment is looked at in Outlook or OWA. Being an event sink this will only have an effect on any new appointments that where added to the calendar. Somebody asked this week about modifying existing appointments in a calendar. This can be done using the same type of code and also including a query for a specific date range that will create a recordset of all the items in the calendar and then iterate though that recordset and add the creator to the location field of the appointment if it doesn’t already exist. Because this script use Exoledb it must be run locally on the server where the public folder is located. The date range in the query is set to modify all appointments between December 2005 and December 2006 and will affect both normal and recurring appointments.

I’ve put a downloadable version of the script here the code itself looks like

calurl = "file://./backofficestorage/youdomain.com/public folders/calenderfoldername"
call updateappointment(calurl,0)
wscript.echo "Reccuring Appointments"
call updateappointment(calurl,1)

sub updateappointment(CalendarURL,instancetype)

set rec = createobject("ADODB.Record")
set rec1 = createobject("ADODB.Record")
set rs = createobject("ADODB.Recordset")
Rec.Open CalendarURL
Set Rs.ActiveConnection = Rec.ActiveConnection
Rs.Source = "SELECT ""DAV:href"", " & _
" ""urn:schemas:calendar:location"", " & _
" ""urn:schemas:calendar:instancetype"", " & _
" ""urn:schemas:calendar:dtstart"", " & _
" ""urn:schemas:calendar:dtend"", " & _
" ""http://schemas.microsoft.com/mapi/proptag/0x3FF8001E"" " & _
"FROM scope('shallow traversal of """ & CalendarURL & """') " & _
"WHERE (""urn:schemas:calendar:dtstart"" >= CAST(""2005-12-01T08:00:00Z"" as 'dateTime'))
" & _
"AND (""urn:schemas:calendar:dtend"" <= CAST(""2006-12-01T08:00:00Z"" as 'dateTime'))"
& _
" AND (""urn:schemas:calendar:instancetype"" = " & instancetype & ")"
If Not (Rs.EOF) Then
Do Until Rs.EOF
ItemURL = Rs.Fields("DAV:Href").Value
wscript.echo ItemURL
creator = " Created by " & Rs.Fields("http://schemas.microsoft.com/mapi/proptag/0x3FF8001E").Value

wscript.echo creator
if instr(rs.fields("urn:schemas:calendar:location"),"Created by") then
wscript.echo "Creator Exists"
rec1.open Rs.Fields("DAV:Href").Value,,3
rec1.fields("urn:schemas:calendar:location") = rec1.fields("urn:schemas:calendar:location")
& creator
wscript.echo "Added Creator"
end if
end if

end sub


Thursday, January 05, 2006

Script to report on the IMF update Version

Now that IMF updates are available though Microsoft update see and the updates should start getting more regular I wanted something that would tell me what version of the IMF update my servers where running. After reading KB907747 the way to do this seemed to be to write a script that would query a few keys in the registry on each of the servers.

“The existing active version of the .dat file that is currently installed on the computer is recorded under the following registry subkey:
HKEY_LOCAL_MACHINE\SOFTWARE\Microsoft\Updates\Exchange Server 2003\SP3
For example, after you install the IMF-KB907747-2005.12.14-x86.exe update, the registry entry is similar to the following:
HKEY_LOCAL_MACHINE\SOFTWARE\Microsoft\Updates\Exchange Server 2003\SP3\KB907747
This registry entry is verified every time that an update is offered for installation. If an update is successfully installed, the registry entry is updated.” And from the Exchange Team Blog “Over the course of the regular update cycle, this date will change while the name/number of the KB itself ‘(KB907747)’ will remain intact”

So a basic script to show the date of the last IMF update package can be done by querying the “HKEY_LOCAL_MACHINE\Software\Microsoft\Updates\Exchange Server 2003\SP3\KB907747\ PackageVersion” registry key

I put this together in a script that will query all the Exchange servers in a domain and then query this registry key on each of these servers and display if there are any IMF updates that have been applied and the date the update was applied. I’ve put a downloadable copy of this script here the script itself looks like this

set conn = createobject("ADODB.Connection")
set com = createobject("ADODB.Command")
Set iAdRootDSE = GetObject("LDAP://RootDSE")
strNameingContext = iAdRootDSE.Get("configurationNamingContext")
Conn.Provider = "ADsDSOObject"
Conn.Open "ADs Provider"
svcQuery = "<LDAP://" & strNameingContext & ">;(objectCategory=msExchExchangeServer);name,serialNumber,distinguishedName;subtree"
Com.ActiveConnection = Conn
Com.CommandText = svcQuery
Set Rs = Com.Execute
Wscript.echo "Exchange Servers Versions IMF Updates"
While Not Rs.EOF
arrSerial = rs.Fields("serialNumber")
For Each Serial In arrSerial
strexserial = Serial
call getIMFversion(rs.fields("name"))

Set Rs = Nothing
Set Com = Nothing
Set Conn = Nothing

function getIMFversion(strComputer)
on error resume next
const HKEY_LOCAL_MACHINE = &H80000002
Set objReg=GetObject("winmgmts:{impersonationLevel=impersonate}!\\" _
& strComputer & "\root\default:StdRegProv")

strKeyPath = "Software\Microsoft\Updates\Exchange Server 2003\SP3\KB907747"
objReg.GetStringValue HKEY_LOCAL_MACHINE, strKeyPath, "PackageVersion", Imfver
objReg.GetStringValue HKEY_LOCAL_MACHINE, strKeyPath, "InstalledDate",
if isnull(Imfver) then
wscript.echo strComputer & " : No IMF Updates Installed"
wscript.echo strComputer & " : " & Imfver
wscript.echo "Update Installed on : " & Imfinstdate
end if

end function

Exporting Active Directory users contacts and the Exchange GAL to a vCard

I’ve written a bit about vCards in the past mostly these where import scripts but one of the other ways you can use vCards with CDOEX is if you want to export active directory users or contacts to be used in another system. Basically with CDOEX you can use Iperson to create or modify Active directory users or contacts see the Exchange SDK for samples. You can also go the same way and use CDOEX and the GetvCardStream stream method to export an Active Directory user or contact to a vcard. eg

set iper = createobject("CDO.Person")
iper.datasource.open "LDAP://userdn"
Set strm = iper.GetvCardStream
strm.savetofile "c:\exp.vcf"

So if we expand out this idea a bit say I want to export the Exchange GAL to use in the palm desktop I can use an ADSI query to return all the distinguishedName’s of the user’s in the Exchange GAL and then use CDOEX and Iperson to connect to each object and use GetvCardStream method to get the Vcard text stream and create a vcard that contains all the users in the Exchange GAL that can them be imported in the palm desktop or any other application that you might want to have the Exchange GAL imported into.

I’ve created 2 versions of the script to do this the first version exptovcardsng.vbs exports all the users in the Exchange GAL to a single vcf file which is useful for programs like the Palm desktop. The other version exports all the users in the gal to separate VCF files for each user. Because these scripts use LDAP they don’t need to be run directly on a Exchange server as long as you have CDOEX installed locally on the workstation you are running the script from. I’ve put a downloadable copy of the 2 scripts here

The script itself looks like

Set objSystemInfo = CreateObject("ADSystemInfo")
set iper = createobject("CDO.Person")
strdname = objSystemInfo.DomainShortName
set conn = createobject("ADODB.Connection")
set com = createobject("ADODB.Command")
Set iAdRootDSE = GetObject("LDAP://RootDSE")
strNameingContext = iAdRootDSE.Get("defaultNamingContext")
Conn.Provider = "ADsDSOObject"
Conn.Open "ADs Provider"
Query = "<LDAP://" & strNameingContext & ">;(&(&(&(& (mailnickname=*)(!msExchHideFromAddressLists=TRUE)(|(&(objectCategory=person)
(objectClass=user)(msExchHomeServerName=*)) )))));samaccountname,displayname,distinguishedName;subtree"
Com.ActiveConnection = Conn
Com.CommandText = Query
Com.Properties("Page Size") = 1000
Set Rs = Com.Execute
vcardwrite = ""
While Not Rs.EOF
iper.datasource.open "LDAP://" & rs.fields("distinguishedName")
Set strm = iper.GetvCardStream
vcardwrite = vcardwrite & strm.readtext & vbcrlf
Set fso = CreateObject("Scripting.FileSystemObject")
set wfile = fso.opentextfile("c:\temp\export.vcf",2,true)
wfile.writeline vcardwrite
set wfile = nothing