以下内容基于RadGrid控件
一、从数据源头保密
如果使用RadGrid细心的同志一定会发现生成的网页源代码中会出现你绑定在DataField的字段名称,而这个是初始化的步骤。所以我们在从数据库select出数据的时候就必须把每个数据的字段名给AS掉,并且as后的名称尽量不要与真实的字段名太相似,这样就可以避免有意的人获取那些字段的名称从而进行更有力的攻击。
二、不让主键有出现的机会
一般这个情况都会出现在懒惰,或者短时间内需要完成的项目,为了方便会直接把主键放在前端,这样就可以直接传递给后台。但不知这样的危险是很大的,有意的人可以改变这些主键的ID,或者使用这些主键的ID进行恶意的攻击。这里我使用的是控件自带的一个属性DataKeyValues,当然这个可能也不是太可靠。应该是把主键存在了视图状态中了,只是经过的加密,一般的反base64加密还是解不开的,安全稍次点。当然最好的方法将主键的ID单独存在在SESSION中。(我相信不会有几千个ID存在里面吧,难道你不做分页或者直接把table给控件绑定?)
三、多列排序不在慢
因为字段都被as掉了,懒的同学可能会喊那样我就没法自己组织sql语句了,即使组织也要花很多的代码去写,这里我只能说你的数据结构没有学好,才导致这样的。完全可以使用键值对,把你的sortexpress的名称作为键真实的字段名作为值,这样就可以了(挑刺的人会说那样直接[]会出现没有的情况就会throw了),我们可以在这之前先判断下是否含有,这总不会出错了吧。
四、最终的方案
只要组织SQL语句的所有值都是来自服务器自己保存的数据而非用户输入的数据都是安全的,当时实际情况并不是。比如模糊搜索,必须要把用户输入的字符串加入sql语句中进行查询。所以这里我们就需要转义,将'转义为'',%转义为[%],_转义为[_],因为like语句的特殊性所以必须经过这三个转义,否则就会被注入,当然还有--(sql语句中的注释符)也要进行适当的转义。
五、实际的锻炼下
因为最近忙项目,所以直接在这个项目中添加了一个页面(不影响项目,因为使用了TFS),所以其中我将会不显示某些涉及项目的内容。
1、前台代码:(因为是项目所以字段名没有as,并且因为涉及项目将会涂抹掉)
1 <%@ Page Language="C#" AutoEventWireup="true" CodeBehind="test.aspx.cs" Inherits="xxxxxxx.xxxxx.test" %> 2 3 <%@ Register Assembly="Telerik.Web.UI" Namespace="Telerik.Web.UI" TagPrefix="telerik" %> 4 5 DOCTYPE html> 6 7 <html xmlns="http://www.w3.org/1999/xhtml"> 8 <head runat="server"> 9 <meta http-equiv="Content-Type" content="text/html; charset=utf-8"/> 10 <title>title> 11 <script type="text/javascript"> 12 function openAddWindow(id) { 13 if (!id) { 14 alert('请选择一行数据'); 15 } 16 else { 17 var oWnd = radopen("xxxxxx.aspx?pid=" + id, "RadWindowManager1"); 18 oWnd.setSize(640, 400); 19 oWnd.center(); 20 oWnd.show(); 21 } 22 } 23 script> 24 head> 25 <body> 26 <form id="form1" runat="server"> 27 <telerik:RadScriptManager ID="ScriptManager1" runat="server" EnableTheming="True"> 28 <Scripts> 29 <asp:ScriptReference Assembly="Telerik.Web.UI" Name="Telerik.Web.UI.Common.Core.js"> 30 </asp:ScriptReference> 31 <asp:ScriptReference Assembly="Telerik.Web.UI" Name="Telerik.Web.UI.Common.jQuery.js"> 32 </asp:ScriptReference> 33 <asp:ScriptReference Assembly="Telerik.Web.UI" Name="Telerik.Web.UI.Common.jQueryInclude.js"> 34 </asp:ScriptReference> 35 Scripts> 36 telerik:RadScriptManager> 37 <telerik:RadAjaxLoadingPanel ID="RadAjaxLoadingPanel1" runat="server" Skin="Default">telerik:RadAjaxLoadingPanel> 38 <telerik:RadAjaxManager ID="RadAjaxManager1" runat="server"> 39 <AjaxSettings> 40 <telerik:AjaxSetting AjaxControlID="RadButton1"> 41 <UpdatedControls> 42 <telerik:AjaxUpdatedControl ControlID="RadGrid1" LoadingPanelID="RadAjaxLoadingPanel1" /> 43 <telerik:AjaxUpdatedControl ControlID="RadTextBox1" /> 44 UpdatedControls> 45 telerik:AjaxSetting> 46 <telerik:AjaxSetting AjaxControlID="RadGrid1"> 47 <UpdatedControls> 48 <telerik:AjaxUpdatedControl ControlID="RadGrid1" LoadingPanelID="RadAjaxLoadingPanel1" /> 49 <telerik:AjaxUpdatedControl ControlID="RadTextBox1" /> 50 UpdatedControls> 51 telerik:AjaxSetting> 52 <telerik:AjaxSetting AjaxControlID="RadButton2"> 53 <UpdatedControls> 54 <telerik:AjaxUpdatedControl ControlID="RadGrid1" /> 55 UpdatedControls> 56 telerik:AjaxSetting> 57 AjaxSettings> 58 telerik:RadAjaxManager> 59 <div> 60 <div> 61 <telerik:RadTextBox ID="RadTextBox1" EnableViewState="false" runat="server">telerik:RadTextBox> 62 <telerik:RadButton ID="RadButton1" EnableViewState="false" runat="server" Text="RadButton" OnClick="RadButton1_Click">telerik:RadButton> 63 div> 64 <telerik:RadGrid AutoGenerateColumns="false" AllowSorting="true" PageSize="5" AllowPaging="true" AllowCustomPaging="true" ID="RadGrid1" runat="server" OnDeleteCommand="RadGrid1_DeleteCommand" OnPageIndexChanged="RadGrid1_PageIndexChanged" OnSortCommand="RadGrid1_SortCommand"> 65 <ClientSettings> 66 <Selecting AllowRowSelect="true" CellSelectionMode="SingleCell" /> 67 ClientSettings> 68 <MasterTableView AllowMultiColumnSorting="true" NoMasterRecordsText="暂无数据" AllowCustomSorting="true" runat="server" DataKeyNames="xxx"> 69 <Columns> 70 <telerik:GridBoundColumn DataField="xxx" AllowSorting="false" HeaderText="编号" UniqueName="bh" >telerik:GridBoundColumn> 71 <telerik:GridBoundColumn DataField="xxx" HeaderText="主题" UniqueName="zt" SortExpression="zt" >telerik:GridBoundColumn> 72 <telerik:GridBoundColumn DataField="xxx" HeaderText="表扬者" UniqueName="byz" SortExpression="byz" >telerik:GridBoundColumn> 73 <telerik:GridBoundColumn DataField="xxx" HeaderText="表扬单位" UniqueName="bydw" SortExpression="bydw" >telerik:GridBoundColumn> 74 <telerik:GridBoundColumn DataField="xxx" HeaderText="责任单位" UniqueName="zrdw" SortExpression="zrdw" >telerik:GridBoundColumn> 75 <telerik:GridBoundColumn DataField="xxx" AllowSorting="false" HeaderText="状态" UniqueName="zt">telerik:GridBoundColumn> 76 <telerik:GridBoundColumn DataField="xxx" HeaderText="回复时间" UniqueName="hfsj" SortExpression="hfsj" >telerik:GridBoundColumn> 77 <telerik:GridButtonColumn HeaderText="操作" CommandName="Delete" ConfirmDialogType="RadWindow" ConfirmTitle="操作" ConfirmText="确定删除" Text="删除">telerik:GridButtonColumn> 78 Columns> 79 <PagerStyle FirstPageText="首页" LastPageText="尾页" Mode="NumericPages" PrevPageText="上一页" NextPageText="下一页" /> 80 MasterTableView> 81 telerik:RadGrid> 82 <div style="text-align:right" > 83 <telerik:RadButton ID="RadButton2" EnableViewState="false" runat="server" Text="add" OnClick="RadButton2_Click" >telerik:RadButton> 84 <telerik:RadButton ID="RadButton3" EnableViewState="false" runat="server" Text="edit" >telerik:RadButton> 85 div> 86 div> 87 <telerik:RadWindowManager ID="RadWindowManager1" runat="server" Skin="Metro">telerik:RadWindowManager> 88 form> 89 body> 90 html>
2、后台代码
using System; using System.Collections.Specialized; using System.Collections.Generic; using System.Linq; using System.Web; using System.Text; using System.Data; using System.Web.UI; using System.Web.UI.WebControls; namespace xxx.xxx { ////// 全面实例化介绍使用RadGrid /// public partial class test : System.Web.UI.Page { private GAP_DLL.BLL.Fc_Praise Bll_Praise = new GAP_DLL.BLL.Fc_Praise(); private Dictionary<string, Telerik.Web.UI.GridSortOrder> _dic_sort = null; //将简称字段名解析 private Dictionary<string, string> DataUnique = new Dictionary<string, string>() { {"zt","xxxx"}, {"byz","xxxx"}, {"bydw","xxxx"}, {"zrdw","xxxx"}, {"hfsj","xxxx"} }; /// /// 存储排序设置 /// private Dictionary<string, Telerik.Web.UI.GridSortOrder> Dic_Sort { get { if (_dic_sort != null) { return _dic_sort; } if (ViewState["gridsort"] != null) { return _dic_sort = ViewState["gridsort"] as Dictionary<string, Telerik.Web.UI.GridSortOrder>; } else { return _dic_sort = new Dictionary<string, Telerik.Web.UI.GridSortOrder>(); } } } /// /// 保存多列排序 /// 如果为NORMAL则删除对应键值对 /// /// 列标识 /// 排序方式 protected void SetSortState(string name, Telerik.Web.UI.GridSortOrder order) { if (Dic_Sort.ContainsKey(name)) { if (order == Telerik.Web.UI.GridSortOrder.None) { Dic_Sort.Remove(name); } else { Dic_Sort[name] = order; } } else { if (order != Telerik.Web.UI.GridSortOrder.None) { Dic_Sort.Add(name, order); } } ViewState["gridsort"] = Dic_Sort; } /// /// 获得组织好的排序sql语句 /// /// protected string GetSortSql() { if (Dic_Sort != null) { StringBuilder sortsql = new StringBuilder(); foreach (KeyValuePair<string,Telerik.Web.UI.GridSortOrder> sortitem in Dic_Sort) { if (DataUnique.ContainsKey(sortitem.Key)) { string field = DataUnique[sortitem.Key]; if (sortitem.Value == Telerik.Web.UI.GridSortOrder.Ascending) { sortsql.Append(" " + field + " ASC,"); } else if (sortitem.Value == Telerik.Web.UI.GridSortOrder.Descending) { sortsql.Append(" " + field + " DESC,"); } } } if (sortsql.Length > 0) { sortsql.Remove(sortsql.Length - 1, 1); } return sortsql.ToString(); } return ""; } /// /// 获得对应的记录 /// /// 条件语句 /// 排序语句 /// 开始索引 /// 结束索引 /// 返回公有的数量 /// protected DataTable GetPraiseData(string key,string order, int begin, int end,out int count) { count = Bll_Praise.GetRecordCount(""); DataSet PraiseSet = Bll_Praise.GetListByPage((!String.IsNullOrEmpty(key)) ? " xxx like '%" + key + "%' " : "", order, begin, end); if (PraiseSet.Tables.Count > 0) { return PraiseSet.Tables[0]; } return null; } protected void Page_Load(object sender, EventArgs e) { if (!IsPostBack) { int count = 0; RadGrid1.DataSource = GetPraiseData(RadTextBox1.Text, "", 0, RadGrid1.PageSize, out count); RadGrid1.VirtualItemCount = count; RadGrid1.DataBind(); } } //删除操作 protected void RadGrid1_DeleteCommand(object sender, Telerik.Web.UI.GridCommandEventArgs e) { //获得对应ID string id = e.Item.OwnerTableView.DataKeyValues[e.Item.ItemIndex]["xxx"].ToString(); //删除操作 } //排序操作 //无需控件绑定 protected void RadGrid1_SortCommand(object sender, Telerik.Web.UI.GridSortCommandEventArgs e) { int begin = RadGrid1.CurrentPageIndex * RadGrid1.PageSize; int end = begin + RadGrid1.PageSize; int count = 0; string sort = ""; //将新的排序增加进视图中 SetSortState(e.SortExpression, e.NewSortOrder); //获得已有的排序SQL语句 sort = GetSortSql(); RadGrid1.DataSource = GetPraiseData(RadTextBox1.Text,sort, begin+1, end, out count); RadGrid1.VirtualItemCount = count; } //分页操作 //无需控件绑定 protected void RadGrid1_PageIndexChanged(object sender, Telerik.Web.UI.GridPageChangedEventArgs e) { int begin = e.NewPageIndex * RadGrid1.PageSize; int end = begin + RadGrid1.PageSize; int count = 0; string sort = ""; //获得已有的排序SQL语句 sort = GetSortSql(); RadGrid1.DataSource = GetPraiseData(RadTextBox1.Text,sort, begin+1, end, out count); RadGrid1.VirtualItemCount = count; } //主题搜索 //省略非法字符过滤 protected void RadButton1_Click(object sender, EventArgs e) { int begin = RadGrid1.CurrentPageIndex * RadGrid1.PageSize; int end = begin + RadGrid1.PageSize; int count = 0; string sort = ""; sort = GetSortSql(); RadGrid1.DataSource = GetPraiseData(RadTextBox1.Text, sort, begin + 1, end, out count); RadGrid1.VirtualItemCount = count; RadGrid1.Rebind(); } //打开对话框 protected void RadButton2_Click(object sender, EventArgs e) { if (RadGrid1.SelectedCellIndexes.Count > 0) { int index = Convert.ToInt32(RadGrid1.SelectedIndexes[0]); string id = RadGrid1.MasterTableView.DataKeyValues[index]["xxx"].ToString(); RadAjaxManager1.ResponseScripts.Add("openAddWindow('" + id + "')"); } else { RadAjaxManager1.Alert("请选择数据"); } } } }
六、思考方式
- 利用控件自身保存主键的方式来进行相关的操作,缺点是需要通过索引,且需要判断索引是否超出保存ID的数量否则报错,并且对方可修改索引来欺骗服务器,解决方案是进行权限判断。
- 将所有的字段名都as掉,因为该控件会在前天写入初始化代码,其中会涉及字段名,并且是明文存储。
- 排序困难,解决方案是组织一个以简称对应真实字段名的键值对。缺点是需要判断是否存在
- 保存排序的状态,避免进行翻页后排序失效,方案为存储带有简称与排序方式的键值对到视图状态中(如果对方修改了视图状态,后台将会不对该修改过的数据进行排序)
- 打开编辑等涉及ID的对话框(因为我们这里都是以对话框的形式,而非跳转页面的形式),如何安全的进行ID传值,解决方案,在服务端判断出所选项,提出ID,使用ajax控件的执行js的方法直接执行,避免使用Js来获得id并保存在js中。
- 缺点是没有对文本输入框的内容进行sql语句过滤,这样基本上所有组织sql语句的数据都来自服务端自己保存的数据,安全性更好。
- 最佳方案是将主键ID保存在session中,因为视图状态的暂时破解不了只是暂时的,不是永远的。还是保存在服务端安全。
- 最不喜欢直接把直接select的数据交给控件,让控件自己进行管理的懒人。(如果是几万条数据那个数量可想而知)