【转】关于web应用程序安全的思考(一)

关于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请求的示例﹕

 

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

26InBlock.gif            else
27ExpandedSubBlockStart.gifContractedSubBlock.gif            dot.gif{
28InBlock.gif                continue;
29ExpandedSubBlockEnd.gif            }

30ExpandedSubBlockEnd.gif        }

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

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

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

56InBlock.gif    
57InBlock.gif    //獲取Request請求字符串
58InBlock.gif    private static string getRequestStr()
59ExpandedSubBlockStart.gifContractedSubBlock.gif    dot.gif{
60InBlock.gif        StringBuilder sb = new StringBuilder();
61InBlock.gif        sb.Append("GET /FrameWorkService/TestRequest.aspx?name=zkw&age=24 HTTP/1.1\r\n");
62InBlock.gif        sb.Append("Host: localhost\r\n");
63InBlock.gif        sb.Append("Accept: */*\r\n");
64InBlock.gif        sb.Append("Accept-Encoding: gzip, deflate\r\n");
65InBlock.gif        sb.Append("Accept-Language: zh-tw\r\n");
66InBlock.gif        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");
67InBlock.gif        sb.Append("UA-CPU: x86\r\n");
68InBlock.gif        sb.Append("Cookie: ASP.NET_SessionId=g5vz3k55q4dhgy3dvmm3dj4x\r\n");
69InBlock.gif        sb.Append("Connection: Close\r\n\r\n");
70InBlock.gif        return sb.ToString();
71ExpandedSubBlockEnd.gif    }

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

81ExpandedBlockEnd.gif}

 

 相關的aspx.cs程序如下﹕

None.gif using  System;
None.gif
using  System.Data;
None.gif
using  System.Configuration;
None.gif
using  System.Collections;
None.gif
using  System.Web;
None.gif
using  System.Web.Security;
None.gif
using  System.Web.UI;
None.gif
using  System.Web.UI.WebControls;
None.gif
using  System.Web.UI.WebControls.WebParts;
None.gif
using  System.Web.UI.HtmlControls;
None.gif
using  System.IO;
None.gif
None.gif
public  partial  class  TestRequest : System.Web.UI.Page
ExpandedBlockStart.gifContractedBlock.gif
dot.gif {
InBlock.gif    
protected void Page_Load(object sender, EventArgs e)
ExpandedSubBlockStart.gifContractedSubBlock.gif    
dot.gif{
InBlock.gif        Request.SaveAs(
"c:/test.txt",true);
InBlock.gif        
using(StreamReader sr = new StreamReader("c:/test.txt"))
ExpandedSubBlockStart.gifContractedSubBlock.gif        
dot.gif{
InBlock.gif            tt_request.Value 
= (sr.ReadToEnd());
ExpandedSubBlockEnd.gif        }

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

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

ExpandedBlockEnd.gif}

aspx頁面:

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

 

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

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

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

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

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

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

现在最多的做法还是通过cookie和session来完成的。

不过最好还是清楚一下cookie和session的原理﹕

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

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


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

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

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

 

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

 

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

 

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

下面是一些框架代碼﹕

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

 7 None.gif      public   class  WebSecurityModule:IHttpModule
 8 ExpandedBlockStart.gifContractedBlock.gif     dot.gif {
 9InBlock.gif
10InBlock.gif
11InBlock.gif
12ExpandedSubBlockStart.gifContractedSubBlock.gif        /**//// <summary>
13InBlock.gif        /// 在AuthorizeRequest事件中,進行驗証和授權
14InBlock.gif        /// </summary>
15ExpandedSubBlockEnd.gif        /// <param name="context"></param>

16InBlock.gif        public void Init(HttpApplication context)
17ExpandedSubBlockStart.gifContractedSubBlock.gif        dot.gif{
18InBlock.gifcontext.AuthorizeRequest  += new EventHandler(OnAuthorize);
19ExpandedSubBlockEnd.gif        }

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

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

39ExpandedSubBlockEnd.gif        }

40ExpandedBlockEnd.gif    }

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

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

转载于:https://www.cnblogs.com/xujiaci/archive/2007/10/03/913549.html

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值