红俊's profile蝈蝈俊的共享空间PhotosBlogListsMore Tools Help

Blog


    May 07

    动态封杀与解封IP

    我们在应对网站的恶意请求时候,一个解决方法就是把有问题的请求IP封杀掉。

    如果想快速处理这种问题,就需要编写一段代码,达到一定门槛,自动封杀。再复杂点就是不是永久封杀,还可以自动在一定时间后解封。

    封杀的逻辑代码看后面提供的。

    需要说明的是:IIS7时,情况发生了不同。

     

    下面的代码,在处理封杀IP时候,不论IIS6还是IIS7 都可以把需要封杀的IP加入封杀列表。但是需要注意的是我们代码写的是全部替换原先的数据。但是在IIS7下,执行的效果是原先的不替换,新加一批封杀IP。当然IIS7下,如果新加的IP原来就有了,则会报如下异常:

    System.Runtime.InteropServices.COMException was caught
      Message="当文件已存在时,无法创建该文件。 (异常来自 HRESULT:0x800700B7)"
      Source="System.DirectoryServices"
      ErrorCode=-2147024713
      StackTrace:
           在 System.DirectoryServices.DirectoryEntry.CommitChanges()
           在 IIS_Security_ConsoleApplication.Program.IPDeny() 位置 D:\MyCodes\IIS_Security_ConsoleApplication\IIS_Security_ConsoleApplication\Program.cs:行号 109
      InnerException:

    这就是说,IIS7, 我们可以通过编程接口增加封杀IP名单,但是没发通过编程接口剔出封杀IP。

     

    参考代码:

    这里提供了两套参考代码,其实原理都是一样的。

    在IIS 6 下,都没有任何问题, IIS 7 下都会有没发删除原先已有数据的问题。

    代码一:


    using System.DirectoryServices;
    using System.Reflection;
    using System;

    class Program
    {

    static void IPDeny()
    {

    try
    {
    string serverName = "localhost";
    // retrieve the directory entry for the root of the IIS server
    System.DirectoryServices.DirectoryEntry IIS = new System.DirectoryServices.DirectoryEntry(string.Format("IIS://{0}/w3svc/1/root", serverName));

    // retrieve the list of currently denied IPs
    Console.WriteLine("Retrieving the list of currently denied IPs.");

    // get the IPSecurity property
    Type typ = IIS.Properties["IPSecurity"][0].GetType();
    object IPSecurity = IIS.Properties["IPSecurity"][0];


    // retrieve the IPDeny list from the IPSecurity object
    Array origIPDenyList = (Array)typ.InvokeMember("IPDeny", BindingFlags.DeclaredOnly | BindingFlags.Public | BindingFlags.NonPublic
    | BindingFlags.Instance | BindingFlags.GetProperty, null, IPSecurity, null);

    // 罗列已经被拒绝的地址
    foreach (string s in origIPDenyList)
    Console.WriteLine("Before: " + s);

    // check GrantByDefault. This has to be set to true,
    // or what we are doing will not work.
    bool bGrantByDefault = (bool)typ.InvokeMember("GrantByDefault", BindingFlags.DeclaredOnly | BindingFlags.Public | BindingFlags.NonPublic
    | BindingFlags.Instance | BindingFlags.GetProperty, null, IPSecurity, null);

    Console.WriteLine("GrantByDefault = " + bGrantByDefault);
    if (!bGrantByDefault)
    {
    // 必须设置 默认允许访问
    typ.InvokeMember("GrantByDefault", BindingFlags.DeclaredOnly | BindingFlags.Public | BindingFlags.NonPublic | BindingFlags.Instance
    | BindingFlags.SetProperty, null, IPSecurity, new object[] { true });
    }


    // 更新被拒绝的IP列表
    // 注意这里是完全替换
    // 如果你想保留原先的拒绝列表,需要原先的拒绝列表也在这个数组中

    Console.WriteLine("Updating the list of denied IPs.");

    object[] newIPDenyList = new object[4];
    newIPDenyList[0] = "192.168.1.21, 255.255.255.255";
    newIPDenyList[1] = "192.168.1.22, 255.255.255.255";
    newIPDenyList[2] = "192.168.1.23, 255.255.255.255";
    newIPDenyList[3] = "192.168.1.24, 255.255.255.255";

    Console.WriteLine("Calling SetProperty");

    // add the updated list back to the IPSecurity object
    typ.InvokeMember("IPDeny", BindingFlags.DeclaredOnly | BindingFlags.Public | BindingFlags.NonPublic | BindingFlags.Instance
    | BindingFlags.SetProperty, null, IPSecurity, new object[] { newIPDenyList });



    IIS.Properties["IPSecurity"][0] = IPSecurity;

    Console.WriteLine("Commiting the changes.");

    // commit the changes
    IIS.CommitChanges();
    IIS.RefreshCache();

    // 检查更新后的数据
    Console.WriteLine("Checking to see if the update took.");

    IPSecurity = IIS.Properties["IPSecurity"][0];
    Array y = (Array)typ.InvokeMember("IPDeny",
    BindingFlags.DeclaredOnly | BindingFlags.Public | BindingFlags.NonPublic | BindingFlags.Instance | BindingFlags.GetProperty,
    null, IPSecurity, null);

    foreach (string s in y)
    Console.WriteLine("After: " + s);
    }
    catch (Exception e)
    {
    Console.WriteLine("Error: " + e.ToString());
    }

    }
    }

     

    代码二:

            using System.DirectoryServices;
    using System.Reflection;
    using System;



    static void SetIPSecurityProperty(string metabasePath, string member, string item)
    {
    // metabasePath is of the form "IIS://<servername>/<path>"
    // for example "IIS://localhost/SMTPSVC/1"
    // member is of the form "IPGrant|IPDeny|DomainGrant|DomainDeny"
    // item is of the form "<ipaddress|domain>", for example, 157.56.236.15 or domain.microsoft.com
    Console.WriteLine("\nEnumerating the IPSecurity property at {0}:", metabasePath);

    try
    {
    if (("IPGrant" != member) && ("IPDeny" != member) && ("DomainGrant" != member) && ("DomainDeny" != member))
    {
    Console.WriteLine(" Failed in SetIPSecurityProperty; second param must be one of IPGrant|IPDeny|DomainGrant|DomainDeny");
    }
    else
    {
    DirectoryEntry path = new DirectoryEntry(metabasePath);
    path.RefreshCache();
    object ipsecObj = path.Invoke("Get", new string[] { "IPSecurity" });
    Type t = ipsecObj.GetType();
    Array data = (Array)t.InvokeMember(member, BindingFlags.GetProperty, null, ipsecObj, null);
    Console.WriteLine(" Old {0} =", member);
    bool exists = false;
    foreach (object dataItem in data)
    {
    Console.WriteLine(" {0}", dataItem.ToString());
    if (dataItem.ToString().StartsWith(item))
    {
    exists = true;
    }
    }

    if (exists)
    {
    Console.WriteLine(" {0} already exists in {1}", item, member);
    }
    else
    {
    object[] newData = new object[data.Length + 1];
    data.CopyTo(newData, 0);
    newData.SetValue(item, data.Length);

    t.InvokeMember(member, BindingFlags.SetProperty, null, ipsecObj, new object[] { newData });

    path.Invoke("Put", new object[] { "IPSecurity", ipsecObj });

    path.CommitChanges();

    path.RefreshCache();
    ipsecObj = path.Invoke("Get", new string[] { "IPSecurity" });
    data = (Array)t.InvokeMember(member, BindingFlags.GetProperty, null, ipsecObj, null);
    Console.WriteLine(" New {0} =", member);
    foreach (object dataItem in data)
    Console.WriteLine(" {0}", dataItem.ToString());
    Console.WriteLine(" Done.");
    }
    }
    }
    catch (Exception ex)
    {
    if ("HRESULT 0x80005006" == ex.Message)
    Console.WriteLine(" Property IPSecurity does not exist at {0}", metabasePath);
    else
    Console.WriteLine("Failed in SetIPSecurityProperty with the following exception: \n{0}", ex.Message);
    }
    }

    static void Main(string[] args)
    {

    // 获取目前服务器上有哪些站点
    DirectoryEntry root = new DirectoryEntry("IIS://localhost/W3SVC");
    foreach (DirectoryEntry dir in root.Children)
    {
    if (dir.SchemaClassName == "IIsWebServer")
    {
    string ww = dir.Properties["ServerComment"].Value.ToString();

    Console.Write("IIS://localhost/W3SVC/{0}/ROOT/ {1}\r\n", dir.Name, ww);
    }
    }


    // IPDeny();

    SetIPSecurityProperty("IIS://localhost/w3svc/1/root", "IPDeny", "192.168.5.79");

    Console.ReadLine();
    }

     

    参考资料:

    Blocking IIS IP Addresses with ASP.NET
    http://www.west-wind.com/WebLog/posts/59731.aspx

    How to Programmatically add IP Addresses to IIS's Deny Access List
    http://www.codeproject.com/KB/security/iiswmi.aspx

    HOWTO: 通过 IP 地址或域名称限制站点访问
    http://support.microsoft.com/default.aspx/kb/324066

    使用ADSI来操作IIS的路径
    http://blog.joycode.com/ghj/archive/2004/06/08/24047.aspx

    Setting IP Security Using System.DirectoryServices
    http://www.cnblogs.com/drw/articles/17951.html

    如何通过WEB方式,来控制iis的禁用IP名单。
    http://blog.joycode.com/ghj/archive/2004/06/08/24075.aspx

    Setting IP Security Using System.DirectoryServices
    http://msdn.microsoft.com/en-us/library/ms524322(VS.85).aspx

    how to automate adding denied IPs for IIS

    http://www.nukeforums.com/forums/viewtopic.php?p=54746&highlight=&sid=1176c746e2037ed24acac86dd53ca747

    IIS 7.0: Configure IPv4 Address and Domain Name Allow Rules
    http://technet2.microsoft.com/windowsserver2008/en/library/d0de9475-0439-4ec1-8337-2bcedacd15c71033.mspx?mfr=true

    May 04

    每个分类取最新的几条的SQL实现

    分类统计时候,我们可能经常会碰到这样的需求,每个分类按照一定顺序,取几条数据,然后在一起显示。

    这个问题的解决方法,我们通过搜索引擎,可以找到很多中。但是不是SQL语句过于复杂,就是在数据量比较大时候,性能特别成问题。

    今天我就碰到这样一个需求。而我自己的解决方案就是SQL过于复杂,或者性能比较差的。为此我在CSDN论坛发了个帖子,看有没有更好的解决方案。

    http://topic.csdn.net/u/20080504/14/5c5866c3-8b91-45ef-ab17-f994f88f8e42.html

    CSDN的 SQL Server 板块  不愧是高手云集,问题发出不到半小时,就获得了近10种解决方案。经过测试,我把性能最高,且SQL不复杂的方案整理出来。特别感谢 jinjazz 的解答。

     

    问题详细描述如下:

    比如,假设我们有下面这样结构的一张表,这张表的数据量非常巨大。

    CREATE TABLE table1
    (
    [ID] [bigint] IDENTITY(1,1) NOT NULL,
    [Name] [nvarchar](128) NOT NULL,
    [class] int not null,
    [date] datetime not null
    )

    class 表示分类编号。 分类数不固定, 至少有上千种分类
    date 表示该条记录被更新的时间
    我们现在想获得每个分类最新被更新的5条记录。

     

    解决方案

    select id,name,class,date from(
    select id,name,class,date ,row_number() over(partition by class order by date desc)
    as rowindex from table1) a
    where rowindex <= 5

     

    解决方案简单说明:

    这个解决方案的关键就是使用了SQL 2005 的 ROW_NUMBER 这个全新的函数。

    ROW_NUMBER ( ) 函数的语法如下:
    ROW_NUMBER ( )     OVER ( [ <partition_by_clause> ] <order_by_clause> )

    OVER 子句中的 PARTITION BY 将结果集分为多个分区。
    OVER 子句中的 ORDER BY 将对 ROW_NUMBER 进行排序。

    下面是MSDN的几个简单例子:

     

    以下示例将根据年初至今的销售额,返回 AdventureWorks 中销售人员的 ROW_NUMBER。

    USE AdventureWorks; 
    GO

    SELECT c.FirstName, c.LastName ,
    ROW_NUMBER() OVER(ORDER BY SalesYTD DESC) AS 'Row Number' ,s.SalesYTD, a.PostalCode

    FROM Sales.SalesPerson s INNER JOIN Person.Contact c ON s.SalesPersonID = c.ContactID
    INNER JOIN Person.Address a ON a.AddressID = c.ContactID
    WHERE TerritoryID IS NOT NULL AND SalesYTD <> 0;


    以下示例将返回行号为 50 到 60 的行(包含这两行),并按 OrderDate 进行排序。

    USE AdventureWorks; 
    GO

    WITH OrderedOrders AS ( SELECT SalesOrderID, OrderDate,
    ROW_NUMBER() OVER (ORDER BY OrderDate) AS 'RowNumber'
    FROM Sales.SalesOrderHeader )

    SELECT * FROM OrderedOrders WHERE RowNumber BETWEEN 50 AND 60;


     

    以下示例说明了如何使用 PARTITION BY 参数。

    USE AdventureWorks; 
    GO

    SELECT c.FirstName, c.LastName ,
    ROW_NUMBER() OVER (PARTITION BY PostalCode ORDER BY SalesYTD DESC) AS 'Row Number' ,
    s.SalesYTD, a.PostalCode

    FROM Sales.SalesPerson s INNER JOIN Person.Contact c ON s.SalesPersonID = c.ContactID
    INNER JOIN Person.Address a ON a.AddressID = c.ContactID
    WHERE TerritoryID IS NOT NULL AND SalesYTD <> 0;