net生活

博客园 首页 新随笔 联系 订阅 管理
  30 Posts :: 0 Stories :: 496 Comments :: 26 Trackbacks
 谢谢大家的关注

关于web应用程序安全的思考(序)中我曾提到﹕web应用程序的安全不应该依赖于客户端的请求信息。

众所周知﹐http协议是开放的﹐因此谁都能向网络上公开的web服务器发送request请求﹐要求一个URL(Uniform Resource Locator 统一资源定位符)

所谓request﹐不过是符合http协议(即遵守http请求语法)的一大段字符串而已﹕

下面是一个aspx的请求示例﹕

GET /FrameWorkService/TestRequest.aspx HTTP/1.1
Connection: Keep
-Alive
Accept: 
*/*
Accept
-Encoding: gzip, deflate
Accept
-Language: zh-tw
Host: localhost
User
-Agent: Mozilla/4.0 (compatible; MSIE 7.0; Windows NT 5.2; .NET CLR 1.1.4322; .NET CLR 2.0.50727; .NET CLR 3.0.04506.30)
UA
-CPU: x86

 

 

下面是一个web service的请求示例﹕

POST /testwssecurity/service2.asmx HTTP/1.1
Content
-Length: 288
Content
-Type: text/xml; charset=utf-8
Expect: 
100-continue
Host: localhost
User
-Agent: Mozilla/4.0 (compatible; MSIE 6.0; MS Web Services Client Protocol 2.0.50727.42)
SOAPAction: 
"http://tempuri.org/HelloWorld"

<?xml version="1.0" encoding="utf-8"?><soap:Envelope xmlns:soap="http://schemas.xmlsoap.org/soap/envelope/" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:xsd="http://www.w3.org/2001/XMLSchema"><soap:Body><HelloWorld xmlns="http://tempuri.org/" /></soap:Body></soap:Envelope>

 

相信大家基本上能理解上述字符串的意义。这表明我们只要组织类似的字符串﹐然后发往相应的web服务器﹐就可以请求到某个URL了﹐也就是说web请求不依赖浏览器(其实web也不依赖服务器﹐它只依赖http协议)

下面的这个程序是C#写的通过socket直接向web服务器发送http请求的示例﹕

 

 1using System;
 2using System.Text;
 3using System.IO;
 4using System.Net;
 5using System.Net.Sockets;
 6
 7public class server
 8{
 9    //建立socket連接
10    private static Socket ConnectSocket(string server, int port)
11    {
12        Socket s = null;
13        IPHostEntry hostEntry = null;
14        hostEntry = Dns.GetHostEntry(server);
15        foreach (IPAddress address in hostEntry.AddressList)
16        {
17            IPEndPoint ipe = new IPEndPoint(address, port);
18            Socket tempSocket =
19                new Socket(ipe.AddressFamily, SocketType.Stream, ProtocolType.Tcp);
20            tempSocket.Connect(ipe);
21            if (tempSocket.Connected)
22            {
23                s = tempSocket;
24                break;
25            }

26            else
27            {
28                continue;
29            }

30        }

31        Console.WriteLine(s==null?"":"連接建立成功﹗");
32        return s;
33    }

34
35    //發送request請求并返回響應字串
36    private static string SocketSendReceive(string request,string server, int port)
37    {
38        Byte[] bytesSent = Encoding.ASCII.GetBytes(request);
39        Byte[] bytesReceived = new Byte[256];
40        Socket s = ConnectSocket(server, port);
41        if (s == null)
42            return ("連接失敗﹗");
43        Console.WriteLine("正在發送請求");
44        s.Send(bytesSent, bytesSent.Length, 0);
45        int bytes = 0;
46        StringBuilder responsestr = new StringBuilder();
47        Console.WriteLine("正在接收web服務器的回應");
48        do
49        {
50            bytes = s.Receive(bytesReceived, bytesReceived.Length, 0);
51            responsestr.Append(Encoding.UTF8.GetString(bytesReceived, 0, bytes));
52        }

53        while (bytes > 0);
54        return responsestr.ToString();
55    }

56    
57    //獲取Request請求字符串
58    private static string getRequestStr()
59    {
60        StringBuilder sb = new StringBuilder();
61        sb.Append("GET /FrameWorkService/TestRequest.aspx?name=zkw&age=24 HTTP/1.1\r\n");
62        sb.Append("Host: localhost\r\n");
63        sb.Append("Accept: */*\r\n");
64        sb.Append("Accept-Encoding: gzip, deflate\r\n");
65        sb.Append("Accept-Language: zh-tw\r\n");
66        sb.Append("User-Agent: Mozilla/8.0 (compatible; MSIE 7.0; Windows NT 5.2; .NET CLR 1.1.4322; .NET CLR 2.0.50727; .NET CLR 3.0.04506.30)\r\n");
67        sb.Append("UA-CPU: x86\r\n");
68        sb.Append("Cookie: ASP.NET_SessionId=g5vz3k55q4dhgy3dvmm3dj4x\r\n");
69        sb.Append("Connection: Close\r\n\r\n");
70        return sb.ToString();
71    }

