|
在關于web應用程序安全的思考(序)中我曾提到﹕web應用程序的安全不應該依賴于客戶端的請求信息。 眾所周知﹐http協(xié)議是開放的﹐因此誰都能向網(wǎng)絡上公開的web服務器發(fā)送request請求﹐要求一個URL(Uniform Resource Locator 統(tǒng)一資源定位符)。 所謂request﹐不過是符合http協(xié)議(即遵守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:///HelloWorld" <?xml version="1.0" encoding="utf-8"?><soap:Envelope xmlns:soap="http://schemas./soap/envelope/" xmlns:xsi="http://www./2001/XMLSchema-instance" xmlns:xsd="http://www./2001/XMLSchema"><soap:Body><HelloWorld xmlns="http:///" /></soap:Body></soap:Envelope>
相信大家基本上能理解上述字符串的意義。這表明我們只要組織類似的字符串﹐然后發(fā)往相應的web服務器﹐就可以請求到某個URL了﹐也就是說web請求不依賴瀏覽器(其實web也不依賴服務器﹐它只依賴http協(xié)議)。 下面的這個程序是C#寫的通過socket直接向web服務器發(fā)送http請求的示例﹕
1
using System;2 using System.Text;3 using System.IO;4 using System.Net;5 using System.Net.Sockets;6 ![]() 7 public class server8 ![]() ![]() {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 else27 ![]() {28 continue;29 }30 }31 Console.WriteLine(s==null?"":"連接建立成功﹗");32 return s;33 }34 ![]() 35 //發(fā)送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("正在發(fā)送請求 ");44 s.Send(bytesSent, bytesSent.Length, 0);45 int bytes = 0;46 StringBuilder responsestr = new StringBuilder();47 Console.WriteLine("正在接收web服務器的回應 ");48 do49 ![]() {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./TR/xhtml1/DTD/xhtml1-transitional.dtd"> <html xmlns="http://www./1999/xhtml" > <head runat="server"> <title>請求字串提取示例</title> </head> <body> 這是Request字符串﹕<br /> <textarea style="width:100%;height:200px" id="tt_request" runat="server"> </textarea> 以下是程式直接提取的參數(shù)﹕<br /> <textarea id="div_querystring" runat="server" style="width:100%;height:100px">![]() </textarea> </body> </html>
由上可知﹐web服務器對于請求方的識別能力是很低的。因此作為web應用程序安全管控的唯一依據(jù)就只能是request的url了﹐因為只有它才是真實的﹐而我們進行安全管控的最終目的也就是 這就是我抽象出來的web安全管控的本質(zhì)﹐依據(jù)這點﹐我們就可以把web安全管控和業(yè)務系統(tǒng)進行解耦。即在request到達其請求的url之前﹐先對這個url和請求方進行權限驗證﹐如果通過﹐我們就放它過去﹐什么都不做﹐如果不通過﹐我們就可以向客戶端發(fā)送相關的拒絕信息﹐并不讓web服務器真正執(zhí)行到那個url﹐完成安全管控。 在web安全管控中﹐授權的除了要識別授權的客體(URL)之外﹐我們還必須識別授權的主體﹐即請求方的認定﹐也就是常說的認證機制。 由于http協(xié)議無狀態(tài)的特點﹐每次request時﹐web服務器都無法識別這個請求是否和上次的請求是否相同。因此認證機制在某種程度上來說其實相當困難。 曾經(jīng)遇到過通過IP來認證的﹐先不說這種機制對于web可以anywhere訪問是一種倒退﹐單是那種IP更改﹐欺騙或通過Proxy訪問就無法適用了。 現(xiàn)在最多的做法還是通過cookie和session來完成的。 不過最好還是清楚一下cookie和session的原理﹕ Cookie﹕cookie其實也是http request header的一部分﹐我們可以把任何值當作cookie發(fā)給web服務器。 至于Session,不知道大家有沒有看過.net的session實現(xiàn)機制﹐每次請求后﹐.net會寫入一個session_id的cookie到客戶端﹐這樣在下次客戶再請求時﹐提取這個cookie來識別。剩下的就和cookie一樣了。
曾經(jīng)有人設計過這樣一個系統(tǒng)﹐要我嘗試攻入其中某個已管控的頁面中。 它是這樣做的﹐在每個要權限的aspx頁面的page_load中判斷Session["userid"]是否為null,如果不是﹐則轉(zhuǎn)向登錄頁面。
在我截獲了網(wǎng)絡上某個已登錄用戶和web服務器通訊的request和response之后﹐提取其cookie信息﹐交將它放入我的request請求中﹐我就以那個登錄用戶的身份執(zhí)行了那支程序了。
但是這并不是說就不能使用cookie和session來作為認證的機制﹐我的意思是﹐web應用程序的安全也是相對的﹐必須建立在基本的網(wǎng)絡安全和用戶安全防范意識之上??梢圆扇“用軙P鍵頁面(如登錄頁面)的會話(例如使用https)或要求用戶每次使用完系統(tǒng)后注銷或關閉瀏覽器﹐以及盡可能多的對cookie和session做更多驗證等。
在認證和授權的原理講完后﹐要在asp.net應用程序中要完成上述的安全管控其實非常簡單﹐設計一個httpmodule﹐然后捕獲相關的事件﹐在這個事件中進行權限判斷即可。 1
![]() /**//// <summary>2 /// 使用HttpModule模組進行web權限管控3 /// </summary>4 /// <remarks>5 /// 自定義一個HttpModule﹐并在AuthorizeRequest事件中完成授權動作6 /// </remarks>7 public class WebSecurityModule:IHttpModule8 ![]() {9 ![]() 10 ![]() 11 ![]() 12 ![]() /**//// <summary>13 /// 在AuthorizeRequest事件中,進行驗証和授權14 /// </summary>15 /// <param name="context"></param>16 public void Init(HttpApplication context)17 ![]() {18 context.AuthorizeRequest += new EventHandler(OnAuthorize);19 }20 21 ![]() /**//// <summary>22 /// 調(diào)用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 //認証﹕提取用戶ID30 string userid = getuserid();31 //授權﹕判斷用戶ID是否有URL的權限32 bool hasright = authroize(userid,HttpContext.Current.Request.Url);33 if (!hasright)34 ![]() {35 //進行無權信息返回36 //如轉(zhuǎn)向無權登錄頁面37 Response.Redirect("error.aspx");38 }39 }40 }最后我們只要將這個類封裝成一個單獨的DLL﹐然后在每個web.config的httpmodules節(jié)中配置即完成了安全管控 |
|
|