PowerShell 1.0: Adding Virtual FTP Directories to IIS 6 or 7

Filed under: IIS, PowerShell, Quick Code — Written by Chrissy on Sunday, July 29th, 2007 @ 4:41 pm

While my firm explores using WebDAV and SharePoint 2007 for exchanging large amounts of files, we're temporarily using FTP dropboxes to fill the void. Last Monday, I setup 11 new accounts and it took a total of one hour to complete the same 15 step process (give or take) for each account. By the time I was finished, I decided automating FTP account creation would be my first PowerShell project. What you see below is part of that project.

The code below creates a virtual directory in the "Default FTP Site" of the machine that is running the PowerShell script. The virtual directory called "NewUser" is mapped to C:\FTP\NewUser and is set to be both readable and writable. For the record, I couldn't get WMI to work (get-wmiobject) and that's the reason I decided to use the .NET's Directory Services support.

$server = $env:computername
$service = New-Object System.DirectoryServices.DirectoryEntry("IIS://$server/MSFTPSVC")
$site = $service.psbase.children |Where-Object { $_.ServerComment -eq 'Default FTP Site' }
$site = New-Object System.DirectoryServices.DirectoryEntry($site.psbase.path+"/Root") # <-- IIS 6 requires this. Not sure why. Otherwise, it never appears to commit changes. This line is not required for IIS 7.
$virtualdir = $site.psbase.children.Add("NewUser","IIsFtpVirtualDir")
$virtualdir.psbase.CommitChanges()
$virtualdir.put("Path","C:\FTP\NewUser")
$virtualdir.put("AccessRead",$true)
$virtualdir.put("AccessWrite",$false)
$virtualdir.psbase.CommitChanges()
$service.psbase.refreshCache() # OPTIONAL

Alternatively, you could do go straight for the path if you know it (IIS 6 seems to like this):

$service = New-Object DirectoryServices.DirectoryEntry("IIS://localhost/MSFTPSVC/1/Root")
$virtualdir = $service.psbase.children.Add("NewUser", "IIsFtpVirtualDir")
$virtualdir.psbase.CommitChanges()
$virtualdir.put("Path","C:\FTP\NewUser")
$virtualdir.put("AccessRead",$true)
$virtualdir.put("AccessWrite",$false)
$virtualdir.psbase.CommitChanges()

If you would like to iterate through each of the virtual directories on your FTP server, you can use the following code:

$service = New-Object System.DirectoryServices.DirectoryEntry("IIS://$env:computername/MSFTPSVC")
$site = $service.psbase.children | Where-Object { $_.ServerComment -eq 'Default FTP Site' }
$virtualdirs = $site.psbase.children.Find("Root","IIsFtpVirtualDir").psbase.children
foreach ($virtualdir in $virtualdirs) {$virtualdir.psbase.name}

This code is likely applicable to many of the objects in the IIS ADSI provider. While I've only tested this on Vista (IIS 7), this should also work for Windows XP and 2003's IIS 6 as Vista uses IIS 6's MMC for management.

Also, if you are wondering how I know when to use psbase or psbase.children, I really don't. I just fumble around until I get it to work. The 4 lines above, specifically $virtualdirs = $site.psbase.children.Find("Root","IIsFtpVirtualDir").psbase.children took me about seven hours to figure out. I hear PowerShell 2.0 will have much better support for Directory Services and hopefully that will include support the IIS FTP service.

Classic ASP: "Push" File Downloads from A Directory Outside the Application Root

Filed under: IIS, Quick Code, VBScript — Written by Chrissy on Monday, January 22nd, 2007 @ 4:04 am

This is some super old code but I used it recently and figured I'd archive it on this site for my future reference. The sample code below aims to allow authenticated users to download files which are not available via direct download (ie. files within the web root). The script accomplishes this by doing the following:

1. Checks to see if the user is logged in (your method may vary)
2. Sets the root directory location
3. Checks to see if the file exists, if so...
4. Retrieves the filesize and adds the appropriate HTTP headers including content disposition, filename, content type and filesize.
5. Uses a binary stream to "push" the download

