注入漏洞总结

学习注入也有一段时间了,虽然学的不多,但想着对注入漏洞做一些总结,于是写下这篇文章,有好的建议欢迎留言。

注入漏洞在十大Web安全漏洞之中,其主要原因是对用户的输入过滤不严,导致程序以危险的方式运行,注入漏洞应用最广泛,杀伤力也很大如最常见的sql注入,命令注入,还有代码注入、xss等。

SQL注入:最常见的Web漏洞之一,作用对象在数据库中,程序没有对用户输入数据的合法性进行验证和过滤,导致sql查询语句被恶意拼接,sql注入漏洞存在的条件:参数用户可控、参数可以动态拼接sql语句并带入数据库查询、参数过滤不严。

注入攻击:SQl注入根据注入效果可以分为:UNION联合注入、报错注入、布尔盲注、时间盲注、堆叠注入、二次注入等,按照提交方式可以分为:GET注入、POST注入、HTTP头注入,以及其他分类。

UNION联合注入:联合注入的前提条件是用户可控参数所在Sql语句为查询语句,且以页面存在回显,如:select * from users where id=”$id”;。此时可以通过更改参数id来实现对数据库的操作。考虑到前提是查询双方具有相同的列,可以使用order by 语句大致判断列数,也可以使用select 1,2,3,4等依次查看。UNION联合注入往往是最实用的,因为判断了回显位后就可以拼接语句union select version()等获取数据库信息,当然如果权限允许也可以在information_schema中查看全部数据库信息。在查询过程中可能会遇到查询结果有多个数据,但页面只显示一段,可以使用concat()、group_concat()等函数。

布尔盲注:与UNION联合注入不同,布尔盲注用于页面无回显信息,但不同的语句会有不同的效果,如:id=1,页面显示登入成功,但当输入id=0(前提是数据库中没有id=0)时,页面显示登入失败或什么都不显示,此时就可以用布尔盲注来判断语句的真假。如id=0’ or length(database())>4 --+ 可以判断数据库的名称的长度,有了长度后就可以逐字符判断,如:id=0’ or substring(database(),1,1)=’s’--+表示判断从数据库名第一个字符开始截取一个字符判断是否是字符s。也可以构造一些特殊语句如:id=0’ord(substring(database(),1,1))=65 --+ ord函数可以将字符转换成ascii码,就可以将65设为变量,使用字典爆破的方法逐一判断,也可以直接爆破数据库名和其他表名、字段名等等。

时间盲注:时间盲注用于当输入不同的语句后页面效果一样的情况,时间盲注多与if(),sleep()函数使用,if(1=1,sleep(1),1)表明如果1=1,Sql查询休眠1秒,否则返回1,可以通过页面响应时间判断语句的正确。其他语句的拼接与布尔盲注一样。

