小题大作:.Text中恶意脚本过滤的新方法

这篇文章是关于.Text如何过滤恶意脚本的思考文章的继续。
我下面所要讲的实现方法,实际上是A Taste of AOP from Solving Problems with OOP and Design Patterns 一文中设计思想的实际应用。实现的原理是:拦截对对象的属性的访问, 在属性返回之前,对属性值进行过滤。
本想好好写一下这篇文章,可测试成功后,急于想把成功与大家分享,再加上成功后的兴奋,竟然感觉脑子里乱七八糟,不知写什么好,算了,先将代码贴出来给大家看看,这样也正好看看大家能不能通过我的代码理解我的实现方法,验证一下我写的代码的可读性。欢迎大家针对我的代码展开批评与讨论:

FilterRealProxy类:一个真实代理, 拦截它所代理对象中方法的返回值,并对需要过滤的返回值进行过滤。

public class FilterRealProxy:RealProxy

     {

         private MarshalByRefObject target;

         public FilterRealProxy(MarshalByRefObject target):base(target.GetType())

         {

              this.target=target;   

         }

 

         public override IMessage Invoke(IMessage msg)

         {

              IMethodCallMessage callMsg=msg as IMethodCallMessage;

              IMethodReturnMessage returnMsg = RemotingServices.ExecuteMessage(target,callMsg);

              if(this.IsMatchType(returnMsg.ReturnValue))//检查返回值是否为String,如果不是String,就没必要进行过滤

              {

                   string returnValue=this.Filter(returnMsg.ReturnValue.ToString(),returnMsg.MethodName);           

                   return new ReturnMessage(returnValue,null,0,null,callMsg);

              }

              return returnMsg;
     }

 

         protected string  Filter(string ReturnValue,string MethodName)

         {

              MethodInfo methodInfo=target.GetType().GetMethod(MethodName);

              object[] attributes=methodInfo.GetCustomAttributes(typeof(StringFilter),true);

              foreach (object attrib in attributes)

              {

                   return FilterHandler.Process(((StringFilter)attrib).FilterType,ReturnValue);

                  

              }

              return ReturnValue;

         }

 

        

          protected bool IsMatchType(object obj)

         {

              return obj is System.String;

         }

     }


StringFilter类:自定义属性类, 定义目标元素的过滤类型 

public class StringFilter:Attribute

     {

         public StringFilter(FilterType filterType)

         {

              this._filterType=filterType;

         }

 

          protected FilterType _filterType;

 

         public FilterType FilterType

         {

              get

              {

                   return _filterType;

              }

         }

 

     }



枚举类:用于指定过滤类型,例如:对script过滤还是对html进行过滤?

[Flags()]

     public enum FilterType

     {

         Script = 1,

         Html =2

     }


过滤处理类:根据过滤类型,调用相应的过滤处理方法。

public class FilterHandler

     {

         private FilterHandler()

         {

             

         }

 

         public static string Process(FilterType filterType,string filterContent)

         {

              switch (filterType)

              {

                   case FilterType.Script:

                       return FilterScript(filterContent);

                       break;

                   case FilterType.Html:

                       return FilterHtml(filterContent);

                       break;

                   default:

                       return filterContent;

                       break;

              }

 

         }

 

         public static string FilterScript(string content)

         {

              string regexstr=@"(?i)<script([^>])*>(\w|\W)*</script([^>])*>";

              return Regex.Replace(content,regexstr,string.Empty,RegexOptions.IgnoreCase);

         }

 

         public static string FilterHtml(string content)

         {

              string newstr=FilterScript(content);

              string regexstr=@"<[^>]*>";

              return Regex.Replace(newstr,regexstr,string.Empty,RegexOptions.IgnoreCase);

         }

 

 

     }

实体类:该实体类必须从MarshalByRefObject继承,所以被过滤的实体类不能再从其他类继承或实现接口,这是使用透明代理的局限性。

public class Entry : MarshalByRefObject

     {

         public Entry()

         {

         }

        

         public static Entry CreateInstance()

         {

              Entry entry=new Entry();

              RealProxy realProxy = new FilterRealProxy(entry);

              object transparentProxy = realProxy.GetTransparentProxy();

              return (Entry)transparentProxy;

             

         }

 

        private string _body;

        

         public virtual string Body

         {

              [StringFilter(FilterType.Scrtipt)] //通过StringFilter设置实体类的属性是否需要过滤,以及指定过滤的方法

              get{return _body;}

              set{_body = value;}

         }

 

     }



NUnit测试代码:

    

         [Test]

         public void EntryTest()

         {

              Entry en=Entry.CreateInstance();

              System.IO.StreamReader sr=new System.IO.StreamReader("script.txt");//得到一个含有script脚本的字符串

              en.Body=sr.ReadToEnd();

              Console.Write(en.Body);

         }