Save this file as download.asp and call it with the filename in the querystring. Example: http://domain.com/downloads/download.asp?filename=myfile.pdf. Also, be sure to give read permissions to IUSR_SvrName to the root directory. Change the authentication requirements as needed:

download.asp

<%
If session("loggedIn") = True Then
 
  strFilePath = "D:\webfiles\downloads"  & request.querystring("filename")
 
  Set objFSO = Server.CreateObject("Scripting.FileSystemObject")
    If objFSO.FileExists(strFilePath) Then
      Set objFile = objFSO.GetFile(strFilePath)
      intFileSize = objFile.Size
      Set objFile = Nothing
 
      strFileName = request.querystring("filename")
      strFileName = replace(request.querystring("filename")," ","-")
      Response.AddHeader "Content-Disposition","attachment; filename=" & strFileName
 
      Response.ContentType = "application/x-msdownload"
      Response.AddHeader "Content-Length", intFileSize
 
      Set objStream = Server.CreateObject("ADODB.Stream")
        objStream.Open
        objStream.Type = 1 'adTypeBinary
        objStream.LoadFromFile strFilePath
        Do While Not objStream.EOS And Response.IsClientConnected
        Response.BinaryWrite objStream.Read(1024)
        Response.Flush()
        Loop
        objStream.Close
      Set objStream = Nothing
    Else
      Response.write "Error finding file."
    End if
  Set objFSO = Nothing
End If
%>

Note: Even if the file is a PDF and a third-party application such as Adobe Reader is set to open the file within the browser, the code below will override that and force a download (by using the "application/x-msdownload" content type).

[The UPDATE below is no longer accurate as an alternative solution has been given below in the comments and subsequently, was added to the code (Thanks a bunch, David!). I wanted to leave it for Googlers looking for a solution, however]

UPDATE: Someone wrote to let me know that they were encountering the error "Response Buffer Limit Exceeded". As it turns out, IIS 6's ASPBufferingLimit is set to a measly 4MB (4194304 bits) so any file over 4MB would produce this error. To fix this issue, you will have to have access to IIS either via the command line or the MMC. Here's how to change the buffering limit via the command line:

************ NOTE: An easier solution is to use the updated Do While/Flush procedure given in the code *****************

cd C:\inetpub\adminscripts
cscript adsutil.vbs set /w3svc/aspbufferinglimit 4294967295

That's a buffering limit of more than 4 Gigabytes. Personally, I'd lop off the last digit and make that number closer to 430MB. Running that script worked immediately on my test machine, even though I do not have "Enable Direct Metabase Edit" checked in IIS' Properties. If it doesn't work for you, restart IIS and see if it works.

OWA: Expired Password Causes Execute Access Forbidden

Filed under: Exchange, IIS, Security, Tech Stuff — Written by Chrissy on Tuesday, January 16th, 2007 @ 2:06 pm

Recently, a user trying to login to OWA encountered the following error:

HTTP 403.1 Forbidden: Execute Access Forbidden
You have attempted to execute a CGI, ISAPI, or other executable program from a directory that does not allow programs to be executed.

Another network administrator noticed that the URL was strange too. The user had been directed to:

https://owa.mydomain.com /iisadmpwd/aexp.htr?https:// owa.mydomain.com/exchange/USA/

A quick Googling showed mentions of an expired password but the user was able to login to the domain so we were a bit baffled. As it turns out, we're in the middle of a migration and the user's account on the old domain which still hosts OWA/Exchange was expired but his password on the new domain account was still valid. The user was also not prompted to change his password in OWA because we did not enable that feature. So if you run into something similar, ensure that the user's account does not have "User Must Change Password At Next Logon" checked.

VBScript: Windows XP/IIS 5.1 DOES Support Denying Access by IP Addresses

Filed under: IIS, Quick Code, Security, VBScript — Written by Chrissy on Tuesday, January 9th, 2007 @ 5:29 pm

In helping a visitor to troubleshoot running my IIS FTP ban script, I realized that while XP makes it appear as though it doesn't support banning users by IP address, it actually does provide that support; you just have to ban the IPs programatically.