报错注入:报错注入可用于Sql语句执行错误后页面会显示错误信息的情况,报错注入的前提条件是后端脚本中有mysql_error等报错函数。常见的报错方法有extractvalue()函数、updatexml()函数、floor型报错、整数溢出报错、几何函数报错等。在Mysql5.1.5版本以上,可以使用XPATH报错(extractvalue函数、updatexml函数),这两个函数用与查询修改xml文档,extractvalue()函数的使用extractvalue(xml文档,文档路径),文档路径的格式是/xx/xx/xx,若格式错误,会显示错误路径。如:id=0’or (select extractvalue(1,concat(‘!’,database()))) --+会显示(!数据库名)路径错误。floor型注入一般要与rand函数、group by一起使用。group by用于给查询数据分组,分组依据为by后面的语句,先创建虚拟表,然后在查询数据时,从数据库中取出数据,看在虚拟表中是否有同样的记录, 如果有,就在相应字段加一,如果没有就直接插入新记录。rand函数用于生成随机数,rand(0)表明生成一个0-1的随机数,但rand()确实是随机数,rand(0)生成的是有规律的随机数,floor函数用于向下取整,所以floor(rand(0)*2)只返回0或1,且为011011011…。查询中如果使用rand()的话,该值会被计算多次,也就是在使用group by 的时候,floor(rand(0)*2)会被执行一次,如果虚拟表中不存在记录,把数据插入虚拟表中时会再被执行一次。分析语句:select count(*) group by floor(rand(0)*2),查询前会建立虚拟表,取第一条记录,执行floor(rand(0*)2),发现结果是0(第一次计算),查询虚拟表,发现0的键值不存在,就会往虚拟表插入新的数据,则floor(rand(0)2)会被再计算一遍,结果为1(第二次计算),插入虚拟表,这时第一条记录查询完毕,查询第二条记录,再次计算floor(rand(0)*2),发现结果为1(第三次计算),查询虚拟表,发现1的键值存在(上图),所以floor(rand(0)*2)不会被计算第二次,直接count(*)+1,第二条记录查询完毕,查询第三条记录,再次计算floor(rand(0)2),发现结果为0(第四次计算),查询虚拟表,发现0的键值不存在,则虚拟表尝试插入一条新的数据,在插入数据时floor(rand(0)2)被再次计算,结果为1(第五次计算),然而1这个主键已经存在于虚拟表中,而新计算的值也为1(应为主键键值必须唯一),所以插入时直接报错了,于是在注入时就可以构造语句0' or (select count(*) from users group by concat(database(),floor(rand(0)*2)))--+爆出数据库信息。在版本号为5.5.47和5.7.17之间,Mysql有一些几何函数如geometrycollection(),multipoint(),polygon(),multipolygon(),linestring(),multilinestring(),这些函数对参数要求是形如(1 2,3 3,2 2 1)这样几何数据,如果不满足要求,则会报错,就可以构造语句:id = 1 and GeometryCollection((select from (select from(select database())a)b))获取数据库信息。根据官方文档说明,只有版本号大于5.5时整数溢出才会报错,在注入过程中不可能输入这么大的数,一般采用按位取反如:~0就表示最大整数值,~0+1就会产生报错信息。Sql语句执行成功的返回值为0,进行逻辑非运算后为1,这个值是可以参与运算的,我们就可以构造语句:id=0’or (select(~0+!(select * from (select database())a))) --+,此方式只适用于Mysql版本号小于5.5.53。整数溢出报错的另一种方式是利用exp函数,exp函数返回e的指定次幂,利用语句:(select exp(~(select * from (select database())a))) --+同样可以完成整数溢出报错。

堆叠注入:在SQL语句中,;代表一个语句的结束,如果在注入过程中注入;+语句,就可以执行构造的语句,堆叠注入的危害巨大,用户可以构造任何合法语句,包括但不限与删除数据,但一般服务器都会限制一次只能执行一条SQl语句。

二次注入:二次注入一般用于可以注册信息的场景,其原理是后端代码对用户上传的参数做了过滤,但是未对从数据库传出的数据进行过滤。分析语句:$username=addslashes($_GET[‘username’]);insert into users(‘username’,’password’) values $username,代码对username参数进行了过滤,也就是对‘进行了转义,如输入test’,实际SQL语句为test\‘,但如果从数据库中取出数据时未再次转义,就可以产生注入点。输入参数test’union select 1,2,version() --+,再从数据库中查询,语句变为:select * from user where username=’test’union select 1,2,version() --+’就可以获取数据库信息。

GET注入:提交数据的方式是 GET , 注入点的位置在 GET 参数部分,可以在url中看到参数名称,但url有长度限制,所以一般参数不能过长。

POST注入:使用 POST 方式提交数据,注入点位置在 POST 数据部分,多见于表单。

HTTP头注入:常见的HTTP头注入有COOKIE、HTTP_CLIENT_IP、HTTP_X_FORWARDED_FOR、HTTP_X_FORWARDED、HTTP_X_CLUSTER_CLIENT_IP、HTTP_FORWARDED_FOR、HTTP_FORWARDED、REMOTE_ADDR、User-agent、Referer。后端脚本从客户端发起的请求中获取cookie信息,并用于SQL语句中,如$_COOKIE函数。用burp suite抓包,修改cookie的值为1’union select 1,version(),3 --+,用于SQL语句中变为select * from user where id=’1’union select 1,version(),3 --+’,所以,从本质上来讲,Cookie注入与传统的SQL注入并无不同,两者都是针对数据库的注入,只是表现形式上略有不同罢了。HTTP_X_FORWARDED_FOR的注入也叫XFF注入,服务器端从HTTP_X_FORWARDED_FOR中获取参数(IP),并带入SQL语句中,可以通过burp suite抓包修改HTTP_X_FORWARDED_FOR头,构造目标SQL语句,其他的头注入也类似,关键在于后端脚本有无获取头信息并利用头信息的参数。