测试结果是得到了一个不含script的字符串。

posted @ 2004-03-10 20:34 dudu 阅读(5439) 评论(36)  编辑 收藏 网摘

  回复  引用  查看    
#1楼 2004-03-10 21:37 | dudu      
不好意思, 情急之下, 竟然忘了感谢JGTM'2004 [MVP]对我的指点!现在补上!欢迎大家对上面的代码进行重构!
  回复  引用    
#2楼 2004-03-10 22:23 | JGTM'2004 [MVP] [未注册用户]
Nicely done! 鼓励先!今天早点儿收工,明天回来给你更多反馈!:)
  回复  引用  查看    
#3楼 2004-03-10 22:43 | dudu      

对FilterHandler类的改进:
public
class FilterHandler

     {

         private FilterHandler()

         {

         }

 

         public static string Process(FilterType filterType,string filterContent)

         {

              if((filterType&FilterType.Script)==FilterType.Script)

              {

                   filterContent=FilterScript(filterContent);

                  

              }

              if((filterType&FilterType.Html)==FilterType.Html)

              {

                   filterContent= FilterHtml(filterContent);

                  

              }

              if((filterType&FilterType.Object)==FilterType.Object)

              {

                   filterContent= FilterObject(filterContent);

                  

              }

              return filterContent;

             

         }

 

         public static string FilterScript(string content)

         {

              string regexstr=@"(?i)<script([^>])*>(\w|\W)*</script([^>])*>";

              return Regex.Replace(content,regexstr,string.Empty,RegexOptions.IgnoreCase);

         }

 

         public static string FilterHtml(string content)

         {

              string newstr=FilterScript(content);

              string regexstr=@"<[^>]*>";

              return Regex.Replace(newstr,regexstr,string.Empty,RegexOptions.IgnoreCase);

         }

 

         public static string FilterObject(string content)

         {

              string regexstr=@"(?i)<Object([^>])*>(\w|\W)*</Object([^>])*>";

              return Regex.Replace(content,regexstr,string.Empty,RegexOptions.IgnoreCase);

         }

 

 

     }

这样就可以同时进行多种过滤,假如我们既要过滤Script又要Object,我们只要这样设置:

FilterType中是这样的

[Flags()]

     public enum FilterType

     {

         Script = 1,

         Html =2,

         Object=4     

     }

然后在Entry类中这样设置:
 public virtual string Body

         {

              [StringFilter((FilterType)5)] 
              get{return _body;}

              set{_body = value;}

         }


  回复  引用  查看    
#4楼 2004-03-11 06:41 | dudu      
原文漏贴了StringFilter类, 已补上。
  回复  引用    
#5楼 2004-03-11 08:21 | Bz [未注册用户]
anyway, you could use the components on codeproject to create dynamic proxy. they used Reflection.Emit.
such as:
http://www.codeproject.com/csharp/DynamicProxy.asp
http://www.codeproject.com/dotnet/dynamicproxy.asp

and there is a small problem in your unit test code. you should close your StreamReader. :)

but really well done. :D
  回复  引用    
#6楼 2004-03-11 08:35 | dudu [未注册用户]
To Bz:
Thanks.I wil study the articles.
You are right! It is my careless mistake!
Here i close the StreamReader.but it is late!
  回复  引用  查看    
#7楼 2004-03-11 11:43 | eng21      

呵呵,那俺就先来谈点个人的一点意见吧:

IsMatchType 不如改为 IsString,当我第一眼看到这个方法的时候,以为应该有两个参数:)

或许FilterHandler.Process 使用抽象工厂会更好,相应的FilterHandler就应该定义接口

switch (filterType) 这种方式也不好,增加新的过滤类型的话要改这里的代码,而不仅仅只是增加新类
——我原是这样考虑,但你这里的FilterHandler实际上就是HtmlFilterHander,所以这里用switch应该也可以。

关于测试,我觉得也不够完整,只能证明方法的“执行”是成功的,至于数据的结果是否正确没有测试到。这里当然是可以看Console的输出,但对于一个自动化的测试来说,我觉得是不够的。


另外问一下,实体类都继承自MarshalByRefObject,会不会有影响?

最后,我对Attribute不是太熟悉,但我觉得似乎只使用Attribute也可以达到这样的效果吧,一定要使用到RealProxy这些内容吗?


  回复  引用    
#8楼 2004-03-12 18:01 | 韩磊 [未注册用户]
下面是Scott的一个新Blog post的内容,也许会有帮助。

Thomas announced version 2.0 of his code highlighter. I have not given it a full try yet, but I thought it might be nice to point out how easy it should be to implement this in the next (0.96) version of .Text.