Here, you can see that the IP address and domain name restrictions section is greyed out. However, you can use the following VBScript to enable and ban users in IIS' Default Web SIte. The first script listed does the following:

1. Ensures that AllowByDefault is set to true (which is the default anyway)
2. Bans a few example IP addresses
3. Confirms the addresses were successfully banned

Ban-a-rama

strComputer = "localhost"
arrBanTheseIPs = Array("10.0.0.200","42.42.42.42")
 
'Set Objects
Set objWebSite = GetObject("IIS://" & strComputer & "/W3SVC/1")
Set objIPRestrict = objWebSite.IPSecurity
 
objIPRestrict.GrantByDefault = True
objIPRestrict.IPDeny = arrBanTheseIPs
objWebSite.IPSecurity = objIPRestrict
objWebSite.SetInfo
 
WScript.Echo "The following IP addresses are now banned:"
arrDeniedIPs = objIPRestrict.IPDeny
for i = 0 to Ubound(arrDeniedIPs)
  WScript.Echo arrDeniedIPs(i)
next
 
'Kill Objects
Set objIPRestrict = Nothing
Set objWebSite = Nothing

To Delete All Previously Banned IPs, you would use the following code which overwrites all the IPs with one invalid IP.

Mass Unban

strComputer = "localhost"
 
'Set Objects
Set objWebSite = GetObject("IIS://" & strComputer & "/W3SVC/1")
Set objIPRestrict = objWebSite.IPSecurity
 
objIPRestrict.GrantByDefault = True
objIPRestrict.IPDeny = Array("0.0.0.0")
objWebSite.IPSecurity = objIPRestrict
objWebSite.SetInfo
 
'Kill Objects
Set objIPRestrict = Nothing
Set objWebSite = Nothing

If you find yourself needing to unban a single IP address, you can use the following code which gathers all the banned IPs except the one you want to delete and rebans them (IPDeny requires a full list each time you set it).

Unban One IP

strComputer = "localhost"
 
'Set Objects
Set objWebSite = GetObject("IIS://" & strComputer & "/W3SVC/1")
Set objIPRestrict = objWebSite.IPSecurity
 
strUnbanSingleIP = "10.0.0.200"
arrIPAddresses = objIPRestrict.IPDeny
 
For i = 0 to ubound(arrIPAddresses)
strClientIP = Left(arrIPAddresses(i),InStr(arrIPAddresses(i),",")-1)
  If strClientIP <> strUnbanSingleIP Then
    If Len(strStillBanned) = 0 Then
      strStillBanned = strClientIP
    Else
      strStillBanned = strStillBanned & "," & strClientIP
  End If
  End If
Next
 
If Len(strStillBanned) = 0 Then strStillBanned = "0.0.0.0" 'just in case it was the only one
arrStillBannedIPs = split(strStillBanned,",")
 
objIPRestrict.IPDeny = arrStillBannedIPs
objWebSite.IPSecurity = objIPRestrict
objWebSite.SetInfo
 
'Kill Objects
Set objIPRestrict = Nothing
Set objWebSite = Nothing

If your script is successful, banned users will see the following message:

You are not authorized to view this page

HTTP 403.6 - Forbidden: IP address rejected

To show all of the current IPs which have been banned, run the following script

View Banned IPs

strComputer = "localhost"
 
'Set Objects
Set objWebSite = GetObject("IIS://" & strComputer & "/W3SVC/1")
Set objIPRestrict = objWebSite.IPSecurity
 
arrDeny = objWebSite.Get("IPSecurity").IPDeny
    For i = 0 to Ubound(arrDeny)
        strBannedIPs = strBannedIPs & arrDeny(i) & vbCrlf
    Next
 
    If len(strBannedIPs) > 0 Then
        msgbox "IP, Subnet: " & vbCrLF & strBannedIPs
    Else
        msgbox "No IPs have been banned."
     End if
 
'Kill Objects
Set objIPRestrict = Nothing
Set objWebSite = Nothing