读写文件:SQL注入还可以通过读写文件来进行,但通常条件苛刻,要对目标主机的路径有所了解,否则就算写入了文件叶无法利用,同时也要有相应的权限来读写文件,否则就算能使用outfile、load_file(),也不能产生实质性效果。如果成功写入文件并且可以访问到,就可以写入webshell,并通过中国菜刀、webacoo等工具进行连接。

一些绕过方法:

大小写绕过:sql语句是不区分大小写的,一些老式的容器、waf等对大小写敏感,如union可以写成Union来绕过,现在基本没怎么老的版本,基本这种方法不可行。

双写绕过:有些waf是过滤危险字符,如把字符转换成空字符,这时就可以通过双写绕过,如uniunionon经过过滤之后变成union,但是好像除了靶场,基本没有会过滤危险字符的,一般都是直接拒绝访问。

编码绕过:可以通过对参数进行编码进行绕过,前提是后端代码有相应解码的功能。

注释绕过:一些waf没有对注释符进行判断,就可以通过注释来绕过,如:id=1 /*!union*/ /*!select*/,或者如union /*kuyed*/ select /*iuysv*/ 1。

宽字节绕过:当客户端与服务端的编码不一致时,有可能可以使用宽字节注入,比如php使用utf8,而mysql使用gbk编码,utf8使用一个字节表示英文,而gbk用两个字节,比如在‘/前面加上df就可以使df与/结合而绕过,通过url编码就变为%df。

Cookie绕过:一些程序员会通过$_REQUEST来获得参数,它会一次检测GET POST COOKIE是否有值,但有时程序只检测get或post的值,这时就可以通过cookie传参数来绕过。

使用其它函数:使用一些偏僻的waf不会检测的函数。

攻击防范:学习一种漏洞,除了学习如何利用,还要清楚的知道如何防范。

防范漏洞的第一个原则就是永远不能相信用户可控的数据,包括数据库中的数据,一些开发人员过于相信从数据库调出来的数据,始终要对数据进行严格的过滤,不要尝试“修复”数据,而应该拒绝有安全隐患的请求,因为人们总能想到绕过“修复”的方法。

参数化查询可以基本防范住sql注入漏洞,也叫预处理语句,传统的sql查询是使用字符串与用户参数动态拼接,PDO 语句可用的bind_param() 方法让你可以给预处理语句中出现的占位符绑定参数,并且接受基本的数据类型参数,基本杜绝了动态拼接恶意语句的可能,下面是一个代码示例。

严谨的代码也是防范注入的一大方向,一些程序员在前期调试的时候会在代码中加入报错函数,但在上线后却忘记删除,或者一些程序员根本没有意识到要删除,这将会给不怀好意的用户提供很多信息,如代码路径、字段名称、数据库版本等。还有一些是只验证数据当下用途(例如,展示或计算),却不考虑数据最终存储位置的数据库表字段的验证需求,可能字符串长度超过了数据库中的限制。

除了尽可能的防范漏洞利用的可能,我们也要尽最大可能降低漏洞被利用后的危害。最小化当前执行业务的用户的权限,可以设置一个拥有写数据权限的用户,和另一个只有读数据权限的用户,这种角色区分可以确保在 SQL 注入攻击目标为只读用户时,攻击者无法写数据或操纵表数据,这种生物隔离区划可以延伸到进一步限制访问权限,这样就可以将 SQL 注入攻击的影响最小化。

XSS注入:XSS有自己的漏洞划分,但它其实也是注入漏洞的一种。

注入攻击:攻击者向web页面(input表单、URL、留言版等位置)插入恶意JavaScript代码,导致管理员/用户访问时触发,从而达到攻击者的目的,XSS攻击可以分为常见的四类,反射型、储存型、DOM型、基于页面型。XSS常见的绕过有大小写、双写、编码、事件绕过等

XSS常用的触发事件:

onclick:点击元素时触发

onerror:图片音频等加载错误时触发

onmouseover:鼠标移动到元素时触发

onmouseout:移出元素时触发

XSS常用的触发标签:

alert("xss");

test

a

:自动触发

:自动触发