0.96 has a pretty simple EntryHandling system, which basically allows you to execute code before of after an Entry updated. (Posts, Articles, Comments, and Trackbacks are all entries).

The interface is still not “locked”, but for now it looks like this:

public interface IEntryFactoryHandler
{
void Process(Entry e);
void Configure();
}

In the web.config, you can configure:

The type of entires which use this handler
Whether the handler is valid for new or existing items (ProcessAction)
If the handler should process before or after connecting to the datastore (ProcessState)
If the handler should process synchronous or asynchronous
You should note:

If ProcessState = ProcessState.PreCommit, the Handler must be processed Synchronously
Configure will always be fired on the main thread, so any items you might need to complete Process should be set and/or referenced here.
Multiple ProcessAction's can be configured, but only one ProcessState can be set.
With that out of the way, we can do a quick example:

using Dottext.Framework.Components;
using Dottext.Framework.EntryHandling

using AylarSolutions.Highlight;
using AylarSolutions.Highlight.Extensions;

namespace CC
{

public class ColorCoderEntryFactoryHandler : IEntryFactoryHandler
{
public void Process(Entry e)
{
CodeBlockHighlighter highlighter = new CodeBlockHighlighter();
highlighter.InputIsHtmlEncoded = true;
e.Body = highlighter.Highlight(e.Body);
}

public void Configure(){} //nothing to set up
}
}


  回复  引用  查看    
#9楼 2004-03-12 18:35 | dudu      
谢谢!.Text中恶意内容过滤就是采用的这种方法, 也就是EntryValidationHandler。
  回复  引用  查看    
#10楼 2004-03-13 10:07 | dudu      
.Text中的过滤方法是一种比较有效的方法, 我的这种方法不太实用, 将简单问题复杂化了。主要是想提供一种新的思路。
  回复  引用    
#11楼 2004-03-14 22:27 | 韩磊 [未注册用户]
.Text用handler的方式,也是相当OO的。最近我打算写一个系列文章,全面地分析.Text。
  回复  引用  查看    
#12楼 2004-03-14 22:36 | dudu      
好啊!正好学习学习!
.Text相当OO的地方有很多。
  回复  引用    
#13楼 2004-03-15 13:24 | 韩磊 [未注册用户]
学习就不敢当了。在撰写过程中,希望能得到你的指教才好呢。呵呵。
  回复  引用    
#14楼 2004-03-17 16:47 | 韩磊 [未注册用户]
结合dudu和Scott的方法,创建新cs文件如下:
using System;
using Dottext.Framework.Configuration;
using Dottext.Framework.Components;
using Dottext.Framework.Util;
using System.Text.RegularExpressions;

namespace Dottext.Framework.EntryHandling
{
/// <summary>
/// Responsible for valiating and formatting the entry before it is save to the database.
/// by Lei Han
/// This Factory should happen Synchronously
/// </summary>
public class HtmlFilterHandler : IEntryFactoryHandler
{
public HtmlFilterHandler()
{
//
// TODO: Add constructor logic here
//
}

#region IEntryFactoryHandler Members

/// <summary>
/// Responsible for valiating and formatting the Comment before it is save to the database.
/// This Factory should happen Synchronously
/// </summary>
/// <param name="entry"></param>
public void Process(Dottext.Framework.Components.Entry entry)
{
entry.Author = FilterAll(entry.Author);
entry.Title = FilterAll(entry.Title);
//……爱过滤什么过滤什么
}

public void Configure(){}

#endregion

#region 过滤功能代码
public static string FilterAll(string content)
{

return FilterScript(FilterHtml(FilterObject(content)));

}

public static string FilterScript(string content){

string regexstr=@"(?i)<script([^>])*>(\w|\W)*</script([^>])*>";

return Regex.Replace(content,regexstr,string.Empty,RegexOptions.IgnoreCase);

}


public static string FilterHtml(string content){

string newstr=FilterScript(content);

string regexstr=@"<[^>]*>";

return Regex.Replace(newstr,regexstr,string.Empty,RegexOptions.IgnoreCase);

}



public static string FilterObject(string content){

string regexstr=@"(?i)<Object([^>])*>(\w|\W)*</Object([^>])*>";

return Regex.Replace(content,regexstr,string.Empty,RegexOptions.IgnoreCase);

}

#endregion

}
}
  回复  引用    
#15楼 2004-03-17 16:57 | 韩磊 [未注册用户]
呵呵,忘了一个东西。得在web.config里面加上这一行:

<EntryHandler type="Dottext.Framework.EntryHandling.HtmlFilterHandler, Dottext.Framework" postType = "BlogPost Article" processAction ="Insert Update" processState="PreCommit" isAsync="false" />

