Retrieving Password from Application Pool

I came across an undoc­u­ment­ed app the oth­er day. For a num­ber of rea­sons, we need­ed to restore the pass­word but it was­n’t doc­u­ment­ed any­where. Luck­i­ly, the ser­vice account was set­up in an app pool. In IIS 7.0 or 7.5, APPCMD can be used to recov­er the pass­word. In 6.0, adsutil.vbs can be used.

cscript.exe /nologo adsutil.vbs GET W3SVC/AppPools/AppPoolName/WAMUserPass

How­ev­er, I want­ed to write my own lit­tle script. Hav­ing a lit­tle tid­bit makes it easy to reuse lat­er for oth­er clients. For exam­ple, I could search AD for SPNs start­ing with “HTTP”, loop through each of their app pools and doc­u­ment the user­name and pass­words for all ser­vice accounts used in this fash­ion. So, here is the lit­tle tid­bit I threw togeth­er.

Option Explicit

Call GetAppPoolUserAndPass("localhost", "ApplicationPoolName")

Private Sub GetAppPoolUserAndPass (byVal strComputer, byVal strAppPool)
	Dim appPool
	On Error Resume Next
	Set appPool = GetObject("IIS://" & strComputer & "/w3svc/AppPools/" & strAppPool)
	If Err Then
		wscript.echo "Error connecting to " & chr(34) & strAppPool & chr(34) & " on " & strComputer
	Else
		wscript.echo strAppPool & vbTab & appPool.WAMUserName & vbTab & appPool.WAMUserPass
	End If
	On Error GoTo 0
End Sub

Here is an exam­ple of just what I men­tioned above. YMMV but this should dis­cov­er IIS box­es and report all the accounts used in their app pools. Note: Pools using built-in accounts will show up with blank pass­words; this is nor­mal; the pass­word isn’t actu­al­ly blank.

Option Explicit

' Determine DNS domain name.
Set objRootDSE = GetObject("LDAP://RootDSE")
strDNSDomain = objRootDSE.Get("defaultNamingContext")

' Determine DNS domain name.
Dim objRootDSE, strDNSDomain
Set objRootDSE = GetObject("LDAP://RootDSE")
strDNSDomain = objRootDSE.Get("defaultNamingContext")

' Use ADO to search Active Directory.
Dim adoCommand, adoConnection
Set adoCommand = CreateObject("ADODB.Command")
Set adoConnection = CreateObject("ADODB.Connection")
adoConnection.Provider = "ADsDSOObject"
adoConnection.Open "Active Directory Provider"
adoCommand.ActiveConnection = adoConnection

' Build Query
Dim strBase, strFilter, strAttributes, strQuery
strBase = ""
strFilter = "(servicePrincipalName=HTTP*)" 'Search for SPN starting w/ HTTP (case insensitive)
strAttributes = "name"
strQuery = strBase & ";" & strFilter & ";" & strAttributes & ";subtree"
adoCommand.CommandText = strQuery
adoCommand.Properties("Page Size") = 100
adoCommand.Properties("Timeout") = 30
adoCommand.Properties("Cache Results") = False

Dim adoRecordset
Set adoRecordset = adoCommand.Execute

If (adoRecordset.EOF = True) Then
    Wscript.Echo "No SPNs Found matching HTTP*"
    Wscript.Quit
End If

Wscript.Echo "Computer Name" & vbTab & "AppPool Name" & vbTab & "User Name" & vbTab & "User Password"

Do Until adoRecordset.EOF
    Call GetApplicationPools(adoRecordset.Fields("name").Value & "." & strDNSDomain)
    adoRecordset.MoveNext
Loop
adoRecordset.Close

' Clean up.
adoConnection.Close