While I haven't tested it, the same scripts should work if you want to deny all IPs except those explicitly listed. To do so, simply set objIPRestrict.GrantByDefault to False and replace the above mentions of IPDeny with IPGrant. Same goes for MSFTPSVC -- if you want to modify the FTP service settings, just change the above instances of "W3SVC" to "MSFTPSVC".

VBScript: Kerberos, Delegation, IIS and User Authentication

Filed under: Active Directory, IIS, Quick Code, VBScript — Written by Chrissy on Wednesday, December 13th, 2006 @ 9:30 am

Recently, I wanted to write a web-based front end to AD User Management for our help desk. The way that I set it up apparently broke some Kerberos delegation rules and even though Microsoft wrote a step-by-step guide on how to get IIS and Kerberos delegation going, the solution didn't work for me. If I turned off Anonymous access and authenticated as myself against a remote webserver (local webserver totally worked), I would get the error 0x80040E37 - Table Does Not Exist. The table does exist, of course, I just don't have the rights to see it. Well, I do but not in the "double hop" manner that I'm attempting it. Kerberos sees that IIS != me.. IIS is only pretending to be me and it doesn't approve.

After a few days of tinkering, this is the solution I decided on. I set the ASP page in IIS to Anonymous Access but ran that access as a user with the privleges to make changes to user accounts. This can be dangerous in more than one way -- if a lesser-privleged user were to somehow have the ability to edit that page, he or she could use it to run any script under the power of that user. So I ensured that unauthorized users were not able to access that file. But now how can I detect who's running that page? Request.servervariables("REMOTE_USER") would be useless since the page is running as that privleged user. I considered what client variables I did have access to and realized that request.servervariables("REMOTE_HOST") would be the handiest. I figured that, using WMI and the IP address, it would be possible to figure out what user is actively logged into the client machine. Sure enough...

Function getLoggedInUser(ipaddr)
  Set objWMIService = GetObject("winmgmts:{impersonationLevel=impersonate}!"& ipaddr & "\root\cimv2")
  Set colSessions = objWMIService.ExecQuery("Select * from Win32_LogonSession Where LogonType = 2 OR LogonType = 10" )
 
  For Each objSession in colSessions
   Set colList = objWMIService.ExecQuery("Associators of {Win32_LogonSession.LogonId=" & objSession.LogonId & "} Where AssocClass=Win32_LoggedOnUser Role=Dependent")
    For Each objItem in colList
    getLoggedInUser = lcase(objItem.Name)
    Next
  Next
End Function

This script, which was derived from a tek-tips.com post, worked flawlessly! FWIT, LoginType 2 is console and 10 is remote desktop.

VBScript: Stop Dictionary FTP Attacks in IIS using VBScript

Filed under: IIS, Quick Code, VBScript — Written by Chrissy on Tuesday, October 31st, 2006 @ 2:36 pm

Spencer Ruport of netortech.com modified my FTP ban script into one that stops dictionary attacks. And he chooses not to ban via the problematic IIS way but instead creating a bad route for the offending IP address. Pretty darn ingenius.

ftpdprotection.vbs