这样就能把咱们的过滤器加载到DotText的EntryHandling列表里面了。让.Text去处理吧,我们只要告诉它,在提交之前,请做这些过滤操作……

  回复  引用    
#16楼 2004-03-17 16:59 | 韩磊 [未注册用户]
嗯,没看清楚dudu的代码。FilterHtml就已经是过滤掉所有可能有害的信息了。把FilterAll改成FilterHtml就好了。
  回复  引用  查看    
#17楼 2004-03-17 19:18 | dudu      
IEntryFactoryHandler与Entry耦合度太高, 比如这里的HtmlFilterHandler只能对Entry实体类进行过滤, 假如现在需要对.Text中另一个实体类BlogConfig进行过滤, HtmlFilterHandler就无能力为力了。我觉得我们更需要一个通用的HtmlFilterHandler, 对任何实体类都可以进行过滤。
  回复  引用    
#18楼 2004-03-29 16:06 | 韩磊 [未注册用户]
有道理
  回复  引用    
#19楼 2004-03-31 17:43 | redbb [未注册用户]
very good article
  回复  引用    
#20楼 2004-04-06 09:00 | 宝玉 [未注册用户]
从代码上看,好像没有对事件(如onclick)进行过滤,还有链接里面带脚本,还有iframe,这些都是隐含的安全问题。
可以看看我发的测试:
http://www.cnblogs.com/dotey/archive/2004/04/06/5267.aspx
  回复  引用  查看    
#21楼 2004-04-06 09:38 | dudu      
这里仅提供一个思路。要解决安全问题, 需要一个完整的解决方案。
在.Text 0.96中有一个EntryValidationHandler在提交文章时进行检查, 它是通过Sgml将文章内容转换成Xml进行检查的。但使用这种方法, 就无法发表从Word中复制过来的HTML内容。
博客园正在考虑一个好的解决方案来解决安全问题。
  回复  引用  查看    
#22楼 2004-05-11 17:35 | sumtec      
你的脚本过滤代码肯定会有漏洞,例如:
<script ...>
s = "</script>";// </script>
...
</script>

就应该能绕过去吧?

  回复  引用    
#23楼 2004-06-16 14:07 | 洛华 [未注册用户]
你们好啊.我的电脑中了恶意脚本,TE修复也删除不了这程序.令我十分烦恼.你们能告诉我怎么删除这恶意程序吗?请大家帮帮忙,谢谢!
  回复  引用    
#24楼 2004-08-02 09:59 | 仪表 [未注册用户]
这样真是太累了
  回复  引用    
#25楼 2004-10-13 12:09 | 小鸟儿 [未注册用户]
你们好厉害啊,但是我看不懂,你们能否把它尽快变成应用程序啊,我的电脑感染了脚本病毒,QQ双击头像立即发出垃圾信息,电脑检测到WINDOWS下SMSS有恶意脚本,我下载过软件,但是我不知道是我下载的不对口,还是我的知识少之又少,我现在也没有解决问题,希望有好心的兄弟姐妹指点一二,hdl4663208@sina.com QQ号码是36205262先谢谢了。
  回复  引用  查看    
#26楼 2004-10-27 09:44 | sun      
 我觉得过滤的还是不周全,在html标记中也有脚本,如下面,怎么处理?
<IMG onmousewheel='return bbimg(this)'src='1.gif' onclick='fun();'  onload='javascript:if(this.width>screen.width-600)this.width=screen.width-600;if(this.height>100)this.height=100;' border=0>
  回复  引用  查看    
#27楼 2004-10-27 09:53 | sun      

比如过滤这个图片中的脚本

脚本为:

<IMG onmousewheel='return bbimg(this)' height=100 src="http://www.moongest.com/bbs/lt/UploadFile/200441110142526851.gif" onload="javascript:if(this.width>screen.width-600)this.width=screen.width-600;if(this.height>100)this.height=100;" border=0>

  回复  引用  查看    
#28楼 [楼主]2004-10-27 09:56 | dudu      
这篇文章提供的是一种思路, 并不是完整的解决方法。
  回复  引用    
#30楼 2004-12-22 18:38 | Steven Shel [未注册用户]
请教一个很郁闷的问题,关于恶意脚本过滤的
我使用的是cnblog的1.0版本架设自己的blog网站
但在发表文章时出现问题
想粘贴以下代码:
“<SPAN style="FONT-SIZE: 9pt; FONT-FAMILY: 宋体; mso-ascii-font-family: Arial; mso-hansi-font-family: Arial; mso-bidi-font-family: Arial; mso-bidi-font-size: 10.5pt"><EMBED codeBase=