Private Sub GetApplicationPools (byVal strComputer)
	Dim objWMIService, colItems, objItem
	
	On Error Resume Next
	Set objWMIService = GetObject("winmgmts:{authenticationLevel=pktPrivacy}\" & strComputer & "rootmicrosoftiisv2")
	Set colItems = objWMIService.ExecQuery("Select * from IIsApplicationPoolSetting")
	If Err Then
		wscript.echo "Error connecting to " & strComputer
	Else
		For Each objItem in colItems
			Wscript.Echo strComputer & vbTab & objItem.Name & vbTab & objItem.WAMUserName & vbTab & objItem.WAMUserPass
		Next
	End If
End Sub

The Power of Scripting: Finding Morto.A

Here I go on anoth­er vbScript tuto­r­i­al.  You might ask why I’m not doing this in pow­er­shell yet and it is sim­ple: I still run into 2003 and XP envi­ron­ments. Oh yeah, and this works. I don’t care what script­ing lan­guage I’m writ­ing in if it gets the job done; you should­n’t either. My $0.02. If you want to down­load this script, click here: Morto.A Detec­tion Script.

A had to do a lit­tle cleanup on a net­work from the Morto.A worm.  The first thing I want­ed to do was find out how bad things were.  They were report­ing a DDOS across their LAN (most­ly 3389) and a lot of oth­er issues.  It as obvi­ous we were going to need to rebuild a few sys­tems but we want­ed to get a grasp out of what the dam­age was.  This were gen­er­al­ly work­ing: logons, shares, etc.

I’m not going to go over what we did on the fire­wall or local net­work but dis­cuss a quick script I threw togeth­er to scan the net­work for infect­ed sys­tems.  The trick was this net­work had mul­ti­ple domains and sev­er­al com­put­ers that weren’t even domain joined.

So, I put my automa­tion hat on and threw togeth­er a script I will out­line below.  I split this into sec­tions to help you see how you can use script­ing to piece togeth­er a com­pli­cat­ed job in sim­ple tasks.  It is pret­ty basic.  I start by defin­ing what I want to do:

Prob­lem state­ment: I need to check every com­put­er in each domain for infec­tion of the Morto.A worm.  I can split this into three dis­tinct steps:

  1. For Each Domain
  2. Get Each Com­put­er
  3. Check for Morto.A

For Each Domain

This is pret­ty sim­ple.  There are advanced tech­niques for find­ing all the domains in a for­est or read­ing from a text file or even a spread­sheet.  I like to keep it sim­ple and just cre­ate an array.

Dim arrDomains
arrDomains = Array("domain1.domain.local", "domain2.domain.local", "domain3.domain.local")
For Each Domain in arrDomains
	Call ComputersAll(Domain)
Next

Get Each Computer

Now, I need to build the “Com­put­er­sAll” sub-rou­tine.  I call this in the domain script by pass­ing each domain in DNS for­mat.  I used DNS domains in my exam­ple on pur­pose (you could use the Dis­tin­guished­Name for­mat “dc=domain1,dc=domain,dc=local” but I pre­fer DNS names for this pur­pose).  By default, each com­put­er in that domain is going to reg­is­ter itself in DNS (assum­ing you set it up prop­er­ly).  I want to get the com­put­er name out of AD and append the dns domain name.  Once that is done, I want to use my “IsCon­nectible” script to check and make sure it is online.  If it pings, I should be able to car­ry out my busi­ness and find out if it is infect­ed.

The real mag­ic is in the lines: str­Base, strAt­trs and str­Fil­ter.  str­Base sets the search base to the domain you sent the sub-rou­tine.  It isn’t going to go out­side this domain for its search but it will search the whole domain due to its use of sub­tree.  The search a spe­cif­ic OU, you have to use the DN for­mat and that is out of scope of this arti­cle.  The strAt­trs is pret­ty straight for­ward.  This is an array of attrib­ut­es you want returned.  You could do DNS­Do­main­Name but some clients set this improp­er­ly.  It is safe to assume (in most cas­es) that the name and the domain name com­prise the DNS domain name.  The str­Fil­ter isn’t doing much but may look com­plex.  Basi­cal­ly, it is look­ing for com­put­ers in AD that aren’t dis­abled.

The oth­er things to pay atten­tion to is the line after

Do Until ADORecordSet.EOF

.  This checks first if the com­put­er is on the net­work through my “IsCon­nectible” script.  It pass­es the FQDN by append­ing “.” and the domain name to the name attribute from the query.

Const adUseClient = 3
Private Sub ComputersAll(byVal strDomain)
	Set adoCommand = CreateObject("ADODB.Command")
	Set adoConnection = CreateObject("ADODB.Connection")
	adoConnection.Provider = "ADsDSOObject"
	adoConnection.cursorLocation = adUseClient
	adoConnection.Open "Active Directory Provider"
	adoCommand.ActiveConnection = adoConnection

	strBase   =  ";"
	strFilter = "(&(objectClass=computer)(!userAccountControl:1.2.840.113556.1.4.803:=2));"
	strAttrs  = "name;"
	strScope  = "subtree"
	strQuery = strBase & strFilter & strAttrs & strScope
	adoCommand.CommandText = strQuery
	adoCommand.Properties("Page Size") = 5000
	adoCommand.Properties("Timeout") = 30
	adoCommand.Properties("Cache Results") = False

	Set adoRecordset = adoCommand.Execute

	If adoRecordset.RecordCount > 0 Then
		' wscript.echo "Computers for " & strDomain & ": " & adoRecordset.RecordCount
		' Loop through every single computer in the domain
		adoRecordset.MoveFirst
		Do Until adoRecordset.EOF
			If IsConnectible(adoRecordset.Fields("name").Value & "." & strDomain) = "Online" Then Call DetectMorto(adoRecordset.Fields("name").Value & "." & strDomain)
			adoRecordset.MoveNext
		Loop
	End If
	adoRecordset.Close
	adoConnection.Close
End Sub

Check For Morto.A

This is the tricky part. I used sev­er­al sources and found two dis­tict char­ac­ter­isitcs of this work:

  1. C:windowssystem32sens32.dll
  2. HKLM­Sys­temW­PA
    1. Key: sr
    2. Val­ue: Sens

So, I need to check and see if a file and reg­istry key exists in order to detect if the machine is infect­ed.  There are a cou­ple of caveats with this: 1) when check­ing if a file exists, the default response is true on error, 2) you need local admin rights to query the c$ share, and 2) the reg­istry check relies on the remote reg­istry ser­vice.  To over­come these caveats, we put error check­ing in.  If an error occurs, it assumes a man­u­al check is need­ed.  We do this to err on the side of cau­tion.  Of course, on a large net­work, this isn’t desir­able.  Feel free to con­tact me if you need more advanced help.  Here is the sub-rou­tine I came up with to do all of the above.  It is mean and nasty and isn’t exact­ly the kind of finesse I pre­fer but, it works…