72
73    public static void Main(string[] args)
74    {
75        string requeststr = getRequestStr();
76        Console.WriteLine("請求字串如下﹕\n{0}",requeststr);
77        string result = SocketSendReceive(requeststr,"localhost",80);
78        Console.WriteLine(result);
79        Console.ReadLine();
80    }

81}

 

 相關的aspx.cs程序如下﹕

using System;
using System.Data;
using System.Configuration;
using System.Collections;
using System.Web;
using System.Web.Security;
using System.Web.UI;
using System.Web.UI.WebControls;
using System.Web.UI.WebControls.WebParts;
using System.Web.UI.HtmlControls;
using System.IO;

public partial class TestRequest : System.Web.UI.Page
{
    
protected void Page_Load(object sender, EventArgs e)
    
{
        Request.SaveAs(
"c:/test.txt",true);
        
using(StreamReader sr = new StreamReader("c:/test.txt"))
        
{
            tt_request.Value 
= (sr.ReadToEnd());
        }

        
foreach (string key in Request.QueryString.AllKeys)
            div_querystring.Value 
+= string.Format("{0}:{1}\r\n", key, Request[key]);
        
if (Session["firsttime"== null)
        
{
            Session[
"firsttime"= DateTime.Now.ToString("yyyy/MM/dd HH:mm:ss");
            Response.Write(
"<b style='color:red'>first request</b></br>");
        }

        Response.Write(
"First Time:" + Session["firsttime"].ToString());
    }

}

aspx頁面:

<%@ Page Language="C#" AutoEventWireup="true" CodeFile="TestRequest.aspx.cs" Inherits="TestRequest" %>
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
<html xmlns="http://www.w3.org/1999/xhtml" >
<head runat="server">
    
<title>請求字串提取示例</title>
</head>
<body>
這是Request字符串﹕
<br />
<textarea style="width:100%;height:200px" id="tt_request" runat="server">
</textarea>&nbsp;
以下是程式直接提取的參數﹕
<br />
<textarea id="div_querystring" runat="server" style="width:100%;height:100px">

</textarea>
</body>
</html>

 

由上可知﹐web服务器对于请求方的识别能力是很低的。因此作为web应用程序安全管控的唯一依据就只能是request的url了﹐因为只有它才是真实的﹐而我们进行安全管控的最终目的也就是

判断这个请求方是否拥有这个url的权限(即授权)

这就是我抽象出来的web安全管控的本质﹐依据这点﹐我们就可以把web安全管控和业务系统进行解耦。即在request到达其请求的url之前﹐先对这个url和请求方进行权限验证﹐如果通过﹐我们就放它过去﹐什么都不做﹐如果不通过﹐我们就可以向客户端发送相关的拒绝信息﹐并不让web服务器真正执行到那个url﹐完成安全管控。

 web安全管控中﹐授权的除了要识别授权的客体(URL)之外﹐我们还必须识别授权的主体﹐即请求方的认定﹐也就是常说的认证机制。

 由于http协议无状态的特点﹐每次request时﹐web服务器都无法识别这个请求是否和上次的请求是否相同。因此认证机制在某种程度上来说其实相当困难。

曾经遇到过通过IP来认证的﹐先不说这种机制对于web可以anywhere访问是一种倒退﹐单是那种IP更改﹐欺骗或通过Proxy访问就无法适用了。

现在最多的做法还是通过cookiesession来完成的。

不过最好还是清楚一下cookiesession的原理﹕

Cookiecookie其实也是http request header的一部分﹐我们可以把任何值当作cookie发给web服务器。

至于Session,不知道大家有没有看过.netsession实现机制﹐每次请求后﹐.net会写入一个session_idcookie到客户端﹐这样在下次客户再请求时﹐提取这个cookie来识别。剩下的就和cookie一样了。


大家可以看一下我上面這個例子﹐在那支request請求的程式中加入相關的session_id的cookie﹐你會發現程式是無法識別是不是真正的session的 

曾经有人设计过这样一个系统﹐要我尝试攻入其中某个已管控的页面中。

它是这样做的﹐在每个要权限的aspx页面的page_load中判断Session["userid"]是否为null,如果不是﹐则转向登录页面。

 

在我截获了网络上某个已登录用户和web服务器通讯的requestresponse之后﹐提取其cookie信息﹐交将它放入我的request请求中﹐我就以那个登录用户的身份执行了那支程序了。

 

但是这并不是说就不能使用cookiesession来作为认证的机制﹐我的意思是﹐web应用程序的安全也是相对的﹐必须建立在基本的网络安全和用户安全防范意识之上。可以采取包括加密会关键页面(如登录页面)的会话(例如使用https)或要求用户每次使用完系统后注销或关闭浏览器﹐以及尽可能多的对cookiesession做更多验证等。

 

在认证和授权的原理讲完后﹐要在asp.net应用程序中要完成上述的安全管控其实非常简单﹐设计一个httpmodule﹐然后捕获相关的事件﹐在这个事件中进行权限判断即可。

下面是一些框架代碼﹕

 1    /// <summary>
 2    /// 使用HttpModule模組進行web權限管控
 3    /// </summary>
 4    /// <remarks>
 5    /// 自定義一個HttpModule﹐并在AuthorizeRequest事件中完成授權動作
 6    /// </remarks>

 7    public class WebSecurityModule:IHttpModule
 8    {
 9
10
11
12        /// <summary>
13        /// 在AuthorizeRequest事件中,進行驗証和授權
14        /// </summary>
15        /// <param name="context"></param>

16        public void Init(HttpApplication context)
17        {
18context.AuthorizeRequest  += new EventHandler(OnAuthorize);
19        }

20            
21        /// <summary>
22        /// 調用PFSAuthorize類進行授權
23        /// </summary>
24        /// <param name="sender"></param>
25        /// <param name="e"></param>
26        /// <remarks>主要是看當前用戶(包括匿名用戶)是否擁有當前Request的url的權限</remarks>

27        public void OnAuthorize(Object sender,EventArgs e)
28        {
29           //認証﹕提取用戶ID
30            string userid = getuserid();
31           //授權﹕判斷用戶ID是否有URL的權限
32              bool hasright = authroize(userid,HttpContext.Current.Request.Url);
33            if (!hasright)
34            {
35                //進行無權信息返回
36                  //如轉向無權登錄頁面
37                  Response.Redirect("error.aspx");
38            }

39        }

40    }

最后我們只要將這個類封裝成一個單獨的DLL﹐然后在每個web.config的httpmodules節中配置即完成了安全管控

后面的文章我會講如何設計這些模塊達到最好擴展性

 

posted on 2006-12-23 11:33 Kevin Zou 阅读(2954) 评论(22)  编辑 收藏 网摘 所属分类: asp.net

Feedback

好文,将继续关注
  回复  引用    

#2楼 2006-12-23 14:12 Cat Chen      
1.截获Session比截获Cookies要高一个难度等级,所以通常认为Session是比较可信的。Cookies是长期有效的,你截获了一个Cookies通常都还有效,而且Cookies本身就包含验证信息,有可能是未加密的。

SessionID是一个不可理解的字符串标记,你截获了也不知道它当前是否还有效,更加不知道这个标记在服务器端对应的验证信息是什么。所以大多数情况下,用Session就能很好的解决验证相关的问题。

你现在已经自己截获自己的SessionID,那当然说它弱。给一个目标论坛你,你去截获Admin的SessionID,你就不会觉得容易的了,因为你必须在线等他上线,上线后你通过特定的方法获取到SessionID必须马上使用,否则就会过期。而如果截获Cookies就简单多了,设置好陷阱我就睡觉去,每天来查一次他的信息是否已被截获就行。

2.在不信任Session的情况下,我个人倾向于使用Cookies保存Token的做法,而且每个用户同一时间只有一个Token,新登录产生新Token并让旧Token失效。

  回复  引用  查看    

#3楼 2006-12-23 14:44 随心所欲      
如果伪造了请求方,你还是不能判断吧?
是不是我没看懂?按照你的思路,重点应该放在如何识别请求方上,但是看你的判断,里面没有对请求方的验证.

  回复  引用  查看    

#4楼 2006-12-23 14:49 tsoukw[未注册用户]
@Cat Chen
你说得非常正确,我讲得只是一些极端的例子而已。
只是想让大家知道在使用session或cookie尽量增加一下难度,例如过期策略,绑定IP一起计算等,这样您的小小的增加成本将换来更大的安全系数,这就像密码多设一位造成的破解难度呈几何级增长一样。
谢谢您的回复和意见!




  回复  引用    

好文章,获益匪浅
  回复  引用    

#6楼 2006-12-23 14:53 tsoukw[未注册用户]
@随心所欲
不是你的那个意思,我上面有解释,认证不是我的重点(包括我在做真实案例时我也是通过session或cookie来完成,但是会适当增加点难度,例如获取session后我还会判断这个key所对应的IP是否也符合)
依照URL进行授权才是我这篇随笔的关键

  回复  引用    

#7楼 2006-12-23 15:04 随心所欲      

如果url都是一样的怎么授权?参数通过内部的调用实现。
你的安全层又分几层?不可能仅仅这一个吧。
总觉得根据url来做安全,不是很有价值,因为url包含的信息太少,甚至可能就没有任何信息(比方说用一个aspx来加载众多ascx的framework)。又或者,使用了url重订向。还有,大量的其他资源的访问是不是也会给你增加很多验证的开销。

  回复  引用  查看    

#8楼 2006-12-23 15:19 银河      
好文章!
关注中...

  回复  引用  查看    

#9楼 2006-12-23 15:47 makeliving[匿名]
通过什么方式获得的下列信息
GET /FrameWorkService/TestRequest.aspx HTTP/1.1
Connection: Keep-Alive
Accept: */*
Accept-Encoding: gzip, deflate
Accept-Language: zh-tw
Host: localhost
User-Agent: Mozilla/4.0 (compatible; MSIE 7.0; Windows NT 5.2; .NET CLR 1.1.4322; .NET CLR 2.0.50727; .NET CLR 3.0.04506.30)
UA-CPU: x86

  回复  引用    

#10楼 2006-12-23 16:11 d[未注册用户]
学习了. (开源的可视化自定义web表单工具, 在: http://my5155.meibu.com )
  回复  引用    

#11楼 2006-12-23 16:28 tsoukw[未注册用户]
@随心所欲
我会在接下来的文章详细阐述这些问题,希望你能继续关注
现在我先简单地回答一下你的问题

如果url都是一样的怎么授权?参数通过内部的调用实现。
--------------------------------
我所谓的URL不是单纯的指那个请求地址,您完全可以从request的信息中提取出很多有意义的东西来。
举个例子吧,例如某个webservice有可能有很多方法,而每个方法的权限都不一样,这时它们访问的URL例如都是xx.asmx,但是我们在授权时,不是只依据xx.asmx,我们完全可以再提取出它请求的真正方法(通过soap body分析)出来,然后接合这两者得到一个真正的,唯一的资源ID,然后再进行授权。
不知道是不是你的那个意思?

你的安全层又分几层?不可能仅仅这一个吧。
----------------------------------------
我要的是通用的web应用程式安全,所以我的安全层只有这一个,这样能方便的使用和维护。
当然如果你说你的dll除了给aspx调用,还给winform调用,这时的安全我想就应该再管控制dll的方法授权了,当然这个不是我讨论的范畴


总觉得根据url来做安全,不是很有价值,因为url包含的信息太少,甚至可能就没有任何信息(比方说用一个aspx来加载众多ascx的framework)。
--------------------------------------------
对于开发者来说,一切URL都有真正的意义,在系统部署之前,我们都能明确的知道某个URL是实现什么功能的,所以完全可以建立这样的资源列表
而使用URL做安全,才是真正的web上安全管控,因为客户端没有任何途径可以避开这层验证。

又或者,使用了url重订向。
---------------------------------------------
我们管控的URL不是用户输入的URL,而是系统真正执行的URL,所以对于重定向,完全可以置之不理。

还有,大量的其他资源的访问是不是也会给你增加很多验证的开销。
----------------------------------------------
这是设计的技巧 ,可以有很多方式,当然最重要的就是只验证一次,然后缓存起来,这样是不会有很大的性能损耗的
其次并不是每种资源都要验证,我们可以预先规定一些不要权限管控的资源(如通过文件类型,目录等)

最后谢谢你的关注,希望你能提出更多自己的看法

  回复  引用    

#12楼 2006-12-23 16:31 tsoukw[未注册用户]
@makeliving[匿名]
这些信息有很多方式可以得到
在客户端可以通过在浏览器上安装如WebDevHelper来截取请求字串
在.net程式中可以通过如
Request.SaveAs("c:/test.txt",true);存到文件系统中
通过Request.InputStream方式得到输入流
还有很多的Request属性来获得

  回复  引用    

#13楼 2006-12-23 18:19 随心所欲      
说到底,你是在用资源分类来管理权限。你的资源就是url(以及从url里面提取出来的访问资源的信息)。
这是一种方法。

但是我更倾向于另外一种方法,就是根据Action来做安全控制(比如到数据库的crud,比如函数、服务的调用)。根据用户请求的命令来做控制。时髦一点说,根据用户访问的service来做控制(SOA)。

可能殊途同归。

  回复  引用  查看    

#14楼 2006-12-23 18:48 tsoukw[未注册用户]
@随心所欲
确实如此,其实在接下来我会把我的设计原理和大家分享。
你将看到我真正的出发点其实就是你所说的通过Action来管控,最终的用户面对的权限将是一个一个的功能,而URL只不过是我完成这个目的的一个基础,我把它用在Web应用程式的安全上,以便能够更好的抽离出具体业务

  回复  引用    

#15楼 2006-12-23 19:39 U2U      
干嘛不直接用httpclient webclient ?不解。
  回复  引用  查看    

一点也看不懂啊! 看来要好好学习了
  回复  引用    

#17楼 2006-12-24 01:21 随心所欲      
一个Action可能会对应几个View(MVC模型).这个是可以理解的。
所以,对与几个View(V1,V2,V3)可能同时使用同一个Action.(A1)
在你的设计里,数据库的记录就会是这样(R1是用户的角色)
V1 R1 yes
V2 R1 yes
V3 R1 yes
有三条记录,因为每个view所对应的url肯定是不一样的。

如果使用Action来控制呢?
A1 R1 yes
一条就够了。

-------
这就是我的看法:通过访问资源控制,不太效率。

  回复  引用  查看    

#18楼 2006-12-27 15:59 YLH      
我用到权限控制是这样一个模型:
public static bool Authenticate(角色ID,资源ID,ActionID,condition)

资源是指一种业务对象,比如订单
Action是指一种操作,比如审核
condition是指一种筛选条件

  回复  引用  查看    

YLH 的方式我比较赞成

如果按楼主的方法 如果用户很多的话 那就配死你吧!
我搞不懂居然还有用 URL做权限的,明明有很多更好的方法不用,还要用这种方式,这不是搬石头扎自己的脚吗?

  回复  引用    

#20楼 2008-02-21 16:15 王孟军!      
可惜在OnAuthorize方法里还不能用SESSION
  回复  引用  查看    




发表评论

昵称: [登录] [注册]

主页:

邮箱:(仅博主可见)

评论内容:

  登录  注册

[使用Ctrl+Enter键快速提交评论]

0 601252




相关文章:

相关链接: