netnerds.net

2Jul/06156

IIS: Instantly Ban IPs Attempting to Login to MS-FTP as Administrator

UPDATE 12/18/06: The startup script has been modified slightly (cscript.exe was changed to wscript.exe). Now, console users will no longer encounter a blank black box upon login.
UPDATE 11/21/06: Now that banning at the IP level has been added to the script, offending users are completely banned before they can even attempt a second login on Windows 2003 machines. Windows 2000 machines still have a slight delay.
UPDATE 1/18/07: I updated the code so that it does not throw out an "unknown" error on line 48 for Win2k users. Thanks to the commenters for figuring that out. Also, check the comments for some really great script modification and ideas posted by blog visitors. Thanks to everyone who has posted!

Recently while reviewing my Windows Events, I noticed over 2800 failed login attempts to my Microsoft FTP server. Apparently, a bot was trying to brute force the Administrator password. Thankfully, I soon determined that there were only two IPs I needed to ban. The FTP server needs to be accessed by my employer's clients so I couldn't just change the port (as I did when I found 30,000+ SSH login attempts to my Linux box). Thus, I decided to instantly ban any IPs attempting to login as Administrator. I did this using a VBScript file (saved as: C:\Scripts\Startup\banftpips.vbs) that is set to execute upon boot-up. This can be done like so.

banftpips.vbs

'****************************************************************************
' This script created by Chrissy LeMaire (clemaire@gmail.com)
' Website: http://netnerds.net/
'
' NO WARRANTIES, etc.
'
' This script instantly bans IP addresses trying to login to FTP
' using the NT account "Administrator"
'
' Run this script on the FTP server. It sits in the back and waits for an
' event viewer "push" that lets it know someone failed FTP authentication.
'
' This script has only been tested on Windows Server 2003. It assumes, as it
' should, that there are no legitimate Administrator account FTP logins.
'
' "What it does"
' 1. Sets an Async Event Sink to notify the script when someone fails MS-FTP auth
' 2. When alerted, the script parses the last day's FTP logs for all FTP sites (this
'    is because the Event Viewer doesn't tell you which FTP site, if you have more than
'    one, is the one getting hit)
' 3. Compiles the list of IPs to be banned and then bans them using IIS /and/
'    IP level banning (thanks Spencer @ netortech.com for the idea)
'*****************************************************************************

' Push Event Viewer Alert
    Set objWMIService = GetObject("winmgmts:{(security)}!root/cimv2")
    Set eventSink = wscript.CreateObject("WbemScripting.SWbemSink", "EVSINK_")
    strWQL = "Select * from __InstanceCreationEvent where TargetInstance isa  'Win32_NTLogEvent' and TargetInstance.SourceName = 'MSFTPSVC' and TargetInstance.EventCode = 100"
    objWMIService.ExecNotificationQueryAsync eventSink,strWQL

' Keep it going forever
While (True)
    Wscript.Sleep(1000)
Wend

Sub EVSINK_OnObjectReady(objObject, objAsyncContext)
If InStr(LCase(objObject.TargetInstance.Message),"administrator") > 0 Then
Set objFTPSVC = GetObject("IIS://localhost/MSFTPSVC")
Set WshShell = CreateObject("WScript.Shell")
Set objFSO = CreateObject("Scripting.FileSystemObject")
Set objLog = CreateObject("MSWC.IISLog")
Set objDictionary = CreateObject("Scripting.Dictionary")
Set objFTPIPSec = objFTPSVC.IPSecurity

'Get IP address of server so we can use it later to give the offending IP a bad route
Set IPConfigSet = GetObject("winmgmts:\\.\root\cimv2").ExecQuery("SELECT * FROM Win32_NetworkAdapterConfiguration WHERE IPEnabled=TRUE")
for each IPConfig in IPConfigSet
  if Not IsNull(IPConfig.DefaultIPGateway) then serverIP =  IPConfig.IPAddress(0)
Next
Set IPConfigSet = Nothing

'Iterate through each FTP site. See #2 up above.
For Each objSITE in objFTPSVC
If lcase(objSITE.class) = "iisftpserver" Then
ftpLogFilePath =  WshShell.ExpandEnvironmentStrings(objSITE.LogFileDirectory) & "\msftpsvc" & objSITE.Name

Set objFolder = objFSO.GetFolder(ftpLogFilePath)
Set objFiles = objFolder.Files
For Each fileName In objFiles
lastFile = fileName
Next
strLogFile = lastFile
Set file = Nothing
Set objFolder = Nothing

'Use the IIS log file parser provided by MSFT
objLog.OpenLogFile strLogFile, 1, "MSFTPSVC", 1, 0
'(FileName,IOMode,ServiceName,ServiceInstance,OutputLogFileFormat)
' 0 = NotApplicable, 1 = ForReading
While NOT objLog.AtEndOfLog
objLog.ReadLogRecord
If LCase(objLog.URIStem) = "administrator" Then
ClientIP = objLog.ClientIP
If objDictionary.Exists(ClientIP) = False Then
   'Kill the route to the machine then add it to the array of banned IPs.
   Set WshShell = WScript.CreateObject("WScript.Shell")
WshShell.Run "ROUTE ADD " & clientIP & " MASK 255.255.255.255 " & serverIP, 1, True
Set WshShell = Nothing
objDictionary.Add ClientIP, "255.255.255.255" '255 is just there for padding.
End If
End If
Wend
objLog.CloseLogFiles 1
End If
Next

'Append the newly banned IPs to the currently banned IPs
If objDictionary.Count > 0 And objFTPIPSec.GrantByDefault = True Then
bannedIPArray = objFTPIPSec.IPDeny
For i = 0 to ubound(bannedIPArray)
clientIP = Left(bannedIPArray(i),InStr(bannedIPArray(i),",")-1)
If objDictionary.Exists(ClientIP) = False Then
objDictionary.Add bannedIPArray(i), "255.255.255.255"
End If
Next

objFTPIPSec.IPDeny = objDictionary.Keys
objFTPSVC.IPSecurity = objFTPIPSec
objFTPSVC.SetInfo
End If

Set objFTPIPSec = Nothing
Set objDictionary = Nothing
Set objLog = Nothing
Set objFSO = Nothing
Set objFTPSVC = Nothing
End If
End Sub

Once the IP has been added to the ban list, the user will no longer be able to connect to the machine via TCP/IP as it has been given a bad route. If the server reboots, it will lose the route but the IP will still be banned in IIS. The offending user will then see the following message:

Connected to ftpserver
530 Connection refused, unknown IP address.
421 Service not available, closing control connection.
Connection closed by remote host.

Note that this bans the IPs on a global FTP level. You will find the banned IPs listed under Windows 2003 @ IIS -> FTP -> Properties and under Windows 2000 @ IIS -> Hostname -> Properties -> FTP Service -> Edit -> Directory Security. This means you will not find it on the properties of the Default FTP Site. This pro-actively bans the IPs from hitting other FTP sites in an IIS setup with multiple FTP sites. In addition, with the new ban at the IP level, the machine can't even contact your server until your Windows server has been rebooted and the manual routes have thus been reset.

Posted by: Chrissy LeMaire   Filed under: IIS, Security, VBScript 156 Comments
1May/061

IIS: MD_CUSTOM_ERROR (6008)

I recently loaded up one of my servers and out of nowhere, I ran into this error:

Server Configuration Error
The server has encountered a configuration error attempting to process your request. The configuration parameter MD_CUSTOM_ERROR (6008) has an invalid value. Please contact the server administrator for assistance.

I searched the web but couldn't find anything useful. I then started exploring the website's configuration under IIS Properties and I received a similar error when loading up the tab "Custom Errors." I saw a malformed error (401;3 was pointing to C:\windows\help for some reason), set it to default and the site worked again.

Posted by: Chrissy LeMaire   Filed under: IIS, Linux 1 Comment
28Apr/0650

ASP: mod_rewrite or URLRewrite with Classic ASP (Sorta)

Using a Custom 404 error script in IIS makes it possible to emulate a very basic URL rewrite.

Sample Environment
- IIS 6
- Website domain: "www.me.com"
- URLRewrite directory: "code"

Steps
1. Create a file named rewrite.asp in the code directory.
2. Add a Custom Error for the 404 Not Found. Open up IIS -> Right click Website -> Properties -> Custom Errors -> 404,URL,"/code/rewrite.asp" or you can do it programatically:

Add custom 404 for a specific directory in a specific website.

<%
on error resume next
myDomain = "www.me.com"
myrewritedir = "code"

Set objW3SVC = GetObject("IIS://localhost/W3SVC")
For Each objSITE in objW3SVC
If objSITE.class = "IIsWebServer" Then

websiteNameArr = objSITE.ServerBindings
for j = 0 to Ubound(websiteNameArr)
websiteName = websiteNameArr(j)

If instr(websiteName,myDomain) > 0 then

Set objIISNewDir = GetObject("IIS://localhost/W3SVC/" & objSite.Name & "/root")
Set CodeDir = objIISNewDir.Create("IIsWebDirectory",myrewritedir )
CodeDir.SetInfo
Set objIISNewDir = Nothing

Set objIISRewriteRootDir = GetObject("IIS://localhost/W3SVC/" & objSite.Name & "/root/" & myrewritedir)
CustomErrors = objIISRewriteRootDir.HttpErrors
For i = 0 To UBound(CustomErrors)
    If Left(CustomErrors(i),3) = "404" then
        CustomErrors(i) = "404,*,URL,/" & myrewritedir & "/rewrite.asp"
        objIISRewriteRootDir.HttpErrors = CustomErrors
objIISRewriteRootDir.SetInfo
        Exit For
End If
Next
Set objIISRewriteRootDir = Nothing
End if
Next

End if
Next
%>

3. Copy and paste the following code into rewrite.asp

Rewrite Code

<%
'In IIS, 404 pages that are directed to an URL have the "error" URL attached in the query string.
'It looks something like this 404;http://www.me.com:80/code/nosuchfileblahblah1.asp
'We're gonna use it.. so grab it.

script = request.servervariables("QUERY_STRING")
if instr(script,"/") > 1 then
     myArray = split(script,"/")
     if instr(myArray(Ubound(myArray)),".asp") = 0 then
     myID = myArray(Ubound(myArray)) 'This is the method for obtaining the ID if you end your URL in the ID. Example: http://www.me.com/code/this-is-good-code/1
     else
     myID = replace(myArray(Ubound(myArray)),".asp","")  'This is the method for obtaining the ID if you end your URL in a fake extension. Example: http://www.me.com/code/this-is-good-code/1.asp
     end if
end if

'Now that you've extracted your code ID, just go about your business, you are done!
'Here's some sample code

if isNumeric(myID) then 'Make sure it's an ID and not some malicious code
response.write "The ID Extracted from the URL is: " & myID
else
response.redirect "http://www.me.com"
end if
%>

4. Call your tricked out webpage URI.

I installed a sample of this script @ netnerds.net. Click on the following URL to see the results: netnerds.net/code/example-in-the-house/1. You can also look around RealCajunRecipes.com. I use that code (heavily modified) in the recipes directory to make SEO friendly URLs. Woo!

Posted by: Chrissy LeMaire   Filed under: IIS 50 Comments
27Apr/0620

ASP: Sustain Remote Cookie Sessions in an ASP/VBScript

I dug up this cold from my old netnerds blog. For Googlers wondering if sustaining a remote session is possible, the answer is yes; I've sustained remote cookie sessions using both ASP & VBScript. I've provided simplified code below. It should be self explanatory. If not, drop me a comment and I'll explain it.

Dirty Code

<%
url1 = "http://www.netnerds.net/session/login.asp"
url2 = "http://www.netnerds.net/session/controlPanel.asp"

data1 = "username=bobby&pass=thepass&submit=Login"

theCookie = httpSessionRequest(1, "POST", url1, data1, noCookie, noViewState)
finalHTML = httpSessionRequest(2, "GET", url2, nodata, theCookie, noViewState)

response.write finalHTML
'---------------------------------------------------------
'THE FUNCTION
'---------------------------------------------------------

Function httpSessionRequest(theStep, method, url, data, cookie, viewState)
'FYI, viewstate code has been ripped out.
'Previously, I screenscraped to get the viewstate hidden field for aspx pages.

baseURL = "http://www.netnerds.net/" 'This is to fix any broken images in the output.

if len(cookie) = 0 then cookie = "dummy=dummy;"
HTTPReferrer = Trim(url)
postVars = Trim(data)

Set XMLHTTP = server.CreateObject("MSXML2.serverXMLHttp")
XMLHTTP.open method, Trim(url), false

if UCASE(method) = "POST" Then
XMLHTTP.SetRequestHeader "Content-Type", "application/x-www-form-urlencoded"
End If
XMLHTTP.SetRequestHeader "Referer", HTTPReferrer 'just in case the server cares
XMLHTTP.setRequestHeader "Cookie", "excuse the Microsoft bug"
XMLHTTP.setRequestHeader "Cookie", cookie

XMLHTTP.send postVars

'wait for response
While XMLHTTP.readyState <> 4
XMLHTTP.waitForResponse 1000
Wend
strHeaders = XMLHTTP.getAllResponseHeaders()

hArr = split(strHeaders,"Set-Cookie: ")
for kk = 1 to ubound(hArr)
theCookie = left(hArr(kk),instr(hArr(kk),"path=/")-2)
myCookie = myCookie & " " & theCookie
next

if len(myCookie) = 0 then mycookie = cookie
sReturn = replace(XMLHTTP.responsetext,"../",baseURL)

if cint(theStep) = 1 then
httpSessionRequest = mycookie
elseif cint(theStep) = 2 then
httpSessionRequest = sReturn
elseif cint(theStep) = 3 then
'You can add stuff here to debug
httpSessionRequest = mycookie
response.write theCookie & "<p>" & mycookie & "<p>" & sReturn  & "<hr /><p>"
end if
set XMLHTTP = nothing
END FUNCTION
%>
Posted by: Chrissy LeMaire   Filed under: IIS, VBScript 20 Comments
26Apr/062

VBScript: Ban IPs in IIS Programatically

I used the following code a while back as part of a solution to automate the banning of spammers via their IP address.

'Here, we will pretend this is an imported list
Dim XMLarr(1)
XMLarr(0) = "65.19.238.21"
XMLarr(1) = "198.31.175.100"

Set objIIS = GetObject("IIS://localhost/w3svc") 'careful, this sets IPDENY on every child web
Set IISipsec = objIIS.IPSecurity
If (IISipsec.GrantByDefault = True) Then
arrIP = IISipsec.IPDeny
arrIPSize = ubound(arrIP)
For i = 0 to arrIPSize
arrIPstring = arrIPstring & "," & arrIP(i) 'going to use for dupes later.
Next
ReDim preserve arrIP(arrIPSize + UBound(XMLarr)+1)
for i = 0 to UBound(XMLarr)
myNum = arrIPSize + i+1
If InStr(arrIPstring,XMLarr(i)) = 0 Then
arrIP(myNum) = XMLarr(i)
End If
Next
IISipsec.IPDeny = arrIP
objIIS.IPSecurity = IISipsec
objIIS.SetInfo
End If
Set IISipsec = nothing
Set objIIS = nothing
Posted by: Chrissy LeMaire   Filed under: IIS, Security, VBScript 2 Comments
24Apr/067

ASP: Easily Parse and Consume RSS in Classic ASP

This script should work right out of the box..

Dirty Code

<%
Call getNews(10)

Sub getNEWS(howManyResults)
myRSSfile = "http://rss.news.yahoo.com/rss/tech"

Set xmlHttp = Server.CreateObject("MSXML2.XMLHTTP.4.0")
xmlHttp.Open "Get", myRSSfile, false
xmlHttp.Send()
myXML = xmlHttp.ResponseText

Set xmlResponse = Server.CreateObject("MSXML2.DomDocument.4.0")
xmlResponse.async = false
xmlResponse.LoadXml(myXML)
Set xmlHttp = Nothing

Set objLst = xmlResponse.getElementsByTagName("item")
Set xmlResponse = Nothing

intNoOfHeadlines = objLst.length -1

For i = 0 To (intNoOfHeadlines)
Set     objHdl = objLst.item(i)

for each child in objHdl.childNodes
Select case lcase(child.nodeName)
    case "title"
          title = child.text
    case "link"
          link = child.text
    case "description"
          description = child.text
     'You can also use the following: author,category,comments,enclosure,guid,pubDate,source
End Select
next

   kk = kk+1
     if kk < howManyresults+1 then
    Response.Write "<br /><a href=""" & link & """>" & title & "</a> <br /> " & description

    end if

Next
End Sub
%>
Posted by: Chrissy LeMaire   Filed under: IIS 7 Comments
10Apr/061

VBScript: IIS Discovery

Find IIS Servers on your domain (or with modifications, your subnet) using this script

Dirty Code

''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''
' Author: Chrissy LeMaire
' Copyright 2003 NetNerds Consulting Group
' Script is provided AS IS with no warranties or guarantees and assumes no liabilities.
' Website: http://www.netnerds.net
''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''
On Error Resume Next

theDomain = "mydomain" ' set this here if you are probing a different domain

Set objNet = CreateObject("WScript.Network")
myFullUsername = trim(objNet.Userdomain & "" & objNet.UserName)
myComputerName = trim(objNet.ComputerName)

if len(theDomain) = 0 then
theDomain = Trim(objNet.Userdomain)
end if
myUsername = Trim(objNet.UserName)
set objNet = nothing

'in case the groups below do not exist

ADMIN = FALSE
Set objGroup = GetObject("WinNT://" & theDomain & "/Domain Admins")
For Each objUser in objGroup.Members
If LCase(myusername) = LCase(objUser.Name) Then
ADMIN = TRUE
End If
Next
Set objGroup = Nothing

Set objGroup = GetObject("WinNT://" & theDomain & "/Enterprise Admins")
For Each objUser in objGroup.Members
If LCase(myusername) = LCase(objUser.Name) Then
ADMIN = TRUE
End If
Next
Set objGroup = Nothing

IIS = FALSE
err.Clear ' to clear any possible errors from above

Set objW3SVC = GetObject("IIS://" & myComputerName & "/W3SVC")
if err.Number = 0 Then
IIS = TRUE
end If

if ADMIN = FALSE Then
MsgBox "You are not a Domain administrator. This script will not work properly."
Wscript.Quit
End If

if IIS = FALSE Then
MsgBox "IIS is not installed. This script will not work properly."
Wscript.Quit
End If
set objW3SVC = Nothing

on error goto 0

startTime = Timer()

'Use the WinNT Directory Services
theDomain = "WinNT://" & theDomain
'Create the Domain object
Set objDomain = GetObject(theDomain)
'Search for Computers in the Domain
objDomain.Filter = Array("Computer")

On error resume next
serverCount = 0
IISServerCount = 0

For Each Computer In objDomain
theServer = Computer.Name
serverCount = ServerCount + 1
Set objW3SVC = GetObject("IIS://" & theServer & "/W3SVC")
Select Case Err.Number
' 70 = permission denied; Strong indication of IIS Server in a DMZ
' 462 = The remote server machine does not exist or is unavailable
' 429 = ActiveX component can't create object
' -2147023169 = CreateObject Failed
' -2147012889  = Name could not be resolved
case  462, 429
thehttpVersion = cint(httpVersion(theServer))
if thehttpVersion > 0 and thehttpVersion < 404 then
IISServerCount = IISServerCount + 1
IISTrue = IISTrue & "IIS Server: " & theServer & VbCrLf
else
IISFalse = IISFalse & "NOT: " & theServer & VbCrLf
end if
case 70,-2147023169,-2147012889
IISUnknown = IISUnknown & "Possibly: " & theServer & VbCrLf
case 0,-2146646000
IISServerCount = IISServerCount + 1
IISTrue = IISTrue & "IIS Server: " & theServer & VbCrLf
set objW3SVC = Nothing
case else
'wscript.echo theServer & ": " & errNumber & ", " & err.Description & "<p>" ' for debugging
end Select

Err.Clear
Next
Set objDomain = Nothing

Function httpVersion(theHost)
On Error Resume Next
Set objxmlHTTP = createobject("MSXML2.ServerxmlHTTP")
theURL= "http://" & theHost
objxmlHTTP.open "GET", theURL, false
objxmlHTTP.send()
tempVersion = objxmlHTTP.getResponseHeader("Server")
If errNumber = -2147012867 Then
NOSERVER = TRUE
Else
NOSERVER = FALSE
End If
set objxmlHTTP = nothing

If instr(tempversion,"Microsoft-IIS/") > 0 then
tempVersion = replace(tempVersion,"Microsoft-IIS/","")
httpVersion = trim(tempVersion)
else
if NOSERVER = TRUE then
httpVersion = "404" ' webserver not found ;)
err.Clear
Else 'there was a webserver there, but probably not an IIS Server
httpVersion = "0"
End If
End If
End Function

finishtime = Timer()
totalTime = finishtime - startTime

myStr = myStr &  "Total Time Taken: " & totalTime & " seconds" &  vbCrLf & vbCrLf
myStr = myStr &  "Total Servers Scanned: " & serverCount & vbCrLf
myStr = myStr &  "Total Servers Found: " & IISServerCount &  vbCrLf & vbCrLf
myStr = myStr &  IISTrue & vbCrLf
myStr = myStr &  IISUnknown & vbCrLf
'myStr = myStr &  IISFalse & vbCrLf
myStr = myStr &  "Done" & vbCrLf

wscript.echo myStr

Requirements: A Windows Domain, local IIS install, domain administrator rights.

Posted by: Chrissy LeMaire   Filed under: IIS 1 Comment
10Apr/061

VBScript: Shortcuts for IIS Log Directories

I love parsing through IIS logfiles but I dislike the directory structure that Microsoft creates for IIS logging. Each site has a randomly generated directory name so, for example, "netnerds.net" could store its files in "C:\windows\system32\logfiles\W3SVC720199813." Who wants to track that down? Not me! Use the attached script to change your ugly directory names into something that's human readable.

Dirty Code

'****************************************************************************
' This script created by Chrissy LeMaire (clemaire@gmail.com)
' Website: http://netnerds.net/
'
' NO WARRANTIES, etc.
'
' This script creates human readable shortcuts to your
' IIS logging directories.
'
' Requirements -- ability to read IIS:// and permissions to
' create subdirectories and files in your logging directory. If you encounter
' any problems, its probably because you don't have proper permissions.
'
' This script has only been tested on Windows Server 2003.
'
' "What it does"
' 1. Reads the master log file path (usually C:\windows\system32\logfiles)
' 2. Creates a directory called Shortcuts in the master log file path
' 3. Creates shortcuts to each directory using the website name
'    listed in the IIS websites tree. Done!
'*****************************************************************************

  Set objW3SVC = GetObject("IIS://localhost/W3SVC")
  MasterLogFilePath = objW3svc.LogFileDirectory
  shortcutDir = MasterLogFilePath & "\Shortcuts"

     Set fso = CreateObject("Scripting.FileSystemObject")
      If fso.FolderExists(shortcutDir) = FALSE Then
   fso.CreateFolder(shortcutDir)
   else
   fso.DeleteFolder(shortcutDir) 'empty it out..keep it clean. so you can run this every few months
   fso.CreateFolder(shortcutDir)
    End If
   Set fso = Nothing

For Each objSITE in objW3SVC
  If objSITE.class = "IIsWebServer" Then
   websiteName = objSITE.ServerComment
   w3LogFilePath =  objSITE.LogFileDirectory & "\w3svc" & objSITE.Name

    if  objSITE.LogType > 0 and objSITE.ServerState <> 4 then 'If logging is enabled and the website is running.

         set WshShell = CreateObject("WScript.Shell")
          set oShellLink = WshShell.CreateShortcut(shortcutDir & "" & websiteName & ".lnk")
          oShellLink.TargetPath = w3LogFilePath
          oShellLink.IconLocation = "explorer.exe, 13"
          oShellLink.Description = "Shortcut Script"
          oShellLink.Save
          set oShellLink = Nothing
          set WshShell = Nothing

      siteList = siteList & websiteName & " = " & w3LogFilePath & vbCrLf
       end if
   End if
  Next

MsgBox siteList 'or not
Posted by: Chrissy LeMaire   Filed under: IIS 1 Comment