First, I try writ­ing a temp file (and delet­ing it if it suc­ceeds). If that works, I should be able to detect if the file exists and con­nect to the remote reg­istry. How­ev­er, per­haps you are an admin that is para­noid and dis­ables the remote reg­istry ser­vice. Well, it will still detect an error and tell you to check man­u­al­ly. Of course, if you don’t have admin rights, it will also tell you to check remote­ly.

Private Sub DetectMorto(byVal strComputer)
	' Detects morto via several methods...
	Dim blnInfected : blnInfected = False
	
	
	' Requires admin rights to properly detect. Check for rights
	Dim objFSO : Set objFSO = CreateObject("Scripting.FileSystemObject")
	On Error Resume Next
	Dim TempFile : Set TempFile = objFSO.CreateTextFile("\" & strComputer & "c$WINDOWStempfile.deleteme", True)
	TempFile.WriteLine("This is a test.")
	TempFile.Close
	Set TempFile = Nothing
	objFSO.DeleteFile("\" & strComputer & "c$WINDOWStempfile.deleteme")	
	If Err Then bnlInfected = "Error"
	Err.Clear
	On Error GoTo 0
	
	' Check for registry key placed by Morto...
	Dim strKeyPath, strEntryName, objReg, strValue
	strKeyPath = "SYSTEMWPA"
	strEntryName = "sr"
	On Error Resume Next
	Set objReg = GetObject("winmgmts:{impersonationLevel=impersonate}!\" & strComputer & "rootdefault:StdRegProv")
	objReg.GetStringValue HKEY_LOCAL_COMPUTER, strKeyPath, strEntryName, strValue

	If Err Then blnInfected = "Error"
	If strValue = "Sens" Then blnInfected = True
	
	' Check for file indicating morto infection
	Dim strMortoSens32
	strMortoSens32 = "\" & strComputer & "c$WINDOWSsystem32Sens32.dll"
	If objFSO.FileExists(strMortoSens32) Then blnInfected = True
	
	' If the Infected flag is set, it is infected...
	If blnInfected = True Then
		wscript.echo "*******MORTO INFECTION: " & strComputer
	ElseIf blnInfected = "Error" Then
		wscript.echo "Error (Check Manually): " & strComputer
	Else
		wscript.echo "Clean: " & strComputer
	End If
End Sub

Conclusion

So, I have tak­en sev­er­al scripts and thrown them togeth­er to find a solu­tion. I can reuse this for any­thing. Per­haps I want to use vbscript to find com­put­ers with a cer­tain file on their hard dri­ve. I can reuse this by just tweak­ing the Mor­to sub-rou­tine.

Part 3: Blocking Bad Hosts - Blocking Them, Easily (CLI Edition)

In part two, I showed you how to use the Local Secu­ri­ty Pol­i­cy GUI to block the bad guys. There were a lot of pret­ty pic­tures for those that pre­fer the GUI. In this ver­sion, I’ll show you how to accom­plish the same thing from the com­mand line. This is my pre­ferred method.  It is much sim­pler to auto­mate and explain.

By fol­low­ing the steps below, you will be able to cre­ate a new pol­i­cy and man­age the fil­ter lists and actions. The goal here will be to put all these pieces togeth­er into a nice tidy pack­age that is ful­ly auto­mat­ed. Read More

Part 2: Blocking Bad Hosts - Blocking Them, Easily (GUI Edition)

In part two, I want to show how you can quick­ly set­up an ipsec pol­i­cy to block the bad hosts you iden­ti­fied in part one. While many meth­ods can be used to block hosts, using the Local Secu­ri­ty Pol­i­cy (secpol.msc) and ipsec is a sim­ple method which can be ful­ly auto­mat­ed.

By fol­low­ing the steps below, you will be able to cre­ate a new pol­i­cy and man­age the fil­ter lists and actions. In part three, I will explain how this can be done from the com­mand line for all you CLI war­riors. This tuto­r­i­al should be accu­rate for: Win­dows XP, Vista, 7 and Serv­er 2003, 2008, 2008R2 (pos­si­bly even 2000) Read More