Set objFTPSVC = GetObject("IIS://localhost/MSFTPSVC")
    Set objFSO = CreateObject("Scripting.FileSystemObject")
    Set objLog = CreateObject("MSWC.IISLog")
    Set objDictionary = CreateObject("Scripting.Dictionary")
 
    Dim Attempts(), IP(), LastAttempt(), IPs
    Dim AlreadyBanned, InProcess, Session
    Dim CutOff, x, i
    IPs = 0
 
    'Iterate through each FTP site.
      For Each objSITE in objFTPSVC
        If lcase(objSITE.class) = "iisftpserver" Then
          ftpLogFilePath =  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
            Do While NOT objLog.AtEndOfLog
              objLog.ReadLogRecord
              If instr(ucase(objLog.Method), "USER") Then
          'Someone is attempting to authenticate with the FTP server
          InProcess = False
          AlreadyBanned = False
          For x = 0 to IPs - 1
            'Check to see if this IP address has already attempted to log in.
            If objLog.ClientIP = IP(x) Then
              'To be nice this code will give the user
              'another attempt every minute.
              'This is to avoid banning a person who
              'attempts to log in multiple times
              'in a day but keeps forgetting their password.
              Attempts(x) = Attempts(x) + datediff("n", objLog.DateTime, lastattempt(x))
              LastAttempt(x) = objLog.DateTime
              InProcess = True
            End If
          Next
          If Not InProcess Then
            'If we aren't keeping track of this IP
            'it's possible the IP has already been
            'added to the ban list.
            AlreadyBanned = objDictionary.Exists(objLog.ClientIP)
          End If
          If Not InProcess And Not AlreadyBanned Then
            'First authentication attempt by this IP
            IPs = IPs + 1
            Redim Preserve IP(IPs)
            Redim Preserve Attempts(IPs)
            Redim Preserve LastAttempt(IPs)
            IP(IPs - 1) = objLog.ClientIP
            Attempts(IPs - 1) = 1
            LastAttempt(IPs - 1) = objLog.DateTime
          End If
          ElseIf instr(ucase(objLog.Method), "PASS") Then
          'The server is responding to an authentication
          'attempt.
          For x = 0 to IPs - 1
            'See if we're keeping track of this IP
              If IP(x) = objLog.ClientIP Then
              'Increment the authentication attempts
              Attempts(x) = Attempts(x) + 1
              If Attempts(x) = 10 Then
                'Ban if necessary
                WScript.Echo "Banning " & objLog.ClientIP & "..."
                objDictionary.Add objLog.ClientIP, "255.255.255.255"
                AddDeadRoute objLog.ClientIP
                  For i = x to (IPs - 2)
                  IP(i) = IP(i + 1)
                  Attempts(i) = Attempts(i + 1)
                Next
                IPs = IPs - 1
                Redim Preserve IP(IPs)
                Redim Preserve Attempts(IPs)
              End If
            End If
          Next
              End If
            Loop
          objLog.CloseLogFiles 1
        End If
      Next
 
    Set objDictionary = Nothing
    Set objLog = Nothing
    Set objFSO = Nothing
    Set objFTPSVC = Nothing
 
    Function addDeadRoute(IP)
    dim adoDBConn, sConnStr, objFSO, objBanBatch, cmdShell, WshShell
    Dim fakeGateway
    'I've been told that IPSec could be used to block certain IPs
    'but 1) I'm not sure WMI can interface with it and 2) I don't
    'know how to begin with. Someone gave me a much easier solution
    'Simply add a route to the routing table pointing to a gateway
    'that doesn't exist.
 
    'Ensure that this IP is on the same subnet as the server
    fakeGateway = "192.168.1.101"
 
    Set WshShell = WScript.CreateObject("WScript.Shell")
    Set objFSO = CreateObject("Scripting.FileSystemObject")
    Set objBanBatch = objFSO.CreateTextFile("W:\config files\blocks\addRoute.bat", True)
    objBanBatch.WriteLine "ROUTE ADD " & IP & " MASK 255.255.255.255 " & fakeGateWay
    objBanBatch.Close
    WshShell.Run """W:\config files\blocks\addRoute.bat""", 1, True
    Set objFSO = Nothing
    End Function

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

Filed under: IIS, Quick Code, Security, VBScript — Written by Chrissy on Sunday, July 2nd, 2006 @ 5:49 pm

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.

IIS: MD_CUSTOM_ERROR (6008)

Filed under: IIS, Linux, Tech Stuff — Written by Chrissy on Monday, May 1st, 2006 @ 7:18 pm

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.

ASP: mod_rewrite or URLRewrite with Classic ASP (Sorta)

Filed under: IIS, Quick Code — Written by Chrissy on Friday, April 28th, 2006 @ 8:01 pm

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!

ASP: Sustain Remote Cookie Sessions in an ASP/VBScript

Filed under: IIS, Quick Code, VBScript — Written by Chrissy on Thursday, April 27th, 2006 @ 12:28 am

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.

Quick 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
%>